diff --git a/src/core/cache.spec.ts b/src/core/store/cache.spec.ts similarity index 60% rename from src/core/cache.spec.ts rename to src/core/store/cache.spec.ts index a1a0a5187..6b5c5aca4 100644 --- a/src/core/cache.spec.ts +++ b/src/core/store/cache.spec.ts @@ -9,9 +9,10 @@ import { Cache, hasUnmeasuredItemsInRange, updateCacheLength, - initCache, + initializeCache, + InstanceCache, } from "./cache"; -import type { Writeable } from "./types"; +import type { Writeable } from "../types"; const range = (length: number, cb: (i: number) => T): T[] => { const array: T[] = []; @@ -30,8 +31,6 @@ describe(getItemSize.name, () => { const cache: Cache = { _length: sizes.length, _sizes: sizes, - _measuredOffsetIndex: 0, - _offsets: [0], _defaultItemSize: 20, }; @@ -51,15 +50,17 @@ describe(setItemSize.name, () => { const cache: Writeable = { _length: filledSizes.length, _sizes: filledSizes, + _defaultItemSize: 20, + }; + const inst: InstanceCache = { _measuredOffsetIndex: 0, _offsets: emptyOffsets, - _defaultItemSize: 20, }; - setItemSize(cache, 0, 123); + setItemSize(cache, inst, 0, 123); expect(cache._sizes).toEqual([123, 20, 20, 20, 20, 20, 20, 20, 20, 20]); - expect(cache._offsets).toEqual([0, -1, -1, -1, -1, -1, -1, -1, -1, -1]); - expect(cache._measuredOffsetIndex).toBe(0); + expect(inst._offsets).toEqual([0, -1, -1, -1, -1, -1, -1, -1, -1, -1]); + expect(inst._measuredOffsetIndex).toBe(0); }); it("should set at middle", () => { @@ -68,15 +69,17 @@ describe(setItemSize.name, () => { const cache: Writeable = { _length: filledSizes.length, _sizes: filledSizes, + _defaultItemSize: 20, + }; + const inst: InstanceCache = { _measuredOffsetIndex: 0, _offsets: emptyOffsets, - _defaultItemSize: 20, }; - setItemSize(cache, 4, 123); + setItemSize(cache, inst, 4, 123); expect(cache._sizes).toEqual([20, 20, 20, 20, 123, 20, 20, 20, 20, 20]); - expect(cache._offsets).toEqual([0, -1, -1, -1, -1, -1, -1, -1, -1, -1]); - expect(cache._measuredOffsetIndex).toBe(0); + expect(inst._offsets).toEqual([0, -1, -1, -1, -1, -1, -1, -1, -1, -1]); + expect(inst._measuredOffsetIndex).toBe(0); }); it("should set at last", () => { @@ -85,15 +88,17 @@ describe(setItemSize.name, () => { const cache: Writeable = { _length: filledSizes.length, _sizes: filledSizes, + _defaultItemSize: 20, + }; + const inst: InstanceCache = { _measuredOffsetIndex: 0, _offsets: emptyOffsets, - _defaultItemSize: 20, }; - setItemSize(cache, emptyOffsets.length - 1, 123); + setItemSize(cache, inst, emptyOffsets.length - 1, 123); expect(cache._sizes).toEqual([20, 20, 20, 20, 20, 20, 20, 20, 20, 123]); - expect(cache._offsets).toEqual([0, -1, -1, -1, -1, -1, -1, -1, -1, -1]); - expect(cache._measuredOffsetIndex).toBe(0); + expect(inst._offsets).toEqual([0, -1, -1, -1, -1, -1, -1, -1, -1, -1]); + expect(inst._measuredOffsetIndex).toBe(0); }); }); @@ -103,15 +108,17 @@ describe(setItemSize.name, () => { const cache: Writeable = { _length: filledSizes.length, _sizes: filledSizes, + _defaultItemSize: 20, + }; + const inst: InstanceCache = { _measuredOffsetIndex: 4, _offsets: [0, 10, 20, 30, 40, -1, -1, -1, -1, -1], - _defaultItemSize: 20, }; - setItemSize(cache, 1, 123); + setItemSize(cache, inst, 1, 123); expect(cache._sizes).toEqual([20, 123, 20, 20, 20, 20, 20, 20, 20, 20]); - expect(cache._offsets).toEqual([0, 10, 20, 30, 40, -1, -1, -1, -1, -1]); - expect(cache._measuredOffsetIndex).toBe(1); + expect(inst._offsets).toEqual([0, 10, 20, 30, 40, -1, -1, -1, -1, -1]); + expect(inst._measuredOffsetIndex).toBe(1); }); it("should not update measuredOffsetIndex if size is changed at measuredOffsetIndex", () => { @@ -119,15 +126,17 @@ describe(setItemSize.name, () => { const cache: Writeable = { _length: filledSizes.length, _sizes: filledSizes, + _defaultItemSize: 20, + }; + const inst: InstanceCache = { _measuredOffsetIndex: 4, _offsets: [0, 10, 20, 30, 40, -1, -1, -1, -1, -1], - _defaultItemSize: 20, }; - setItemSize(cache, 4, 123); + setItemSize(cache, inst, 4, 123); expect(cache._sizes).toEqual([20, 20, 20, 20, 123, 20, 20, 20, 20, 20]); - expect(cache._offsets).toEqual([0, 10, 20, 30, 40, -1, -1, -1, -1, -1]); - expect(cache._measuredOffsetIndex).toBe(4); + expect(inst._offsets).toEqual([0, 10, 20, 30, 40, -1, -1, -1, -1, -1]); + expect(inst._measuredOffsetIndex).toBe(4); }); it("should not update measuredOffsetIndex if size is changed after measuredOffsetIndex", () => { @@ -135,15 +144,17 @@ describe(setItemSize.name, () => { const cache: Writeable = { _length: filledSizes.length, _sizes: filledSizes, + _defaultItemSize: 20, + }; + const inst: InstanceCache = { _measuredOffsetIndex: 4, _offsets: [0, 10, 20, 30, 40, -1, -1, -1, -1, -1], - _defaultItemSize: 20, }; - setItemSize(cache, 5, 123); + setItemSize(cache, inst, 5, 123); expect(cache._sizes).toEqual([20, 20, 20, 20, 20, 123, 20, 20, 20, 20]); - expect(cache._offsets).toEqual([0, 10, 20, 30, 40, -1, -1, -1, -1, -1]); - expect(cache._measuredOffsetIndex).toBe(4); + expect(inst._offsets).toEqual([0, 10, 20, 30, 40, -1, -1, -1, -1, -1]); + expect(inst._measuredOffsetIndex).toBe(4); }); }); @@ -154,12 +165,14 @@ describe(setItemSize.name, () => { const cache: Writeable = { _length: filledSizes.length, _sizes: filledSizes, + _defaultItemSize: 20, + }; + const inst: InstanceCache = { _measuredOffsetIndex: 0, _offsets: emptyOffsets, - _defaultItemSize: 20, }; - const res = setItemSize(cache, 0, 123); + const res = setItemSize(cache, inst, 0, 123); expect(res).toBe(false); }); @@ -169,12 +182,14 @@ describe(setItemSize.name, () => { const cache: Writeable = { _length: filledSizes.length, _sizes: filledSizes, + _defaultItemSize: 20, + }; + const inst: InstanceCache = { _measuredOffsetIndex: 0, _offsets: emptyOffsets, - _defaultItemSize: 20, }; - const res = setItemSize(cache, 0, 123); + const res = setItemSize(cache, inst, 0, 123); expect(res).toBe(true); }); }); @@ -184,31 +199,33 @@ describe(computeTotalSize.name, () => { it("should succeed if sizes is filled", () => { const filledSizes = range(10, () => 20); const emptyOffsets = range(10, (i) => (i === 0 ? 0 : -1)); - const cache: Writeable = { + const cache: Cache = { _length: filledSizes.length, _sizes: filledSizes, + _defaultItemSize: 30, + }; + const inst: InstanceCache = { _measuredOffsetIndex: 0, _offsets: emptyOffsets, - _defaultItemSize: 30, }; - expect(computeTotalSize(cache)).toBe(sum(filledSizes)); - expect(cache._offsets).toEqual([ - 0, 20, 40, 60, 80, 100, 120, 140, 160, 180, - ]); + expect(computeTotalSize(cache, inst)).toBe(sum(filledSizes)); + expect(inst._offsets).toEqual([0, 20, 40, 60, 80, 100, 120, 140, 160, 180]); }); it("should succeed if sizes is not filled", () => { const emptySizes = range(10, () => -1); const emptyOffsets = range(10, (i) => (i === 0 ? 0 : -1)); - const cache: Writeable = { + const cache: Cache = { _length: emptySizes.length, _sizes: emptySizes, + _defaultItemSize: 30, + }; + const inst: InstanceCache = { _measuredOffsetIndex: 0, _offsets: emptyOffsets, - _defaultItemSize: 30, }; - expect(computeTotalSize(cache)).toBe(sum(range(10, () => 30))); - expect(cache._offsets).toEqual([ + expect(computeTotalSize(cache, inst)).toBe(sum(range(10, () => 30))); + expect(inst._offsets).toEqual([ 0, 30, 60, 90, 120, 150, 180, 210, 240, 270, ]); }); @@ -216,30 +233,34 @@ describe(computeTotalSize.name, () => { it("should return 0 if cache length is 0", () => { const filledSizes: number[] = []; const emptyOffsets: number[] = []; - const cache: Writeable = { + const cache: Cache = { _length: filledSizes.length, _sizes: filledSizes, + _defaultItemSize: 30, + }; + const inst: InstanceCache = { _measuredOffsetIndex: 0, _offsets: emptyOffsets, - _defaultItemSize: 30, }; - expect(computeTotalSize(cache)).toBe(0); - expect(cache._offsets).toEqual([]); + expect(computeTotalSize(cache, inst)).toBe(0); + expect(inst._offsets).toEqual([]); }); describe("with cached offsets", () => { it("should start from cached offset if measuredOffsetIndex is at cached", () => { const filledSizes = range(10, () => 20); const offsets = [0, 11, 22, 33, -1, -1, -1, -1, -1, -1]; - const cache: Writeable = { + const cache: Cache = { _length: filledSizes.length, _sizes: filledSizes, + _defaultItemSize: 30, + }; + const inst: InstanceCache = { _measuredOffsetIndex: 2, _offsets: offsets, - _defaultItemSize: 30, }; - expect(computeTotalSize(cache)).toBe(sum(range(8, () => 20)) + 22); - expect(cache._offsets).toEqual([ + expect(computeTotalSize(cache, inst)).toBe(sum(range(8, () => 20)) + 22); + expect(inst._offsets).toEqual([ 0, 11, 22, 42, 62, 82, 102, 122, 142, 162, ]); }); @@ -247,15 +268,17 @@ describe(computeTotalSize.name, () => { it("should return cached offset + 1 item size if measuredOffsetIndex is at end", () => { const filledSizes = range(10, () => 20); const offsets = [0, 11, 22, 33, 44, 55, 66, 77, 88, 99]; - const cache: Writeable = { + const cache: Cache = { _length: filledSizes.length, _sizes: filledSizes, + _defaultItemSize: 30, + }; + const inst: InstanceCache = { _measuredOffsetIndex: 9, _offsets: offsets, - _defaultItemSize: 30, }; - expect(computeTotalSize(cache)).toBe(99 + 20); - expect(cache._offsets).toEqual([0, 11, 22, 33, 44, 55, 66, 77, 88, 99]); + expect(computeTotalSize(cache, inst)).toBe(99 + 20); + expect(inst._offsets).toEqual([0, 11, 22, 33, 44, 55, 66, 77, 88, 99]); }); }); }); @@ -264,105 +287,117 @@ describe(computeStartOffset.name, () => { it("should get 0 if index is at start", () => { const filledSizes = range(10, () => 20); const emptyOffsets = range(10, (i) => (i === 0 ? 0 : -1)); - const cache: Writeable = { + const cache: Cache = { _length: filledSizes.length, _sizes: filledSizes, + _defaultItemSize: 30, + }; + const inst: InstanceCache = { _measuredOffsetIndex: 0, _offsets: emptyOffsets, - _defaultItemSize: 30, }; - expect(computeStartOffset(cache, 0)).toBe(0); - expect(cache._offsets).toEqual([0, -1, -1, -1, -1, -1, -1, -1, -1, -1]); + expect(computeStartOffset(cache, inst, 0)).toBe(0); + expect(inst._offsets).toEqual([0, -1, -1, -1, -1, -1, -1, -1, -1, -1]); }); it("should get 1 item if index is at start", () => { const filledSizes = range(10, () => 20); const emptyOffsets = range(10, (i) => (i === 0 ? 0 : -1)); - const cache: Writeable = { + const cache: Cache = { _length: filledSizes.length, _sizes: filledSizes, + _defaultItemSize: 30, + }; + const inst: InstanceCache = { _measuredOffsetIndex: 0, _offsets: emptyOffsets, - _defaultItemSize: 30, }; - expect(computeStartOffset(cache, 1)).toBe(20); - expect(cache._offsets).toEqual([0, 20, -1, -1, -1, -1, -1, -1, -1, -1]); + expect(computeStartOffset(cache, inst, 1)).toBe(20); + expect(inst._offsets).toEqual([0, 20, -1, -1, -1, -1, -1, -1, -1, -1]); }); it("should get total - 1 item if index is at last", () => { const filledSizes = range(10, () => 20); const emptyOffsets = range(10, (i) => (i === 0 ? 0 : -1)); - const cache: Writeable = { + const cache: Cache = { _length: filledSizes.length, _sizes: filledSizes, + _defaultItemSize: 30, + }; + const inst: InstanceCache = { _measuredOffsetIndex: 0, _offsets: emptyOffsets, - _defaultItemSize: 30, }; const last = filledSizes.length - 1; - expect(computeStartOffset(cache, last)).toBe( + expect(computeStartOffset(cache, inst, last)).toBe( sum(filledSizes) - filledSizes[last]! ); - expect(cache._offsets).toEqual([ - 0, 20, 40, 60, 80, 100, 120, 140, 160, 180, - ]); + expect(inst._offsets).toEqual([0, 20, 40, 60, 80, 100, 120, 140, 160, 180]); }); it("should resolve default height", () => { const emptySizes = range(10, () => -1); const emptyOffsets = range(10, (i) => (i === 0 ? 0 : -1)); - const cache: Writeable = { + const cache: Cache = { _length: emptySizes.length, _sizes: emptySizes, + _defaultItemSize: 30, + }; + const inst: InstanceCache = { _measuredOffsetIndex: 0, _offsets: emptyOffsets, - _defaultItemSize: 30, }; - expect(computeStartOffset(cache, 2)).toBe(60); - expect(cache._offsets).toEqual([0, 30, 60, -1, -1, -1, -1, -1, -1, -1]); + expect(computeStartOffset(cache, inst, 2)).toBe(60); + expect(inst._offsets).toEqual([0, 30, 60, -1, -1, -1, -1, -1, -1, -1]); }); describe("with cached offsets", () => { it("should return cached offset if index is before measuredOffsetIndex", () => { const filledSizes = range(10, () => 20); const offsets = [0, 11, 22, 33, -1, -1, -1, -1, -1, -1]; - const cache: Writeable = { + const cache: Cache = { _length: filledSizes.length, _sizes: filledSizes, + _defaultItemSize: 30, + }; + const inst: InstanceCache = { _measuredOffsetIndex: 3, _offsets: offsets, - _defaultItemSize: 30, }; - expect(computeStartOffset(cache, 2)).toBe(22); - expect(cache._offsets).toEqual([0, 11, 22, 33, -1, -1, -1, -1, -1, -1]); + expect(computeStartOffset(cache, inst, 2)).toBe(22); + expect(inst._offsets).toEqual([0, 11, 22, 33, -1, -1, -1, -1, -1, -1]); }); it("should return cached offset if index is the same as measuredOffsetIndex", () => { const filledSizes = range(10, () => 20); const offsets = [0, 11, 22, 33, -1, -1, -1, -1, -1, -1]; - const cache: Writeable = { + const cache: Cache = { _length: filledSizes.length, _sizes: filledSizes, + _defaultItemSize: 30, + }; + const inst: InstanceCache = { _measuredOffsetIndex: 3, _offsets: offsets, - _defaultItemSize: 30, }; - expect(computeStartOffset(cache, 3)).toBe(33); - expect(cache._offsets).toEqual([0, 11, 22, 33, -1, -1, -1, -1, -1, -1]); + expect(computeStartOffset(cache, inst, 3)).toBe(33); + expect(inst._offsets).toEqual([0, 11, 22, 33, -1, -1, -1, -1, -1, -1]); }); it("should start from cached offset if index is after measuredOffsetIndex", () => { const filledSizes = range(10, () => 20); const offsets = [0, 11, 22, 33, -1, -1, -1, -1, -1, -1]; - const cache: Writeable = { + const cache: Cache = { _length: filledSizes.length, _sizes: filledSizes, + _defaultItemSize: 30, + }; + const inst: InstanceCache = { _measuredOffsetIndex: 3, _offsets: offsets, - _defaultItemSize: 30, }; - expect(computeStartOffset(cache, 5)).toBe(33 + 20 * 2); - expect(cache._offsets).toEqual([0, 11, 22, 33, 53, 73, -1, -1, -1, -1]); + expect(computeStartOffset(cache, inst, 5)).toBe(33 + 20 * 2); + expect(inst._offsets).toEqual([0, 11, 22, 33, 53, 73, -1, -1, -1, -1]); }); }); }); @@ -372,8 +407,6 @@ describe("findEndIndex", () => { const cache: Cache = { _length: filledSizes.length, _sizes: filledSizes, - _measuredOffsetIndex: 0, - _offsets: [0], _defaultItemSize: 30, }; @@ -442,8 +475,6 @@ describe("findEndIndex", () => { const emptyCache: Cache = { _length: emptySizes.length, _sizes: emptySizes, - _measuredOffsetIndex: 0, - _offsets: [0], _defaultItemSize: 25, }; expect(findEndIndex(emptyCache, 0, 100)).toBe(4); @@ -451,193 +482,210 @@ describe("findEndIndex", () => { }); describe("findStartIndex", () => { - const initFilledCache = (sizes: number[]): Writeable => { - return { - _length: sizes.length, - _sizes: sizes, - _measuredOffsetIndex: sizes.length - 1, - _offsets: sizes.reduce((acc, s, i) => { - acc.push(i === 0 ? 0 : acc[i - 1]! + s); - return acc; - }, [] as number[]), - _defaultItemSize: 30, - }; + const initFilledCache = (sizes: number[]): [Cache, InstanceCache] => { + return [ + { + _length: sizes.length, + _sizes: sizes, + _defaultItemSize: 30, + }, + { + _measuredOffsetIndex: sizes.length - 1, + _offsets: sizes.reduce((acc, s, i) => { + acc.push(i === 0 ? 0 : acc[i - 1]! + s); + return acc; + }, [] as number[]), + }, + ]; }; it("should resolve default height", () => { - const initEmptyCache = (): Writeable => { + const initEmptyCache = (): [Cache, InstanceCache] => { const emptySizes = range(10, () => -1); - return { - _length: emptySizes.length, - _sizes: emptySizes, - _measuredOffsetIndex: 0, - _offsets: [0], - _defaultItemSize: 25, - }; + return [ + { + _length: emptySizes.length, + _sizes: emptySizes, + _defaultItemSize: 25, + }, + { + _measuredOffsetIndex: 0, + _offsets: [0], + }, + ]; }; - expect(findStartIndexWithOffset(initEmptyCache(), 100, 0)).toBe(4); + expect(findStartIndexWithOffset(...initEmptyCache(), 100, 0)).toBe(4); }); describe("start from start", () => { it("should get start if offset is at start", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 0, 0)).toBe(0); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 0, 0)).toBe(0); }); it("should get start if offset is at start + 1px", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 1, 0)).toBe(0); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 1, 0)).toBe(0); }); it("should get end if offset is at end", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, sum(cache._sizes), 0)).toBe( + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, sum(cache._sizes), 0)).toBe( cache._length - 1 ); }); it("should get end if offset is at end - 1px", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, sum(cache._sizes) - 1, 0)).toBe( - cache._length - 1 - ); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect( + findStartIndexWithOffset(cache, inst, sum(cache._sizes) - 1, 0) + ).toBe(cache._length - 1); }); it("should get 1 if offset fits index 1", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 20, 0)).toBe(1); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 20, 0)).toBe(1); }); it("should get 1 if offset fits index 1 + 1px", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 21, 0)).toBe(1); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 21, 0)).toBe(1); }); it("should get 1 if offset fits index 1 - 1px", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 19, 0)).toBe(1); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 19, 0)).toBe(1); }); it("should get 1 if offset fits index 1.5", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 30, 0)).toBe(1); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 30, 0)).toBe(1); }); it("should get 2 if offset fits index 1.5 + 1px", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 31, 0)).toBe(2); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 31, 0)).toBe(2); }); it("should get 2 if offset fits index 1.5 + 0.01px", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 30.01, 0)).toBe(2); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 30.01, 0)).toBe(2); }); it("should get 1 if offset fits index 1.5 - 1px", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 29, 0)).toBe(1); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 29, 0)).toBe(1); }); it("should get 1 if offset fits index 1.5 - 0.01px", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 29.99, 0)).toBe(1); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 29.99, 0)).toBe(1); }); it("should get 2 if offset fits index 2", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 40, 0)).toBe(2); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 40, 0)).toBe(2); }); it("should get 2 if offset fits index 2 + 1px", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 41, 0)).toBe(2); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 41, 0)).toBe(2); }); it("should get 2 if offset fits index 2 - 1px", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 39, 0)).toBe(2); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 39, 0)).toBe(2); }); }); describe("start from end", () => { it("should get start if offset is at start", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 0, cache._length)).toBe(0); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 0, cache._length)).toBe(0); }); it("should get start if offset is at start + 1px", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 1, cache._length)).toBe(0); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 1, cache._length)).toBe(0); }); it("should get end if offset is at end", () => { - const cache = initFilledCache(range(10, () => 20)); + const [cache, inst] = initFilledCache(range(10, () => 20)); expect( - findStartIndexWithOffset(cache, sum(cache._sizes), cache._length) + findStartIndexWithOffset(cache, inst, sum(cache._sizes), cache._length) ).toBe(cache._length - 1); }); it("should get end if offset is at end - 1px", () => { - const cache = initFilledCache(range(10, () => 20)); + const [cache, inst] = initFilledCache(range(10, () => 20)); expect( - findStartIndexWithOffset(cache, sum(cache._sizes) - 1, cache._length) + findStartIndexWithOffset( + cache, + inst, + sum(cache._sizes) - 1, + cache._length + ) ).toBe(cache._length - 1); }); it("should get 1 if offset fits index 1", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 20, cache._length)).toBe(1); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 20, cache._length)).toBe(1); }); it("should get 1 if offset fits index 1 + 1px", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 21, cache._length)).toBe(1); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 21, cache._length)).toBe(1); }); it("should get 1 if offset fits index 1 - 1px", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 19, cache._length)).toBe(1); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 19, cache._length)).toBe(1); }); it("should get 1 if offset fits index 1.5", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 30, cache._length)).toBe(1); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 30, cache._length)).toBe(1); }); it("should get 2 if offset fits index 1.5 + 1px", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 31, cache._length)).toBe(2); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 31, cache._length)).toBe(2); }); it("should get 2 if offset fits index 1.5 + 0.01px", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 30.01, cache._length)).toBe(2); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 30.01, cache._length)).toBe( + 2 + ); }); it("should get 1 if offset fits index 1.5 - 1px", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 29, cache._length)).toBe(1); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 29, cache._length)).toBe(1); }); it("should get 1 if offset fits index 1.5 - 0.01px", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 29.99, cache._length)).toBe(1); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 29.99, cache._length)).toBe( + 1 + ); }); it("should get 2 if offset fits index 2", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 40, cache._length)).toBe(2); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 40, cache._length)).toBe(2); }); it("should get 2 if offset fits index 2 + 1px", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 41, cache._length)).toBe(2); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 41, cache._length)).toBe(2); }); it("should get 2 if offset fits index 2 - 1px", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 39, cache._length)).toBe(2); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 39, cache._length)).toBe(2); }); }); @@ -645,38 +693,46 @@ describe("findStartIndex", () => { const offset = (cache: Cache, i: number) => sum(cache._sizes.slice(0, i)); it("should get start if offset is at start", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, 0, 4)).toBe(0); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, 0, 4)).toBe(0); }); it("should get end if offset is at end", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, sum(cache._sizes), 4)).toBe( + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, sum(cache._sizes), 4)).toBe( cache._sizes.length - 1 ); }); it("should get prevStartIndex if offset fits prevStartIndex", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, offset(cache, 4), 4)).toBe(4); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect(findStartIndexWithOffset(cache, inst, offset(cache, 4), 4)).toBe( + 4 + ); }); it("should get prevStartIndex if offset fits prevStartIndex + 1px", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, offset(cache, 4) + 1, 4)).toBe(4); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect( + findStartIndexWithOffset(cache, inst, offset(cache, 4) + 1, 4) + ).toBe(4); }); it("should get prevStartIndex if offset fits prevStartIndex - 1px", () => { - const cache = initFilledCache(range(10, () => 20)); - expect(findStartIndexWithOffset(cache, offset(cache, 4) - 1, 4)).toBe(4); + const [cache, inst] = initFilledCache(range(10, () => 20)); + expect( + findStartIndexWithOffset(cache, inst, offset(cache, 4) - 1, 4) + ).toBe(4); }); }); describe("both", () => { it("should get same in forward and backward search", () => { - const cache = initFilledCache(range(10, (i) => (i % 2 === 0 ? 21 : 41))); - expect(findStartIndexWithOffset(cache, 1, 0)).toBe(0); - expect(findStartIndexWithOffset(cache, 1, 1)).toBe(0); + const [cache, inst] = initFilledCache( + range(10, (i) => (i % 2 === 0 ? 21 : 41)) + ); + expect(findStartIndexWithOffset(cache, inst, 1, 0)).toBe(0); + expect(findStartIndexWithOffset(cache, inst, 1, 1)).toBe(0); }); }); }); @@ -687,8 +743,6 @@ describe(hasUnmeasuredItemsInRange.name, () => { const cache: Cache = { _length: sizes.length, _sizes: sizes, - _measuredOffsetIndex: 0, - _offsets: [0], _defaultItemSize: 30, }; expect(hasUnmeasuredItemsInRange(cache, 0, sizes.length - 1)).toBe(false); @@ -699,8 +753,6 @@ describe(hasUnmeasuredItemsInRange.name, () => { const cache: Cache = { _length: sizes.length, _sizes: sizes, - _measuredOffsetIndex: 0, - _offsets: [0], _defaultItemSize: 30, }; expect(hasUnmeasuredItemsInRange(cache, 1, 2)).toBe(true); @@ -711,23 +763,29 @@ describe(hasUnmeasuredItemsInRange.name, () => { const cache: Cache = { _length: sizes.length, _sizes: sizes, - _measuredOffsetIndex: 0, - _offsets: [0], _defaultItemSize: 30, }; expect(hasUnmeasuredItemsInRange(cache, 1, 2)).toBe(true); }); }); -describe(initCache.name, () => { +describe(initializeCache.name, () => { it("should create cache", () => { - expect(initCache(10, 23)).toMatchInlineSnapshot(` + const cache: Cache = { + _defaultItemSize: 23, + _length: 0, + _sizes: [], + }; + const inst: InstanceCache = { + _measuredOffsetIndex: 0, + _offsets: [], + }; + initializeCache(cache as Writeable, inst, 10); + expect(cache).toMatchInlineSnapshot(` { "_defaultItemSize": 23, "_length": 10, - "_measuredOffsetIndex": 0, - "_offsets": [ - 0, + "_sizes": [ -1, -1, -1, @@ -737,9 +795,15 @@ describe(initCache.name, () => { -1, -1, -1, - ], - "_sizes": [ -1, + ], + } + `); + expect(inst).toMatchInlineSnapshot(` + { + "_measuredOffsetIndex": 0, + "_offsets": [ + 0, -1, -1, -1, @@ -757,16 +821,28 @@ describe(initCache.name, () => { describe(updateCacheLength.name, () => { it("should increase cache length", () => { - const cache = initCache(10, 40); - const res = updateCacheLength(cache as Writeable, 15, undefined); + const cache: Cache = { + _defaultItemSize: 40, + _length: 0, + _sizes: [], + }; + const inst: InstanceCache = { + _measuredOffsetIndex: 0, + _offsets: [], + }; + initializeCache(cache as Writeable, inst, 10); + const res = updateCacheLength( + cache as Writeable, + inst, + 15, + undefined + ); expect(res).toEqual([40 * 5, false]); expect(cache).toMatchInlineSnapshot(` { "_defaultItemSize": 40, "_length": 15, - "_measuredOffsetIndex": 0, - "_offsets": [ - 0, + "_sizes": [ -1, -1, -1, @@ -781,9 +857,15 @@ describe(updateCacheLength.name, () => { -1, -1, -1, - ], - "_sizes": [ -1, + ], + } + `); + expect(inst).toMatchInlineSnapshot(` + { + "_measuredOffsetIndex": 0, + "_offsets": [ + 0, -1, -1, -1, @@ -804,24 +886,42 @@ describe(updateCacheLength.name, () => { }); it("should decrease cache length", () => { - const cache = initCache(10, 40); + const cache: Cache = { + _defaultItemSize: 40, + _length: 0, + _sizes: [], + }; + const inst: InstanceCache = { + _measuredOffsetIndex: 0, + _offsets: [], + }; + initializeCache(cache as Writeable, inst, 10); (cache as Writeable)._sizes[9] = 123; - const res = updateCacheLength(cache as Writeable, 5, undefined); + const res = updateCacheLength( + cache as Writeable, + inst, + 5, + undefined + ); expect(res).toEqual([40 * 4 + 123, true]); expect(cache).toMatchInlineSnapshot(` { "_defaultItemSize": 40, "_length": 5, - "_measuredOffsetIndex": 0, - "_offsets": [ - 0, + "_sizes": [ -1, -1, -1, -1, - ], - "_sizes": [ -1, + ], + } + `); + expect(inst).toMatchInlineSnapshot(` + { + "_measuredOffsetIndex": 0, + "_offsets": [ + 0, -1, -1, -1, @@ -832,24 +932,42 @@ describe(updateCacheLength.name, () => { }); it("should recover cache length from 0", () => { - const cache = initCache(10, 40); + const cache: Cache = { + _defaultItemSize: 40, + _length: 0, + _sizes: [], + }; + const inst: InstanceCache = { + _measuredOffsetIndex: 0, + _offsets: [], + }; + initializeCache(cache as Writeable, inst, 10); const initialCache = JSON.parse(JSON.stringify(cache)); - updateCacheLength(cache as Writeable, 0); - updateCacheLength(cache as Writeable, 10); + const initialInst = JSON.parse(JSON.stringify(inst)); + updateCacheLength(cache as Writeable, inst, 0); + updateCacheLength(cache as Writeable, inst, 10); expect(cache).toEqual(initialCache); + expect(inst).toEqual(initialInst); }); it("should increase cache length with shifting", () => { - const cache = initCache(10, 40); - const res = updateCacheLength(cache as Writeable, 15, true); + const cache: Cache = { + _defaultItemSize: 40, + _length: 0, + _sizes: [], + }; + const inst: InstanceCache = { + _measuredOffsetIndex: 0, + _offsets: [], + }; + initializeCache(cache as Writeable, inst, 10); + const res = updateCacheLength(cache as Writeable, inst, 15, true); expect(res).toEqual([40 * 5, false]); expect(cache).toMatchInlineSnapshot(` { "_defaultItemSize": 40, "_length": 15, - "_measuredOffsetIndex": 0, - "_offsets": [ - 0, + "_sizes": [ -1, -1, -1, @@ -864,9 +982,15 @@ describe(updateCacheLength.name, () => { -1, -1, -1, - ], - "_sizes": [ -1, + ], + } + `); + expect(inst).toMatchInlineSnapshot(` + { + "_measuredOffsetIndex": 0, + "_offsets": [ + 0, -1, -1, -1, @@ -887,24 +1011,37 @@ describe(updateCacheLength.name, () => { }); it("should decrease cache length with shifting", () => { - const cache = initCache(10, 40); + const cache: Cache = { + _defaultItemSize: 40, + _length: 0, + _sizes: [], + }; + const inst: InstanceCache = { + _measuredOffsetIndex: 0, + _offsets: [], + }; + initializeCache(cache as Writeable, inst, 10); (cache as Writeable)._sizes[0] = 123; - const res = updateCacheLength(cache as Writeable, 5, true); + const res = updateCacheLength(cache as Writeable, inst, 5, true); expect(res).toEqual([40 * 4 + 123, true]); expect(cache).toMatchInlineSnapshot(` { "_defaultItemSize": 40, "_length": 5, - "_measuredOffsetIndex": 0, - "_offsets": [ - 0, + "_sizes": [ -1, -1, -1, -1, - ], - "_sizes": [ -1, + ], + } + `); + expect(inst).toMatchInlineSnapshot(` + { + "_measuredOffsetIndex": 0, + "_offsets": [ + 0, -1, -1, -1, diff --git a/src/core/cache.ts b/src/core/store/cache.ts similarity index 74% rename from src/core/cache.ts rename to src/core/store/cache.ts index 2d789fb8f..f2cab089b 100644 --- a/src/core/cache.ts +++ b/src/core/store/cache.ts @@ -1,5 +1,5 @@ -import type { DeepReadonly, Writeable } from "./types"; -import { clamp, median, min } from "./utils"; +import type { DeepReadonly, Writeable } from "../types"; +import { clamp, median, min } from "../utils"; export const UNCACHED = -1; @@ -7,9 +7,12 @@ export type Cache = DeepReadonly<{ _defaultItemSize: number; _length: number; _sizes: number[]; +}>; + +export type InstanceCache = { _measuredOffsetIndex: number; _offsets: number[]; -}>; +}; export const getItemSize = (cache: Cache, index: number): number => { const size = cache._sizes[index]!; @@ -18,40 +21,42 @@ export const getItemSize = (cache: Cache, index: number): number => { export const setItemSize = ( cache: Writeable, + inst: InstanceCache, index: number, size: number ): boolean => { const isInitialMeasurement = cache._sizes[index] === UNCACHED; cache._sizes[index] = size; // mark as dirty - cache._measuredOffsetIndex = min(index, cache._measuredOffsetIndex); + inst._measuredOffsetIndex = min(index, inst._measuredOffsetIndex); return isInitialMeasurement; }; export const computeOffset = ( - cache: Writeable, + cache: Cache, + inst: InstanceCache, index: number ): number => { if (!cache._length) return 0; - if (cache._measuredOffsetIndex >= index) { - return cache._offsets[index]!; + if (inst._measuredOffsetIndex >= index) { + return inst._offsets[index]!; } - let i = cache._measuredOffsetIndex; - let top = cache._offsets[i]!; + let i = inst._measuredOffsetIndex; + let top = inst._offsets[i]!; while (i < index) { top += getItemSize(cache, i); - cache._offsets[++i] = top; + inst._offsets[++i] = top; } // mark as measured - cache._measuredOffsetIndex = index; + inst._measuredOffsetIndex = index; return top; }; -export const computeTotalSize = (cache: Writeable): number => { +export const computeTotalSize = (cache: Cache, inst: InstanceCache): number => { if (!cache._length) return 0; return ( - computeOffset(cache, cache._length - 1) + + computeOffset(cache, inst, cache._length - 1) + getItemSize(cache, cache._length - 1) ); }; @@ -90,14 +95,15 @@ export const findIndex = ( }; export const findStartIndexWithOffset = ( - cache: Writeable, + cache: Cache, + inst: InstanceCache, offset: number, initialIndex: number ): number => { return findIndex( cache, initialIndex, - offset - computeOffset(cache, initialIndex) + offset - computeOffset(cache, inst, initialIndex) ); }; @@ -121,8 +127,9 @@ export const estimateDefaultItemSize = (cache: Writeable) => { median(measuredSizes); }; -const appendCache = ( +export const initializeCache = ( cache: Writeable, + inst: InstanceCache, length: number, prepend?: boolean ) => { @@ -130,25 +137,14 @@ const appendCache = ( for (let i = cache._length; i < length; i++) { cache._sizes[key](UNCACHED); // first offset must be 0 - cache._offsets.push(i === 0 ? 0 : UNCACHED); + inst._offsets.push(i === 0 ? 0 : UNCACHED); } cache._length = length; }; -export const initCache = (length: number, itemSize: number): Cache => { - const cache: Cache = { - _defaultItemSize: itemSize, - _length: 0, - _measuredOffsetIndex: 0, - _sizes: [], - _offsets: [], - }; - appendCache(cache as Writeable, length); - return cache; -}; - export const updateCacheLength = ( cache: Writeable, + inst: InstanceCache, length: number, isShift?: boolean ): [number, boolean] => { @@ -165,19 +161,19 @@ export const updateCacheLength = ( acc + (removed === UNCACHED ? cache._defaultItemSize : removed), 0 ); - cache._offsets.splice(diff); + inst._offsets.splice(diff); } else { // Added shift = cache._defaultItemSize * diff; - appendCache(cache, cache._length + diff, isShift); + initializeCache(cache, inst, cache._length + diff, isShift); } - cache._measuredOffsetIndex = isShift + inst._measuredOffsetIndex = isShift ? // Discard cache for now 0 : // measuredOffsetIndex shouldn't be less than 0 because it makes scrollSize NaN and cause infinite rerender. // https://github.com/inokawa/virtua/pull/160 - clamp(length - 1, 0, cache._measuredOffsetIndex); + clamp(length - 1, 0, inst._measuredOffsetIndex); cache._length = length; return [shift, isRemove]; }; diff --git a/src/core/store.ts b/src/core/store/index.ts similarity index 93% rename from src/core/store.ts rename to src/core/store/index.ts index 42f0cb918..235b4563f 100644 --- a/src/core/store.ts +++ b/src/core/store/index.ts @@ -1,6 +1,6 @@ import { findStartIndexWithOffset, - initCache, + initializeCache, getItemSize, computeTotalSize, findIndex as findEndIndex, @@ -11,9 +11,10 @@ import { hasUnmeasuredItemsInRange, estimateDefaultItemSize, updateCacheLength, + InstanceCache, } from "./cache"; -import type { CacheSnapshot, Writeable } from "./types"; -import { abs, clamp, max, min } from "./utils"; +import type { CacheSnapshot, Writeable } from "../types"; +import { abs, clamp, max, min } from "../utils"; export type ScrollJump = Readonly; export type ItemResize = Readonly<[index: number, size: number]>; @@ -96,7 +97,7 @@ export const createVirtualStore = ( elementsCount: number, itemSize: number = 40, initialItemCount: number = 0, - cache: Cache = initCache(elementsCount, itemSize), + cacheSnapshot?: CacheSnapshot, isReverse?: boolean, shouldAutoEstimateItemSize?: boolean ): VirtualStore => { @@ -110,9 +111,18 @@ export const createVirtualStore = ( let _resized = false; let _prevRange: ItemsRange = [0, initialItemCount]; + const cache: Cache = (cacheSnapshot as unknown as Cache) || { + _defaultItemSize: itemSize, + _length: 0, + _sizes: [], + }; + const inst: InstanceCache = { + _measuredOffsetIndex: 0, + _offsets: [], + }; + const subscribers = new Set<[number, Subscriber]>(); - const getScrollSize = (): number => - computeTotalSize(cache as Writeable); + const getScrollSize = (): number => computeTotalSize(cache, inst); const getScrollOffsetMax = () => getScrollSize() - viewportSize; const clampScrollOffset = (value: number): number => { @@ -126,6 +136,8 @@ export const createVirtualStore = ( return _scrollDirection !== prev; }; + initializeCache(cache as Writeable, inst, elementsCount); + return { _getCache() { return JSON.parse(JSON.stringify(cache)) as unknown as CacheSnapshot; @@ -133,7 +145,8 @@ export const createVirtualStore = ( _getRange() { const [prevStartIndex, prevEndIndex] = _prevRange; const start = findStartIndexWithOffset( - cache as Writeable, + cache, + inst, scrollOffset, // Clamp because prevStartIndex may exceed the limit when children decreased a lot after scrolling min(prevStartIndex, cache._length - 1) @@ -149,7 +162,8 @@ export const createVirtualStore = ( }, _hasUnmeasuredItemsInTargetViewport(offset) { const startIndex = findStartIndexWithOffset( - cache as Writeable, + cache, + inst, offset, _prevRange[0] // TODO binary search may be better here ); @@ -163,7 +177,7 @@ export const createVirtualStore = ( ); }, _getItemOffset(index) { - const offset = computeStartOffset(cache as Writeable, index); + const offset = computeStartOffset(cache, inst, index); if (isReverse) { return offset + max(0, viewportSize - getScrollSize()); } @@ -253,7 +267,7 @@ export const createVirtualStore = ( // Update item sizes let isNewItemMeasured = false; updated.forEach(([index, size]) => { - if (setItemSize(cache as Writeable, index, size)) { + if (setItemSize(cache as Writeable, inst, index, size)) { isNewItemMeasured = true; } }); @@ -285,6 +299,7 @@ export const createVirtualStore = ( const [shift, isRemove] = updateCacheLength( cache as Writeable, + inst, payload[0], true ); @@ -296,7 +311,7 @@ export const createVirtualStore = ( mutated = UPDATE_SCROLL + UPDATE_JUMP; _isShifting = true; } else { - updateCacheLength(cache as Writeable, payload[0]); + updateCacheLength(cache as Writeable, inst, payload[0]); } break; } diff --git a/src/react/VList.tsx b/src/react/VList.tsx index 74441a350..d22d1c3e7 100644 --- a/src/react/VList.tsx +++ b/src/react/VList.tsx @@ -40,7 +40,6 @@ import { } from "./Viewport"; import { CustomItemComponent, ListItem } from "./ListItem"; import { CacheSnapshot, ScrollToIndexAlign } from "../core/types"; -import { Cache } from "../core/cache"; import { flushSync } from "react-dom"; export type ScrollMode = "reverse" | "rtl"; @@ -212,7 +211,7 @@ export const VList = forwardRef( count, initialItemSize, initialItemCount, - cache as unknown as Cache | undefined, + cache, mode === "reverse", !initialItemSize ); diff --git a/src/react/WVList.tsx b/src/react/WVList.tsx index 18789172b..b8f9e03fc 100644 --- a/src/react/WVList.tsx +++ b/src/react/WVList.tsx @@ -38,7 +38,6 @@ import { Viewport as DefaultViewport, } from "./Viewport"; import { CustomItemComponent, ListItem } from "./ListItem"; -import { Cache } from "../core/cache"; import { flushSync } from "react-dom"; type CustomItemComponentOrElement = @@ -159,7 +158,7 @@ export const WVList = forwardRef( count, initialItemSize, initialItemCount, - cache as unknown as Cache | undefined, + cache, false, !initialItemSize );