@tanstack/virtual-core
Advanced tools
Comparing version
@@ -74,2 +74,3 @@ export * from './utils.js'; | ||
isScrollingResetDelay?: number; | ||
enabled?: boolean; | ||
} | ||
@@ -86,4 +87,4 @@ export declare class Virtualizer<TScrollElement extends Element | Window, TItemElement extends Element> { | ||
private pendingMeasuredCacheIndexes; | ||
scrollRect: Rect; | ||
scrollOffset: number; | ||
scrollRect: Rect | null; | ||
scrollOffset: number | null; | ||
scrollDirection: ScrollDirection | null; | ||
@@ -105,4 +106,5 @@ private scrollAdjustments; | ||
private getSize; | ||
private getScrollOffset; | ||
private getFurthestMeasurement; | ||
private getMeasurementOptions; | ||
private getFurthestMeasurement; | ||
private getMeasurements; | ||
@@ -119,5 +121,5 @@ calculateRange: () => { | ||
getVirtualItems: () => VirtualItem[]; | ||
getVirtualItemForOffset: (offset: number) => VirtualItem; | ||
getVirtualItemForOffset: (offset: number) => VirtualItem | undefined; | ||
getOffsetForAlignment: (toOffset: number, align: ScrollAlignment) => number; | ||
getOffsetForIndex: (index: number, align?: ScrollAlignment) => readonly [number, "auto"] | readonly [number, "start" | "center" | "end"]; | ||
getOffsetForIndex: (index: number, align?: ScrollAlignment) => readonly [number, "auto"] | readonly [number, "start" | "center" | "end"] | undefined; | ||
private isDynamicMode; | ||
@@ -124,0 +126,0 @@ private cancelScrollToIndex; |
@@ -174,2 +174,4 @@ import { debounce, memo, notUndefined, approxEqual } from "./utils.js"; | ||
this.pendingMeasuredCacheIndexes = []; | ||
this.scrollRect = null; | ||
this.scrollOffset = null; | ||
this.scrollDirection = null; | ||
@@ -235,2 +237,3 @@ this.scrollAdjustments = 0; | ||
isScrollingResetDelay: 150, | ||
enabled: true, | ||
...opts2 | ||
@@ -254,7 +257,8 @@ }; | ||
this.scrollElement = null; | ||
this.targetWindow = null; | ||
this.observer.disconnect(); | ||
this.measureElementCache.clear(); | ||
}; | ||
this._didMount = () => { | ||
this.measureElementCache.forEach(this.observer.observe); | ||
return () => { | ||
this.observer.disconnect(); | ||
this.cleanup(); | ||
@@ -265,5 +269,9 @@ }; | ||
var _a; | ||
const scrollElement = this.options.getScrollElement(); | ||
const scrollElement = this.options.enabled ? this.options.getScrollElement() : null; | ||
if (this.scrollElement !== scrollElement) { | ||
this.cleanup(); | ||
if (!scrollElement) { | ||
this.notify(false, false); | ||
return; | ||
} | ||
this.scrollElement = scrollElement; | ||
@@ -275,3 +283,3 @@ if (this.scrollElement && "ownerDocument" in this.scrollElement) { | ||
} | ||
this._scrollToOffset(this.scrollOffset, { | ||
this._scrollToOffset(this.getScrollOffset(), { | ||
adjustments: void 0, | ||
@@ -289,3 +297,3 @@ behavior: void 0 | ||
this.scrollAdjustments = 0; | ||
this.scrollDirection = isScrolling ? this.scrollOffset < offset ? "forward" : "backward" : null; | ||
this.scrollDirection = isScrolling ? this.getScrollOffset() < offset ? "forward" : "backward" : null; | ||
this.scrollOffset = offset; | ||
@@ -300,24 +308,17 @@ const prevIsScrolling = this.isScrolling; | ||
this.getSize = () => { | ||
if (!this.options.enabled) { | ||
this.scrollRect = null; | ||
return 0; | ||
} | ||
this.scrollRect = this.scrollRect ?? this.options.initialRect; | ||
return this.scrollRect[this.options.horizontal ? "width" : "height"]; | ||
}; | ||
this.getMeasurementOptions = memo( | ||
() => [ | ||
this.options.count, | ||
this.options.paddingStart, | ||
this.options.scrollMargin, | ||
this.options.getItemKey | ||
], | ||
(count, paddingStart, scrollMargin, getItemKey) => { | ||
this.pendingMeasuredCacheIndexes = []; | ||
return { | ||
count, | ||
paddingStart, | ||
scrollMargin, | ||
getItemKey | ||
}; | ||
}, | ||
{ | ||
key: false | ||
this.getScrollOffset = () => { | ||
if (!this.options.enabled) { | ||
this.scrollOffset = null; | ||
return 0; | ||
} | ||
); | ||
this.scrollOffset = this.scrollOffset ?? (typeof this.options.initialOffset === "function" ? this.options.initialOffset() : this.options.initialOffset); | ||
return this.scrollOffset; | ||
}; | ||
this.getFurthestMeasurement = (measurements, index) => { | ||
@@ -350,5 +351,38 @@ const furthestMeasurementsFound = /* @__PURE__ */ new Map(); | ||
}; | ||
this.getMeasurementOptions = memo( | ||
() => [ | ||
this.options.count, | ||
this.options.paddingStart, | ||
this.options.scrollMargin, | ||
this.options.getItemKey, | ||
this.options.enabled | ||
], | ||
(count, paddingStart, scrollMargin, getItemKey, enabled) => { | ||
this.pendingMeasuredCacheIndexes = []; | ||
return { | ||
count, | ||
paddingStart, | ||
scrollMargin, | ||
getItemKey, | ||
enabled | ||
}; | ||
}, | ||
{ | ||
key: false | ||
} | ||
); | ||
this.getMeasurements = memo( | ||
() => [this.getMeasurementOptions(), this.itemSizeCache], | ||
({ count, paddingStart, scrollMargin, getItemKey }, itemSizeCache) => { | ||
({ count, paddingStart, scrollMargin, getItemKey, enabled }, itemSizeCache) => { | ||
if (!enabled) { | ||
this.measurementsCache = []; | ||
this.itemSizeCache.clear(); | ||
return []; | ||
} | ||
if (this.measurementsCache.length === 0) { | ||
this.measurementsCache = this.options.initialMeasurementsCache; | ||
this.measurementsCache.forEach((item) => { | ||
this.itemSizeCache.set(item.key, item.size); | ||
}); | ||
} | ||
const min = this.pendingMeasuredCacheIndexes.length > 0 ? Math.min(...this.pendingMeasuredCacheIndexes) : 0; | ||
@@ -383,3 +417,3 @@ this.pendingMeasuredCacheIndexes = []; | ||
this.calculateRange = memo( | ||
() => [this.getMeasurements(), this.getSize(), this.scrollOffset], | ||
() => [this.getMeasurements(), this.getSize(), this.getScrollOffset()], | ||
(measurements, outerSize, scrollOffset) => { | ||
@@ -429,3 +463,3 @@ return this.range = measurements.length > 0 && outerSize > 0 ? calculateRange({ | ||
this._measureElement = (node, entry) => { | ||
const item = this.measurementsCache[this.indexFromElement(node)]; | ||
const item = this.getMeasurements()[this.indexFromElement(node)]; | ||
if (!item || !node.isConnected) { | ||
@@ -455,7 +489,7 @@ this.measureElementCache.forEach((cached, key) => { | ||
if (delta !== 0) { | ||
if (this.shouldAdjustScrollPositionOnItemSizeChange !== void 0 ? this.shouldAdjustScrollPositionOnItemSizeChange(item, delta, this) : item.start < this.scrollOffset + this.scrollAdjustments) { | ||
if (this.shouldAdjustScrollPositionOnItemSizeChange !== void 0 ? this.shouldAdjustScrollPositionOnItemSizeChange(item, delta, this) : item.start < this.getScrollOffset() + this.scrollAdjustments) { | ||
if (process.env.NODE_ENV !== "production" && this.options.debug) { | ||
console.info("correction", delta); | ||
} | ||
this._scrollToOffset(this.scrollOffset, { | ||
this._scrollToOffset(this.getScrollOffset(), { | ||
adjustments: this.scrollAdjustments += delta, | ||
@@ -494,2 +528,5 @@ behavior: void 0 | ||
const measurements = this.getMeasurements(); | ||
if (measurements.length === 0) { | ||
return void 0; | ||
} | ||
return notUndefined( | ||
@@ -506,6 +543,7 @@ measurements[findNearestBinarySearch( | ||
const size = this.getSize(); | ||
const scrollOffset = this.getScrollOffset(); | ||
if (align === "auto") { | ||
if (toOffset <= this.scrollOffset) { | ||
if (toOffset <= scrollOffset) { | ||
align = "start"; | ||
} else if (toOffset >= this.scrollOffset + size) { | ||
} else if (toOffset >= scrollOffset + size) { | ||
align = "end"; | ||
@@ -525,3 +563,3 @@ } else { | ||
const scrollSize = this.scrollElement ? "document" in this.scrollElement ? this.scrollElement.document.documentElement[scrollSizeProp] : this.scrollElement[scrollSizeProp] : 0; | ||
const maxOffset = scrollSize - this.getSize(); | ||
const maxOffset = scrollSize - size; | ||
return Math.max(Math.min(maxOffset, toOffset), 0); | ||
@@ -531,13 +569,18 @@ }; | ||
index = Math.max(0, Math.min(index, this.options.count - 1)); | ||
const measurement = notUndefined(this.getMeasurements()[index]); | ||
const item = this.getMeasurements()[index]; | ||
if (!item) { | ||
return void 0; | ||
} | ||
const size = this.getSize(); | ||
const scrollOffset = this.getScrollOffset(); | ||
if (align === "auto") { | ||
if (measurement.end >= this.scrollOffset + this.getSize() - this.options.scrollPaddingEnd) { | ||
if (item.end >= scrollOffset + size - this.options.scrollPaddingEnd) { | ||
align = "end"; | ||
} else if (measurement.start <= this.scrollOffset + this.options.scrollPaddingStart) { | ||
} else if (item.start <= scrollOffset + this.options.scrollPaddingStart) { | ||
align = "start"; | ||
} else { | ||
return [this.scrollOffset, align]; | ||
return [scrollOffset, align]; | ||
} | ||
} | ||
const toOffset = align === "end" ? measurement.end + this.options.scrollPaddingEnd : measurement.start - this.options.scrollPaddingStart; | ||
const toOffset = align === "end" ? item.end + this.options.scrollPaddingEnd : item.start - this.options.scrollPaddingStart; | ||
return [this.getOffsetForAlignment(toOffset, align), align]; | ||
@@ -572,4 +615,7 @@ }; | ||
} | ||
const [toOffset, align] = this.getOffsetForIndex(index, initialAlign); | ||
this._scrollToOffset(toOffset, { adjustments: void 0, behavior }); | ||
const offsetAndAlign = this.getOffsetForIndex(index, initialAlign); | ||
if (!offsetAndAlign) | ||
return; | ||
const [offset, align] = offsetAndAlign; | ||
this._scrollToOffset(offset, { adjustments: void 0, behavior }); | ||
if (behavior !== "smooth" && this.isDynamicMode() && this.targetWindow) { | ||
@@ -582,4 +628,6 @@ this.scrollToIndexTimeoutId = this.targetWindow.setTimeout(() => { | ||
if (elementInDOM) { | ||
const [toOffset2] = this.getOffsetForIndex(index, align); | ||
if (!approxEqual(toOffset2, this.scrollOffset)) { | ||
const [latestOffset] = notUndefined( | ||
this.getOffsetForIndex(index, align) | ||
); | ||
if (!approxEqual(latestOffset, this.getScrollOffset())) { | ||
this.scrollToIndex(index, { align, behavior }); | ||
@@ -600,3 +648,3 @@ } | ||
} | ||
this._scrollToOffset(this.scrollOffset + delta, { | ||
this._scrollToOffset(this.getScrollOffset() + delta, { | ||
adjustments: void 0, | ||
@@ -631,9 +679,2 @@ behavior | ||
this.setOptions(opts); | ||
this.scrollRect = this.options.initialRect; | ||
this.scrollOffset = typeof this.options.initialOffset === "function" ? this.options.initialOffset() : this.options.initialOffset; | ||
this.measurementsCache = this.options.initialMeasurementsCache; | ||
this.measurementsCache.forEach((item) => { | ||
this.itemSizeCache.set(item.key, item.size); | ||
}); | ||
this.notify(false, false); | ||
} | ||
@@ -640,0 +681,0 @@ } |
{ | ||
"name": "@tanstack/virtual-core", | ||
"version": "3.5.1", | ||
"version": "3.6.0", | ||
"description": "Headless UI for virtualizing scrollable elements in TS/JS + Frameworks", | ||
@@ -5,0 +5,0 @@ "author": "Tanner Linsley", |
175
src/index.ts
@@ -321,2 +321,3 @@ import { approxEqual, memo, notUndefined, debounce } from './utils' | ||
isScrollingResetDelay?: number | ||
enabled?: boolean | ||
} | ||
@@ -337,4 +338,4 @@ | ||
private pendingMeasuredCacheIndexes: number[] = [] | ||
scrollRect: Rect | ||
scrollOffset: number | ||
scrollRect: Rect | null = null | ||
scrollOffset: number | null = null | ||
scrollDirection: ScrollDirection | null = null | ||
@@ -380,13 +381,2 @@ private scrollAdjustments: number = 0 | ||
this.setOptions(opts) | ||
this.scrollRect = this.options.initialRect | ||
this.scrollOffset = | ||
typeof this.options.initialOffset === 'function' | ||
? this.options.initialOffset() | ||
: this.options.initialOffset | ||
this.measurementsCache = this.options.initialMeasurementsCache | ||
this.measurementsCache.forEach((item) => { | ||
this.itemSizeCache.set(item.key, item.size) | ||
}) | ||
this.notify(false, false) | ||
} | ||
@@ -419,2 +409,3 @@ | ||
isScrollingResetDelay: 150, | ||
enabled: true, | ||
...opts, | ||
@@ -444,8 +435,9 @@ } | ||
this.scrollElement = null | ||
this.targetWindow = null | ||
this.observer.disconnect() | ||
this.measureElementCache.clear() | ||
} | ||
_didMount = () => { | ||
this.measureElementCache.forEach(this.observer.observe) | ||
return () => { | ||
this.observer.disconnect() | ||
this.cleanup() | ||
@@ -456,3 +448,5 @@ } | ||
_willUpdate = () => { | ||
const scrollElement = this.options.getScrollElement() | ||
const scrollElement = this.options.enabled | ||
? this.options.getScrollElement() | ||
: null | ||
@@ -462,2 +456,7 @@ if (this.scrollElement !== scrollElement) { | ||
if (!scrollElement) { | ||
this.notify(false, false) | ||
return | ||
} | ||
this.scrollElement = scrollElement | ||
@@ -471,3 +470,3 @@ | ||
this._scrollToOffset(this.scrollOffset, { | ||
this._scrollToOffset(this.getScrollOffset(), { | ||
adjustments: undefined, | ||
@@ -488,3 +487,3 @@ behavior: undefined, | ||
this.scrollDirection = isScrolling | ||
? this.scrollOffset < offset | ||
? this.getScrollOffset() < offset | ||
? 'forward' | ||
@@ -505,26 +504,27 @@ : 'backward' | ||
private getSize = () => { | ||
if (!this.options.enabled) { | ||
this.scrollRect = null | ||
return 0 | ||
} | ||
this.scrollRect = this.scrollRect ?? this.options.initialRect | ||
return this.scrollRect[this.options.horizontal ? 'width' : 'height'] | ||
} | ||
private getMeasurementOptions = memo( | ||
() => [ | ||
this.options.count, | ||
this.options.paddingStart, | ||
this.options.scrollMargin, | ||
this.options.getItemKey, | ||
], | ||
(count, paddingStart, scrollMargin, getItemKey) => { | ||
this.pendingMeasuredCacheIndexes = [] | ||
return { | ||
count, | ||
paddingStart, | ||
scrollMargin, | ||
getItemKey, | ||
} | ||
}, | ||
{ | ||
key: false, | ||
}, | ||
) | ||
private getScrollOffset = () => { | ||
if (!this.options.enabled) { | ||
this.scrollOffset = null | ||
return 0 | ||
} | ||
this.scrollOffset = | ||
this.scrollOffset ?? | ||
(typeof this.options.initialOffset === 'function' | ||
? this.options.initialOffset() | ||
: this.options.initialOffset) | ||
return this.scrollOffset | ||
} | ||
private getFurthestMeasurement = ( | ||
@@ -571,5 +571,44 @@ measurements: VirtualItem[], | ||
private getMeasurementOptions = memo( | ||
() => [ | ||
this.options.count, | ||
this.options.paddingStart, | ||
this.options.scrollMargin, | ||
this.options.getItemKey, | ||
this.options.enabled, | ||
], | ||
(count, paddingStart, scrollMargin, getItemKey, enabled) => { | ||
this.pendingMeasuredCacheIndexes = [] | ||
return { | ||
count, | ||
paddingStart, | ||
scrollMargin, | ||
getItemKey, | ||
enabled, | ||
} | ||
}, | ||
{ | ||
key: false, | ||
}, | ||
) | ||
private getMeasurements = memo( | ||
() => [this.getMeasurementOptions(), this.itemSizeCache], | ||
({ count, paddingStart, scrollMargin, getItemKey }, itemSizeCache) => { | ||
( | ||
{ count, paddingStart, scrollMargin, getItemKey, enabled }, | ||
itemSizeCache, | ||
) => { | ||
if (!enabled) { | ||
this.measurementsCache = [] | ||
this.itemSizeCache.clear() | ||
return [] | ||
} | ||
if (this.measurementsCache.length === 0) { | ||
this.measurementsCache = this.options.initialMeasurementsCache | ||
this.measurementsCache.forEach((item) => { | ||
this.itemSizeCache.set(item.key, item.size) | ||
}) | ||
} | ||
const min = | ||
@@ -628,3 +667,3 @@ this.pendingMeasuredCacheIndexes.length > 0 | ||
calculateRange = memo( | ||
() => [this.getMeasurements(), this.getSize(), this.scrollOffset], | ||
() => [this.getMeasurements(), this.getSize(), this.getScrollOffset()], | ||
(measurements, outerSize, scrollOffset) => { | ||
@@ -687,3 +726,3 @@ return (this.range = | ||
) => { | ||
const item = this.measurementsCache[this.indexFromElement(node)] | ||
const item = this.getMeasurements()[this.indexFromElement(node)] | ||
@@ -723,3 +762,3 @@ if (!item || !node.isConnected) { | ||
? this.shouldAdjustScrollPositionOnItemSizeChange(item, delta, this) | ||
: item.start < this.scrollOffset + this.scrollAdjustments | ||
: item.start < this.getScrollOffset() + this.scrollAdjustments | ||
) { | ||
@@ -730,3 +769,3 @@ if (process.env.NODE_ENV !== 'production' && this.options.debug) { | ||
this._scrollToOffset(this.scrollOffset, { | ||
this._scrollToOffset(this.getScrollOffset(), { | ||
adjustments: (this.scrollAdjustments += delta), | ||
@@ -774,3 +813,5 @@ behavior: undefined, | ||
const measurements = this.getMeasurements() | ||
if (measurements.length === 0) { | ||
return undefined | ||
} | ||
return notUndefined( | ||
@@ -790,7 +831,8 @@ measurements[ | ||
const size = this.getSize() | ||
const scrollOffset = this.getScrollOffset() | ||
if (align === 'auto') { | ||
if (toOffset <= this.scrollOffset) { | ||
if (toOffset <= scrollOffset) { | ||
align = 'start' | ||
} else if (toOffset >= this.scrollOffset + size) { | ||
} else if (toOffset >= scrollOffset + size) { | ||
align = 'end' | ||
@@ -819,3 +861,3 @@ } else { | ||
const maxOffset = scrollSize - this.getSize() | ||
const maxOffset = scrollSize - size | ||
@@ -828,17 +870,17 @@ return Math.max(Math.min(maxOffset, toOffset), 0) | ||
const measurement = notUndefined(this.getMeasurements()[index]) | ||
const item = this.getMeasurements()[index] | ||
if (!item) { | ||
return undefined | ||
} | ||
const size = this.getSize() | ||
const scrollOffset = this.getScrollOffset() | ||
if (align === 'auto') { | ||
if ( | ||
measurement.end >= | ||
this.scrollOffset + this.getSize() - this.options.scrollPaddingEnd | ||
) { | ||
if (item.end >= scrollOffset + size - this.options.scrollPaddingEnd) { | ||
align = 'end' | ||
} else if ( | ||
measurement.start <= | ||
this.scrollOffset + this.options.scrollPaddingStart | ||
) { | ||
} else if (item.start <= scrollOffset + this.options.scrollPaddingStart) { | ||
align = 'start' | ||
} else { | ||
return [this.scrollOffset, align] as const | ||
return [scrollOffset, align] as const | ||
} | ||
@@ -849,4 +891,4 @@ } | ||
align === 'end' | ||
? measurement.end + this.options.scrollPaddingEnd | ||
: measurement.start - this.options.scrollPaddingStart | ||
? item.end + this.options.scrollPaddingEnd | ||
: item.start - this.options.scrollPaddingStart | ||
@@ -897,6 +939,9 @@ return [this.getOffsetForAlignment(toOffset, align), align] as const | ||
const [toOffset, align] = this.getOffsetForIndex(index, initialAlign) | ||
const offsetAndAlign = this.getOffsetForIndex(index, initialAlign) | ||
if (!offsetAndAlign) return | ||
this._scrollToOffset(toOffset, { adjustments: undefined, behavior }) | ||
const [offset, align] = offsetAndAlign | ||
this._scrollToOffset(offset, { adjustments: undefined, behavior }) | ||
if (behavior !== 'smooth' && this.isDynamicMode() && this.targetWindow) { | ||
@@ -911,5 +956,7 @@ this.scrollToIndexTimeoutId = this.targetWindow.setTimeout(() => { | ||
if (elementInDOM) { | ||
const [toOffset] = this.getOffsetForIndex(index, align) | ||
const [latestOffset] = notUndefined( | ||
this.getOffsetForIndex(index, align), | ||
) | ||
if (!approxEqual(toOffset, this.scrollOffset)) { | ||
if (!approxEqual(latestOffset, this.getScrollOffset())) { | ||
this.scrollToIndex(index, { align, behavior }) | ||
@@ -933,3 +980,3 @@ } | ||
this._scrollToOffset(this.scrollOffset + delta, { | ||
this._scrollToOffset(this.getScrollOffset() + delta, { | ||
adjustments: undefined, | ||
@@ -936,0 +983,0 @@ behavior, |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
191139
3.62%2660
4.85%