You've already forked element-web
mirror of
https://github.com/element-hq/element-web.git
synced 2026-10-24 23:19:26 +03:00
* Update react * Update tanstack patch --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
109 lines
6.8 KiB
Diff
109 lines
6.8 KiB
Diff
diff --git a/dist/esm/index.d.ts b/dist/esm/index.d.ts
|
|
index 039bff8..39d31ff 100644
|
|
--- a/dist/esm/index.d.ts
|
|
+++ b/dist/esm/index.d.ts
|
|
@@ -78,6 +78,12 @@ export interface VirtualizerOptions<TScrollElement extends Element | Window, TIt
|
|
initialMeasurementsCache?: Array<VirtualItem>;
|
|
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 7032b66..104bd75 100644
|
|
--- a/dist/esm/index.js
|
|
+++ b/dist/esm/index.js
|
|
@@ -321,7 +321,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];
|
|
}
|
|
@@ -913,9 +930,19 @@ class Virtualizer {
|
|
const itemSize = this.itemSizeCache.get(key) ?? cachedSize;
|
|
const delta = size - itemSize;
|
|
if (delta !== 0) {
|
|
- const wasAtEnd = this.options.anchorTo === "end" && ((_b = this.scrollState) == null ? void 0 : _b.behavior) !== "smooth" && this.getVirtualDistanceFromEnd() <= this.options.scrollEndThreshold;
|
|
+ // `this.scrollOffset` only updates when the async scroll event fires, so
|
|
+ // within a frame it can be stale in two ways: (a) an earlier resize in the
|
|
+ // same ResizeObserver batch already queued `scrollAdjustments`, and (b) a
|
|
+ // programmatic scroll (scrollToOffset / the reconcile loop) wrote scrollTop
|
|
+ // this frame — recorded synchronously in `_intendedScrollOffset` but not yet
|
|
+ // reflected by getScrollOffset(). Evaluating "was the view at the end?" (or
|
|
+ // "is this item above the fold?") against the stale offset makes resizes
|
|
+ // during a scroll-to-end land uncompensated, stranding the view above the
|
|
+ // bottom. Use the synchronous truth when a write is pending.
|
|
+ const effectiveScrollOffset = this._intendedScrollOffset ?? (this.getScrollOffset() + this.scrollAdjustments);
|
|
+ const wasAtEnd = this.options.anchorTo === "end" && ((_b = this.scrollState) == null ? void 0 : _b.behavior) !== "smooth" && this.getTotalSize() - this.getSize() - effectiveScrollOffset <= this.options.scrollEndThreshold;
|
|
const prevTotalSize = wasAtEnd ? this.getTotalSize() : 0;
|
|
- const scrollOffsetWithAdj = this.getScrollOffset() + this.scrollAdjustments;
|
|
+ const scrollOffsetWithAdj = effectiveScrollOffset;
|
|
const isFirstMeasure = !this.itemSizeCache.has(key);
|
|
const defaultShouldAdjust = isFirstMeasure ? (
|
|
// First measurement: compensate any item whose top sits above the
|
|
@@ -928,9 +955,12 @@ class Virtualizer {
|
|
// below — e.g. a streaming chat message growing at its bottom)
|
|
// changes size *below* the anchor point, so shifting scrollTop by the
|
|
// delta would drag the viewport downward on every growth (#1218).
|
|
- // Also skip during backward scroll to avoid the "items jump while
|
|
- // scrolling up" cascade.
|
|
- itemStart + itemSize <= scrollOffsetWithAdj && this.scrollDirection !== "backward"
|
|
+ // Upstream also skips this while scrolling backwards, a guard against
|
|
+ // corrections firing from a stale scroll offset. The effective-offset
|
|
+ // fix above removes the staleness, and keeping the guard meant every
|
|
+ // image or link preview that finished loading above the viewport
|
|
+ // shoved the view while scrolling up — so the guard is dropped.
|
|
+ itemStart + itemSize <= scrollOffsetWithAdj
|
|
);
|
|
const shouldAdjustScroll = ((_c = this.scrollState) == null ? void 0 : _c.behavior) !== "smooth" && (this.shouldAdjustScrollPositionOnItemSizeChange !== void 0 ? this.shouldAdjustScrollPositionOnItemSizeChange(
|
|
// The callback expects a VirtualItem; build one lazily only
|
|
@@ -1197,16 +1227,19 @@ class Virtualizer {
|
|
this._iosDeferredAdjustment += delta;
|
|
return false;
|
|
} else {
|
|
- const target = this.getScrollOffset() + this.scrollAdjustments + delta;
|
|
+ // A programmatic scroll written this frame is not yet visible through
|
|
+ // getScrollOffset(); build the correction on the offset that was written.
|
|
+ const base = this._intendedScrollOffset ?? this.getScrollOffset();
|
|
+ const target = base + this.scrollAdjustments + delta;
|
|
const el = this.scrollElement;
|
|
const maxAtWrite = el !== null && ("scrollHeight" in el || "document" in el) ? this.getMaxScrollOffset() : null;
|
|
this._clampedAdjustment = maxAtWrite !== null && target > maxAtWrite + 0.5 ? { target, maxAtWrite } : null;
|
|
- this._scrollToOffset(this.getScrollOffset(), {
|
|
+ this._scrollToOffset(base, {
|
|
adjustments: this.scrollAdjustments += delta,
|
|
behavior
|
|
});
|
|
if (this.scrollOffset !== null) {
|
|
- this.scrollOffset += this.scrollAdjustments;
|
|
+ this.scrollOffset = base + this.scrollAdjustments;
|
|
if (this.scrollOffset < 0) this.scrollOffset = 0;
|
|
this.scrollAdjustments = 0;
|
|
}
|