@tanstack/virtual-core
Advanced tools
Comparing version 3.5.1 to 3.6.0
@@ -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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
191139
2660