Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 15 additions & 26 deletions src/angular/VGrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
type GridSpan,
type VirtualStore,
ACTION_ITEMS_LENGTH_CHANGE,
ACTION_RELAYOUT,
relayout,
UPDATE_SCROLL_END_EVENT,
UPDATE_SIZE_EVENT,
microtask,
Expand Down Expand Up @@ -441,11 +441,11 @@ export class VGrid<R = number, C = number> implements OnInit, VGridHandle {
});
/** @internal */
protected rowCount = computed(
() => this._stateVersion() && this._rowStore.$getItemsLength(),
() => this._stateVersion() && this._rowLayout.$getLength(),
);
/** @internal */
protected colCount = computed(
() => this._stateVersion() && this._colStore.$getItemsLength(),
() => this._stateVersion() && this._colLayout.$getLength(),
);

private _rowSizes = computed(() =>
Expand Down Expand Up @@ -521,26 +521,10 @@ export class VGrid<R = number, C = number> implements OnInit, VGridHandle {
this._colLayout.$setPinned(headerCols, footerCols);
const rowLength = getAxisLength(rows);
const colLength = getAxisLength(cols);
if (rowLength !== this._rowStore.$getItemsLength()) {
this._rowStore.$update(ACTION_ITEMS_LENGTH_CHANGE, [rowLength]);
}
if (colLength !== this._colStore.$getItemsLength()) {
this._colStore.$update(ACTION_ITEMS_LENGTH_CHANGE, [colLength]);
}
this._rowStore.$update(
ACTION_RELAYOUT,
this._rowLayout.$relayout(
rowSizes,
this._rowStore.$getVisibleOffset(),
),
);
this._colStore.$update(
ACTION_RELAYOUT,
this._colLayout.$relayout(
colSizes,
this._colStore.$getVisibleOffset(),
),
);
this._rowStore.$update(ACTION_ITEMS_LENGTH_CHANGE, rowLength);
this._colStore.$update(ACTION_ITEMS_LENGTH_CHANGE, colLength);
relayout(this._rowStore, this._rowLayout, rowSizes);
relayout(this._colStore, this._colLayout, colSizes);
});
});

Expand Down Expand Up @@ -586,7 +570,12 @@ export class VGrid<R = number, C = number> implements OnInit, VGridHandle {
));
const rowStore = (this._rowStore = createVirtualStore(rowLayout));
const colStore = (this._colStore = createVirtualStore(colLayout));
this.driver = createContainerGridDriver(rowStore, colStore);
this.driver = createContainerGridDriver(
rowStore,
colStore,
rowLayout,
colLayout,
);

