@oxyhq/bloom 0.6.22 → 0.6.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.ScrollRestorationProvider = ScrollRestorationProvider;
7
7
  exports.useScrollRestoration = useScrollRestoration;
8
8
  var _react = require("react");
9
- var _native = require("@react-navigation/native");
9
+ var _expoRouter = require("expo-router");
10
10
  var _scrollableWeb = require("./scrollable.web.js");
11
11
  var _store = require("./store.js");
12
12
  var _jsxRuntime = require("react/jsx-runtime");
@@ -14,20 +14,68 @@ var _jsxRuntime = require("react/jsx-runtime");
14
14
  * Web variant of the scroll-restoration primitive.
15
15
  *
16
16
  * Mirrors the proven Bluesky pattern (`history.scrollRestoration = 'manual'`
17
- * plus an in-memory `Map<routeKey, offset>` saved on blur and restored on focus
18
- * inside a single `requestAnimationFrame`) with one deliberate difference:
19
- * Bluesky restores the WINDOW scroller, whereas Oxy apps keep multi-column
20
- * layouts whose feed scrolls an INNER container. So we restore the offset of a
21
- * caller-registered scrollable (a ref to an element / RN scroll component, or
22
- * the `'window'` sentinel), keyed by the active navigation route.
17
+ * plus an in-memory `Map<routeKey, offset>`) with two deliberate differences
18
+ * forced by Oxy's layouts and the behaviour of the (expo-router-wrapped)
19
+ * React Navigation web stack:
20
+ *
21
+ * 1. Bluesky restores the WINDOW scroller, whereas Oxy apps keep multi-column
22
+ * layouts whose feed scrolls an INNER container. So we restore the offset
23
+ * of a caller-registered scrollable (a ref to an element / RN scroll
24
+ * component, or the `'window'` sentinel), keyed by the active route.
25
+ *
26
+ * 2. The web stack HIDES the background screen on push. While
27
+ * hidden, the previous screen's scroll container collapses
28
+ * (`scrollHeight === clientHeight`) and the navigator forces its
29
+ * `scrollTop` to 0. The screen is NOT unmounted, so a virtualized list
30
+ * (e.g. FlashList) keeps its rows but re-lays them out over SEVERAL frames
31
+ * once the screen is re-shown. Two problems follow, both handled here:
32
+ *
33
+ * (a) A blur-time read of `scrollTop` returns the navigator's forced 0,
34
+ * not the user's real offset — saving it would clobber the good
35
+ * value. We therefore persist the last offset OBSERVED by the live
36
+ * scroll listener, never a fresh read taken at blur time.
37
+ *
38
+ * (b) A single-frame restore writes `scrollTop` while the list is still
39
+ * collapsed; the write is clamped to 0 and never re-applied once the
40
+ * content grows. We therefore re-apply the target offset across a
41
+ * bounded run of animation frames, stopping as soon as the write
42
+ * sticks (the content has grown tall enough) or a small frame cap is
43
+ * reached.
23
44
  *
24
45
  * Native bundlers use `./index.ts` (a no-op); web bundlers select this file via
25
46
  * the `"browser"` export condition in `package.json`.
47
+ *
48
+ * The navigation hooks are imported from `expo-router` (which re-exports
49
+ * `useFocusEffect` and `useRoute` from its bundled React Navigation core) rather
50
+ * than from `@react-navigation/native` directly. Every Oxy app uses expo-router
51
+ * as its router, so it is always a DIRECT, top-level dependency that resolves
52
+ * cleanly under Bun's isolated linker — whereas `@react-navigation/native` is
53
+ * only a nested/transitive dependency of expo-router and would fail to resolve
54
+ * when bundling those apps.
26
55
  */
27
56
 
28
57
  const ScrollOffsetContext = /*#__PURE__*/(0, _react.createContext)(null);
29
58
  ScrollOffsetContext.displayName = 'BloomScrollOffsetContext';
30
59
 
60
+ /**
61
+ * Maximum number of animation frames the focus restore will re-apply the saved
62
+ * offset before giving up. A virtualized list re-lays out its rows over a
63
+ * handful of frames after its screen is re-shown; ~30 frames (≈0.5s at 60fps)
64
+ * is comfortably longer than any observed relayout while staying short enough
65
+ * that the loop never lingers as a perceptible cost. The loop normally exits
66
+ * far earlier — as soon as the write sticks.
67
+ */
68
+ const RESTORE_FRAME_CAP = 30;
69
+
70
+ /**
71
+ * Tolerance (in CSS pixels) for considering a restore "stuck". After writing
72
+ * `element.scrollTop = target`, the browser may clamp it to the current
73
+ * `scrollHeight - clientHeight`; if the resulting offset is within this many
74
+ * pixels of the target we treat the restore as complete. Sub-pixel rounding and
75
+ * fractional device-pixel ratios make an exact equality check unreliable.
76
+ */
77
+ const RESTORE_STICK_TOLERANCE_PX = 2;
78
+
31
79
  /**
32
80
  * Switch the browser to manual scroll restoration exactly once per document.
33
81
  *
@@ -67,15 +115,19 @@ function useScrollOffsetStore() {
67
115
  * multiple scrollables).
68
116
  *
69
117
  * Behaviour (web):
70
- * - On every scroll while the screen is focused, the current offset is saved.
71
- * - On focus, the saved offset is applied in a single `requestAnimationFrame`,
72
- * giving a remounted list one frame to lay out its content first (no retry
73
- * loops, no hide/show tricks).
74
- * - On blur, the latest offset is captured as a final safety net.
118
+ * - On every scroll while the screen is focused, the current offset is recorded
119
+ * in memory and persisted. This live stream of saves is the source of truth.
120
+ * - On focus, the saved offset is re-applied across a bounded run of animation
121
+ * frames, stopping as soon as the write sticks (the list has re-rendered its
122
+ * rows and grown tall enough) or {@link RESTORE_FRAME_CAP} is reached. A
123
+ * saved offset of 0 is a no-op (nothing to restore).
124
+ * - On blur, the LAST OBSERVED offset is persisted as a final safety net — not
125
+ * a fresh `scrollTop` read, which the navigator may already have forced to 0
126
+ * while collapsing the hidden screen.
75
127
  */
