react-native-gtkx 0.2.0-alpha.2 → 0.3.0-alpha.1
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/README.md +5 -2
- package/dist/aliases/index.d.ts +84 -0
- package/dist/aliases/index.js +208 -0
- package/dist/aliases/index.js.map +1 -0
- package/dist/animated/index.d.ts +2 -0
- package/dist/animated/index.js +5 -0
- package/dist/animated/index.js.map +1 -1
- package/dist/animated/types.d.ts +1 -0
- package/dist/animated/types.js.map +1 -1
- package/dist/animated/value-animation.js +17 -4
- package/dist/animated/value-animation.js.map +1 -1
- package/dist/apis/host.gtkx.js +9 -12
- package/dist/apis/host.gtkx.js.map +1 -1
- package/dist/apis/index.d.ts +4 -2
- package/dist/apis/index.js +2 -0
- package/dist/apis/index.js.map +1 -1
- package/dist/apis/keyboard.d.ts +60 -0
- package/dist/apis/keyboard.js +63 -0
- package/dist/apis/keyboard.js.map +1 -0
- package/dist/apis/log-box.d.ts +15 -0
- package/dist/apis/log-box.js +46 -0
- package/dist/apis/log-box.js.map +1 -0
- package/dist/apis/platform.d.ts +18 -1
- package/dist/apis/platform.js.map +1 -1
- package/dist/common/index.d.ts +0 -1
- package/dist/common/index.js +24 -1
- package/dist/common/index.js.map +1 -1
- package/dist/components/animated.d.ts +70 -6
- package/dist/components/animated.js +559 -46
- package/dist/components/animated.js.map +1 -1
- package/dist/components/driven-size.d.ts +27 -0
- package/dist/components/driven-size.js +85 -0
- package/dist/components/driven-size.js.map +1 -0
- package/dist/components/find-node-handle.d.ts +11 -0
- package/dist/components/find-node-handle.js +53 -0
- package/dist/components/find-node-handle.js.map +1 -0
- package/dist/components/flat-list.d.ts +3 -2
- package/dist/components/flat-list.js.map +1 -1
- package/dist/components/frame-scheduler.d.ts +16 -0
- package/dist/components/frame-scheduler.js +37 -0
- package/dist/components/frame-scheduler.js.map +1 -0
- package/dist/components/image.d.ts +6 -1
- package/dist/components/image.js +15 -4
- package/dist/components/image.js.map +1 -1
- package/dist/components/index.d.ts +7 -5
- package/dist/components/index.js +5 -3
- package/dist/components/index.js.map +1 -1
- package/dist/components/measure.d.ts +51 -1
- package/dist/components/measure.js +101 -2
- package/dist/components/measure.js.map +1 -1
- package/dist/components/pressable.d.ts +61 -1
- package/dist/components/pressable.js +151 -4
- package/dist/components/pressable.js.map +1 -1
- package/dist/components/rect-store.d.ts +58 -0
- package/dist/components/rect-store.js +124 -3
- package/dist/components/rect-store.js.map +1 -1
- package/dist/components/root.js +6 -1
- package/dist/components/root.js.map +1 -1
- package/dist/components/scroll-phase.d.ts +35 -0
- package/dist/components/scroll-phase.js +26 -0
- package/dist/components/scroll-phase.js.map +1 -0
- package/dist/components/scroll-view.d.ts +39 -3
- package/dist/components/scroll-view.js +378 -43
- package/dist/components/scroll-view.js.map +1 -1
- package/dist/components/text-input.js +9 -25
- package/dist/components/text-input.js.map +1 -1
- package/dist/components/text.d.ts +6 -2
- package/dist/components/text.js +43 -8
- package/dist/components/text.js.map +1 -1
- package/dist/components/use-focus.d.ts +27 -0
- package/dist/components/use-focus.js +116 -0
- package/dist/components/use-focus.js.map +1 -0
- package/dist/components/use-layout-child.d.ts +13 -1
- package/dist/components/use-layout-child.js +188 -5
- package/dist/components/use-layout-child.js.map +1 -1
- package/dist/components/view.d.ts +5 -2
- package/dist/components/view.js +18 -13
- package/dist/components/view.js.map +1 -1
- package/dist/components/virtualized-list.d.ts +45 -5
- package/dist/components/virtualized-list.js +90 -24
- package/dist/components/virtualized-list.js.map +1 -1
- package/dist/components/wheel-scroll-session.d.ts +7 -0
- package/dist/components/wheel-scroll-session.js +53 -0
- package/dist/components/wheel-scroll-session.js.map +1 -0
- package/dist/components/widget-retention.d.ts +35 -0
- package/dist/components/widget-retention.js +169 -0
- package/dist/components/widget-retention.js.map +1 -0
- package/dist/contracts.d.ts +4 -0
- package/dist/contracts.js.map +1 -1
- package/dist/dnd/context.d.ts +69 -0
- package/dist/dnd/context.js +149 -0
- package/dist/dnd/context.js.map +1 -0
- package/dist/dnd/draggable.d.ts +59 -0
- package/dist/dnd/draggable.js +149 -0
- package/dist/dnd/draggable.js.map +1 -0
- package/dist/dnd/droppable.d.ts +24 -0
- package/dist/dnd/droppable.js +74 -0
- package/dist/dnd/droppable.js.map +1 -0
- package/dist/dnd/gtk-controllers.d.ts +39 -0
- package/dist/dnd/gtk-controllers.js +110 -0
- package/dist/dnd/gtk-controllers.js.map +1 -0
- package/dist/dnd/index.d.ts +6 -0
- package/dist/dnd/index.js +31 -0
- package/dist/dnd/index.js.map +1 -0
- package/dist/dnd/order.d.ts +7 -0
- package/dist/dnd/order.js +20 -0
- package/dist/dnd/order.js.map +1 -0
- package/dist/dnd/payload.d.ts +14 -0
- package/dist/dnd/payload.js +41 -0
- package/dist/dnd/payload.js.map +1 -0
- package/dist/dnd/sortable.d.ts +49 -0
- package/dist/dnd/sortable.js +307 -0
- package/dist/dnd/sortable.js.map +1 -0
- package/dist/dnd/types.d.ts +290 -0
- package/dist/dnd/types.js +25 -0
- package/dist/dnd/types.js.map +1 -0
- package/dist/gesture-handler-compat/builder.d.ts +251 -0
- package/dist/gesture-handler-compat/builder.js +513 -0
- package/dist/gesture-handler-compat/builder.js.map +1 -0
- package/dist/gesture-handler-compat/composition.d.ts +23 -0
- package/dist/gesture-handler-compat/composition.js +111 -0
- package/dist/gesture-handler-compat/composition.js.map +1 -0
- package/dist/gesture-handler-compat/deciders.d.ts +3 -0
- package/dist/gesture-handler-compat/deciders.js +47 -0
- package/dist/gesture-handler-compat/deciders.js.map +1 -0
- package/dist/gesture-handler-compat/detector-runtime.d.ts +44 -0
- package/dist/gesture-handler-compat/detector-runtime.js +374 -0
- package/dist/gesture-handler-compat/detector-runtime.js.map +1 -0
- package/dist/gesture-handler-compat/detector.d.ts +22 -0
- package/dist/gesture-handler-compat/detector.js +104 -0
- package/dist/gesture-handler-compat/detector.js.map +1 -0
- package/dist/gesture-handler-compat/fling.d.ts +24 -0
- package/dist/gesture-handler-compat/fling.js +123 -0
- package/dist/gesture-handler-compat/fling.js.map +1 -0
- package/dist/gesture-handler-compat/force-touch.d.ts +13 -0
- package/dist/gesture-handler-compat/force-touch.js +40 -0
- package/dist/gesture-handler-compat/force-touch.js.map +1 -0
- package/dist/gesture-handler-compat/hooks.d.ts +157 -0
- package/dist/gesture-handler-compat/hooks.js +268 -0
- package/dist/gesture-handler-compat/hooks.js.map +1 -0
- package/dist/gesture-handler-compat/hover.d.ts +2 -0
- package/dist/gesture-handler-compat/hover.js +22 -0
- package/dist/gesture-handler-compat/hover.js.map +1 -0
- package/dist/gesture-handler-compat/index.d.ts +98 -0
- package/dist/gesture-handler-compat/index.js +255 -0
- package/dist/gesture-handler-compat/index.js.map +1 -0
- package/dist/gesture-handler-compat/long-press.d.ts +6 -0
- package/dist/gesture-handler-compat/long-press.js +21 -0
- package/dist/gesture-handler-compat/long-press.js.map +1 -0
- package/dist/gesture-handler-compat/manual.d.ts +2 -0
- package/dist/gesture-handler-compat/manual.js +18 -0
- package/dist/gesture-handler-compat/manual.js.map +1 -0
- package/dist/gesture-handler-compat/native.d.ts +7 -0
- package/dist/gesture-handler-compat/native.js +34 -0
- package/dist/gesture-handler-compat/native.js.map +1 -0
- package/dist/gesture-handler-compat/orchestrator.d.ts +77 -0
- package/dist/gesture-handler-compat/orchestrator.js +284 -0
- package/dist/gesture-handler-compat/orchestrator.js.map +1 -0
- package/dist/gesture-handler-compat/pan.d.ts +23 -0
- package/dist/gesture-handler-compat/pan.js +116 -0
- package/dist/gesture-handler-compat/pan.js.map +1 -0
- package/dist/gesture-handler-compat/recognizer.d.ts +268 -0
- package/dist/gesture-handler-compat/recognizer.js +1056 -0
- package/dist/gesture-handler-compat/recognizer.js.map +1 -0
- package/dist/gesture-handler-compat/relations.d.ts +43 -0
- package/dist/gesture-handler-compat/relations.js +128 -0
- package/dist/gesture-handler-compat/relations.js.map +1 -0
- package/dist/gesture-handler-compat/tap.d.ts +6 -0
- package/dist/gesture-handler-compat/tap.js +29 -0
- package/dist/gesture-handler-compat/tap.js.map +1 -0
- package/dist/gesture-handler-compat/touchpad.d.ts +41 -0
- package/dist/gesture-handler-compat/touchpad.js +64 -0
- package/dist/gesture-handler-compat/touchpad.js.map +1 -0
- package/dist/gesture-handler-compat/types.d.ts +441 -0
- package/dist/gesture-handler-compat/types.js +167 -0
- package/dist/gesture-handler-compat/types.js.map +1 -0
- package/dist/gtk/controllers.d.ts +39 -0
- package/dist/gtk/controllers.js +92 -0
- package/dist/gtk/controllers.js.map +1 -0
- package/dist/gtk/index.d.ts +2 -1
- package/dist/gtk/index.js +7 -1
- package/dist/gtk/index.js.map +1 -1
- package/dist/gtkx/bridge/geometry.js +6 -4
- package/dist/gtkx/bridge/geometry.js.map +1 -1
- package/dist/gtkx/bridge/index.d.ts +5 -3
- package/dist/gtkx/bridge/index.js +6 -3
- package/dist/gtkx/bridge/index.js.map +1 -1
- package/dist/gtkx/bridge/slot-portal.js.map +1 -1
- package/dist/gtkx/bridge/use-signal.js +1 -1
- package/dist/gtkx/bridge/use-signal.js.map +1 -1
- package/dist/gtkx/bridge/view-box.d.ts +56 -0
- package/dist/gtkx/bridge/view-box.js +394 -9
- package/dist/gtkx/bridge/view-box.js.map +1 -1
- package/dist/gtkx/bridge/widget-css.d.ts +17 -0
- package/dist/gtkx/bridge/widget-css.js +72 -0
- package/dist/gtkx/bridge/widget-css.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/layout/driven-size.d.ts +12 -0
- package/dist/layout/driven-size.js +70 -0
- package/dist/layout/driven-size.js.map +1 -0
- package/dist/layout/engine.d.ts +17 -1
- package/dist/layout/engine.js +3 -20
- package/dist/layout/engine.js.map +1 -1
- package/dist/layout/node.d.ts +13 -0
- package/dist/layout/node.js +14 -0
- package/dist/layout/node.js.map +1 -1
- package/dist/layout/yoga.d.ts +3 -2
- package/dist/layout/yoga.js +5 -2
- package/dist/layout/yoga.js.map +1 -1
- package/dist/mcp/data/generated.d.ts +257 -60
- package/dist/mcp/data/generated.js +306 -62
- package/dist/mcp/data/generated.js.map +1 -1
- package/dist/metro/index.d.ts +10 -0
- package/dist/metro/index.js +21 -12
- package/dist/metro/index.js.map +1 -1
- package/dist/navigation/sidebar.d.ts +16 -0
- package/dist/navigation/sidebar.js +65 -1
- package/dist/navigation/sidebar.js.map +1 -1
- package/dist/reanimated-compat/animated-ref.d.ts +32 -0
- package/dist/reanimated-compat/animated-ref.js +57 -0
- package/dist/reanimated-compat/animated-ref.js.map +1 -0
- package/dist/reanimated-compat/animation.d.ts +177 -0
- package/dist/reanimated-compat/animation.js +540 -0
- package/dist/reanimated-compat/animation.js.map +1 -0
- package/dist/reanimated-compat/color.d.ts +28 -0
- package/dist/reanimated-compat/color.js +249 -0
- package/dist/reanimated-compat/color.js.map +1 -0
- package/dist/reanimated-compat/decay.d.ts +39 -0
- package/dist/reanimated-compat/decay.js +96 -0
- package/dist/reanimated-compat/decay.js.map +1 -0
- package/dist/reanimated-compat/easing.d.ts +30 -0
- package/dist/reanimated-compat/easing.js +76 -0
- package/dist/reanimated-compat/easing.js.map +1 -0
- package/dist/reanimated-compat/hooks.d.ts +14 -0
- package/dist/reanimated-compat/hooks.js +217 -0
- package/dist/reanimated-compat/hooks.js.map +1 -0
- package/dist/reanimated-compat/index.d.ts +258 -0
- package/dist/reanimated-compat/index.js +464 -0
- package/dist/reanimated-compat/index.js.map +1 -0
- package/dist/reanimated-compat/interpolation.d.ts +18 -0
- package/dist/reanimated-compat/interpolation.js +122 -0
- package/dist/reanimated-compat/interpolation.js.map +1 -0
- package/dist/reanimated-compat/layout-animation-config.d.ts +43 -0
- package/dist/reanimated-compat/layout-animation-config.js +78 -0
- package/dist/reanimated-compat/layout-animation-config.js.map +1 -0
- package/dist/reanimated-compat/layout-animation-presets.d.ts +117 -0
- package/dist/reanimated-compat/layout-animation-presets.js +498 -0
- package/dist/reanimated-compat/layout-animation-presets.js.map +1 -0
- package/dist/reanimated-compat/layout-animation-runtime.d.ts +21 -0
- package/dist/reanimated-compat/layout-animation-runtime.js +311 -0
- package/dist/reanimated-compat/layout-animation-runtime.js.map +1 -0
- package/dist/reanimated-compat/layout-animation-view.d.ts +27 -0
- package/dist/reanimated-compat/layout-animation-view.js +266 -0
- package/dist/reanimated-compat/layout-animation-view.js.map +1 -0
- package/dist/reanimated-compat/layout-animation.d.ts +171 -0
- package/dist/reanimated-compat/layout-animation.js +469 -0
- package/dist/reanimated-compat/layout-animation.js.map +1 -0
- package/dist/reanimated-compat/layout-transitions.d.ts +83 -0
- package/dist/reanimated-compat/layout-transitions.js +350 -0
- package/dist/reanimated-compat/layout-transitions.js.map +1 -0
- package/dist/reanimated-compat/mutable.d.ts +29 -0
- package/dist/reanimated-compat/mutable.js +139 -0
- package/dist/reanimated-compat/mutable.js.map +1 -0
- package/dist/reanimated-compat/props.d.ts +18 -0
- package/dist/reanimated-compat/props.js +111 -0
- package/dist/reanimated-compat/props.js.map +1 -0
- package/dist/reanimated-compat/scroll-handler.d.ts +119 -0
- package/dist/reanimated-compat/scroll-handler.js +274 -0
- package/dist/reanimated-compat/scroll-handler.js.map +1 -0
- package/dist/reanimated-compat/scroll-offset.d.ts +12 -0
- package/dist/reanimated-compat/scroll-offset.js +104 -0
- package/dist/reanimated-compat/scroll-offset.js.map +1 -0
- package/dist/reanimated-compat/style.d.ts +90 -0
- package/dist/reanimated-compat/style.js +452 -0
- package/dist/reanimated-compat/style.js.map +1 -0
- package/dist/reanimated-compat/test-timers.d.ts +28 -0
- package/dist/reanimated-compat/test-timers.js +136 -0
- package/dist/reanimated-compat/test-timers.js.map +1 -0
- package/dist/reanimated-compat/threads.d.ts +13 -0
- package/dist/reanimated-compat/threads.js +41 -0
- package/dist/reanimated-compat/threads.js.map +1 -0
- package/dist/reanimated-compat/tracking.d.ts +41 -0
- package/dist/reanimated-compat/tracking.js +106 -0
- package/dist/reanimated-compat/tracking.js.map +1 -0
- package/dist/reanimated-compat/updater-animations.d.ts +36 -0
- package/dist/reanimated-compat/updater-animations.js +273 -0
- package/dist/reanimated-compat/updater-animations.js.map +1 -0
- package/dist/responder/system.d.ts +80 -7
- package/dist/responder/system.js +143 -23
- package/dist/responder/system.js.map +1 -1
- package/dist/responder/use-responder.d.ts +11 -0
- package/dist/responder/use-responder.js +225 -6
- package/dist/responder/use-responder.js.map +1 -1
- package/dist/runner/host-dev.js +1 -1
- package/dist/runner/host-dev.js.map +1 -1
- package/dist/runner/host.js +1 -1
- package/dist/runner/host.js.map +1 -1
- package/dist/runner/index.js +3 -3
- package/dist/runner/index.js.map +1 -1
- package/dist/sea/gtkx-config-module.js +1 -1
- package/dist/sea/gtkx-config-module.js.map +1 -1
- package/dist/style/absolute-insets.d.ts +26 -0
- package/dist/style/absolute-insets.js +113 -0
- package/dist/style/absolute-insets.js.map +1 -0
- package/dist/style/animated-size.d.ts +25 -0
- package/dist/style/animated-size.js +225 -0
- package/dist/style/animated-size.js.map +1 -0
- package/dist/style/colors.d.ts +17 -0
- package/dist/style/colors.js +25 -6
- package/dist/style/colors.js.map +1 -1
- package/dist/style/imperative-css.d.ts +21 -0
- package/dist/style/imperative-css.js +63 -0
- package/dist/style/imperative-css.js.map +1 -0
- package/dist/style/index.d.ts +2 -1
- package/dist/style/index.js +2 -1
- package/dist/style/index.js.map +1 -1
- package/dist/style/split-style.js +5 -0
- package/dist/style/split-style.js.map +1 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/unsupported-export.d.ts +8 -0
- package/dist/unsupported-export.js +66 -0
- package/dist/unsupported-export.js.map +1 -0
- package/dist/vite/index.d.ts +21 -8
- package/dist/vite/index.js +127 -82
- package/dist/vite/index.js.map +1 -1
- package/dist/vitest/index.d.ts +2 -2
- package/dist/vitest/index.js.map +1 -1
- package/dist/worklets-compat/index.d.ts +4 -0
- package/dist/worklets-compat/index.js +28 -0
- package/dist/worklets-compat/index.js.map +1 -0
- package/dist/worklets-compat/surface.d.ts +110 -0
- package/dist/worklets-compat/surface.js +153 -0
- package/dist/worklets-compat/surface.js.map +1 -0
- package/package.json +25 -9
- package/types.d.ts +9 -0
- package/dist/common/list.d.ts +0 -82
- package/dist/common/list.js +0 -166
- package/dist/common/list.js.map +0 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// `useAnimatedProps`: the same trick as `useAnimatedStyle`, applied to props
|
|
2
|
+
// instead of to a style.
|
|
3
|
+
//
|
|
4
|
+
// The mapper returns ordinary values every frame. On the first run each
|
|
5
|
+
// NUMERIC leaf is replaced by an animated node and the mapping is remembered;
|
|
6
|
+
// later runs push new values into those nodes, so a running animation costs
|
|
7
|
+
// zero React renders. `createAnimatedComponent` spreads the result onto the
|
|
8
|
+
// wrapped component without resolving it, and the component subscribes.
|
|
9
|
+
//
|
|
10
|
+
// WHY THIS IS ALMOST FREE HERE, and what it is actually for. The SVG shapes
|
|
11
|
+
// already accept `number | AnimatedNode` on every numeric geometry and paint
|
|
12
|
+
// prop and drive their own invalidation from it — `src/components/svg/
|
|
13
|
+
// animated-support.ts` subscribes and rebuilds the path descriptor straight
|
|
14
|
+
// into `queueDraw`. So an animated `r`, `strokeWidth` or `strokeDashoffset`
|
|
15
|
+
// reaches GTK through a channel that has been there since the SVG epic, and
|
|
16
|
+
// this file only has to hand it a node instead of a number. That is the whole
|
|
17
|
+
// mechanism: `useAnimatedProps` is not a second write path, it is the first
|
|
18
|
+
// one addressed by prop name rather than by style key.
|
|
19
|
+
//
|
|
20
|
+
// A NON-numeric prop is not driveable, for the same reason a colour is not
|
|
21
|
+
// driveable in a style: nothing downstream subscribes to it, so it can only
|
|
22
|
+
// land on the next React render. It says so once, by name, rather than being
|
|
23
|
+
// dropped silently — and it ASKS FOR that render rather than waiting for one
|
|
24
|
+
// that may never come.
|
|
25
|
+
//
|
|
26
|
+
// The asking is not a refinement. `react-native-drawer-layout`'s `Overlay`
|
|
27
|
+
// (measured in the gallery's upstream-libraries section) is a full-screen `Animated.View`
|
|
28
|
+
// whose `pointerEvents` is `"none"` until the drawer starts opening and
|
|
29
|
+
// `"auto"` after; nothing else in that component re-renders, so a value only
|
|
30
|
+
// applied "on the next render" was applied on no render at all. The overlay
|
|
31
|
+
// went permanently targetable the first time the drawer opened and swallowed
|
|
32
|
+
// every press in the app underneath it — the silent failure this package
|
|
33
|
+
// refuses everywhere else. One React render per CHANGE (not per frame: the
|
|
34
|
+
// value has to actually differ) is the honest cost, and the warning is what
|
|
35
|
+
// makes it visible.
|
|
36
|
+
import { createStyleNode } from "./style";
|
|
37
|
+
// One warning per prop name per session, matching the style path's policy.
|
|
38
|
+
const warned = new Set();
|
|
39
|
+
const warnUndriveableProp = (property) => {
|
|
40
|
+
if (warned.has(property)) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
warned.add(property);
|
|
44
|
+
const isProduction = typeof process !== "undefined" && process.env.NODE_ENV === "production";
|
|
45
|
+
if (!isProduction) {
|
|
46
|
+
console.warn(`react-native-reanimated: useAnimatedProps changed \`${property}\`, which is not a number, so nothing ` +
|
|
47
|
+
"downstream subscribes to it — only numeric props are driven at frame rate here (the SVG geometry " +
|
|
48
|
+
"and paint numbers). The new value is applied by a React render instead, one per change. " +
|
|
49
|
+
"See docs/api.md.");
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
/** @internal Test seam: the warning is once per session by design. */
|
|
53
|
+
export const resetUndriveablePropWarnings = () => {
|
|
54
|
+
warned.clear();
|
|
55
|
+
};
|
|
56
|
+
// Only top-level numbers. Not a simplification: a prop is driveable exactly
|
|
57
|
+
// when its receiver duck-types an animated node in that position, and the
|
|
58
|
+
// components that do (the SVG shapes) take `number | AnimatedNode` — never a
|
|
59
|
+
// nested object.
|
|
60
|
+
const numericKeysOf = (source) => Object.keys(source).filter((key) => typeof source[key] === "number");
|
|
61
|
+
export const createAnimatedProps = (source, reuse) => {
|
|
62
|
+
const numeric = numericKeysOf(source);
|
|
63
|
+
const signature = numeric.join("|");
|
|
64
|
+
const nodes = new Map();
|
|
65
|
+
for (const key of numeric) {
|
|
66
|
+
const existing = reuse?.get(key);
|
|
67
|
+
if (existing) {
|
|
68
|
+
existing.__push(source[key]);
|
|
69
|
+
nodes.set(key, existing);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
nodes.set(key, createStyleNode(source[key]));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const props = { ...source };
|
|
76
|
+
for (const [key, node] of nodes) {
|
|
77
|
+
props[key] = node;
|
|
78
|
+
}
|
|
79
|
+
// The last values seen for everything that is NOT node-backed, so a change
|
|
80
|
+
// can be reported rather than silently dropped.
|
|
81
|
+
let staticSnapshot = source;
|
|
82
|
+
return {
|
|
83
|
+
props,
|
|
84
|
+
nodes,
|
|
85
|
+
apply(next) {
|
|
86
|
+
if (numericKeysOf(next).join("|") !== signature) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
for (const [key, node] of nodes) {
|
|
90
|
+
node.__push(next[key]);
|
|
91
|
+
}
|
|
92
|
+
let needsRender = false;
|
|
93
|
+
for (const key of Object.keys(next)) {
|
|
94
|
+
if (nodes.has(key)) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (!Object.is(next[key], staticSnapshot[key])) {
|
|
98
|
+
warnUndriveableProp(key);
|
|
99
|
+
props[key] = next[key];
|
|
100
|
+
// The caller rebuilds and re-renders on false, which is the only
|
|
101
|
+
// channel a non-node prop has. Mutating `props` alone left the new
|
|
102
|
+
// value sitting in an object React had already committed.
|
|
103
|
+
needsRender = true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
staticSnapshot = next;
|
|
107
|
+
return !needsRender;
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
//# sourceMappingURL=props.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"props.js","sourceRoot":"","sources":["../../src/reanimated-compat/props.ts"],"names":[],"mappings":"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","sourcesContent":["// `useAnimatedProps`: the same trick as `useAnimatedStyle`, applied to props\n// instead of to a style.\n//\n// The mapper returns ordinary values every frame. On the first run each\n// NUMERIC leaf is replaced by an animated node and the mapping is remembered;\n// later runs push new values into those nodes, so a running animation costs\n// zero React renders. `createAnimatedComponent` spreads the result onto the\n// wrapped component without resolving it, and the component subscribes.\n//\n// WHY THIS IS ALMOST FREE HERE, and what it is actually for. The SVG shapes\n// already accept `number | AnimatedNode` on every numeric geometry and paint\n// prop and drive their own invalidation from it — `src/components/svg/\n// animated-support.ts` subscribes and rebuilds the path descriptor straight\n// into `queueDraw`. So an animated `r`, `strokeWidth` or `strokeDashoffset`\n// reaches GTK through a channel that has been there since the SVG epic, and\n// this file only has to hand it a node instead of a number. That is the whole\n// mechanism: `useAnimatedProps` is not a second write path, it is the first\n// one addressed by prop name rather than by style key.\n//\n// A NON-numeric prop is not driveable, for the same reason a colour is not\n// driveable in a style: nothing downstream subscribes to it, so it can only\n// land on the next React render. It says so once, by name, rather than being\n// dropped silently — and it ASKS FOR that render rather than waiting for one\n// that may never come.\n//\n// The asking is not a refinement. `react-native-drawer-layout`'s `Overlay`\n// (measured in the gallery's upstream-libraries section) is a full-screen `Animated.View`\n// whose `pointerEvents` is `\"none\"` until the drawer starts opening and\n// `\"auto\"` after; nothing else in that component re-renders, so a value only\n// applied \"on the next render\" was applied on no render at all. The overlay\n// went permanently targetable the first time the drawer opened and swallowed\n// every press in the app underneath it — the silent failure this package\n// refuses everywhere else. One React render per CHANGE (not per frame: the\n// value has to actually differ) is the honest cost, and the warning is what\n// makes it visible.\nimport { createStyleNode, type StyleNode } from \"./style\"\n\nexport type PropsObject = Record<string, unknown>\n\n// One warning per prop name per session, matching the style path's policy.\nconst warned = new Set<string>()\n\nconst warnUndriveableProp = (property: string): void => {\n if (warned.has(property)) {\n return\n }\n warned.add(property)\n const isProduction =\n typeof process !== \"undefined\" && process.env.NODE_ENV === \"production\"\n if (!isProduction) {\n console.warn(\n `react-native-reanimated: useAnimatedProps changed \\`${property}\\`, which is not a number, so nothing ` +\n \"downstream subscribes to it — only numeric props are driven at frame rate here (the SVG geometry \" +\n \"and paint numbers). The new value is applied by a React render instead, one per change. \" +\n \"See docs/api.md.\",\n )\n }\n}\n\n/** @internal Test seam: the warning is once per session by design. */\nexport const resetUndriveablePropWarnings = (): void => {\n warned.clear()\n}\n\n// Only top-level numbers. Not a simplification: a prop is driveable exactly\n// when its receiver duck-types an animated node in that position, and the\n// components that do (the SVG shapes) take `number | AnimatedNode` — never a\n// nested object.\nconst numericKeysOf = (source: PropsObject): string[] =>\n Object.keys(source).filter((key) => typeof source[key] === \"number\")\n\nexport type AnimatedPropsObject = {\n /** Stable object spread onto the component; never replaced in place. */\n readonly props: PropsObject\n /** The nodes behind it, reusable when the props have to be rebuilt. */\n readonly nodes: ReadonlyMap<string, StyleNode>\n /**\n * Publishes a fresh updater result. Returns false when the SHAPE changed\n * (a prop became numeric, stopped being numeric, or appeared) and the\n * caller must rebuild — the nodes the component subscribed to no longer\n * describe these props.\n */\n apply(next: PropsObject): boolean\n}\n\nexport const createAnimatedProps = (\n source: PropsObject,\n reuse?: ReadonlyMap<string, StyleNode>,\n): AnimatedPropsObject => {\n const numeric = numericKeysOf(source)\n const signature = numeric.join(\"|\")\n\n const nodes = new Map<string, StyleNode>()\n for (const key of numeric) {\n const existing = reuse?.get(key)\n if (existing) {\n existing.__push(source[key] as number)\n nodes.set(key, existing)\n } else {\n nodes.set(key, createStyleNode(source[key] as number))\n }\n }\n\n const props: PropsObject = { ...source }\n for (const [key, node] of nodes) {\n props[key] = node\n }\n\n // The last values seen for everything that is NOT node-backed, so a change\n // can be reported rather than silently dropped.\n let staticSnapshot = source\n\n return {\n props,\n nodes,\n apply(next) {\n if (numericKeysOf(next).join(\"|\") !== signature) {\n return false\n }\n for (const [key, node] of nodes) {\n node.__push(next[key] as number)\n }\n let needsRender = false\n for (const key of Object.keys(next)) {\n if (nodes.has(key)) {\n continue\n }\n if (!Object.is(next[key], staticSnapshot[key])) {\n warnUndriveableProp(key)\n props[key] = next[key]\n // The caller rebuilds and re-renders on false, which is the only\n // channel a non-node prop has. Mutating `props` alone left the new\n // value sitting in an object React had already committed.\n needsRender = true\n }\n }\n staticSnapshot = next\n return !needsRender\n },\n }\n}\n"]}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { ScrollEvent } from "../components/scroll-view";
|
|
2
|
+
import type { DependencyList } from "./hooks";
|
|
3
|
+
/**
|
|
4
|
+
* The event a scroll handler receives — Reanimated's shape, which is RN's
|
|
5
|
+
* native scroll payload FLATTENED (`event.contentOffset`, not
|
|
6
|
+
* `event.nativeEvent.contentOffset`) plus the event name.
|
|
7
|
+
*
|
|
8
|
+
* It carries exactly the three measurements this platform can report, which
|
|
9
|
+
* are the three {@link ScrollEvent} already carries. RN's payload also has
|
|
10
|
+
* `contentInset`, `velocity` and `zoomScale`; none has a source in a
|
|
11
|
+
* `GtkScrolledWindow`, and inventing zeros for them would be a number a
|
|
12
|
+
* caller could not tell from a measurement.
|
|
13
|
+
*/
|
|
14
|
+
export type AnimatedScrollEvent = ScrollEvent["nativeEvent"] & {
|
|
15
|
+
eventName: string;
|
|
16
|
+
};
|
|
17
|
+
export type ScrollHandlerCallback<Context extends Record<string, unknown>> = (event: AnimatedScrollEvent, context: Context) => void;
|
|
18
|
+
export type ScrollHandlers<Context extends Record<string, unknown>> = {
|
|
19
|
+
onScroll?: ScrollHandlerCallback<Context>;
|
|
20
|
+
/** The user-driven scroll session began — native for touchpad, grouped for wheel. */
|
|
21
|
+
onBeginDrag?: ScrollHandlerCallback<Context>;
|
|
22
|
+
/** That session ended. A wheel gets no momentum pair afterwards. */
|
|
23
|
+
onEndDrag?: ScrollHandlerCallback<Context>;
|
|
24
|
+
/** The scroller kept moving on its own after the sequence ended. */
|
|
25
|
+
onMomentumBegin?: ScrollHandlerCallback<Context>;
|
|
26
|
+
/** That movement came to rest. */
|
|
27
|
+
onMomentumEnd?: ScrollHandlerCallback<Context>;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* @internal The hook without the hook: the translation from a
|
|
31
|
+
* {@link ScrollEvent} to a handler call, plus the one context object every
|
|
32
|
+
* call shares. Split out because this repo's unit project has no React
|
|
33
|
+
* renderer — the hook below is two refs around this.
|
|
34
|
+
*
|
|
35
|
+
* `latest` is read per event rather than captured, so a re-render that
|
|
36
|
+
* changed the handlers is picked up without changing the returned function.
|
|
37
|
+
*/
|
|
38
|
+
export declare const createScrollHandler: <Context extends Record<string, unknown> = Record<string, unknown>>(latest: () => ScrollHandlers<Context> | ScrollHandlerCallback<Context> | undefined, sharedContext?: Context) => ((event: ScrollEvent) => void);
|
|
39
|
+
/**
|
|
40
|
+
* Returns a scroll handler to pass as a scrollable's `onScroll`. The identity
|
|
41
|
+
* is stable across renders — a scrollable that re-attached its signal handler
|
|
42
|
+
* on every render would be a worse deal than the render this hook exists to
|
|
43
|
+
* avoid.
|
|
44
|
+
*/
|
|
45
|
+
export declare const useAnimatedScrollHandler: <Context extends Record<string, unknown> = Record<string, unknown>>(handlers: ScrollHandlers<Context> | ScrollHandlerCallback<Context> | undefined, dependencies?: DependencyList) => ((event: ScrollEvent) => void);
|
|
46
|
+
/** Upstream's return shape. Both fields mean something here. */
|
|
47
|
+
export type UseHandlerContext<Context extends Record<string, unknown>> = {
|
|
48
|
+
context: Context;
|
|
49
|
+
doDependenciesDiffer: boolean;
|
|
50
|
+
useWeb: boolean;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* The context object a hand-built handler shares across its calls, plus the
|
|
54
|
+
* two questions upstream answers alongside it.
|
|
55
|
+
*
|
|
56
|
+
* `doDependenciesDiffer` is **always false**, and that is a statement rather
|
|
57
|
+
* than a stub: upstream needs it because a worklet is a by-value snapshot
|
|
58
|
+
* that goes stale, so a changed dependency has to force a REBUILD of the
|
|
59
|
+
* handler on the UI runtime. Here a handler is an ordinary closure read out
|
|
60
|
+
* of a ref at call time, so it is never stale and there is never anything to
|
|
61
|
+
* rebuild. A caller that feeds this into `useEvent`'s `rebuild` argument
|
|
62
|
+
* therefore never rebuilds, which is correct.
|
|
63
|
+
*
|
|
64
|
+
* `useWeb` is upstream's `SHOULD_BE_USE_WEB`, and it is true for the same
|
|
65
|
+
* reason it is true for react-native-windows: one runtime, no worklet
|
|
66
|
+
* boundary, events delivered as ordinary props (docs/research/reanimated.md).
|
|
67
|
+
*/
|
|
68
|
+
export declare const useHandler: <Event extends object, Context extends Record<string, unknown> = Record<string, unknown>>(handlers: Record<string, ((event: Event, context: Context) => void) | undefined>, dependencies?: DependencyList) => UseHandlerContext<Context>;
|
|
69
|
+
/**
|
|
70
|
+
* Runs `handler` for each of `eventNames` the platform can report. The
|
|
71
|
+
* returned value goes on a scrollable's `onScroll`, exactly as the result of
|
|
72
|
+
* {@link useAnimatedScrollHandler} does — and it is the same object underneath:
|
|
73
|
+
* one callable carrying the phase sink for whichever phase names were asked
|
|
74
|
+
* for, so a `ScrollView` given a handler that asked for none installs no phase
|
|
75
|
+
* machinery.
|
|
76
|
+
*
|
|
77
|
+
* `rebuild` is accepted and ignored, for the reason `doDependenciesDiffer` is
|
|
78
|
+
* always false: nothing here goes stale, so there is nothing to rebuild.
|
|
79
|
+
*
|
|
80
|
+
* **Scroll event names only.** A name outside that set throws where it is
|
|
81
|
+
* asked for, naming itself: `onGestureHandlerStateChange` and the touch names
|
|
82
|
+
* belong to systems this platform implements elsewhere and would not reach a
|
|
83
|
+
* handler registered here, and a subscription that can never fire is the
|
|
84
|
+
* failure mode this package refuses everywhere else.
|
|
85
|
+
*/
|
|
86
|
+
export declare const useEvent: <Event extends object>(handler: (event: Event & {
|
|
87
|
+
eventName: string;
|
|
88
|
+
}) => void, eventNames?: readonly string[], rebuild?: boolean) => ((event: ScrollEvent) => void);
|
|
89
|
+
/** Anything `scrollTo` can be pointed at: RN's scroll methods live on the ref. */
|
|
90
|
+
type ScrollableHandle = {
|
|
91
|
+
scrollTo?: (options: {
|
|
92
|
+
x?: number;
|
|
93
|
+
y?: number;
|
|
94
|
+
animated?: boolean;
|
|
95
|
+
}) => void;
|
|
96
|
+
};
|
|
97
|
+
type ScrollableRef = (() => ScrollableHandle | null) | {
|
|
98
|
+
current: ScrollableHandle | null;
|
|
99
|
+
} | null | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* Scrolls the view an `useAnimatedRef` points at — the other half of the pair
|
|
102
|
+
* above, and the reason it is in this file: a list that reads its own offset
|
|
103
|
+
* through the handler almost always writes one back, and
|
|
104
|
+
* `react-native-reanimated-dnd`'s `useSortableList` does exactly that inside
|
|
105
|
+
* a `useAnimatedReaction`.
|
|
106
|
+
*
|
|
107
|
+
* Upstream is a worklet reaching the shadow tree directly. Here it is the
|
|
108
|
+
* ordinary imperative `scrollTo` every RN scrollable already exposes, called
|
|
109
|
+
* synchronously, for the same reason the handler needs no event system: this
|
|
110
|
+
* IS the thread that owns the widget. The argument order is upstream's
|
|
111
|
+
* (`x` then `y`, not RN's options object), so library call sites are
|
|
112
|
+
* unchanged, and `animated` carries through to `ScrollView`, which ignores it.
|
|
113
|
+
*
|
|
114
|
+
* A ref that is not (yet) pointing at a scrollable is ignored rather than
|
|
115
|
+
* throwing: upstream does the same, and the first frames of a list whose ref
|
|
116
|
+
* has not been attached are a normal state, not an error.
|
|
117
|
+
*/
|
|
118
|
+
export declare const scrollTo: (animatedRef: ScrollableRef, x: number, y: number, animated: boolean) => void;
|
|
119
|
+
export {};
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
// `useAnimatedScrollHandler` — Reanimated's "react to a scroll without
|
|
2
|
+
// rendering".
|
|
3
|
+
//
|
|
4
|
+
// On mobile it is the sharpest edge of the whole architecture: the scroll
|
|
5
|
+
// event is delivered to the UI runtime by native code, so the handler runs on
|
|
6
|
+
// the UI thread at 60/120Hz and the JS thread never hears about it. That is
|
|
7
|
+
// what `useEvent` and the whole event-name subscription machinery underneath
|
|
8
|
+
// this hook exist to arrange.
|
|
9
|
+
//
|
|
10
|
+
// Here the arrangement is already the case, and it has been since the
|
|
11
|
+
// ScrollView shipped. `emitScroll` in src/components/scroll-view.tsx runs
|
|
12
|
+
// from a `GtkAdjustment::value-changed` handler — a C callback on the GTK
|
|
13
|
+
// main loop, which is this JS thread — and calls the `onScroll` prop
|
|
14
|
+
// directly. Nothing about that path goes through React: no state is set, no
|
|
15
|
+
// render is scheduled, and the windowed list core already relies on exactly
|
|
16
|
+
// this to re-window during a scroll. A handler that writes a shared value
|
|
17
|
+
// from there gets what Reanimated promises, by the same reasoning
|
|
18
|
+
// docs/research/reanimated.md gives for the rest of this surface.
|
|
19
|
+
//
|
|
20
|
+
// So the hook is a translation, not an implementation of an event system:
|
|
21
|
+
// it returns a plain function to hand to a scrollable's `onScroll`, and the
|
|
22
|
+
// scrollable calls it. That is also how Reanimated's own web path resolves —
|
|
23
|
+
// `SHOULD_BE_USE_WEB` routes `useEvent` to a JS listener attached as an
|
|
24
|
+
// ordinary prop.
|
|
25
|
+
//
|
|
26
|
+
// THE FOUR PHASE HANDLERS are called now, and what they mean is the input
|
|
27
|
+
// device's rather than the platform's. Measured on GTK 4.22.4 under a real
|
|
28
|
+
// pointer (docs/research/scroll-phases.md):
|
|
29
|
+
//
|
|
30
|
+
// - a MOUSE WHEEL gives GTK no sequence — one `::scroll` per detent and
|
|
31
|
+
// nothing around it. This platform groups a burst into a desktop scroll
|
|
32
|
+
// SESSION and reports begin/end (never momentum), which is what lets a
|
|
33
|
+
// phase-aware consumer capture state before its first detent. This is an
|
|
34
|
+
// intentional desktop extension: RN has no wheel to define here.
|
|
35
|
+
// - a TOUCHPAD GLIDE produces all four. The sequence has a real beginning
|
|
36
|
+
// and end, and the scrolled window's own kinetic animation carries the
|
|
37
|
+
// content on afterwards.
|
|
38
|
+
//
|
|
39
|
+
// The one approximation: `onBeginDrag` is the scroll SEQUENCE beginning, not
|
|
40
|
+
// a finger landing on the content. A touchpad never touches the content, so
|
|
41
|
+
// "the user started driving this scroller" is the closest true statement —
|
|
42
|
+
// and it is the statement consumers act on.
|
|
43
|
+
//
|
|
44
|
+
// The routing is the other half. Upstream hands ONE value to `onScroll` and
|
|
45
|
+
// the native side delivers five event names into it; `@gorhom/bottom-sheet`
|
|
46
|
+
// relies on exactly that and passes no phase prop at all. Here the handler
|
|
47
|
+
// carries a phase SINK (src/components/scroll-phase.ts) that `ScrollView`
|
|
48
|
+
// delivers into, so a library call site is unchanged. The sink is attached
|
|
49
|
+
// only while a phase handler is actually present, which is what lets a
|
|
50
|
+
// `ScrollView` given a plain `onScroll`-only handler install nothing.
|
|
51
|
+
import { useRef, useState } from "react";
|
|
52
|
+
import { setScrollPhaseSink, } from "../components/scroll-phase";
|
|
53
|
+
/** Upstream's event name for each phase, carried on the flattened event. */
|
|
54
|
+
const EVENT_NAME_OF_PHASE = {
|
|
55
|
+
beginDrag: "onScrollBeginDrag",
|
|
56
|
+
endDrag: "onScrollEndDrag",
|
|
57
|
+
momentumBegin: "onMomentumScrollBegin",
|
|
58
|
+
momentumEnd: "onMomentumScrollEnd",
|
|
59
|
+
};
|
|
60
|
+
const PHASE_KEY_OF_PHASE = {
|
|
61
|
+
beginDrag: "onBeginDrag",
|
|
62
|
+
endDrag: "onEndDrag",
|
|
63
|
+
momentumBegin: "onMomentumBegin",
|
|
64
|
+
momentumEnd: "onMomentumEnd",
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Whether `handlers` asks for any phase at all. The answer decides whether a
|
|
68
|
+
* `ScrollView` installs its phase machinery, so it is asked of the CURRENT
|
|
69
|
+
* handlers on every render rather than once: a component that grows a phase
|
|
70
|
+
* handler later gets one, and one that never has any never pays for one.
|
|
71
|
+
*/
|
|
72
|
+
const wantsPhase = (handlers, phase) => {
|
|
73
|
+
if (typeof handlers !== "object" || handlers === null) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
if (phase !== undefined) {
|
|
77
|
+
return (typeof handlers[PHASE_KEY_OF_PHASE[phase]] === "function");
|
|
78
|
+
}
|
|
79
|
+
return Object.values(PHASE_KEY_OF_PHASE).some((key) => typeof handlers[key] === "function");
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* @internal The hook without the hook: the translation from a
|
|
83
|
+
* {@link ScrollEvent} to a handler call, plus the one context object every
|
|
84
|
+
* call shares. Split out because this repo's unit project has no React
|
|
85
|
+
* renderer — the hook below is two refs around this.
|
|
86
|
+
*
|
|
87
|
+
* `latest` is read per event rather than captured, so a re-render that
|
|
88
|
+
* changed the handlers is picked up without changing the returned function.
|
|
89
|
+
*/
|
|
90
|
+
export const createScrollHandler = (latest,
|
|
91
|
+
// The context object, when the caller already owns one — `useHandler`
|
|
92
|
+
// hands out its own and a handler built from it has to share it.
|
|
93
|
+
sharedContext) => {
|
|
94
|
+
// Upstream's `useHandler` gives every handler call the SAME mutable object
|
|
95
|
+
// across the whole scroll, and `@gorhom/bottom-sheet` uses it to carry the
|
|
96
|
+
// offset a drag started at. One object per handler is that contract.
|
|
97
|
+
const context = sharedContext ?? {};
|
|
98
|
+
const handle = (event) => {
|
|
99
|
+
const current = latest();
|
|
100
|
+
const onScroll = typeof current === "function" ? current : current?.onScroll;
|
|
101
|
+
onScroll?.({ ...event.nativeEvent, eventName: "onScroll" }, context);
|
|
102
|
+
};
|
|
103
|
+
// The phase sink shares that same context object, which is the whole
|
|
104
|
+
// reason gorhom's lock works: `onBeginDrag` writes the offset the drag
|
|
105
|
+
// started at into it and `onScroll` reads it back on the next frame.
|
|
106
|
+
setScrollPhaseSink(handle, {
|
|
107
|
+
wants: (phase) => wantsPhase(latest(), phase),
|
|
108
|
+
deliver: (phase, event) => {
|
|
109
|
+
const current = latest();
|
|
110
|
+
if (typeof current !== "object" || current === null) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const callback = current[PHASE_KEY_OF_PHASE[phase]];
|
|
114
|
+
callback?.({ ...event.nativeEvent, eventName: EVENT_NAME_OF_PHASE[phase] }, context);
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
return handle;
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Returns a scroll handler to pass as a scrollable's `onScroll`. The identity
|
|
121
|
+
* is stable across renders — a scrollable that re-attached its signal handler
|
|
122
|
+
* on every render would be a worse deal than the render this hook exists to
|
|
123
|
+
* avoid.
|
|
124
|
+
*/
|
|
125
|
+
export const useAnimatedScrollHandler = (handlers,
|
|
126
|
+
// Accepted for source parity and unused, for the reason the rest of this
|
|
127
|
+
// surface gives: nothing here is captured by a Babel plugin, so a handler
|
|
128
|
+
// read out of a ref is always the current one and there is no stale
|
|
129
|
+
// closure for a dependency array to refresh.
|
|
130
|
+
dependencies) => {
|
|
131
|
+
void dependencies;
|
|
132
|
+
// The "latest ref" pattern, and the two disables below are what it costs.
|
|
133
|
+
// Upstream rebuilds the handler OBJECT on every render and the returned
|
|
134
|
+
// function has to call the current one; keeping it in a ref is the only
|
|
135
|
+
// way to do that without a new function each time, which is the whole
|
|
136
|
+
// point of the hook. Nothing reads the ref during render — the read
|
|
137
|
+
// happens inside the scroll callback, which runs from a GTK signal long
|
|
138
|
+
// after the commit. `useState` holds the function itself for the same
|
|
139
|
+
// reason `useAnimatedRef` reaches for it: that value IS the return value,
|
|
140
|
+
// and a ref there really would be a render-time read.
|
|
141
|
+
const latest = useRef(handlers);
|
|
142
|
+
// eslint-disable-next-line react-hooks/refs
|
|
143
|
+
latest.current = handlers;
|
|
144
|
+
// eslint-disable-next-line react-hooks/refs
|
|
145
|
+
const [stable] = useState(() => createScrollHandler(() => latest.current));
|
|
146
|
+
return stable;
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* The context object a hand-built handler shares across its calls, plus the
|
|
150
|
+
* two questions upstream answers alongside it.
|
|
151
|
+
*
|
|
152
|
+
* `doDependenciesDiffer` is **always false**, and that is a statement rather
|
|
153
|
+
* than a stub: upstream needs it because a worklet is a by-value snapshot
|
|
154
|
+
* that goes stale, so a changed dependency has to force a REBUILD of the
|
|
155
|
+
* handler on the UI runtime. Here a handler is an ordinary closure read out
|
|
156
|
+
* of a ref at call time, so it is never stale and there is never anything to
|
|
157
|
+
* rebuild. A caller that feeds this into `useEvent`'s `rebuild` argument
|
|
158
|
+
* therefore never rebuilds, which is correct.
|
|
159
|
+
*
|
|
160
|
+
* `useWeb` is upstream's `SHOULD_BE_USE_WEB`, and it is true for the same
|
|
161
|
+
* reason it is true for react-native-windows: one runtime, no worklet
|
|
162
|
+
* boundary, events delivered as ordinary props (docs/research/reanimated.md).
|
|
163
|
+
*/
|
|
164
|
+
export const useHandler = (handlers, dependencies) => {
|
|
165
|
+
void handlers;
|
|
166
|
+
void dependencies;
|
|
167
|
+
const [context] = useState(() => ({}));
|
|
168
|
+
return { context, doDependenciesDiffer: false, useWeb: true };
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* The event names this platform has a source for. Everything a scroll can
|
|
172
|
+
* report, and nothing else — there is no native event registry here to
|
|
173
|
+
* subscribe an arbitrary name against, so a name outside this set is refused
|
|
174
|
+
* by name rather than accepted and never fired.
|
|
175
|
+
*/
|
|
176
|
+
const SCROLL_EVENT_NAMES = new Set([
|
|
177
|
+
"onScroll",
|
|
178
|
+
...Object.values(EVENT_NAME_OF_PHASE),
|
|
179
|
+
]);
|
|
180
|
+
const PHASE_OF_EVENT_NAME = new Map(Object.keys(EVENT_NAME_OF_PHASE).map((phase) => [
|
|
181
|
+
EVENT_NAME_OF_PHASE[phase],
|
|
182
|
+
phase,
|
|
183
|
+
]));
|
|
184
|
+
/**
|
|
185
|
+
* Runs `handler` for each of `eventNames` the platform can report. The
|
|
186
|
+
* returned value goes on a scrollable's `onScroll`, exactly as the result of
|
|
187
|
+
* {@link useAnimatedScrollHandler} does — and it is the same object underneath:
|
|
188
|
+
* one callable carrying the phase sink for whichever phase names were asked
|
|
189
|
+
* for, so a `ScrollView` given a handler that asked for none installs no phase
|
|
190
|
+
* machinery.
|
|
191
|
+
*
|
|
192
|
+
* `rebuild` is accepted and ignored, for the reason `doDependenciesDiffer` is
|
|
193
|
+
* always false: nothing here goes stale, so there is nothing to rebuild.
|
|
194
|
+
*
|
|
195
|
+
* **Scroll event names only.** A name outside that set throws where it is
|
|
196
|
+
* asked for, naming itself: `onGestureHandlerStateChange` and the touch names
|
|
197
|
+
* belong to systems this platform implements elsewhere and would not reach a
|
|
198
|
+
* handler registered here, and a subscription that can never fire is the
|
|
199
|
+
* failure mode this package refuses everywhere else.
|
|
200
|
+
*/
|
|
201
|
+
export const useEvent = (handler, eventNames = [], rebuild = false) => {
|
|
202
|
+
void rebuild;
|
|
203
|
+
for (const name of eventNames) {
|
|
204
|
+
if (!SCROLL_EVENT_NAMES.has(name)) {
|
|
205
|
+
throw new Error(`[react-native-gtkx] useEvent("${name}") is not supported. ` +
|
|
206
|
+
`This platform delivers scroll events only — ${[...SCROLL_EVENT_NAMES].join(", ")} — ` +
|
|
207
|
+
"because a GtkAdjustment is the one event source there is to subscribe to. " +
|
|
208
|
+
"See docs/api.md for the gesture and touch surfaces, which are their own systems.");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const latest = useRef(handler);
|
|
212
|
+
// eslint-disable-next-line react-hooks/refs
|
|
213
|
+
latest.current = handler;
|
|
214
|
+
const names = useRef(eventNames);
|
|
215
|
+
// eslint-disable-next-line react-hooks/refs
|
|
216
|
+
names.current = eventNames;
|
|
217
|
+
// eslint-disable-next-line react-hooks/refs
|
|
218
|
+
const [stable] = useState(() => {
|
|
219
|
+
const call = (nativeEvent, eventName) => {
|
|
220
|
+
if (names.current.includes(eventName)) {
|
|
221
|
+
latest.current({ ...nativeEvent, eventName });
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
const emit = (event) => {
|
|
225
|
+
call(event.nativeEvent, "onScroll");
|
|
226
|
+
};
|
|
227
|
+
setScrollPhaseSink(emit, {
|
|
228
|
+
wants: () => names.current.some((name) => PHASE_OF_EVENT_NAME.has(name)),
|
|
229
|
+
deliver: (phase, event) => {
|
|
230
|
+
call(event.nativeEvent, EVENT_NAME_OF_PHASE[phase]);
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
// Upstream's return value carries this, and `useScrollOffset` reaches
|
|
234
|
+
// through it to register a view TAG with the native event registry. There
|
|
235
|
+
// is no such registry here and no tag to give it, so reaching for it
|
|
236
|
+
// fails where it is reached for rather than silently doing nothing.
|
|
237
|
+
Object.defineProperty(emit, "workletEventHandler", {
|
|
238
|
+
get() {
|
|
239
|
+
throw new Error("[react-native-gtkx] useEvent(...).workletEventHandler is not supported. " +
|
|
240
|
+
"It registers a native view tag with Reanimated's event registry, and this " +
|
|
241
|
+
"platform has neither. Hand the value useEvent returns to a scrollable's " +
|
|
242
|
+
"`onScroll` prop instead — that IS the subscription here.");
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
return emit;
|
|
246
|
+
});
|
|
247
|
+
return stable;
|
|
248
|
+
};
|
|
249
|
+
/**
|
|
250
|
+
* Scrolls the view an `useAnimatedRef` points at — the other half of the pair
|
|
251
|
+
* above, and the reason it is in this file: a list that reads its own offset
|
|
252
|
+
* through the handler almost always writes one back, and
|
|
253
|
+
* `react-native-reanimated-dnd`'s `useSortableList` does exactly that inside
|
|
254
|
+
* a `useAnimatedReaction`.
|
|
255
|
+
*
|
|
256
|
+
* Upstream is a worklet reaching the shadow tree directly. Here it is the
|
|
257
|
+
* ordinary imperative `scrollTo` every RN scrollable already exposes, called
|
|
258
|
+
* synchronously, for the same reason the handler needs no event system: this
|
|
259
|
+
* IS the thread that owns the widget. The argument order is upstream's
|
|
260
|
+
* (`x` then `y`, not RN's options object), so library call sites are
|
|
261
|
+
* unchanged, and `animated` carries through to `ScrollView`, which ignores it.
|
|
262
|
+
*
|
|
263
|
+
* A ref that is not (yet) pointing at a scrollable is ignored rather than
|
|
264
|
+
* throwing: upstream does the same, and the first frames of a list whose ref
|
|
265
|
+
* has not been attached are a normal state, not an error.
|
|
266
|
+
*/
|
|
267
|
+
export const scrollTo = (animatedRef, x, y, animated) => {
|
|
268
|
+
if (!animatedRef) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const handle = typeof animatedRef === "function" ? animatedRef() : animatedRef.current;
|
|
272
|
+
handle?.scrollTo?.({ x, y, animated });
|
|
273
|
+
};
|
|
274
|
+
//# sourceMappingURL=scroll-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-handler.js","sourceRoot":"","sources":["../../src/reanimated-compat/scroll-handler.ts"],"names":[],"mappings":"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","sourcesContent":["// `useAnimatedScrollHandler` — Reanimated's \"react to a scroll without\n// rendering\".\n//\n// On mobile it is the sharpest edge of the whole architecture: the scroll\n// event is delivered to the UI runtime by native code, so the handler runs on\n// the UI thread at 60/120Hz and the JS thread never hears about it. That is\n// what `useEvent` and the whole event-name subscription machinery underneath\n// this hook exist to arrange.\n//\n// Here the arrangement is already the case, and it has been since the\n// ScrollView shipped. `emitScroll` in src/components/scroll-view.tsx runs\n// from a `GtkAdjustment::value-changed` handler — a C callback on the GTK\n// main loop, which is this JS thread — and calls the `onScroll` prop\n// directly. Nothing about that path goes through React: no state is set, no\n// render is scheduled, and the windowed list core already relies on exactly\n// this to re-window during a scroll. A handler that writes a shared value\n// from there gets what Reanimated promises, by the same reasoning\n// docs/research/reanimated.md gives for the rest of this surface.\n//\n// So the hook is a translation, not an implementation of an event system:\n// it returns a plain function to hand to a scrollable's `onScroll`, and the\n// scrollable calls it. That is also how Reanimated's own web path resolves —\n// `SHOULD_BE_USE_WEB` routes `useEvent` to a JS listener attached as an\n// ordinary prop.\n//\n// THE FOUR PHASE HANDLERS are called now, and what they mean is the input\n// device's rather than the platform's. Measured on GTK 4.22.4 under a real\n// pointer (docs/research/scroll-phases.md):\n//\n// - a MOUSE WHEEL gives GTK no sequence — one `::scroll` per detent and\n// nothing around it. This platform groups a burst into a desktop scroll\n// SESSION and reports begin/end (never momentum), which is what lets a\n// phase-aware consumer capture state before its first detent. This is an\n// intentional desktop extension: RN has no wheel to define here.\n// - a TOUCHPAD GLIDE produces all four. The sequence has a real beginning\n// and end, and the scrolled window's own kinetic animation carries the\n// content on afterwards.\n//\n// The one approximation: `onBeginDrag` is the scroll SEQUENCE beginning, not\n// a finger landing on the content. A touchpad never touches the content, so\n// \"the user started driving this scroller\" is the closest true statement —\n// and it is the statement consumers act on.\n//\n// The routing is the other half. Upstream hands ONE value to `onScroll` and\n// the native side delivers five event names into it; `@gorhom/bottom-sheet`\n// relies on exactly that and passes no phase prop at all. Here the handler\n// carries a phase SINK (src/components/scroll-phase.ts) that `ScrollView`\n// delivers into, so a library call site is unchanged. The sink is attached\n// only while a phase handler is actually present, which is what lets a\n// `ScrollView` given a plain `onScroll`-only handler install nothing.\nimport { useRef, useState } from \"react\"\nimport {\n setScrollPhaseSink,\n type ScrollPhase,\n} from \"../components/scroll-phase\"\nimport type { ScrollEvent } from \"../components/scroll-view\"\nimport type { DependencyList } from \"./hooks\"\n\n/**\n * The event a scroll handler receives — Reanimated's shape, which is RN's\n * native scroll payload FLATTENED (`event.contentOffset`, not\n * `event.nativeEvent.contentOffset`) plus the event name.\n *\n * It carries exactly the three measurements this platform can report, which\n * are the three {@link ScrollEvent} already carries. RN's payload also has\n * `contentInset`, `velocity` and `zoomScale`; none has a source in a\n * `GtkScrolledWindow`, and inventing zeros for them would be a number a\n * caller could not tell from a measurement.\n */\nexport type AnimatedScrollEvent = ScrollEvent[\"nativeEvent\"] & {\n eventName: string\n}\n\nexport type ScrollHandlerCallback<Context extends Record<string, unknown>> = (\n event: AnimatedScrollEvent,\n context: Context,\n) => void\n\nexport type ScrollHandlers<Context extends Record<string, unknown>> = {\n onScroll?: ScrollHandlerCallback<Context>\n /** The user-driven scroll session began — native for touchpad, grouped for wheel. */\n onBeginDrag?: ScrollHandlerCallback<Context>\n /** That session ended. A wheel gets no momentum pair afterwards. */\n onEndDrag?: ScrollHandlerCallback<Context>\n /** The scroller kept moving on its own after the sequence ended. */\n onMomentumBegin?: ScrollHandlerCallback<Context>\n /** That movement came to rest. */\n onMomentumEnd?: ScrollHandlerCallback<Context>\n}\n\n/** Upstream's event name for each phase, carried on the flattened event. */\nconst EVENT_NAME_OF_PHASE: Record<ScrollPhase, string> = {\n beginDrag: \"onScrollBeginDrag\",\n endDrag: \"onScrollEndDrag\",\n momentumBegin: \"onMomentumScrollBegin\",\n momentumEnd: \"onMomentumScrollEnd\",\n}\n\nconst PHASE_KEY_OF_PHASE = {\n beginDrag: \"onBeginDrag\",\n endDrag: \"onEndDrag\",\n momentumBegin: \"onMomentumBegin\",\n momentumEnd: \"onMomentumEnd\",\n} as const satisfies Record<ScrollPhase, string>\n\n/**\n * Whether `handlers` asks for any phase at all. The answer decides whether a\n * `ScrollView` installs its phase machinery, so it is asked of the CURRENT\n * handlers on every render rather than once: a component that grows a phase\n * handler later gets one, and one that never has any never pays for one.\n */\nconst wantsPhase = (handlers: unknown, phase?: ScrollPhase): boolean => {\n if (typeof handlers !== \"object\" || handlers === null) {\n return false\n }\n if (phase !== undefined) {\n return (\n typeof (handlers as Record<string, unknown>)[\n PHASE_KEY_OF_PHASE[phase]\n ] === \"function\"\n )\n }\n return Object.values(PHASE_KEY_OF_PHASE).some(\n (key) => typeof (handlers as Record<string, unknown>)[key] === \"function\",\n )\n}\n\n/**\n * @internal The hook without the hook: the translation from a\n * {@link ScrollEvent} to a handler call, plus the one context object every\n * call shares. Split out because this repo's unit project has no React\n * renderer — the hook below is two refs around this.\n *\n * `latest` is read per event rather than captured, so a re-render that\n * changed the handlers is picked up without changing the returned function.\n */\nexport const createScrollHandler = <\n Context extends Record<string, unknown> = Record<string, unknown>,\n>(\n latest: () =>\n ScrollHandlers<Context> | ScrollHandlerCallback<Context> | undefined,\n // The context object, when the caller already owns one — `useHandler`\n // hands out its own and a handler built from it has to share it.\n sharedContext?: Context,\n): ((event: ScrollEvent) => void) => {\n // Upstream's `useHandler` gives every handler call the SAME mutable object\n // across the whole scroll, and `@gorhom/bottom-sheet` uses it to carry the\n // offset a drag started at. One object per handler is that contract.\n const context = sharedContext ?? ({} as Context)\n const handle = (event: ScrollEvent): void => {\n const current = latest()\n const onScroll = typeof current === \"function\" ? current : current?.onScroll\n onScroll?.({ ...event.nativeEvent, eventName: \"onScroll\" }, context)\n }\n // The phase sink shares that same context object, which is the whole\n // reason gorhom's lock works: `onBeginDrag` writes the offset the drag\n // started at into it and `onScroll` reads it back on the next frame.\n setScrollPhaseSink(handle, {\n wants: (phase) => wantsPhase(latest(), phase),\n deliver: (phase, event) => {\n const current = latest()\n if (typeof current !== \"object\" || current === null) {\n return\n }\n const callback = current[PHASE_KEY_OF_PHASE[phase]] as\n ScrollHandlerCallback<Context> | undefined\n callback?.(\n { ...event.nativeEvent, eventName: EVENT_NAME_OF_PHASE[phase] },\n context,\n )\n },\n })\n return handle\n}\n\n/**\n * Returns a scroll handler to pass as a scrollable's `onScroll`. The identity\n * is stable across renders — a scrollable that re-attached its signal handler\n * on every render would be a worse deal than the render this hook exists to\n * avoid.\n */\nexport const useAnimatedScrollHandler = <\n Context extends Record<string, unknown> = Record<string, unknown>,\n>(\n handlers:\n ScrollHandlers<Context> | ScrollHandlerCallback<Context> | undefined,\n // Accepted for source parity and unused, for the reason the rest of this\n // surface gives: nothing here is captured by a Babel plugin, so a handler\n // read out of a ref is always the current one and there is no stale\n // closure for a dependency array to refresh.\n dependencies?: DependencyList,\n): ((event: ScrollEvent) => void) => {\n void dependencies\n // The \"latest ref\" pattern, and the two disables below are what it costs.\n // Upstream rebuilds the handler OBJECT on every render and the returned\n // function has to call the current one; keeping it in a ref is the only\n // way to do that without a new function each time, which is the whole\n // point of the hook. Nothing reads the ref during render — the read\n // happens inside the scroll callback, which runs from a GTK signal long\n // after the commit. `useState` holds the function itself for the same\n // reason `useAnimatedRef` reaches for it: that value IS the return value,\n // and a ref there really would be a render-time read.\n const latest = useRef(handlers)\n // eslint-disable-next-line react-hooks/refs\n latest.current = handlers\n // eslint-disable-next-line react-hooks/refs\n const [stable] = useState(() =>\n createScrollHandler<Context>(() => latest.current),\n )\n return stable\n}\n\n// --- the primitives the hook above is built on --------------------------\n//\n// `useHandler` and `useEvent` are what upstream builds every scroll handler\n// out of, and libraries reach for them directly to build their own. Both are\n// implementable over this seam, and neither needs the event system they exist\n// to arrange on mobile — for the reason at the top of this file.\n\n/** Upstream's return shape. Both fields mean something here. */\nexport type UseHandlerContext<Context extends Record<string, unknown>> = {\n context: Context\n doDependenciesDiffer: boolean\n useWeb: boolean\n}\n\n/**\n * The context object a hand-built handler shares across its calls, plus the\n * two questions upstream answers alongside it.\n *\n * `doDependenciesDiffer` is **always false**, and that is a statement rather\n * than a stub: upstream needs it because a worklet is a by-value snapshot\n * that goes stale, so a changed dependency has to force a REBUILD of the\n * handler on the UI runtime. Here a handler is an ordinary closure read out\n * of a ref at call time, so it is never stale and there is never anything to\n * rebuild. A caller that feeds this into `useEvent`'s `rebuild` argument\n * therefore never rebuilds, which is correct.\n *\n * `useWeb` is upstream's `SHOULD_BE_USE_WEB`, and it is true for the same\n * reason it is true for react-native-windows: one runtime, no worklet\n * boundary, events delivered as ordinary props (docs/research/reanimated.md).\n */\nexport const useHandler = <\n Event extends object,\n Context extends Record<string, unknown> = Record<string, unknown>,\n>(\n handlers: Record<\n string,\n ((event: Event, context: Context) => void) | undefined\n >,\n dependencies?: DependencyList,\n): UseHandlerContext<Context> => {\n void handlers\n void dependencies\n const [context] = useState(() => ({}) as Context)\n return { context, doDependenciesDiffer: false, useWeb: true }\n}\n\n/**\n * The event names this platform has a source for. Everything a scroll can\n * report, and nothing else — there is no native event registry here to\n * subscribe an arbitrary name against, so a name outside this set is refused\n * by name rather than accepted and never fired.\n */\nconst SCROLL_EVENT_NAMES = new Set<string>([\n \"onScroll\",\n ...Object.values(EVENT_NAME_OF_PHASE),\n])\n\nconst PHASE_OF_EVENT_NAME = new Map<string, ScrollPhase>(\n (Object.keys(EVENT_NAME_OF_PHASE) as ScrollPhase[]).map((phase) => [\n EVENT_NAME_OF_PHASE[phase],\n phase,\n ]),\n)\n\n/**\n * Runs `handler` for each of `eventNames` the platform can report. The\n * returned value goes on a scrollable's `onScroll`, exactly as the result of\n * {@link useAnimatedScrollHandler} does — and it is the same object underneath:\n * one callable carrying the phase sink for whichever phase names were asked\n * for, so a `ScrollView` given a handler that asked for none installs no phase\n * machinery.\n *\n * `rebuild` is accepted and ignored, for the reason `doDependenciesDiffer` is\n * always false: nothing here goes stale, so there is nothing to rebuild.\n *\n * **Scroll event names only.** A name outside that set throws where it is\n * asked for, naming itself: `onGestureHandlerStateChange` and the touch names\n * belong to systems this platform implements elsewhere and would not reach a\n * handler registered here, and a subscription that can never fire is the\n * failure mode this package refuses everywhere else.\n */\nexport const useEvent = <Event extends object>(\n handler: (event: Event & { eventName: string }) => void,\n eventNames: readonly string[] = [],\n rebuild = false,\n): ((event: ScrollEvent) => void) => {\n void rebuild\n for (const name of eventNames) {\n if (!SCROLL_EVENT_NAMES.has(name)) {\n throw new Error(\n `[react-native-gtkx] useEvent(\"${name}\") is not supported. ` +\n `This platform delivers scroll events only — ${[...SCROLL_EVENT_NAMES].join(\", \")} — ` +\n \"because a GtkAdjustment is the one event source there is to subscribe to. \" +\n \"See docs/api.md for the gesture and touch surfaces, which are their own systems.\",\n )\n }\n }\n const latest = useRef(handler)\n // eslint-disable-next-line react-hooks/refs\n latest.current = handler\n const names = useRef(eventNames)\n // eslint-disable-next-line react-hooks/refs\n names.current = eventNames\n // eslint-disable-next-line react-hooks/refs\n const [stable] = useState(() => {\n const call = (nativeEvent: object, eventName: string): void => {\n if (names.current.includes(eventName)) {\n latest.current({ ...nativeEvent, eventName } as Event & {\n eventName: string\n })\n }\n }\n const emit = (event: ScrollEvent): void => {\n call(event.nativeEvent, \"onScroll\")\n }\n setScrollPhaseSink(emit, {\n wants: () => names.current.some((name) => PHASE_OF_EVENT_NAME.has(name)),\n deliver: (phase, event) => {\n call(event.nativeEvent, EVENT_NAME_OF_PHASE[phase])\n },\n })\n // Upstream's return value carries this, and `useScrollOffset` reaches\n // through it to register a view TAG with the native event registry. There\n // is no such registry here and no tag to give it, so reaching for it\n // fails where it is reached for rather than silently doing nothing.\n Object.defineProperty(emit, \"workletEventHandler\", {\n get() {\n throw new Error(\n \"[react-native-gtkx] useEvent(...).workletEventHandler is not supported. \" +\n \"It registers a native view tag with Reanimated's event registry, and this \" +\n \"platform has neither. Hand the value useEvent returns to a scrollable's \" +\n \"`onScroll` prop instead — that IS the subscription here.\",\n )\n },\n })\n return emit\n })\n return stable\n}\n\n/** Anything `scrollTo` can be pointed at: RN's scroll methods live on the ref. */\ntype ScrollableHandle = {\n scrollTo?: (options: { x?: number; y?: number; animated?: boolean }) => void\n}\n\ntype ScrollableRef =\n | (() => ScrollableHandle | null)\n | { current: ScrollableHandle | null }\n | null\n | undefined\n\n/**\n * Scrolls the view an `useAnimatedRef` points at — the other half of the pair\n * above, and the reason it is in this file: a list that reads its own offset\n * through the handler almost always writes one back, and\n * `react-native-reanimated-dnd`'s `useSortableList` does exactly that inside\n * a `useAnimatedReaction`.\n *\n * Upstream is a worklet reaching the shadow tree directly. Here it is the\n * ordinary imperative `scrollTo` every RN scrollable already exposes, called\n * synchronously, for the same reason the handler needs no event system: this\n * IS the thread that owns the widget. The argument order is upstream's\n * (`x` then `y`, not RN's options object), so library call sites are\n * unchanged, and `animated` carries through to `ScrollView`, which ignores it.\n *\n * A ref that is not (yet) pointing at a scrollable is ignored rather than\n * throwing: upstream does the same, and the first frames of a list whose ref\n * has not been attached are a normal state, not an error.\n */\nexport const scrollTo = (\n animatedRef: ScrollableRef,\n x: number,\n y: number,\n animated: boolean,\n): void => {\n if (!animatedRef) {\n return\n }\n const handle =\n typeof animatedRef === \"function\" ? animatedRef() : animatedRef.current\n handle?.scrollTo?.({ x, y, animated })\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SharedValue } from "./mutable";
|
|
2
|
+
type MakeMutable = <T>(initial: T) => SharedValue<T>;
|
|
3
|
+
/** Either spelling of a ref, as everything else on this surface accepts. */
|
|
4
|
+
type ScrollableRef = (() => unknown) | {
|
|
5
|
+
current: unknown;
|
|
6
|
+
} | null | undefined;
|
|
7
|
+
export declare const createScrollOffsetHooks: (makeMutable: MakeMutable) => {
|
|
8
|
+
useScrollOffset: (animatedRef: ScrollableRef, providedOffset?: SharedValue<number>) => SharedValue<number>;
|
|
9
|
+
/** Upstream's own deprecated alias, kept so library call sites are unchanged. */
|
|
10
|
+
useScrollViewOffset: (animatedRef: ScrollableRef, providedOffset?: SharedValue<number>) => SharedValue<number>;
|
|
11
|
+
};
|
|
12
|
+
export {};
|