const rerender = (sync?: boolean) => {
this._stateVersion.set(
Expand Down Expand Up @@ -666,10 +655,10 @@ export class VGrid<R = number, C = number> implements OnInit, VGridHandle {
return this._colStore.$getItemOffset(index);
}
getRowSize(index: number): number {
return this._rowStore.$getItemSize(index);
return this._rowLayout.$getItemSize(index);
}
getColSize(index: number): number {
return this._colStore.$getItemSize(index);
return this._colLayout.$getItemSize(index);
}
scrollToIndex(opts: GridScrollToIndexOpts): void {
gridScrollToIndex(
Expand Down
19 changes: 7 additions & 12 deletions src/angular/VMasonry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
createContainerDriver,
createMasonryLayout,
ACTION_ITEMS_LENGTH_CHANGE,
ACTION_RELAYOUT,
relayout,
createVirtualStore,
getScrollSize,
scrollBy,
Expand Down Expand Up @@ -299,7 +299,7 @@ export class VMasonry<T> implements OnInit, VMasonryHandle {
offset: store.$getItemOffset(i),
crossOffset: toCrossValue(lane / lanes, (lane * gap) / lanes),
crossSize,
hide: store.$isUnmeasuredItem(i),
hide: layout.$isSizeEqual(i),
});
}
return items;
Expand All @@ -326,20 +326,15 @@ export class VMasonry<T> implements OnInit, VMasonryHandle {
const data = this.data();
if (!this._store) return;
untracked(() => {
if (data.length !== this._store.$getItemsLength()) {
this._store.$update(ACTION_ITEMS_LENGTH_CHANGE, [data.length]);
}
this._store.$update(ACTION_ITEMS_LENGTH_CHANGE, data.length);
});
});
effect(() => {
const lanes = this.lanes();
const gap = this.gap();
if (!this._store) return;
untracked(() => {
this._store.$update(
ACTION_RELAYOUT,
this._layout.$relayout(lanes, gap, this._store.$getVisibleOffset()),
);
relayout(this._store, this._layout, lanes, gap);
});
});

Expand Down Expand Up @@ -380,7 +375,7 @@ export class VMasonry<T> implements OnInit, VMasonryHandle {
this.cacheProp(),
));
const store = (this._store = createVirtualStore(layout));
this.driver = createContainerDriver(store, false);
this.driver = createContainerDriver(store, layout, false);
store.$subscribe(UPDATE_VIRTUAL_STATE, (sync) => {
this._stateVersion.set(store.$getStateVersion());
if (sync) {
Expand Down Expand Up @@ -417,10 +412,10 @@ export class VMasonry<T> implements OnInit, VMasonryHandle {
return this._store.$getItemOffset(index);
}
getItemSize(index: number): number {
return this._store.$getItemSize(index);
return this._layout.$getItemSize(index);
}
scrollToIndex(index: number, opts?: ScrollToIndexOpts): void {
scrollToIndex(this.driver, this._store, index, opts);
scrollToIndex(this.driver, this._store, this._layout, index, opts);
}
scrollTo(offset: number): void {
scrollTo(this.driver, offset);
Expand Down
19 changes: 9 additions & 10 deletions src/angular/Virtualizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ export class Virtualizer<T> implements OnInit, VirtualizerHandle {
index,
data: item,
offset: store.$getItemOffset(index),
hide: store.$isUnmeasuredItem(index),
hide: this._layout.$isSizeEqual(index),
attrs: itemProps?.({ item, index }),
};
});
Expand Down Expand Up @@ -304,12 +304,11 @@ export class Virtualizer<T> implements OnInit, VirtualizerHandle {
effect(() => {
const len = this.data().length;
if (!this._store) return;
if (len !== this._store.$getItemsLength()) {
this._store.$update(ACTION_ITEMS_LENGTH_CHANGE, [
len,
untracked(this.shift),
]);
}
this._store.$update(
ACTION_ITEMS_LENGTH_CHANGE,
len,
untracked(this.shift),
);
});
effect(() => {
const startMargin = this.startMargin();
Expand Down Expand Up @@ -350,7 +349,7 @@ export class Virtualizer<T> implements OnInit, VirtualizerHandle {
this.cacheProp(),
));
const store = (this._store = createVirtualStore(layout, ssrCount));
this.driver = createContainerDriver(store, this.horizontal());
this.driver = createContainerDriver(store, layout, this.horizontal());
store.$subscribe(UPDATE_VIRTUAL_STATE, (sync) => {
this._stateVersion.set(store.$getStateVersion());
if (sync) {
Expand Down Expand Up @@ -390,10 +389,10 @@ export class Virtualizer<T> implements OnInit, VirtualizerHandle {
return this._store.$getItemOffset(index);
}
getItemSize(index: number): number {
return this._store.$getItemSize(index);
return this._layout.$getItemSize(index);
}
scrollToIndex(index: number, opts?: ScrollToIndexOpts): void {
scrollToIndex(this.driver, this._store, index, opts);
scrollToIndex(this.driver, this._store, this._layout, index, opts);
}
scrollTo(offset: number): void {
scrollTo(this.driver, offset);
Expand Down
19 changes: 9 additions & 10 deletions src/angular/WindowVirtualizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export class WindowVirtualizer<T> implements OnInit, WindowVirtualizerHandle {
index,
data: item,
offset: store.$getItemOffset(index),
hide: store.$isUnmeasuredItem(index),
hide: this._layout.$isSizeEqual(index),
});
}
return items;
Expand All @@ -230,12 +230,11 @@ export class WindowVirtualizer<T> implements OnInit, WindowVirtualizerHandle {
effect(() => {
const len = this.data().length;
if (!this._store) return;
if (len !== this._store.$getItemsLength()) {
this._store.$update(ACTION_ITEMS_LENGTH_CHANGE, [
len,
untracked(this.shift),
]);
}
this._store.$update(
ACTION_ITEMS_LENGTH_CHANGE,
len,
untracked(this.shift),
);
});

afterNextRender({
Expand Down Expand Up @@ -268,7 +267,7 @@ export class WindowVirtualizer<T> implements OnInit, WindowVirtualizerHandle {
this.cacheProp(),
));
const store = (this._store = createVirtualStore(layout, ssrCount));
this.driver = createWindowDriver(store, this.horizontal());
this.driver = createWindowDriver(store, layout, this.horizontal());
store.$subscribe(UPDATE_VIRTUAL_STATE, (sync) => {
this._stateVersion.set(store.$getStateVersion());
if (sync) {
Expand Down Expand Up @@ -305,9 +304,9 @@ export class WindowVirtualizer<T> implements OnInit, WindowVirtualizerHandle {
return this._store.$getItemOffset(index);
}
getItemSize(index: number): number {
return this._store.$getItemSize(index);
return this._layout.$getItemSize(index);
}
scrollToIndex(index: number, opts?: ScrollToIndexOpts): void {
scrollToIndex(this.driver, this._store, index, opts);
scrollToIndex(this.driver, this._store, this._layout, index, opts);
}
}
57 changes: 57 additions & 0 deletions src/compensation.browser.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
cleanupScroll,
expectPosition,
expectVirtualized,
expectVirtualizedAndScrollable,
findFirstVisibleItem,
findLastVisibleItem,
getItem,
Expand Down Expand Up @@ -1627,6 +1628,62 @@ describe("children change", () => {
.toBeDefined();
});

it("recovering when the destination of smooth scrolling is removed", async () => {
const ITEM_SIZE = 100;
const ITEM_COUNT = 100;
const REMAINING_COUNT = 50;
const ref = createRef<VirtualizerHandle>();
// The sizes are given beforehand, so the scroll size is known before the items are measured
const Sized = ({ count }: { count: number }) => (
<div style={{ height: 400, overflowY: "auto" }}>
<Virtualizer ref={ref} data={range(count)} itemSize={ITEM_SIZE}>
{(i) => (
<div key={i} style={{ height: ITEM_SIZE }}>
item-{i}
</div>
)}
</Virtualizer>
</div>
);
const root = render(<Sized count={ITEM_COUNT} />);
const { viewport, container } = await getVirtualizer(root);
await expect.poll(() => getItem(container, "item-0")).toBeDefined();

const scroll = new Promise((resolve) =>
viewport.addEventListener("scroll", resolve, { once: true }),
);
ref.current!.scrollToIndex(ITEM_COUNT - 10, { smooth: true });
await scroll;
// delete many with the destination
rerender(root, <Sized count={REMAINING_COUNT} />);
await expect
.poll(() => getItem(container, `item-${REMAINING_COUNT}`))
.toBeUndefined();
// The range is widened to the destination, so the last item is rendered while the smooth scroll runs
const last = getItem(container, `item-${REMAINING_COUNT - 1}`)!;
(last.firstElementChild as HTMLElement).style.height = `${ITEM_SIZE * 3}px`;
await expect
.poll(() => viewport.scrollHeight)
.toBe(ITEM_SIZE * (REMAINING_COUNT + 2));
await settle(viewport, container);

// The items added later are measured if they are rendered, which hides the error, so add them out of the viewport
viewport.scrollTop = 0;
await settle(viewport, container);
// add many
rerender(root, <Sized count={ITEM_COUNT} />);
await expect
.poll(() => viewport.scrollHeight)
.toBe(ITEM_SIZE * (ITEM_COUNT + 2));

// check if an error didn't occur
await expectVirtualizedAndScrollable(
root,
"item-0",
`item-${ITEM_COUNT - 1}`,
);
});

it("recovering when changed a lot after scrolling", async () => {
const FEW_ITEM_COUNT = 4;
const MANY_ITEM_COUNT = 1004;
Expand Down
Loading
Loading