@tanstack/virtual-core
Advanced tools
+104
-15
@@ -169,2 +169,3 @@ "use strict"; | ||
| this.lanesSettling = false; | ||
| this.pendingScrollAnchor = null; | ||
| this.scrollRect = null; | ||
@@ -237,2 +238,3 @@ this.scrollOffset = null; | ||
| this.setOptions = (opts2) => { | ||
| var _a, _b; | ||
| const merged = { | ||
@@ -258,2 +260,5 @@ debug: false, | ||
| lanes: 1, | ||
| anchorTo: "start", | ||
| followOnAppend: false, | ||
| scrollEndThreshold: 1, | ||
| isScrollingResetDelay: 150, | ||
@@ -270,3 +275,32 @@ enabled: true, | ||
| } | ||
| const prevOptions = this.options; | ||
| let anchor = null; | ||
| let followOnAppend = null; | ||
| if (prevOptions !== void 0 && prevOptions.enabled && merged.enabled && merged.anchorTo === "end" && this.scrollElement !== null) { | ||
| const prevCount = prevOptions.count; | ||
| const nextCount = merged.count; | ||
| const measurements = this.getMeasurements(); | ||
| const prevFirstKey = prevCount > 0 ? ((_a = measurements[0]) == null ? void 0 : _a.key) ?? prevOptions.getItemKey(0) : null; | ||
| const prevLastKey = prevCount > 0 ? ((_b = measurements[prevCount - 1]) == null ? void 0 : _b.key) ?? prevOptions.getItemKey(prevCount - 1) : null; | ||
| const didCountChange = nextCount !== prevCount; | ||
| const didEdgeKeysChange = didCountChange || prevCount > 0 && nextCount > 0 && (merged.getItemKey(0) !== prevFirstKey || merged.getItemKey(nextCount - 1) !== prevLastKey); | ||
| if (didEdgeKeysChange) { | ||
| const item = prevCount > 0 ? this.getVirtualItemForOffset(this.getScrollOffset()) ?? measurements[0] : null; | ||
| if (item) { | ||
| anchor = [item.key, this.getScrollOffset() - item.start]; | ||
| } | ||
| const behavior = merged.followOnAppend === true ? "auto" : merged.followOnAppend || null; | ||
| if (behavior && nextCount > prevCount && this.isAtEnd(prevOptions.scrollEndThreshold) && (prevCount === 0 || merged.getItemKey(nextCount - 1) !== prevLastKey)) { | ||
| followOnAppend = behavior; | ||
| } | ||
| } | ||
| } | ||
| this.options = merged; | ||
| if (anchor || followOnAppend) { | ||
| this.pendingScrollAnchor = [ | ||
| (anchor == null ? void 0 : anchor[0]) ?? null, | ||
| (anchor == null ? void 0 : anchor[1]) ?? 0, | ||
| followOnAppend | ||
| ]; | ||
| } | ||
| }; | ||
@@ -403,2 +437,24 @@ this.notify = (sync) => { | ||
| } | ||
| const anchor = this.pendingScrollAnchor; | ||
| this.pendingScrollAnchor = null; | ||
| if (anchor && this.scrollElement && this.options.enabled) { | ||
| const [key, offset, followOnAppend] = anchor; | ||
| if (key !== null) { | ||
| const { count, getItemKey } = this.options; | ||
| let index = 0; | ||
| while (index < count && getItemKey(index) !== key) { | ||
| index++; | ||
| } | ||
| const item = index < count ? this.getMeasurements()[index] : void 0; | ||
| if (item) { | ||
| const delta = item.start + offset - this.getScrollOffset(); | ||
| if (!utils.approxEqual(delta, 0)) { | ||
| this.applyScrollAdjustment(delta); | ||
| } | ||
| } | ||
| } | ||
| if (followOnAppend) { | ||
| this.scrollToEnd({ behavior: followOnAppend }); | ||
| } | ||
| } | ||
| }; | ||
@@ -729,3 +785,3 @@ this._flushIosDeferredIfReady = () => { | ||
| this.resizeItem = (index, size) => { | ||
| var _a; | ||
| var _a, _b; | ||
| if (index < 0 || index >= this.options.count) return; | ||
@@ -750,3 +806,5 @@ let cachedSize; | ||
| if (delta !== 0) { | ||
| if (((_a = this.scrollState) == null ? void 0 : _a.behavior) !== "smooth" && (this.shouldAdjustScrollPositionOnItemSizeChange !== void 0 ? this.shouldAdjustScrollPositionOnItemSizeChange( | ||
| const wasAtEnd = this.options.anchorTo === "end" && ((_a = this.scrollState) == null ? void 0 : _a.behavior) !== "smooth" && this.getVirtualDistanceFromEnd() <= this.options.scrollEndThreshold; | ||
| const prevTotalSize = wasAtEnd ? this.getTotalSize() : 0; | ||
| const shouldAdjustScroll = ((_b = this.scrollState) == null ? void 0 : _b.behavior) !== "smooth" && (this.shouldAdjustScrollPositionOnItemSizeChange !== void 0 ? this.shouldAdjustScrollPositionOnItemSizeChange( | ||
| // The callback expects a VirtualItem; build one lazily only | ||
@@ -772,15 +830,3 @@ // when the consumer actually supplied a custom predicate. | ||
| itemStart < this.getScrollOffset() + this.scrollAdjustments && this.scrollDirection !== "backward" | ||
| ))) { | ||
| if (process.env.NODE_ENV !== "production" && this.options.debug) { | ||
| console.info("correction", delta); | ||
| } | ||
| if (isIOSWebKit() && (this.isScrolling || this._iosTouching || this._iosJustTouchEnded)) { | ||
| this._iosDeferredAdjustment += delta; | ||
| } else { | ||
| this._scrollToOffset(this.getScrollOffset(), { | ||
| adjustments: this.scrollAdjustments += delta, | ||
| behavior: void 0 | ||
| }); | ||
| } | ||
| } | ||
| )); | ||
| if (this.pendingMin === null || index < this.pendingMin) { | ||
@@ -791,2 +837,7 @@ this.pendingMin = index; | ||
| this.itemSizeCacheVersion++; | ||
| if (wasAtEnd) { | ||
| this.applyScrollAdjustment(this.getTotalSize() - prevTotalSize); | ||
| } else if (shouldAdjustScroll) { | ||
| this.applyScrollAdjustment(delta); | ||
| } | ||
| this.notify(false); | ||
@@ -835,2 +886,14 @@ } | ||
| }; | ||
| this.getVirtualDistanceFromEnd = () => { | ||
| return Math.max( | ||
| this.getTotalSize() - this.getSize() - this.getScrollOffset(), | ||
| 0 | ||
| ); | ||
| }; | ||
| this.getDistanceFromEnd = () => { | ||
| return Math.max(this.getMaxScrollOffset() - this.getScrollOffset(), 0); | ||
| }; | ||
| this.isAtEnd = (threshold = this.options.scrollEndThreshold) => { | ||
| return this.getDistanceFromEnd() <= threshold; | ||
| }; | ||
| this.getOffsetForAlignment = (toOffset, align, itemSize = 0) => { | ||
@@ -925,2 +988,14 @@ if (!this.scrollElement) return 0; | ||
| }; | ||
| this.scrollToEnd = ({ behavior = "auto" } = {}) => { | ||
| if (this.options.count > 0) { | ||
| this.scrollToIndex(this.options.count - 1, { | ||
| align: "end", | ||
| behavior | ||
| }); | ||
| return; | ||
| } | ||
| this.scrollToOffset(Math.max(this.getTotalSize() - this.getSize(), 0), { | ||
| behavior | ||
| }); | ||
| }; | ||
| this.getTotalSize = () => { | ||
@@ -991,2 +1066,16 @@ var _a; | ||
| } | ||
| applyScrollAdjustment(delta, behavior) { | ||
| if (delta === 0) return; | ||
| if (process.env.NODE_ENV !== "production" && this.options.debug) { | ||
| console.info("correction", delta); | ||
| } | ||
| if (isIOSWebKit() && (this.isScrolling || this._iosTouching || this._iosJustTouchEnded)) { | ||
| this._iosDeferredAdjustment += delta; | ||
| } else { | ||
| this._scrollToOffset(this.getScrollOffset(), { | ||
| adjustments: this.scrollAdjustments += delta, | ||
| behavior | ||
| }); | ||
| } | ||
| } | ||
| scheduleScrollReconcile() { | ||
@@ -993,0 +1082,0 @@ if (!this.targetWindow) { |
+12
-0
@@ -7,2 +7,4 @@ export declare const _resetIOSDetectionForTests: () => void; | ||
| type ScrollBehavior = 'auto' | 'smooth' | 'instant'; | ||
| type ScrollAnchor = 'start' | 'end'; | ||
| type FollowOnAppend = boolean | ScrollBehavior; | ||
| export interface ScrollToOptions { | ||
@@ -14,2 +16,3 @@ align?: ScrollAlignment; | ||
| type ScrollToIndexOptions = ScrollToOptions; | ||
| type ScrollToEndOptions = Pick<ScrollToOptions, 'behavior'>; | ||
| export interface Range { | ||
@@ -79,2 +82,5 @@ startIndex: number; | ||
| lanes?: number; | ||
| anchorTo?: ScrollAnchor; | ||
| followOnAppend?: FollowOnAppend; | ||
| scrollEndThreshold?: number; | ||
| isScrollingResetDelay?: number; | ||
@@ -103,2 +109,3 @@ useScrollendEvent?: boolean; | ||
| private lanesSettling; | ||
| private pendingScrollAnchor; | ||
| scrollRect: Rect | null; | ||
@@ -124,2 +131,3 @@ scrollOffset: number | null; | ||
| private notify; | ||
| private applyScrollAdjustment; | ||
| private maybeNotify; | ||
@@ -164,2 +172,5 @@ private cleanup; | ||
| private getMaxScrollOffset; | ||
| private getVirtualDistanceFromEnd; | ||
| getDistanceFromEnd: () => number; | ||
| isAtEnd: (threshold?: number) => boolean; | ||
| getOffsetForAlignment: (toOffset: number, align: ScrollAlignment, itemSize?: number) => number; | ||
@@ -170,2 +181,3 @@ getOffsetForIndex: (index: number, align?: ScrollAlignment) => readonly [number, "auto"] | readonly [number, "start" | "center" | "end"] | undefined; | ||
| scrollBy: (delta: number, { behavior }?: ScrollToOffsetOptions) => void; | ||
| scrollToEnd: ({ behavior }?: ScrollToEndOptions) => void; | ||
| getTotalSize: () => number; | ||
@@ -172,0 +184,0 @@ /** |
+12
-0
@@ -7,2 +7,4 @@ export declare const _resetIOSDetectionForTests: () => void; | ||
| type ScrollBehavior = 'auto' | 'smooth' | 'instant'; | ||
| type ScrollAnchor = 'start' | 'end'; | ||
| type FollowOnAppend = boolean | ScrollBehavior; | ||
| export interface ScrollToOptions { | ||
@@ -14,2 +16,3 @@ align?: ScrollAlignment; | ||
| type ScrollToIndexOptions = ScrollToOptions; | ||
| type ScrollToEndOptions = Pick<ScrollToOptions, 'behavior'>; | ||
| export interface Range { | ||
@@ -79,2 +82,5 @@ startIndex: number; | ||
| lanes?: number; | ||
| anchorTo?: ScrollAnchor; | ||
| followOnAppend?: FollowOnAppend; | ||
| scrollEndThreshold?: number; | ||
| isScrollingResetDelay?: number; | ||
@@ -103,2 +109,3 @@ useScrollendEvent?: boolean; | ||
| private lanesSettling; | ||
| private pendingScrollAnchor; | ||
| scrollRect: Rect | null; | ||
@@ -124,2 +131,3 @@ scrollOffset: number | null; | ||
| private notify; | ||
| private applyScrollAdjustment; | ||
| private maybeNotify; | ||
@@ -164,2 +172,5 @@ private cleanup; | ||
| private getMaxScrollOffset; | ||
| private getVirtualDistanceFromEnd; | ||
| getDistanceFromEnd: () => number; | ||
| isAtEnd: (threshold?: number) => boolean; | ||
| getOffsetForAlignment: (toOffset: number, align: ScrollAlignment, itemSize?: number) => number; | ||
@@ -170,2 +181,3 @@ getOffsetForIndex: (index: number, align?: ScrollAlignment) => readonly [number, "auto"] | readonly [number, "start" | "center" | "end"] | undefined; | ||
| scrollBy: (delta: number, { behavior }?: ScrollToOffsetOptions) => void; | ||
| scrollToEnd: ({ behavior }?: ScrollToEndOptions) => void; | ||
| getTotalSize: () => number; | ||
@@ -172,0 +184,0 @@ /** |
+105
-16
| import { createLazyMeasurementsView } from "./lazy-measurements.js"; | ||
| import { memo, notUndefined, approxEqual, debounce } from "./utils.js"; | ||
| import { memo, approxEqual, notUndefined, debounce } from "./utils.js"; | ||
| let _isIOSResult; | ||
@@ -167,2 +167,3 @@ const isIOSWebKit = () => { | ||
| this.lanesSettling = false; | ||
| this.pendingScrollAnchor = null; | ||
| this.scrollRect = null; | ||
@@ -235,2 +236,3 @@ this.scrollOffset = null; | ||
| this.setOptions = (opts2) => { | ||
| var _a, _b; | ||
| const merged = { | ||
@@ -256,2 +258,5 @@ debug: false, | ||
| lanes: 1, | ||
| anchorTo: "start", | ||
| followOnAppend: false, | ||
| scrollEndThreshold: 1, | ||
| isScrollingResetDelay: 150, | ||
@@ -268,3 +273,32 @@ enabled: true, | ||
| } | ||
| const prevOptions = this.options; | ||
| let anchor = null; | ||
| let followOnAppend = null; | ||
| if (prevOptions !== void 0 && prevOptions.enabled && merged.enabled && merged.anchorTo === "end" && this.scrollElement !== null) { | ||
| const prevCount = prevOptions.count; | ||
| const nextCount = merged.count; | ||
| const measurements = this.getMeasurements(); | ||
| const prevFirstKey = prevCount > 0 ? ((_a = measurements[0]) == null ? void 0 : _a.key) ?? prevOptions.getItemKey(0) : null; | ||
| const prevLastKey = prevCount > 0 ? ((_b = measurements[prevCount - 1]) == null ? void 0 : _b.key) ?? prevOptions.getItemKey(prevCount - 1) : null; | ||
| const didCountChange = nextCount !== prevCount; | ||
| const didEdgeKeysChange = didCountChange || prevCount > 0 && nextCount > 0 && (merged.getItemKey(0) !== prevFirstKey || merged.getItemKey(nextCount - 1) !== prevLastKey); | ||
| if (didEdgeKeysChange) { | ||
| const item = prevCount > 0 ? this.getVirtualItemForOffset(this.getScrollOffset()) ?? measurements[0] : null; | ||
| if (item) { | ||
| anchor = [item.key, this.getScrollOffset() - item.start]; | ||
| } | ||
| const behavior = merged.followOnAppend === true ? "auto" : merged.followOnAppend || null; | ||
| if (behavior && nextCount > prevCount && this.isAtEnd(prevOptions.scrollEndThreshold) && (prevCount === 0 || merged.getItemKey(nextCount - 1) !== prevLastKey)) { | ||
| followOnAppend = behavior; | ||
| } | ||
| } | ||
| } | ||
| this.options = merged; | ||
| if (anchor || followOnAppend) { | ||
| this.pendingScrollAnchor = [ | ||
| (anchor == null ? void 0 : anchor[0]) ?? null, | ||
| (anchor == null ? void 0 : anchor[1]) ?? 0, | ||
| followOnAppend | ||
| ]; | ||
| } | ||
| }; | ||
@@ -401,2 +435,24 @@ this.notify = (sync) => { | ||
| } | ||
| const anchor = this.pendingScrollAnchor; | ||
| this.pendingScrollAnchor = null; | ||
| if (anchor && this.scrollElement && this.options.enabled) { | ||
| const [key, offset, followOnAppend] = anchor; | ||
| if (key !== null) { | ||
| const { count, getItemKey } = this.options; | ||
| let index = 0; | ||
| while (index < count && getItemKey(index) !== key) { | ||
| index++; | ||
| } | ||
| const item = index < count ? this.getMeasurements()[index] : void 0; | ||
| if (item) { | ||
| const delta = item.start + offset - this.getScrollOffset(); | ||
| if (!approxEqual(delta, 0)) { | ||
| this.applyScrollAdjustment(delta); | ||
| } | ||
| } | ||
| } | ||
| if (followOnAppend) { | ||
| this.scrollToEnd({ behavior: followOnAppend }); | ||
| } | ||
| } | ||
| }; | ||
@@ -727,3 +783,3 @@ this._flushIosDeferredIfReady = () => { | ||
| this.resizeItem = (index, size) => { | ||
| var _a; | ||
| var _a, _b; | ||
| if (index < 0 || index >= this.options.count) return; | ||
@@ -748,3 +804,5 @@ let cachedSize; | ||
| if (delta !== 0) { | ||
| if (((_a = this.scrollState) == null ? void 0 : _a.behavior) !== "smooth" && (this.shouldAdjustScrollPositionOnItemSizeChange !== void 0 ? this.shouldAdjustScrollPositionOnItemSizeChange( | ||
| const wasAtEnd = this.options.anchorTo === "end" && ((_a = this.scrollState) == null ? void 0 : _a.behavior) !== "smooth" && this.getVirtualDistanceFromEnd() <= this.options.scrollEndThreshold; | ||
| const prevTotalSize = wasAtEnd ? this.getTotalSize() : 0; | ||
| const shouldAdjustScroll = ((_b = this.scrollState) == null ? void 0 : _b.behavior) !== "smooth" && (this.shouldAdjustScrollPositionOnItemSizeChange !== void 0 ? this.shouldAdjustScrollPositionOnItemSizeChange( | ||
| // The callback expects a VirtualItem; build one lazily only | ||
@@ -770,15 +828,3 @@ // when the consumer actually supplied a custom predicate. | ||
| itemStart < this.getScrollOffset() + this.scrollAdjustments && this.scrollDirection !== "backward" | ||
| ))) { | ||
| if (process.env.NODE_ENV !== "production" && this.options.debug) { | ||
| console.info("correction", delta); | ||
| } | ||
| if (isIOSWebKit() && (this.isScrolling || this._iosTouching || this._iosJustTouchEnded)) { | ||
| this._iosDeferredAdjustment += delta; | ||
| } else { | ||
| this._scrollToOffset(this.getScrollOffset(), { | ||
| adjustments: this.scrollAdjustments += delta, | ||
| behavior: void 0 | ||
| }); | ||
| } | ||
| } | ||
| )); | ||
| if (this.pendingMin === null || index < this.pendingMin) { | ||
@@ -789,2 +835,7 @@ this.pendingMin = index; | ||
| this.itemSizeCacheVersion++; | ||
| if (wasAtEnd) { | ||
| this.applyScrollAdjustment(this.getTotalSize() - prevTotalSize); | ||
| } else if (shouldAdjustScroll) { | ||
| this.applyScrollAdjustment(delta); | ||
| } | ||
| this.notify(false); | ||
@@ -833,2 +884,14 @@ } | ||
| }; | ||
| this.getVirtualDistanceFromEnd = () => { | ||
| return Math.max( | ||
| this.getTotalSize() - this.getSize() - this.getScrollOffset(), | ||
| 0 | ||
| ); | ||
| }; | ||
| this.getDistanceFromEnd = () => { | ||
| return Math.max(this.getMaxScrollOffset() - this.getScrollOffset(), 0); | ||
| }; | ||
| this.isAtEnd = (threshold = this.options.scrollEndThreshold) => { | ||
| return this.getDistanceFromEnd() <= threshold; | ||
| }; | ||
| this.getOffsetForAlignment = (toOffset, align, itemSize = 0) => { | ||
@@ -923,2 +986,14 @@ if (!this.scrollElement) return 0; | ||
| }; | ||
| this.scrollToEnd = ({ behavior = "auto" } = {}) => { | ||
| if (this.options.count > 0) { | ||
| this.scrollToIndex(this.options.count - 1, { | ||
| align: "end", | ||
| behavior | ||
| }); | ||
| return; | ||
| } | ||
| this.scrollToOffset(Math.max(this.getTotalSize() - this.getSize(), 0), { | ||
| behavior | ||
| }); | ||
| }; | ||
| this.getTotalSize = () => { | ||
@@ -989,2 +1064,16 @@ var _a; | ||
| } | ||
| applyScrollAdjustment(delta, behavior) { | ||
| if (delta === 0) return; | ||
| if (process.env.NODE_ENV !== "production" && this.options.debug) { | ||
| console.info("correction", delta); | ||
| } | ||
| if (isIOSWebKit() && (this.isScrolling || this._iosTouching || this._iosJustTouchEnded)) { | ||
| this._iosDeferredAdjustment += delta; | ||
| } else { | ||
| this._scrollToOffset(this.getScrollOffset(), { | ||
| adjustments: this.scrollAdjustments += delta, | ||
| behavior | ||
| }); | ||
| } | ||
| } | ||
| scheduleScrollReconcile() { | ||
@@ -991,0 +1080,0 @@ if (!this.targetWindow) { |
+1
-1
| { | ||
| "name": "@tanstack/virtual-core", | ||
| "version": "3.15.0", | ||
| "version": "3.16.0", | ||
| "description": "Headless UI for virtualizing scrollable elements in TS/JS + Frameworks", | ||
@@ -5,0 +5,0 @@ "author": "Tanner Linsley", |
+176
-21
@@ -36,2 +36,6 @@ import { createLazyMeasurementsView } from './lazy-measurements' | ||
| type ScrollAnchor = 'start' | 'end' | ||
| type FollowOnAppend = boolean | ScrollBehavior | ||
| export interface ScrollToOptions { | ||
@@ -46,2 +50,4 @@ align?: ScrollAlignment | ||
| type ScrollToEndOptions = Pick<ScrollToOptions, 'behavior'> | ||
| export interface Range { | ||
@@ -333,2 +339,5 @@ startIndex: number | ||
| lanes?: number | ||
| anchorTo?: ScrollAnchor | ||
| followOnAppend?: FollowOnAppend | ||
| scrollEndThreshold?: number | ||
| isScrollingResetDelay?: number | ||
@@ -358,2 +367,8 @@ useScrollendEvent?: boolean | ||
| type PendingScrollAnchor = [ | ||
| key: Key | null, | ||
| offset: number, | ||
| followOnAppend: ScrollBehavior | null, | ||
| ] | ||
| export class Virtualizer< | ||
@@ -381,2 +396,3 @@ TScrollElement extends Element | Window, | ||
| private lanesSettling = false | ||
| private pendingScrollAnchor: PendingScrollAnchor | null = null | ||
| scrollRect: Rect | null = null | ||
@@ -506,2 +522,5 @@ scrollOffset: number | null = null | ||
| lanes: 1, | ||
| anchorTo: 'start', | ||
| followOnAppend: false, | ||
| scrollEndThreshold: 1, | ||
| isScrollingResetDelay: 150, | ||
@@ -520,3 +539,71 @@ enabled: true, | ||
| const prevOptions = this.options as | ||
| | Required<VirtualizerOptions<TScrollElement, TItemElement>> | ||
| | undefined | ||
| let anchor: [Key, number] | null = null | ||
| let followOnAppend: ScrollBehavior | null = null | ||
| if ( | ||
| prevOptions !== undefined && | ||
| prevOptions.enabled && | ||
| merged.enabled && | ||
| merged.anchorTo === 'end' && | ||
| this.scrollElement !== null | ||
| ) { | ||
| const prevCount = prevOptions.count | ||
| const nextCount = merged.count | ||
| const measurements = this.getMeasurements() | ||
| const prevFirstKey = | ||
| prevCount > 0 | ||
| ? (measurements[0]?.key ?? prevOptions.getItemKey(0)) | ||
| : null | ||
| const prevLastKey = | ||
| prevCount > 0 | ||
| ? (measurements[prevCount - 1]?.key ?? | ||
| prevOptions.getItemKey(prevCount - 1)) | ||
| : null | ||
| const didCountChange = nextCount !== prevCount | ||
| const didEdgeKeysChange = | ||
| didCountChange || | ||
| (prevCount > 0 && | ||
| nextCount > 0 && | ||
| (merged.getItemKey(0) !== prevFirstKey || | ||
| merged.getItemKey(nextCount - 1) !== prevLastKey)) | ||
| if (didEdgeKeysChange) { | ||
| const item = | ||
| prevCount > 0 | ||
| ? (this.getVirtualItemForOffset(this.getScrollOffset()) ?? | ||
| measurements[0]) | ||
| : null | ||
| if (item) { | ||
| anchor = [item.key, this.getScrollOffset() - item.start] | ||
| } | ||
| const behavior = | ||
| merged.followOnAppend === true | ||
| ? 'auto' | ||
| : merged.followOnAppend || null | ||
| if ( | ||
| behavior && | ||
| nextCount > prevCount && | ||
| this.isAtEnd(prevOptions.scrollEndThreshold) && | ||
| (prevCount === 0 || merged.getItemKey(nextCount - 1) !== prevLastKey) | ||
| ) { | ||
| followOnAppend = behavior | ||
| } | ||
| } | ||
| } | ||
| this.options = merged | ||
| if (anchor || followOnAppend) { | ||
| this.pendingScrollAnchor = [ | ||
| anchor?.[0] ?? null, | ||
| anchor?.[1] ?? 0, | ||
| followOnAppend, | ||
| ] | ||
| } | ||
| } | ||
@@ -528,2 +615,22 @@ | ||
| private applyScrollAdjustment(delta: number, behavior?: ScrollBehavior) { | ||
| if (delta === 0) return | ||
| if (process.env.NODE_ENV !== 'production' && this.options.debug) { | ||
| console.info('correction', delta) | ||
| } | ||
| if ( | ||
| isIOSWebKit() && | ||
| (this.isScrolling || this._iosTouching || this._iosJustTouchEnded) | ||
| ) { | ||
| this._iosDeferredAdjustment += delta | ||
| } else { | ||
| this._scrollToOffset(this.getScrollOffset(), { | ||
| adjustments: (this.scrollAdjustments += delta), | ||
| behavior, | ||
| }) | ||
| } | ||
| } | ||
| private maybeNotify = memo( | ||
@@ -697,2 +804,30 @@ () => { | ||
| } | ||
| const anchor = this.pendingScrollAnchor | ||
| this.pendingScrollAnchor = null | ||
| if (anchor && this.scrollElement && this.options.enabled) { | ||
| const [key, offset, followOnAppend] = anchor | ||
| if (key !== null) { | ||
| const { count, getItemKey } = this.options | ||
| let index = 0 | ||
| while (index < count && getItemKey(index) !== key) { | ||
| index++ | ||
| } | ||
| const item = index < count ? this.getMeasurements()[index] : undefined | ||
| if (item) { | ||
| const delta = item.start + offset - this.getScrollOffset() | ||
| if (!approxEqual(delta, 0)) { | ||
| this.applyScrollAdjustment(delta) | ||
| } | ||
| } | ||
| } | ||
| if (followOnAppend) { | ||
| this.scrollToEnd({ behavior: followOnAppend }) | ||
| } | ||
| } | ||
| } | ||
@@ -1296,4 +1431,9 @@ | ||
| if (delta !== 0) { | ||
| if ( | ||
| const wasAtEnd = | ||
| this.options.anchorTo === 'end' && | ||
| this.scrollState?.behavior !== 'smooth' && | ||
| this.getVirtualDistanceFromEnd() <= this.options.scrollEndThreshold | ||
| const prevTotalSize = wasAtEnd ? this.getTotalSize() : 0 | ||
| const shouldAdjustScroll = | ||
| this.scrollState?.behavior !== 'smooth' && | ||
| (this.shouldAdjustScrollPositionOnItemSizeChange !== undefined | ||
@@ -1322,22 +1462,2 @@ ? this.shouldAdjustScrollPositionOnItemSizeChange( | ||
| this.scrollDirection !== 'backward') | ||
| ) { | ||
| if (process.env.NODE_ENV !== 'production' && this.options.debug) { | ||
| console.info('correction', delta) | ||
| } | ||
| // On iOS WebKit, writing scrollTop while a finger is on screen or | ||
| // momentum-scroll is running cancels the in-flight scroll. Defer | ||
| // the adjustment until iOS is fully settled — flushed by either | ||
| // the scroll callback or the touchend grace-timer. | ||
| if ( | ||
| isIOSWebKit() && | ||
| (this.isScrolling || this._iosTouching || this._iosJustTouchEnded) | ||
| ) { | ||
| this._iosDeferredAdjustment += delta | ||
| } else { | ||
| this._scrollToOffset(this.getScrollOffset(), { | ||
| adjustments: (this.scrollAdjustments += delta), | ||
| behavior: undefined, | ||
| }) | ||
| } | ||
| } | ||
@@ -1350,2 +1470,8 @@ if (this.pendingMin === null || index < this.pendingMin) { | ||
| if (wasAtEnd) { | ||
| this.applyScrollAdjustment(this.getTotalSize() - prevTotalSize) | ||
| } else if (shouldAdjustScroll) { | ||
| this.applyScrollAdjustment(delta) | ||
| } | ||
| this.notify(false) | ||
@@ -1413,2 +1539,17 @@ } | ||
| private getVirtualDistanceFromEnd = () => { | ||
| return Math.max( | ||
| this.getTotalSize() - this.getSize() - this.getScrollOffset(), | ||
| 0, | ||
| ) | ||
| } | ||
| getDistanceFromEnd = () => { | ||
| return Math.max(this.getMaxScrollOffset() - this.getScrollOffset(), 0) | ||
| } | ||
| isAtEnd = (threshold = this.options.scrollEndThreshold) => { | ||
| return this.getDistanceFromEnd() <= threshold | ||
| } | ||
| getOffsetForAlignment = ( | ||
@@ -1549,2 +1690,16 @@ toOffset: number, | ||
| scrollToEnd = ({ behavior = 'auto' }: ScrollToEndOptions = {}) => { | ||
| if (this.options.count > 0) { | ||
| this.scrollToIndex(this.options.count - 1, { | ||
| align: 'end', | ||
| behavior, | ||
| }) | ||
| return | ||
| } | ||
| this.scrollToOffset(Math.max(this.getTotalSize() - this.getSize(), 0), { | ||
| behavior, | ||
| }) | ||
| } | ||
| getTotalSize = () => { | ||
@@ -1551,0 +1706,0 @@ const measurements = this.getMeasurements() |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
363170
7.84%4607
7.44%30
3.45%