76
128
  function useScrollRestoration(target, options) {
77
129
  const store = useScrollOffsetStore();
78
- const route = (0, _native.useRoute)();
130
+ const route = (0, _expoRouter.useRoute)();
79
131
  const subKey = options?.key;
80
132
  const enabled = options?.enabled ?? true;
81
133
  const scrollKey = (0, _store.deriveScrollKey)(route.key, subKey);
@@ -88,35 +140,76 @@ function useScrollRestoration(target, options) {
88
140
  enabledRef.current = enabled;
89
141
  const scrollKeyRef = (0, _react.useRef)(scrollKey);
90
142
  scrollKeyRef.current = scrollKey;
91
- (0, _native.useFocusEffect)(
143
+ (0, _expoRouter.useFocusEffect)(
92
144
  // The effect identity is intentionally stable across renders: it reads all
93
- // varying inputs from refs. React Navigation re-runs it on each focus.
145
+ // varying inputs from refs. expo-router's `useFocusEffect` re-runs it on
146
+ // each focus.
94
147
  (0, _react.useCallback)(() => {
95
148
  const key = scrollKeyRef.current;
96
149
  if (!enabledRef.current || key === null) return undefined;
97
150
  const scroller = (0, _scrollableWeb.createScroller)(targetRef.current);
98
151
  const element = targetRef.current === 'window' ? typeof window !== 'undefined' ? window : null : resolveScrollEventTarget(targetRef.current);
152
+
153
+ // The last offset the live scroll listener observed for this focus
154
+ // session. This — not a blur-time `getOffset()` — is what we persist on
155
+ // blur, because by blur time the navigator may have collapsed the
156
+ // hidden screen and forced its `scrollTop` to 0 (bug A). `null` means
157
+ // the user never scrolled this session, so there is nothing newer to
158
+ // persist than what the scroll listener already saved live.
159
+ let lastObservedOffset = null;
99
160
  const save = () => {
100
161
  const currentKey = scrollKeyRef.current;
101
- if (enabledRef.current && currentKey !== null) {
102
- store.save(currentKey, scroller.getOffset());
103
- }
162
+ if (!enabledRef.current || currentKey === null) return;
163
+ const offset = scroller.getOffset();
164
+ // Ignore a spurious 0 produced by the navigator collapsing a hidden
165
+ // background screen: while collapsed the container cannot scroll, so
166
+ // its `scrollTop` is forced to 0. Persisting it would clobber the
167
+ // good offset recorded by earlier live saves (bug A). A genuine
168
+ // scroll-to-top keeps the container scrollable and is saved normally.
169
+ if (offset === 0 && !scroller.canScroll()) return;
170
+ lastObservedOffset = offset;
171
+ store.save(currentKey, offset);
104
172
  };
105
173
 
106
- // Restore on the next frame so a freshly remounted list has rendered
107
- // its content (and thus reached its full scroll height) before we move.
108
- const frame = requestAnimationFrame(() => {
109
- scroller.setOffset(store.read(key));
110
- });
174
+ // Restore across a bounded run of frames. A freshly re-shown
175
+ // virtualized list re-lays out its rows over several frames, so a
176
+ // single write while it is still collapsed would be clamped to 0 and
177
+ // never re-applied (bug B). We re-apply each frame until the write
178
+ // sticks or the frame cap is hit.
179
+ const targetOffset = store.read(key);
180
+ let rafId = null;
181
+ if (targetOffset > 0 && typeof requestAnimationFrame !== 'undefined') {
182
+ let framesLeft = RESTORE_FRAME_CAP;
183
+ const applyOffset = () => {
184
+ rafId = null;
185
+ scroller.setOffset(targetOffset);
186
+ framesLeft -= 1;
187
+ // Stop once the write took effect (content grew tall enough) or we
188
+ // exhaust the frame budget. `getOffset` re-reads the clamped value.
189
+ const reached = Math.abs(scroller.getOffset() - targetOffset) <= RESTORE_STICK_TOLERANCE_PX;
190
+ if (!reached && framesLeft > 0) {
191
+ rafId = requestAnimationFrame(applyOffset);
192
+ }
193
+ };
194
+ rafId = requestAnimationFrame(applyOffset);
195
+ }
111
196
  element?.addEventListener('scroll', save, {
112
197
  passive: true
113
198
  });
114
199
  return () => {
115
- cancelAnimationFrame(frame);
200
+ if (rafId !== null) cancelAnimationFrame(rafId);
116
201
  element?.removeEventListener('scroll', save);
117
- // Final capture on blur, covering navigations that don't fire a
118
- // trailing scroll event.
119
- save();
202
+ // Final capture on blur: persist the last offset the scroll listener
203
+ // OBSERVED, never a fresh read (which the navigator may have forced
204
+ // to 0 while collapsing the hidden screen). When the user never
205
+ // scrolled this session there is nothing newer to persist than the
206
+ // live saves already recorded.
207
+ if (lastObservedOffset !== null) {
208
+ const currentKey = scrollKeyRef.current;
209
+ if (enabledRef.current && currentKey !== null) {
210
+ store.save(currentKey, lastObservedOffset);
211
+ }
212
+ }
120
213
  };
121
214
  }, [store]));
122
215
  }
@@ -1 +1 @@
1
- {"version":3,"names":["_react","require","_native","_scrollableWeb","_store","_jsxRuntime","ScrollOffsetContext","createContext","displayName","history","scrollRestoration","ScrollRestorationProvider","children","store","useMemo","ScrollOffsetStore","jsx","Provider","value","useScrollOffsetStore","useContext","Error","useScrollRestoration","target","options","route","useRoute","subKey","key","enabled","scrollKey","deriveScrollKey","targetRef","useRef","current","enabledRef","scrollKeyRef","useFocusEffect","useCallback","undefined","scroller","createScroller","element","window","resolveScrollEventTarget","save","currentKey","getOffset","frame","requestAnimationFrame","setOffset","read","addEventListener","passive","cancelAnimationFrame","removeEventListener","EventTarget","handle","getScrollableNode","node"],"sourceRoot":"../../../src","sources":["scroll/index.web.tsx"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_react","require","_expoRouter","_scrollableWeb","_store","_jsxRuntime","ScrollOffsetContext","createContext","displayName","RESTORE_FRAME_CAP","RESTORE_STICK_TOLERANCE_PX","history","scrollRestoration","ScrollRestorationProvider","children","store","useMemo","ScrollOffsetStore","jsx","Provider","value","useScrollOffsetStore","useContext","Error","useScrollRestoration","target","options","route","useRoute","subKey","key","enabled","scrollKey","deriveScrollKey","targetRef","useRef","current","enabledRef","scrollKeyRef","useFocusEffect","useCallback","undefined","scroller","createScroller","element","window","resolveScrollEventTarget","lastObservedOffset","save","currentKey","offset","getOffset","canScroll","targetOffset","read","rafId","requestAnimationFrame","framesLeft","applyOffset","setOffset","reached","Math","abs","addEventListener","passive","cancelAnimationFrame","removeEventListener","EventTarget","handle","getScrollableNode","node"],"sourceRoot":"../../../src","sources":["scroll/index.web.tsx"],"mappings":"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","ignoreList":[]}
@@ -6,8 +6,8 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.createScroller = createScroller;
7
7
  /**
8
8
  * A normalized read/write interface over whatever the caller registered, so
9
- * the hook does not branch on target shape. Both methods are no-ops when the
10
- * underlying element is not yet (or no longer) attached.
9
+ * the hook does not branch on target shape. All methods are safe no-ops when
10
+ * the underlying element is not yet (or no longer) attached.
11
11
  */
12
12
 
13
13
  function isElement(value) {
@@ -47,7 +47,8 @@ function createScroller(target) {
47
47
  getOffset: () => typeof window === 'undefined' ? 0 : window.scrollY,
48
48
  setOffset: offset => {
49
49
  if (typeof window !== 'undefined') window.scrollTo(0, offset);
50
- }
50
+ },
51
+ canScroll: () => true
51
52
  };
52
53
  }
53
54
  return {
@@ -58,6 +59,10 @@ function createScroller(target) {
58
59
  setOffset: offset => {
59
60
  const element = resolveElement(target);
60
61
  if (element) element.scrollTop = offset;
62
+ },
63
+ canScroll: () => {
64
+ const element = resolveElement(target);
65
+ return element ? element.scrollHeight > element.clientHeight : false;
61
66
  }
62
67
  };
63
68
  }
@@ -1 +1 @@
1
- {"version":3,"names":["isElement","value","HTMLElement","hasGetScrollableNode","getScrollableNode","resolveElement","target","current","node","createScroller","getOffset","window","scrollY","setOffset","offset","scrollTo","element","scrollTop"],"sourceRoot":"../../../src","sources":["scroll/scrollable.web.ts"],"mappings":";;;;;;AAEA;AACA;AACA;AACA;AACA;;AAMA,SAASA,SAASA,CAACC,KAAc,EAAwB;EACvD,OAAO,OAAOC,WAAW,KAAK,WAAW,IAAID,KAAK,YAAYC,WAAW;AAC3E;AAEA,SAASC,oBAAoBA,CAC3BF,KAAc,EACkD;EAChE,OACE,OAAOA,KAAK,KAAK,QAAQ,IACzBA,KAAK,KAAK,IAAI,IACd,OAAQA,KAAK,CAAsBG,iBAAiB,KAAK,UAAU;AAEvE;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASC,cAAcA,CAACC,MAA+B,EAAsB;EAC3E,IAAIA,MAAM,KAAK,QAAQ,EAAE,OAAO,IAAI;EAEpC,MAAMC,OAAO,GAAID,MAAM,CAA0BC,OAAO;EACxD,IAAIA,OAAO,IAAI,IAAI,EAAE,OAAO,IAAI;EAEhC,IAAIP,SAAS,CAACO,OAAO,CAAC,EAAE,OAAOA,OAAO;EAEtC,IAAIJ,oBAAoB,CAACI,OAAO,CAAC,EAAE;IACjC,MAAMC,IAAI,GAAGD,OAAO,CAACH,iBAAiB,CAAC,CAAC;IACxC,IAAIJ,SAAS,CAACQ,IAAI,CAAC,EAAE,OAAOA,IAAI;EAClC;EAEA,OAAO,IAAI;AACb;;AAEA;AACA;AACA;AACA;AACA;AACO,SAASC,cAAcA,CAACH,MAA+B,EAAoB;EAChF,IAAIA,MAAM,KAAK,QAAQ,EAAE;IACvB,OAAO;MACLI,SAAS,EAAEA,CAAA,KAAO,OAAOC,MAAM,KAAK,WAAW,GAAG,CAAC,GAAGA,MAAM,CAACC,OAAQ;MACrEC,SAAS,EAAGC,MAAM,IAAK;QACrB,IAAI,OAAOH,MAAM,KAAK,WAAW,EAAEA,MAAM,CAACI,QAAQ,CAAC,CAAC,EAAED,MAAM,CAAC;MAC/D;IACF,CAAC;EACH;EAEA,OAAO;IACLJ,SAAS,EAAEA,CAAA,KAAM;MACf,MAAMM,OAAO,GAAGX,cAAc,CAACC,MAAM,CAAC;MACtC,OAAOU,OAAO,GAAGA,OAAO,CAACC,SAAS,GAAG,CAAC;IACxC,CAAC;IACDJ,SAAS,EAAGC,MAAM,IAAK;MACrB,MAAME,OAAO,GAAGX,cAAc,CAACC,MAAM,CAAC;MACtC,IAAIU,OAAO,EAAEA,OAAO,CAACC,SAAS,GAAGH,MAAM;IACzC;EACF,CAAC;AACH","ignoreList":[]}
1
+ {"version":3,"names":["isElement","value","HTMLElement","hasGetScrollableNode","getScrollableNode","resolveElement","target","current","node","createScroller","getOffset","window","scrollY","setOffset","offset","scrollTo","canScroll","element","scrollTop","scrollHeight","clientHeight"],"sourceRoot":"../../../src","sources":["scroll/scrollable.web.ts"],"mappings":";;;;;;AAEA;AACA;AACA;AACA;AACA;;AAkBA,SAASA,SAASA,CAACC,KAAc,EAAwB;EACvD,OAAO,OAAOC,WAAW,KAAK,WAAW,IAAID,KAAK,YAAYC,WAAW;AAC3E;AAEA,SAASC,oBAAoBA,CAC3BF,KAAc,EACkD;EAChE,OACE,OAAOA,KAAK,KAAK,QAAQ,IACzBA,KAAK,KAAK,IAAI,IACd,OAAQA,KAAK,CAAsBG,iBAAiB,KAAK,UAAU;AAEvE;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASC,cAAcA,CAACC,MAA+B,EAAsB;EAC3E,IAAIA,MAAM,KAAK,QAAQ,EAAE,OAAO,IAAI;EAEpC,MAAMC,OAAO,GAAID,MAAM,CAA0BC,OAAO;EACxD,IAAIA,OAAO,IAAI,IAAI,EAAE,OAAO,IAAI;EAEhC,IAAIP,SAAS,CAACO,OAAO,CAAC,EAAE,OAAOA,OAAO;EAEtC,IAAIJ,oBAAoB,CAACI,OAAO,CAAC,EAAE;IACjC,MAAMC,IAAI,GAAGD,OAAO,CAACH,iBAAiB,CAAC,CAAC;IACxC,IAAIJ,SAAS,CAACQ,IAAI,CAAC,EAAE,OAAOA,IAAI;EAClC;EAEA,OAAO,IAAI;AACb;;AAEA;AACA;AACA;AACA;AACA;AACO,SAASC,cAAcA,CAACH,MAA+B,EAAoB;EAChF,IAAIA,MAAM,KAAK,QAAQ,EAAE;IACvB,OAAO;MACLI,SAAS,EAAEA,CAAA,KAAO,OAAOC,MAAM,KAAK,WAAW,GAAG,CAAC,GAAGA,MAAM,CAACC,OAAQ;MACrEC,SAAS,EAAGC,MAAM,IAAK;QACrB,IAAI,OAAOH,MAAM,KAAK,WAAW,EAAEA,MAAM,CAACI,QAAQ,CAAC,CAAC,EAAED,MAAM,CAAC;MAC/D,CAAC;MACDE,SAAS,EAAEA,CAAA,KAAM;IACnB,CAAC;EACH;EAEA,OAAO;IACLN,SAAS,EAAEA,CAAA,KAAM;MACf,MAAMO,OAAO,GAAGZ,cAAc,CAACC,MAAM,CAAC;MACtC,OAAOW,OAAO,GAAGA,OAAO,CAACC,SAAS,GAAG,CAAC;IACxC,CAAC;IACDL,SAAS,EAAGC,MAAM,IAAK;MACrB,MAAMG,OAAO,GAAGZ,cAAc,CAACC,MAAM,CAAC;MACtC,IAAIW,OAAO,EAAEA,OAAO,CAACC,SAAS,GAAGJ,MAAM;IACzC,CAAC;IACDE,SAAS,EAAEA,CAAA,KAAM;MACf,MAAMC,OAAO,GAAGZ,cAAc,CAACC,MAAM,CAAC;MACtC,OAAOW,OAAO,GAAGA,OAAO,CAACE,YAAY,GAAGF,OAAO,CAACG,YAAY,GAAG,KAAK;IACtE;EACF,CAAC;AACH","ignoreList":[]}
@@ -4,24 +4,72 @@
4
4
  * Web variant of the scroll-restoration primitive.
5
5
  *
6
6
  * Mirrors the proven Bluesky pattern (`history.scrollRestoration = 'manual'`
7
- * plus an in-memory `Map<routeKey, offset>` saved on blur and restored on focus
8
- * inside a single `requestAnimationFrame`) with one deliberate difference:
9
- * Bluesky restores the WINDOW scroller, whereas Oxy apps keep multi-column
10
- * layouts whose feed scrolls an INNER container. So we restore the offset of a
11
- * caller-registered scrollable (a ref to an element / RN scroll component, or
12
- * the `'window'` sentinel), keyed by the active navigation route.
7
+ * plus an in-memory `Map<routeKey, offset>`) with two deliberate differences
8
+ * forced by Oxy's layouts and the behaviour of the (expo-router-wrapped)
9
+ * React Navigation web stack:
10
+ *
11
+ * 1. Bluesky restores the WINDOW scroller, whereas Oxy apps keep multi-column
12
+ * layouts whose feed scrolls an INNER container. So we restore the offset
13
+ * of a caller-registered scrollable (a ref to an element / RN scroll
14
+ * component, or the `'window'` sentinel), keyed by the active route.
15
+ *
16
+ * 2. The web stack HIDES the background screen on push. While
17
+ * hidden, the previous screen's scroll container collapses
18
+ * (`scrollHeight === clientHeight`) and the navigator forces its
19
+ * `scrollTop` to 0. The screen is NOT unmounted, so a virtualized list
20
+ * (e.g. FlashList) keeps its rows but re-lays them out over SEVERAL frames
21
+ * once the screen is re-shown. Two problems follow, both handled here:
22
+ *
23
+ * (a) A blur-time read of `scrollTop` returns the navigator's forced 0,
24
+ * not the user's real offset — saving it would clobber the good
25
+ * value. We therefore persist the last offset OBSERVED by the live
26
+ * scroll listener, never a fresh read taken at blur time.
27
+ *
28
+ * (b) A single-frame restore writes `scrollTop` while the list is still
29
+ * collapsed; the write is clamped to 0 and never re-applied once the
30
+ * content grows. We therefore re-apply the target offset across a
31
+ * bounded run of animation frames, stopping as soon as the write
32
+ * sticks (the content has grown tall enough) or a small frame cap is
33
+ * reached.
13
34
  *
14
35
  * Native bundlers use `./index.ts` (a no-op); web bundlers select this file via
15
36
  * the `"browser"` export condition in `package.json`.
37
+ *
38
+ * The navigation hooks are imported from `expo-router` (which re-exports
39
+ * `useFocusEffect` and `useRoute` from its bundled React Navigation core) rather
40
+ * than from `@react-navigation/native` directly. Every Oxy app uses expo-router
41
+ * as its router, so it is always a DIRECT, top-level dependency that resolves
42
+ * cleanly under Bun's isolated linker — whereas `@react-navigation/native` is
43
+ * only a nested/transitive dependency of expo-router and would fail to resolve
44
+ * when bundling those apps.
16
45
  */
17
46
  import { createContext, useCallback, useContext, useMemo, useRef } from 'react';
18
- import { useFocusEffect, useRoute } from '@react-navigation/native';
47
+ import { useFocusEffect, useRoute } from 'expo-router';
19
48
  import { createScroller } from "./scrollable.web.js";
20
49
  import { ScrollOffsetStore, deriveScrollKey } from "./store.js";
21
50
  import { jsx as _jsx } from "react/jsx-runtime";
22
51
  const ScrollOffsetContext = /*#__PURE__*/createContext(null);
23
52
  ScrollOffsetContext.displayName = 'BloomScrollOffsetContext';
24
53
 
54
+ /**
55
+ * Maximum number of animation frames the focus restore will re-apply the saved
56
+ * offset before giving up. A virtualized list re-lays out its rows over a
57
+ * handful of frames after its screen is re-shown; ~30 frames (≈0.5s at 60fps)
58
+ * is comfortably longer than any observed relayout while staying short enough
59
+ * that the loop never lingers as a perceptible cost. The loop normally exits
60
+ * far earlier — as soon as the write sticks.
61
+ */
62
+ const RESTORE_FRAME_CAP = 30;
63
+
64
+ /**
65
+ * Tolerance (in CSS pixels) for considering a restore "stuck". After writing
66
+ * `element.scrollTop = target`, the browser may clamp it to the current
67
+ * `scrollHeight - clientHeight`; if the resulting offset is within this many
68
+ * pixels of the target we treat the restore as complete. Sub-pixel rounding and
69
+ * fractional device-pixel ratios make an exact equality check unreliable.
70
+ */
71
+ const RESTORE_STICK_TOLERANCE_PX = 2;
72
+
25
73
  /**
26
74
  * Switch the browser to manual scroll restoration exactly once per document.
27
75
  *
@@ -61,11 +109,15 @@ function useScrollOffsetStore() {
61
109
  * multiple scrollables).
62
110
  *
63
111
  * Behaviour (web):
64
- * - On every scroll while the screen is focused, the current offset is saved.
65
- * - On focus, the saved offset is applied in a single `requestAnimationFrame`,
66
- * giving a remounted list one frame to lay out its content first (no retry
67
- * loops, no hide/show tricks).
68
- * - On blur, the latest offset is captured as a final safety net.
112
+ * - On every scroll while the screen is focused, the current offset is recorded
113
+ * in memory and persisted. This live stream of saves is the source of truth.
114
+ * - On focus, the saved offset is re-applied across a bounded run of animation
115
+ * frames, stopping as soon as the write sticks (the list has re-rendered its
116
+ * rows and grown tall enough) or {@link RESTORE_FRAME_CAP} is reached. A
117
+ * saved offset of 0 is a no-op (nothing to restore).
118
+ * - On blur, the LAST OBSERVED offset is persisted as a final safety net — not
119
+ * a fresh `scrollTop` read, which the navigator may already have forced to 0
120
+ * while collapsing the hidden screen.
69
121
  */
70
122
  export function useScrollRestoration(target, options) {
71
123
  const store = useScrollOffsetStore();
@@ -84,33 +136,74 @@ export function useScrollRestoration(target, options) {
84
136
  scrollKeyRef.current = scrollKey;
85
137
  useFocusEffect(
86
138
  // The effect identity is intentionally stable across renders: it reads all
87
- // varying inputs from refs. React Navigation re-runs it on each focus.
139
+ // varying inputs from refs. expo-router's `useFocusEffect` re-runs it on
140
+ // each focus.
88
141
  useCallback(() => {
89
142
  const key = scrollKeyRef.current;
90
143
  if (!enabledRef.current || key === null) return undefined;
91
144
  const scroller = createScroller(targetRef.current);
92
145
  const element = targetRef.current === 'window' ? typeof window !== 'undefined' ? window : null : resolveScrollEventTarget(targetRef.current);
146
+
147
+ // The last offset the live scroll listener observed for this focus
148
+ // session. This — not a blur-time `getOffset()` — is what we persist on
149
+ // blur, because by blur time the navigator may have collapsed the
150
+ // hidden screen and forced its `scrollTop` to 0 (bug A). `null` means
151
+ // the user never scrolled this session, so there is nothing newer to
152
+ // persist than what the scroll listener already saved live.
153
+ let lastObservedOffset = null;
93
154
  const save = () => {
94
155
  const currentKey = scrollKeyRef.current;
95
- if (enabledRef.current && currentKey !== null) {
96
- store.save(currentKey, scroller.getOffset());
97
- }
156
+ if (!enabledRef.current || currentKey === null) return;
157
+ const offset = scroller.getOffset();
158
+ // Ignore a spurious 0 produced by the navigator collapsing a hidden
159
+ // background screen: while collapsed the container cannot scroll, so
160
+ // its `scrollTop` is forced to 0. Persisting it would clobber the
161
+ // good offset recorded by earlier live saves (bug A). A genuine
162
+ // scroll-to-top keeps the container scrollable and is saved normally.
163
+ if (offset === 0 && !scroller.canScroll()) return;
164
+ lastObservedOffset = offset;
165
+ store.save(currentKey, offset);
98
166
  };
99
167
 
100
- // Restore on the next frame so a freshly remounted list has rendered
101
- // its content (and thus reached its full scroll height) before we move.
102
- const frame = requestAnimationFrame(() => {
103
- scroller.setOffset(store.read(key));
104
- });
168
+ // Restore across a bounded run of frames. A freshly re-shown
169
+ // virtualized list re-lays out its rows over several frames, so a
170
+ // single write while it is still collapsed would be clamped to 0 and
171
+ // never re-applied (bug B). We re-apply each frame until the write
172
+ // sticks or the frame cap is hit.
173
+ const targetOffset = store.read(key);
174
+ let rafId = null;
175
+ if (targetOffset > 0 && typeof requestAnimationFrame !== 'undefined') {
176
+ let framesLeft = RESTORE_FRAME_CAP;
177
+ const applyOffset = () => {
178
+ rafId = null;
179
+ scroller.setOffset(targetOffset);
180
+ framesLeft -= 1;
181
+ // Stop once the write took effect (content grew tall enough) or we
182
+ // exhaust the frame budget. `getOffset` re-reads the clamped value.
183
+ const reached = Math.abs(scroller.getOffset() - targetOffset) <= RESTORE_STICK_TOLERANCE_PX;
184
+ if (!reached && framesLeft > 0) {
185
+ rafId = requestAnimationFrame(applyOffset);
186
+ }
187
+ };
188
+ rafId = requestAnimationFrame(applyOffset);
189
+ }
105
190
  element?.addEventListener('scroll', save, {
106
191
  passive: true
107
192
  });
108
193
  return () => {
109
- cancelAnimationFrame(frame);
194
+ if (rafId !== null) cancelAnimationFrame(rafId);
110
195
  element?.removeEventListener('scroll', save);
111
- // Final capture on blur, covering navigations that don't fire a
112
- // trailing scroll event.
113
- save();
196
+ // Final capture on blur: persist the last offset the scroll listener
197
+ // OBSERVED, never a fresh read (which the navigator may have forced
198
+ // to 0 while collapsing the hidden screen). When the user never
199
+ // scrolled this session there is nothing newer to persist than the
200
+ // live saves already recorded.
201
+ if (lastObservedOffset !== null) {
202
+ const currentKey = scrollKeyRef.current;
203
+ if (enabledRef.current && currentKey !== null) {
204
+ store.save(currentKey, lastObservedOffset);
205
+ }
206
+ }
114
207
  };
115
208
  }, [store]));
116
209
  }
@@ -1 +1 @@
1
- {"version":3,"names":["createContext","useCallback","useContext","useMemo","useRef","useFocusEffect","useRoute","createScroller","ScrollOffsetStore","deriveScrollKey","jsx","_jsx","ScrollOffsetContext","displayName","history","scrollRestoration","ScrollRestorationProvider","children","store","Provider","value","useScrollOffsetStore","Error","useScrollRestoration","target","options","route","subKey","key","enabled","scrollKey","targetRef","current","enabledRef","scrollKeyRef","undefined","scroller","element","window","resolveScrollEventTarget","save","currentKey","getOffset","frame","requestAnimationFrame","setOffset","read","addEventListener","passive","cancelAnimationFrame","removeEventListener","EventTarget","handle","getScrollableNode","node"],"sourceRoot":"../../../src","sources":["scroll/index.web.tsx"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["createContext","useCallback","useContext","useMemo","useRef","useFocusEffect","useRoute","createScroller","ScrollOffsetStore","deriveScrollKey","jsx","_jsx","ScrollOffsetContext","displayName","RESTORE_FRAME_CAP","RESTORE_STICK_TOLERANCE_PX","history","scrollRestoration","ScrollRestorationProvider","children","store","Provider","value","useScrollOffsetStore","Error","useScrollRestoration","target","options","route","subKey","key","enabled","scrollKey","targetRef","current","enabledRef","scrollKeyRef","undefined","scroller","element","window","resolveScrollEventTarget","lastObservedOffset","save","currentKey","offset","getOffset","canScroll","targetOffset","read","rafId","requestAnimationFrame","framesLeft","applyOffset","setOffset","reached","Math","abs","addEventListener","passive","cancelAnimationFrame","removeEventListener","EventTarget","handle","getScrollableNode","node"],"sourceRoot":"../../../src","sources":["scroll/index.web.tsx"],"mappings":"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","ignoreList":[]}
@@ -2,8 +2,8 @@
2
2
 
3
3
  /**
4
4
  * A normalized read/write interface over whatever the caller registered, so
5
- * the hook does not branch on target shape. Both methods are no-ops when the
6
- * underlying element is not yet (or no longer) attached.
5
+ * the hook does not branch on target shape. All methods are safe no-ops when
6
+ * the underlying element is not yet (or no longer) attached.
7
7
  */
8
8
 
9
9
  function isElement(value) {
@@ -43,7 +43,8 @@ export function createScroller(target) {
43
43
  getOffset: () => typeof window === 'undefined' ? 0 : window.scrollY,
44
44
  setOffset: offset => {
45
45
  if (typeof window !== 'undefined') window.scrollTo(0, offset);
46
- }
46
+ },
47
+ canScroll: () => true
47
48
  };
48
49
  }
49
50
  return {
@@ -54,6 +55,10 @@ export function createScroller(target) {
54
55
  setOffset: offset => {
55
56
  const element = resolveElement(target);
56
57
  if (element) element.scrollTop = offset;
58
+ },
59
+ canScroll: () => {
60
+ const element = resolveElement(target);
61
+ return element ? element.scrollHeight > element.clientHeight : false;
57
62
  }
58
63
  };
59
64
  }
@@ -1 +1 @@
1
- {"version":3,"names":["isElement","value","HTMLElement","hasGetScrollableNode","getScrollableNode","resolveElement","target","current","node","createScroller","getOffset","window","scrollY","setOffset","offset","scrollTo","element","scrollTop"],"sourceRoot":"../../../src","sources":["scroll/scrollable.web.ts"],"mappings":";;AAEA;AACA;AACA;AACA;AACA;;AAMA,SAASA,SAASA,CAACC,KAAc,EAAwB;EACvD,OAAO,OAAOC,WAAW,KAAK,WAAW,IAAID,KAAK,YAAYC,WAAW;AAC3E;AAEA,SAASC,oBAAoBA,CAC3BF,KAAc,EACkD;EAChE,OACE,OAAOA,KAAK,KAAK,QAAQ,IACzBA,KAAK,KAAK,IAAI,IACd,OAAQA,KAAK,CAAsBG,iBAAiB,KAAK,UAAU;AAEvE;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASC,cAAcA,CAACC,MAA+B,EAAsB;EAC3E,IAAIA,MAAM,KAAK,QAAQ,EAAE,OAAO,IAAI;EAEpC,MAAMC,OAAO,GAAID,MAAM,CAA0BC,OAAO;EACxD,IAAIA,OAAO,IAAI,IAAI,EAAE,OAAO,IAAI;EAEhC,IAAIP,SAAS,CAACO,OAAO,CAAC,EAAE,OAAOA,OAAO;EAEtC,IAAIJ,oBAAoB,CAACI,OAAO,CAAC,EAAE;IACjC,MAAMC,IAAI,GAAGD,OAAO,CAACH,iBAAiB,CAAC,CAAC;IACxC,IAAIJ,SAAS,CAACQ,IAAI,CAAC,EAAE,OAAOA,IAAI;EAClC;EAEA,OAAO,IAAI;AACb;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,cAAcA,CAACH,MAA+B,EAAoB;EAChF,IAAIA,MAAM,KAAK,QAAQ,EAAE;IACvB,OAAO;MACLI,SAAS,EAAEA,CAAA,KAAO,OAAOC,MAAM,KAAK,WAAW,GAAG,CAAC,GAAGA,MAAM,CAACC,OAAQ;MACrEC,SAAS,EAAGC,MAAM,IAAK;QACrB,IAAI,OAAOH,MAAM,KAAK,WAAW,EAAEA,MAAM,CAACI,QAAQ,CAAC,CAAC,EAAED,MAAM,CAAC;MAC/D;IACF,CAAC;EACH;EAEA,OAAO;IACLJ,SAAS,EAAEA,CAAA,KAAM;MACf,MAAMM,OAAO,GAAGX,cAAc,CAACC,MAAM,CAAC;MACtC,OAAOU,OAAO,GAAGA,OAAO,CAACC,SAAS,GAAG,CAAC;IACxC,CAAC;IACDJ,SAAS,EAAGC,MAAM,IAAK;MACrB,MAAME,OAAO,GAAGX,cAAc,CAACC,MAAM,CAAC;MACtC,IAAIU,OAAO,EAAEA,OAAO,CAACC,SAAS,GAAGH,MAAM;IACzC;EACF,CAAC;AACH","ignoreList":[]}
1
+ {"version":3,"names":["isElement","value","HTMLElement","hasGetScrollableNode","getScrollableNode","resolveElement","target","current","node","createScroller","getOffset","window","scrollY","setOffset","offset","scrollTo","canScroll","element","scrollTop","scrollHeight","clientHeight"],"sourceRoot":"../../../src","sources":["scroll/scrollable.web.ts"],"mappings":";;AAEA;AACA;AACA;AACA;AACA;;AAkBA,SAASA,SAASA,CAACC,KAAc,EAAwB;EACvD,OAAO,OAAOC,WAAW,KAAK,WAAW,IAAID,KAAK,YAAYC,WAAW;AAC3E;AAEA,SAASC,oBAAoBA,CAC3BF,KAAc,EACkD;EAChE,OACE,OAAOA,KAAK,KAAK,QAAQ,IACzBA,KAAK,KAAK,IAAI,IACd,OAAQA,KAAK,CAAsBG,iBAAiB,KAAK,UAAU;AAEvE;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASC,cAAcA,CAACC,MAA+B,EAAsB;EAC3E,IAAIA,MAAM,KAAK,QAAQ,EAAE,OAAO,IAAI;EAEpC,MAAMC,OAAO,GAAID,MAAM,CAA0BC,OAAO;EACxD,IAAIA,OAAO,IAAI,IAAI,EAAE,OAAO,IAAI;EAEhC,IAAIP,SAAS,CAACO,OAAO,CAAC,EAAE,OAAOA,OAAO;EAEtC,IAAIJ,oBAAoB,CAACI,OAAO,CAAC,EAAE;IACjC,MAAMC,IAAI,GAAGD,OAAO,CAACH,iBAAiB,CAAC,CAAC;IACxC,IAAIJ,SAAS,CAACQ,IAAI,CAAC,EAAE,OAAOA,IAAI;EAClC;EAEA,OAAO,IAAI;AACb;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,cAAcA,CAACH,MAA+B,EAAoB;EAChF,IAAIA,MAAM,KAAK,QAAQ,EAAE;IACvB,OAAO;MACLI,SAAS,EAAEA,CAAA,KAAO,OAAOC,MAAM,KAAK,WAAW,GAAG,CAAC,GAAGA,MAAM,CAACC,OAAQ;MACrEC,SAAS,EAAGC,MAAM,IAAK;QACrB,IAAI,OAAOH,MAAM,KAAK,WAAW,EAAEA,MAAM,CAACI,QAAQ,CAAC,CAAC,EAAED,MAAM,CAAC;MAC/D,CAAC;MACDE,SAAS,EAAEA,CAAA,KAAM;IACnB,CAAC;EACH;EAEA,OAAO;IACLN,SAAS,EAAEA,CAAA,KAAM;MACf,MAAMO,OAAO,GAAGZ,cAAc,CAACC,MAAM,CAAC;MACtC,OAAOW,OAAO,GAAGA,OAAO,CAACC,SAAS,GAAG,CAAC;IACxC,CAAC;IACDL,SAAS,EAAGC,MAAM,IAAK;MACrB,MAAMG,OAAO,GAAGZ,cAAc,CAACC,MAAM,CAAC;MACtC,IAAIW,OAAO,EAAEA,OAAO,CAACC,SAAS,GAAGJ,MAAM;IACzC,CAAC;IACDE,SAAS,EAAEA,CAAA,KAAM;MACf,MAAMC,OAAO,GAAGZ,cAAc,CAACC,MAAM,CAAC;MACtC,OAAOW,OAAO,GAAGA,OAAO,CAACE,YAAY,GAAGF,OAAO,CAACG,YAAY,GAAG,KAAK;IACtE;EACF,CAAC;AACH","ignoreList":[]}
@@ -12,11 +12,15 @@ export declare function ScrollRestorationProvider({ children, }: ScrollRestorati
12
12
  * multiple scrollables).
13
13
  *
14
14
  * Behaviour (web):
15
- * - On every scroll while the screen is focused, the current offset is saved.
16
- * - On focus, the saved offset is applied in a single `requestAnimationFrame`,
17
- * giving a remounted list one frame to lay out its content first (no retry
18
- * loops, no hide/show tricks).
19
- * - On blur, the latest offset is captured as a final safety net.
15
+ * - On every scroll while the screen is focused, the current offset is recorded
16
+ * in memory and persisted. This live stream of saves is the source of truth.
17
+ * - On focus, the saved offset is re-applied across a bounded run of animation
18
+ * frames, stopping as soon as the write sticks (the list has re-rendered its
19
+ * rows and grown tall enough) or {@link RESTORE_FRAME_CAP} is reached. A
20
+ * saved offset of 0 is a no-op (nothing to restore).
21
+ * - On blur, the LAST OBSERVED offset is persisted as a final safety net — not
22
+ * a fresh `scrollTop` read, which the navigator may already have forced to 0
23
+ * while collapsing the hidden screen.
20
24
  */
21
25
  export declare function useScrollRestoration(target: ScrollRestorationTarget, options?: UseScrollRestorationOptions): void;
22
26
  //# sourceMappingURL=index.web.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.web.d.ts","sourceRoot":"","sources":["../../../../src/scroll/index.web.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EACV,8BAA8B,EAC9B,uBAAuB,EACvB,2BAA2B,EAC5B,MAAM,SAAS,CAAC;AAEjB,YAAY,EACV,gBAAgB,EAChB,8BAA8B,EAC9B,uBAAuB,EACvB,2BAA2B,GAC5B,MAAM,SAAS,CAAC;AAgBjB;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,QAAQ,GACT,EAAE,8BAA8B,2CAOhC;AAYD;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,uBAAuB,EAC/B,OAAO,CAAC,EAAE,2BAA2B,GACpC,IAAI,CAyDN"}
1
+ {"version":3,"file":"index.web.d.ts","sourceRoot":"","sources":["../../../../src/scroll/index.web.tsx"],"names":[],"mappings":"AAgDA,OAAO,KAAK,EACV,8BAA8B,EAC9B,uBAAuB,EACvB,2BAA2B,EAC5B,MAAM,SAAS,CAAC;AAEjB,YAAY,EACV,gBAAgB,EAChB,8BAA8B,EAC9B,uBAAuB,EACvB,2BAA2B,GAC5B,MAAM,SAAS,CAAC;AAmCjB;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,QAAQ,GACT,EAAE,8BAA8B,2CAOhC;AAYD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,uBAAuB,EAC/B,OAAO,CAAC,EAAE,2BAA2B,GACpC,IAAI,CAqGN"}
@@ -1,12 +1,24 @@
1
1
  import type { ScrollRestorationTarget } from './types';
2
2
  /**
3
3
  * A normalized read/write interface over whatever the caller registered, so
4
- * the hook does not branch on target shape. Both methods are no-ops when the
5
- * underlying element is not yet (or no longer) attached.
4
+ * the hook does not branch on target shape. All methods are safe no-ops when
5
+ * the underlying element is not yet (or no longer) attached.
6
6
  */
7
7
  export interface ResolvedScroller {
8
8
  getOffset: () => number;
9
9
  setOffset: (offset: number) => void;
10
+ /**
11
+ * Whether the scroll container can currently hold a non-zero offset, i.e.
12
+ * its content is taller than its viewport (`scrollHeight > clientHeight`).
13
+ *
14
+ * React Navigation's web stack collapses a hidden background screen so its
15
+ * content height drops to the viewport height; while collapsed the container
16
+ * cannot be scrolled and its `scrollTop` is forced to 0. The hook uses this
17
+ * to ignore a spurious 0 read coming from a collapsed container rather than
18
+ * persisting it over a previously-saved good offset. The `'window'` scroller
19
+ * is never collapsed by the navigator, so it always reports `true`.
20
+ */
21
+ canScroll: () => boolean;
10
22
  }
11
23
  /**
12
24
  * Build a {@link ResolvedScroller} for a target. The window sentinel reads and
@@ -1 +1 @@
1
- {"version":3,"file":"scrollable.web.d.ts","sourceRoot":"","sources":["../../../../src/scroll/scrollable.web.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAoB,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAEzE;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,MAAM,CAAC;IACxB,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC;AAuCD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,uBAAuB,GAAG,gBAAgB,CAoBhF"}
1
+ {"version":3,"file":"scrollable.web.d.ts","sourceRoot":"","sources":["../../../../src/scroll/scrollable.web.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAoB,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAEzE;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,MAAM,CAAC;IACxB,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC;;;;;;;;;;OAUG;IACH,SAAS,EAAE,MAAM,OAAO,CAAC;CAC1B;AAuCD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,uBAAuB,GAAG,gBAAgB,CAyBhF"}
@@ -12,11 +12,15 @@ export declare function ScrollRestorationProvider({ children, }: ScrollRestorati
12
12
  * multiple scrollables).
13
13
  *
14
14
  * Behaviour (web):
15
- * - On every scroll while the screen is focused, the current offset is saved.
16
- * - On focus, the saved offset is applied in a single `requestAnimationFrame`,
17
- * giving a remounted list one frame to lay out its content first (no retry
18
- * loops, no hide/show tricks).
19
- * - On blur, the latest offset is captured as a final safety net.
15
+ * - On every scroll while the screen is focused, the current offset is recorded
16
+ * in memory and persisted. This live stream of saves is the source of truth.
17
+ * - On focus, the saved offset is re-applied across a bounded run of animation
18
+ * frames, stopping as soon as the write sticks (the list has re-rendered its
19
+ * rows and grown tall enough) or {@link RESTORE_FRAME_CAP} is reached. A
20
+ * saved offset of 0 is a no-op (nothing to restore).
21
+ * - On blur, the LAST OBSERVED offset is persisted as a final safety net — not
22
+ * a fresh `scrollTop` read, which the navigator may already have forced to 0
23
+ * while collapsing the hidden screen.
20
24
  */
21
25
  export declare function useScrollRestoration(target: ScrollRestorationTarget, options?: UseScrollRestorationOptions): void;
22
26
  //# sourceMappingURL=index.web.d.ts.map