diff --git a/dist/esm/index.d.ts b/dist/esm/index.d.ts index 387489bd25c9ebcf6afd97df5e741706fbdec65d..0e9961bb69b411824ba91f56694774e801d599c4 100644 --- a/dist/esm/index.d.ts +++ b/dist/esm/index.d.ts @@ -78,6 +78,12 @@ export interface VirtualizerOptions; lanes?: number; anchorTo?: ScrollAnchor; + /** + * With `anchorTo: "end"`, reject an item as the scroll anchor (e.g. a transient + * placeholder/sentinel row whose key does not survive the update); anchoring + * steps inward to the nearest accepted item. Element-web patch, pending upstream. + */ + isValidAnchorItem?: (item: VirtualItem) => boolean; followOnAppend?: FollowOnAppend; scrollEndThreshold?: number; isScrollingResetDelay?: number; diff --git a/dist/esm/index.js b/dist/esm/index.js index 00fd4a4b0a0049193f8862c2604cb76013d605d5..37c058b25d30f42f82617d9cc4c2831bc0b13704 100644 --- a/dist/esm/index.js +++ b/dist/esm/index.js @@ -295,7 +295,24 @@ class Virtualizer { const didEdgeKeysChange = didCountChange || prevCount > 0 && nextCount > 0 && (merged.getItemKey(0) !== prevFirstKey || merged.getItemKey(nextCount - 1) !== prevLastKey); if (didEdgeKeysChange) { edgeKeysChanged = true; - const item = prevCount > 0 ? this.getVirtualItemForOffset(this.getScrollOffset()) ?? measurements[0] : null; + let item = prevCount > 0 ? this.getVirtualItemForOffset(this.getScrollOffset()) ?? measurements[0] : null; + // With anchorTo: "end" the scroll is re-pinned by the key of the item + // under the current offset. If the consumer rejects that item as an + // anchor via isValidAnchorItem — e.g. a transient placeholder/sentinel + // row (loading spinner, "load more", skeleton) whose key does not + // survive the update — re-pinning by its key would fail and the + // prepend would go uncompensated (the list jumps to the edge). Such + // rows only sit at the extreme edges, so step one index inward to the + // nearest accepted item, whose key persists across the update. + const isValidAnchorItem = merged.isValidAnchorItem; + if (item && isValidAnchorItem && !isValidAnchorItem(item)) { + const step = item.index === 0 ? 1 : -1; + let i = item.index; + while (i >= 0 && i < measurements.length && measurements[i] && !isValidAnchorItem(measurements[i])) { + i += step; + } + item = measurements[i] ?? item; + } if (item) { anchor = [item.key, this.getScrollOffset() - item.start]; }