Skip to content

Commit fcd7aa2

Browse files
committed
refactor(web): resolve the thread restore target once
Name the saved-row case once at the top of the restore effect, compute the end offset once per frame, clamp a single target, and read the saved row by the index the effect already depends on.
1 parent 6f15f40 commit fcd7aa2

1 file changed

Lines changed: 25 additions & 37 deletions

File tree

‎apps/web/src/components/chat/MessagesTimeline.tsx‎

Lines changed: 25 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -820,13 +820,13 @@ export const MessagesTimeline = memo(function MessagesTimeline({
820820
if (viewport) void list.scrollToOffset({ offset: viewport.scrollTop, animated: false });
821821
setPositionedThreadKey(listIdentityKey);
822822
};
823-
const position = rememberedPosition;
823+
const savedRow = rememberedPosition?.atEnd === false ? rememberedPosition : null;
824824
// A restore to the end only stops correcting on a gesture. ChatView's own
825825
// listeners decide whether that gesture leaves the end, so a wheel down
826826
// or a click at the bottom keeps following.
827827
const cancelForNavigation = () => {
828828
cancelRestoration();
829-
if (position?.atEnd === false) onManualNavigation();
829+
if (savedRow) onManualNavigation();
830830
};
831831
const onScrollKey = (event: globalThis.KeyboardEvent) => {
832832
if (
@@ -842,26 +842,24 @@ export const MessagesTimeline = memo(function MessagesTimeline({
842842
viewport?.addEventListener("touchmove", cancelForNavigation, { passive: true });
843843
viewport?.addEventListener("pointerdown", cancelForNavigation, { passive: true });
844844
viewport?.ownerDocument.addEventListener("keydown", onScrollKey);
845-
if (position?.atEnd === false) onManualNavigation();
845+
if (savedRow) onManualNavigation();
846846
if (cancelPositionRestoreRef) cancelPositionRestoreRef.current = cancelRestoration;
847-
const scrolling =
848-
position?.atEnd === false
849-
? restoreRowIndex >= 0
850-
? list.scrollToIndex({
851-
index: restoreRowIndex,
852-
animated: false,
853-
viewPosition: 0,
854-
viewOffset: -position.offsetWithinRow,
855-
})
856-
: list.scrollToOffset({ offset: position.scrollOffset, animated: false })
857-
: list.scrollToEnd({ animated: false });
847+
const scrolling = savedRow
848+
? restoreRowIndex >= 0
849+
? list.scrollToIndex({
850+
index: restoreRowIndex,
851+
animated: false,
852+
viewPosition: 0,
853+
viewOffset: -savedRow.offsetWithinRow,
854+
})
855+
: list.scrollToOffset({ offset: savedRow.scrollOffset, animated: false })
856+
: list.scrollToEnd({ animated: false });
858857
void Promise.resolve(scrolling).then(() => {
859858
if (cancelled) return;
860-
if (position?.atEnd === false && restoreRowIndex < 0) {
859+
if (savedRow && restoreRowIndex < 0) {
861860
setPositionedThreadKey(listIdentityKey);
862861
return;
863862
}
864-
const savedRow = position?.atEnd === false ? position : null;
865863
// Scrolling starts from estimated row sizes, so the first jump lands
866864
// short once real rows measure. Keep correcting toward the target (the
867865
// saved row, or the end) until it and the DOM agree for two frames.
@@ -870,40 +868,30 @@ export const MessagesTimeline = memo(function MessagesTimeline({
870868
const reconcile = () => {
871869
if (cancelled) return;
872870
const element = list.getScrollableNode();
871+
const endOffset = element ? Math.max(0, element.scrollHeight - element.clientHeight) : 0;
873872
if (++frames > THREAD_RESTORE_MAX_FRAMES) {
874873
// End maintenance only re-pins within a viewport of the end, so
875874
// hand over from the current end rather than wherever this stopped.
876-
if (!savedRow && element) {
877-
void list.scrollToOffset({
878-
offset: Math.max(0, element.scrollHeight - element.clientHeight),
879-
animated: false,
880-
});
881-
}
875+
if (!savedRow && element)
876+
void list.scrollToOffset({ offset: endOffset, animated: false });
882877
setPositionedThreadKey(listIdentityKey);
883878
return;
884879
}
885-
const state = list.getState();
886-
const rowIndex = savedRow ? state.indexByKey(savedRow.rowId) : undefined;
887-
const row = rowIndex === undefined ? undefined : state.elementAtIndex(rowIndex);
880+
// The effect restarts when the saved row's index changes.
881+
const row = savedRow ? list.getState().elementAtIndex(restoreRowIndex) : null;
888882
// The saved row can mount a frame after the estimated scroll.
889883
if (!element || (savedRow && !row)) {
890884
settleFrame = requestAnimationFrame(reconcile);
891885
return;
892886
}
893-
const endOffset = Math.max(0, element.scrollHeight - element.clientHeight);
894-
const offset =
887+
const target =
895888
savedRow && row
896-
? Math.max(
897-
0,
898-
Math.min(
899-
element.scrollTop +
900-
row.getBoundingClientRect().top -
901-
element.getBoundingClientRect().top +
902-
savedRow.offsetWithinRow,
903-
endOffset,
904-
),
905-
)
889+
? element.scrollTop +
890+
row.getBoundingClientRect().top -
891+
element.getBoundingClientRect().top +
892+
savedRow.offsetWithinRow
906893
: endOffset;
894+
const offset = Math.max(0, Math.min(target, endOffset));
907895
if (Math.abs(element.scrollTop - offset) > 1) {
908896
stableFrames = 0;
909897
void list.scrollToOffset({ offset, animated: false }).then(() => {

0 commit comments

Comments
 (0)