@oxyhq/bloom 0.6.23 → 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.
- package/lib/commonjs/scroll/index.web.js +16 -6
- package/lib/commonjs/scroll/index.web.js.map +1 -1
- package/lib/module/scroll/index.web.js +14 -4
- package/lib/module/scroll/index.web.js.map +1 -1
- package/lib/typescript/commonjs/scroll/index.web.d.ts.map +1 -1
- package/lib/typescript/module/scroll/index.web.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/scroll-web.test.tsx +26 -17
- package/src/scroll/index.web.tsx +14 -4
|
@@ -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
|
|
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");
|
|
@@ -15,14 +15,15 @@ var _jsxRuntime = require("react/jsx-runtime");
|
|
|
15
15
|
*
|
|
16
16
|
* Mirrors the proven Bluesky pattern (`history.scrollRestoration = 'manual'`
|
|
17
17
|
* plus an in-memory `Map<routeKey, offset>`) with two deliberate differences
|
|
18
|
-
* forced by Oxy's layouts and the behaviour of
|
|
18
|
+
* forced by Oxy's layouts and the behaviour of the (expo-router-wrapped)
|
|
19
|
+
* React Navigation web stack:
|
|
19
20
|
*
|
|
20
21
|
* 1. Bluesky restores the WINDOW scroller, whereas Oxy apps keep multi-column
|
|
21
22
|
* layouts whose feed scrolls an INNER container. So we restore the offset
|
|
22
23
|
* of a caller-registered scrollable (a ref to an element / RN scroll
|
|
23
24
|
* component, or the `'window'` sentinel), keyed by the active route.
|
|
24
25
|
*
|
|
25
|
-
* 2.
|
|
26
|
+
* 2. The web stack HIDES the background screen on push. While
|
|
26
27
|
* hidden, the previous screen's scroll container collapses
|
|
27
28
|
* (`scrollHeight === clientHeight`) and the navigator forces its
|
|
28
29
|
* `scrollTop` to 0. The screen is NOT unmounted, so a virtualized list
|
|
@@ -43,6 +44,14 @@ var _jsxRuntime = require("react/jsx-runtime");
|
|
|
43
44
|
*
|
|
44
45
|
* Native bundlers use `./index.ts` (a no-op); web bundlers select this file via
|
|
45
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.
|
|
46
55
|
*/
|
|
47
56
|
|
|
48
57
|
const ScrollOffsetContext = /*#__PURE__*/(0, _react.createContext)(null);
|
|
@@ -118,7 +127,7 @@ function useScrollOffsetStore() {
|
|
|
118
127
|
*/
|
|
119
128
|
function useScrollRestoration(target, options) {
|
|
120
129
|
const store = useScrollOffsetStore();
|
|
121
|
-
const route = (0,
|
|
130
|
+
const route = (0, _expoRouter.useRoute)();
|
|
122
131
|
const subKey = options?.key;
|
|
123
132
|
const enabled = options?.enabled ?? true;
|
|
124
133
|
const scrollKey = (0, _store.deriveScrollKey)(route.key, subKey);
|
|
@@ -131,9 +140,10 @@ function useScrollRestoration(target, options) {
|
|
|
131
140
|
enabledRef.current = enabled;
|
|
132
141
|
const scrollKeyRef = (0, _react.useRef)(scrollKey);
|
|
133
142
|
scrollKeyRef.current = scrollKey;
|
|
134
|
-
(0,
|
|
143
|
+
(0, _expoRouter.useFocusEffect)(
|
|
135
144
|
// The effect identity is intentionally stable across renders: it reads all
|
|
136
|
-
// varying inputs from refs.
|
|
145
|
+
// varying inputs from refs. expo-router's `useFocusEffect` re-runs it on
|
|
146
|
+
// each focus.
|
|
137
147
|
(0, _react.useCallback)(() => {
|
|
138
148
|
const key = scrollKeyRef.current;
|
|
139
149
|
if (!enabledRef.current || key === null) return undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","require","
|
|
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":[]}
|
|
@@ -5,14 +5,15 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Mirrors the proven Bluesky pattern (`history.scrollRestoration = 'manual'`
|
|
7
7
|
* plus an in-memory `Map<routeKey, offset>`) with two deliberate differences
|
|
8
|
-
* forced by Oxy's layouts and the behaviour of
|
|
8
|
+
* forced by Oxy's layouts and the behaviour of the (expo-router-wrapped)
|
|
9
|
+
* React Navigation web stack:
|
|
9
10
|
*
|
|
10
11
|
* 1. Bluesky restores the WINDOW scroller, whereas Oxy apps keep multi-column
|
|
11
12
|
* layouts whose feed scrolls an INNER container. So we restore the offset
|
|
12
13
|
* of a caller-registered scrollable (a ref to an element / RN scroll
|
|
13
14
|
* component, or the `'window'` sentinel), keyed by the active route.
|
|
14
15
|
*
|
|
15
|
-
* 2.
|
|
16
|
+
* 2. The web stack HIDES the background screen on push. While
|
|
16
17
|
* hidden, the previous screen's scroll container collapses
|
|
17
18
|
* (`scrollHeight === clientHeight`) and the navigator forces its
|
|
18
19
|
* `scrollTop` to 0. The screen is NOT unmounted, so a virtualized list
|
|
@@ -33,9 +34,17 @@
|
|
|
33
34
|
*
|
|
34
35
|
* Native bundlers use `./index.ts` (a no-op); web bundlers select this file via
|
|
35
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.
|
|
36
45
|
*/
|
|
37
46
|
import { createContext, useCallback, useContext, useMemo, useRef } from 'react';
|
|
38
|
-
import { useFocusEffect, useRoute } from '
|
|
47
|
+
import { useFocusEffect, useRoute } from 'expo-router';
|
|
39
48
|
import { createScroller } from "./scrollable.web.js";
|
|
40
49
|
import { ScrollOffsetStore, deriveScrollKey } from "./store.js";
|
|
41
50
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
@@ -127,7 +136,8 @@ export function useScrollRestoration(target, options) {
|
|
|
127
136
|
scrollKeyRef.current = scrollKey;
|
|
128
137
|
useFocusEffect(
|
|
129
138
|
// The effect identity is intentionally stable across renders: it reads all
|
|
130
|
-
// varying inputs from refs.
|
|
139
|
+
// varying inputs from refs. expo-router's `useFocusEffect` re-runs it on
|
|
140
|
+
// each focus.
|
|
131
141
|
useCallback(() => {
|
|
132
142
|
const key = scrollKeyRef.current;
|
|
133
143
|
if (!enabledRef.current || key === null) return undefined;
|
|
@@ -1 +1 @@
|
|
|
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":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASA,aAAa,EAAEC,WAAW,EAAEC,UAAU,EAAEC,OAAO,EAAEC,MAAM,QAAQ,OAAO;AAC/E,SAASC,cAAc,EAAEC,QAAQ,QAAQ,
|
|
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":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.web.d.ts","sourceRoot":"","sources":["../../../../src/scroll/index.web.tsx"],"names":[],"mappings":"
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.web.d.ts","sourceRoot":"","sources":["../../../../src/scroll/index.web.tsx"],"names":[],"mappings":"
|
|
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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/bloom",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.24",
|
|
4
4
|
"description": "Bloom UI — Oxy ecosystem component library for React Native + Expo + Web",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -631,7 +631,6 @@
|
|
|
631
631
|
}
|
|
632
632
|
},
|
|
633
633
|
"devDependencies": {
|
|
634
|
-
"@react-navigation/native": "^7.0.0",
|
|
635
634
|
"@storybook/addon-docs": "^10",
|
|
636
635
|
"@storybook/react-vite": "^10",
|
|
637
636
|
"@testing-library/react-native": "^13.3.3",
|
|
@@ -641,6 +640,7 @@
|
|
|
641
640
|
"@types/react-native": "*",
|
|
642
641
|
"@vitejs/plugin-react": "^6.0.2",
|
|
643
642
|
"expo-font": "^56.0.5",
|
|
643
|
+
"expo-router": "^56.0.0",
|
|
644
644
|
"jest": "^30.3.0",
|
|
645
645
|
"jest-environment-jsdom": "^30.4.1",
|
|
646
646
|
"react": "19.2.0",
|
|
@@ -662,9 +662,9 @@
|
|
|
662
662
|
"vite": "^7"
|
|
663
663
|
},
|
|
664
664
|
"peerDependencies": {
|
|
665
|
-
"@react-navigation/native": ">=6.0.0",
|
|
666
665
|
"expo": "*",
|
|
667
666
|
"expo-font": "*",
|
|
667
|
+
"expo-router": ">=3.0.0",
|
|
668
668
|
"react": ">=18.0.0",
|
|
669
669
|
"react-dom": ">=18.0.0",
|
|
670
670
|
"react-native": ">=0.73.0",
|
|
@@ -676,15 +676,15 @@
|
|
|
676
676
|
"sonner-native": ">=0.17.0"
|
|
677
677
|
},
|
|
678
678
|
"peerDependenciesMeta": {
|
|
679
|
-
"@react-navigation/native": {
|
|
680
|
-
"optional": true
|
|
681
|
-
},
|
|
682
679
|
"expo": {
|
|
683
680
|
"optional": true
|
|
684
681
|
},
|
|
685
682
|
"expo-font": {
|
|
686
683
|
"optional": true
|
|
687
684
|
},
|
|
685
|
+
"expo-router": {
|
|
686
|
+
"optional": true
|
|
687
|
+
},
|
|
688
688
|
"react-dom": {
|
|
689
689
|
"optional": true
|
|
690
690
|
},
|
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
// Exercises the WEB scroll-restoration hook (`scroll/index.web`) against the
|
|
6
|
-
// exact failure mode it was written to survive:
|
|
7
|
-
// collapses a hidden background screen (forcing its
|
|
8
|
-
// and a re-shown virtualized list re-lays out its
|
|
9
|
-
// full scroll height — over SEVERAL frames after
|
|
6
|
+
// exact failure mode it was written to survive: the (expo-router-wrapped)
|
|
7
|
+
// React Navigation web stack collapses a hidden background screen (forcing its
|
|
8
|
+
// `scrollTop` to 0) on push, and a re-shown virtualized list re-lays out its
|
|
9
|
+
// rows — and thus reaches its full scroll height — over SEVERAL frames after
|
|
10
|
+
// focus.
|
|
10
11
|
//
|
|
11
|
-
// We mock
|
|
12
|
-
// mount and its cleanup on unmount, and
|
|
13
|
-
// so the multi-frame restore is
|
|
12
|
+
// We mock `expo-router` (the module the hook imports its navigation hooks from)
|
|
13
|
+
// so `useFocusEffect` runs the effect on mount and its cleanup on unmount, and
|
|
14
|
+
// drive `requestAnimationFrame` manually so the multi-frame restore is
|
|
15
|
+
// deterministic.
|
|
14
16
|
|
|
15
17
|
import { createElement, useRef, type ReactNode } from 'react';
|
|
16
18
|
import { act } from 'react';
|
|
@@ -21,22 +23,29 @@ import { createRoot, type Root } from 'react-dom/client';
|
|
|
21
23
|
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT =
|
|
22
24
|
true;
|
|
23
25
|
|
|
24
|
-
// ----
|
|
26
|
+
// ---- expo-router mock ------------------------------------------------------
|
|
25
27
|
// `useFocusEffect` here mirrors the real contract closely enough for this hook:
|
|
26
28
|
// it runs the callback in a layout effect and runs the returned cleanup on
|
|
27
29
|
// unmount. `useRoute` yields a stable per-test route key.
|
|
28
30
|
|
|
29
31
|
let currentRouteKey = 'route-test';
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
// `virtual: true` keeps the test isolated from the real (native-heavy)
|
|
34
|
+
// `expo-router` package: only the two hooks the scroll primitive consumes are
|
|
35
|
+
// stubbed, and jest never has to resolve the full module from disk.
|
|
36
|
+
jest.mock(
|
|
37
|
+
'expo-router',
|
|
38
|
+
() => {
|
|
39
|
+
const react = jest.requireActual<typeof import('react')>('react');
|
|
40
|
+
return {
|
|
41
|
+
useFocusEffect: (effect: () => undefined | (() => void)) => {
|
|
42
|
+
react.useEffect(effect, [effect]);
|
|
43
|
+
},
|
|
44
|
+
useRoute: () => ({ key: currentRouteKey, name: 'Test', params: {} }),
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
{ virtual: true },
|
|
48
|
+
);
|
|
40
49
|
|
|
41
50
|
// Imported AFTER the mock is registered.
|
|
42
51
|
import {
|
package/src/scroll/index.web.tsx
CHANGED
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Mirrors the proven Bluesky pattern (`history.scrollRestoration = 'manual'`
|
|
5
5
|
* plus an in-memory `Map<routeKey, offset>`) with two deliberate differences
|
|
6
|
-
* forced by Oxy's layouts and the behaviour of
|
|
6
|
+
* forced by Oxy's layouts and the behaviour of the (expo-router-wrapped)
|
|
7
|
+
* React Navigation web stack:
|
|
7
8
|
*
|
|
8
9
|
* 1. Bluesky restores the WINDOW scroller, whereas Oxy apps keep multi-column
|
|
9
10
|
* layouts whose feed scrolls an INNER container. So we restore the offset
|
|
10
11
|
* of a caller-registered scrollable (a ref to an element / RN scroll
|
|
11
12
|
* component, or the `'window'` sentinel), keyed by the active route.
|
|
12
13
|
*
|
|
13
|
-
* 2.
|
|
14
|
+
* 2. The web stack HIDES the background screen on push. While
|
|
14
15
|
* hidden, the previous screen's scroll container collapses
|
|
15
16
|
* (`scrollHeight === clientHeight`) and the navigator forces its
|
|
16
17
|
* `scrollTop` to 0. The screen is NOT unmounted, so a virtualized list
|
|
@@ -31,9 +32,17 @@
|
|
|
31
32
|
*
|
|
32
33
|
* Native bundlers use `./index.ts` (a no-op); web bundlers select this file via
|
|
33
34
|
* the `"browser"` export condition in `package.json`.
|
|
35
|
+
*
|
|
36
|
+
* The navigation hooks are imported from `expo-router` (which re-exports
|
|
37
|
+
* `useFocusEffect` and `useRoute` from its bundled React Navigation core) rather
|
|
38
|
+
* than from `@react-navigation/native` directly. Every Oxy app uses expo-router
|
|
39
|
+
* as its router, so it is always a DIRECT, top-level dependency that resolves
|
|
40
|
+
* cleanly under Bun's isolated linker — whereas `@react-navigation/native` is
|
|
41
|
+
* only a nested/transitive dependency of expo-router and would fail to resolve
|
|
42
|
+
* when bundling those apps.
|
|
34
43
|
*/
|
|
35
44
|
import { createContext, useCallback, useContext, useMemo, useRef } from 'react';
|
|
36
|
-
import { useFocusEffect, useRoute } from '
|
|
45
|
+
import { useFocusEffect, useRoute } from 'expo-router';
|
|
37
46
|
|
|
38
47
|
import { createScroller } from './scrollable.web';
|
|
39
48
|
import { ScrollOffsetStore, deriveScrollKey } from './store';
|
|
@@ -147,7 +156,8 @@ export function useScrollRestoration(
|
|
|
147
156
|
|
|
148
157
|
useFocusEffect(
|
|
149
158
|
// The effect identity is intentionally stable across renders: it reads all
|
|
150
|
-
// varying inputs from refs.
|
|
159
|
+
// varying inputs from refs. expo-router's `useFocusEffect` re-runs it on
|
|
160
|
+
// each focus.
|
|
151
161
|
useCallback(
|
|
152
162
|
() => {
|
|
153
163
|
const key = scrollKeyRef.current;
|