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,104 @@
|
|
|
1
|
+
// `useScrollOffset` (and `useScrollViewOffset`, its own deprecated alias
|
|
2
|
+
// upstream) — a shared value that always holds a scrollable's current offset.
|
|
3
|
+
//
|
|
4
|
+
// Upstream builds it out of `useEvent`, which subscribes a native view tag to
|
|
5
|
+
// the scroll event names and lets the UI runtime write the value. Here it is
|
|
6
|
+
// shorter and reads the same: an `AnimatedRef` on a scrollable resolves to the
|
|
7
|
+
// `GtkScrolledWindow` behind it, and its adjustments already emit
|
|
8
|
+
// `value-changed` on the loop this JS is on — the same signal
|
|
9
|
+
// `ScrollView.emitScroll` runs from. So the value moves with the scroll, with
|
|
10
|
+
// no React render and no event machinery, which is what the hook promises.
|
|
11
|
+
//
|
|
12
|
+
// **It costs nothing to be untracked.** Nothing is connected until a hook
|
|
13
|
+
// actually points at a scrollable, and it is disconnected when the component
|
|
14
|
+
// unmounts or the ref moves to a different one. A scrollable nobody tracks is
|
|
15
|
+
// the widget it always was.
|
|
16
|
+
import { useEffect, useRef, useState } from "react";
|
|
17
|
+
import { widgetForHandle } from "../components/measure";
|
|
18
|
+
import { warnOnce } from "../style/dev-warning";
|
|
19
|
+
import { Gtk } from "../gtkx/bridge/index";
|
|
20
|
+
const instanceOf = (animatedRef) => {
|
|
21
|
+
if (!animatedRef) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return typeof animatedRef === "function" ? animatedRef() : animatedRef.current;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The scrolled window a ref stands for. A `ScrollView`'s handle IS the
|
|
28
|
+
* scrolled window; a windowed list's handle resolves through to the
|
|
29
|
+
* `ScrollView` it renders (`registerHandleAlias`), which is the same rule
|
|
30
|
+
* `findNodeHandle` and `measureLayout` already follow — so this works on a
|
|
31
|
+
* `FlatList` ref without knowing anything about lists.
|
|
32
|
+
*/
|
|
33
|
+
const scrolledWindowOf = (animatedRef) => {
|
|
34
|
+
const widget = widgetForHandle(instanceOf(animatedRef));
|
|
35
|
+
return widget instanceof Gtk.ScrolledWindow ? widget : null;
|
|
36
|
+
};
|
|
37
|
+
export const createScrollOffsetHooks = (makeMutable) => {
|
|
38
|
+
const useScrollOffset = (animatedRef,
|
|
39
|
+
// Upstream's second argument: write into the caller's shared value
|
|
40
|
+
// instead of minting one, so two hooks can share an offset.
|
|
41
|
+
providedOffset) => {
|
|
42
|
+
// useState rather than a lazily-filled ref for the reason `useSharedValue`
|
|
43
|
+
// reaches for it: this value IS the return value, so a ref would be a
|
|
44
|
+
// render-time read.
|
|
45
|
+
const [internal] = useState(() => makeMutable(0));
|
|
46
|
+
const offset = providedOffset ?? internal;
|
|
47
|
+
// The widget currently subscribed to, so a re-render that resolved to the
|
|
48
|
+
// same one reconnects nothing.
|
|
49
|
+
const attached = useRef(null);
|
|
50
|
+
const detach = useRef(null);
|
|
51
|
+
// No dependency array on purpose. A ref is attached by React AFTER the
|
|
52
|
+
// render that created it, so the first run of this effect commonly
|
|
53
|
+
// resolves to nothing and the second one — a commit later, with the list
|
|
54
|
+
// mounted — is the one that finds the scroller. Upstream solves the same
|
|
55
|
+
// problem with `animatedRef.observe`; re-resolving per commit is the same
|
|
56
|
+
// answer without a second observer protocol, and it is one WeakMap lookup
|
|
57
|
+
// when the answer has not changed.
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const widget = scrolledWindowOf(animatedRef);
|
|
60
|
+
if (widget === attached.current) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
detach.current?.();
|
|
64
|
+
detach.current = null;
|
|
65
|
+
attached.current = widget;
|
|
66
|
+
if (!widget) {
|
|
67
|
+
if (instanceOf(animatedRef) !== null) {
|
|
68
|
+
warnOnce("useScrollOffset", "useScrollOffset was given a ref that is not on a scrollable, so the " +
|
|
69
|
+
"offset will stay at 0. Pass the ref to a ScrollView, FlatList or " +
|
|
70
|
+
"Animated.ScrollView.");
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const hadjustment = widget.getHadjustment();
|
|
75
|
+
const vadjustment = widget.getVadjustment();
|
|
76
|
+
const update = () => {
|
|
77
|
+
// Upstream's own rule, mirrored exactly: the horizontal offset when
|
|
78
|
+
// there is one, the vertical otherwise. One shared value cannot carry
|
|
79
|
+
// two axes, and this is the choice every consumer is written against.
|
|
80
|
+
const x = hadjustment.getValue();
|
|
81
|
+
offset.value = x === 0 ? vadjustment.getValue() : x;
|
|
82
|
+
};
|
|
83
|
+
update();
|
|
84
|
+
hadjustment.on("value-changed", update);
|
|
85
|
+
vadjustment.on("value-changed", update);
|
|
86
|
+
detach.current = () => {
|
|
87
|
+
hadjustment.off("value-changed", update);
|
|
88
|
+
vadjustment.off("value-changed", update);
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
useEffect(() => () => {
|
|
92
|
+
detach.current?.();
|
|
93
|
+
detach.current = null;
|
|
94
|
+
attached.current = null;
|
|
95
|
+
}, []);
|
|
96
|
+
return offset;
|
|
97
|
+
};
|
|
98
|
+
return {
|
|
99
|
+
useScrollOffset,
|
|
100
|
+
/** Upstream's own deprecated alias, kept so library call sites are unchanged. */
|
|
101
|
+
useScrollViewOffset: useScrollOffset,
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
//# sourceMappingURL=scroll-offset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-offset.js","sourceRoot":"","sources":["../../src/reanimated-compat/scroll-offset.ts"],"names":[],"mappings":"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","sourcesContent":["// `useScrollOffset` (and `useScrollViewOffset`, its own deprecated alias\n// upstream) — a shared value that always holds a scrollable's current offset.\n//\n// Upstream builds it out of `useEvent`, which subscribes a native view tag to\n// the scroll event names and lets the UI runtime write the value. Here it is\n// shorter and reads the same: an `AnimatedRef` on a scrollable resolves to the\n// `GtkScrolledWindow` behind it, and its adjustments already emit\n// `value-changed` on the loop this JS is on — the same signal\n// `ScrollView.emitScroll` runs from. So the value moves with the scroll, with\n// no React render and no event machinery, which is what the hook promises.\n//\n// **It costs nothing to be untracked.** Nothing is connected until a hook\n// actually points at a scrollable, and it is disconnected when the component\n// unmounts or the ref moves to a different one. A scrollable nobody tracks is\n// the widget it always was.\nimport { useEffect, useRef, useState } from \"react\"\nimport { widgetForHandle } from \"../components/measure\"\nimport { warnOnce } from \"../style/dev-warning\"\nimport { Gtk } from \"../gtkx/bridge/index\"\nimport type { SharedValue } from \"./mutable\"\n\ntype MakeMutable = <T>(initial: T) => SharedValue<T>\n\n/** Either spelling of a ref, as everything else on this surface accepts. */\ntype ScrollableRef = (() => unknown) | { current: unknown } | null | undefined\n\nconst instanceOf = (animatedRef: ScrollableRef): unknown => {\n if (!animatedRef) {\n return null\n }\n return typeof animatedRef === \"function\" ? animatedRef() : animatedRef.current\n}\n\n/**\n * The scrolled window a ref stands for. A `ScrollView`'s handle IS the\n * scrolled window; a windowed list's handle resolves through to the\n * `ScrollView` it renders (`registerHandleAlias`), which is the same rule\n * `findNodeHandle` and `measureLayout` already follow — so this works on a\n * `FlatList` ref without knowing anything about lists.\n */\nconst scrolledWindowOf = (\n animatedRef: ScrollableRef,\n): Gtk.ScrolledWindow | null => {\n const widget = widgetForHandle(instanceOf(animatedRef))\n return widget instanceof Gtk.ScrolledWindow ? widget : null\n}\n\nexport const createScrollOffsetHooks = (makeMutable: MakeMutable) => {\n const useScrollOffset = (\n animatedRef: ScrollableRef,\n // Upstream's second argument: write into the caller's shared value\n // instead of minting one, so two hooks can share an offset.\n providedOffset?: SharedValue<number>,\n ): SharedValue<number> => {\n // useState rather than a lazily-filled ref for the reason `useSharedValue`\n // reaches for it: this value IS the return value, so a ref would be a\n // render-time read.\n const [internal] = useState(() => makeMutable(0))\n const offset = providedOffset ?? internal\n\n // The widget currently subscribed to, so a re-render that resolved to the\n // same one reconnects nothing.\n const attached = useRef<Gtk.ScrolledWindow | null>(null)\n const detach = useRef<(() => void) | null>(null)\n\n // No dependency array on purpose. A ref is attached by React AFTER the\n // render that created it, so the first run of this effect commonly\n // resolves to nothing and the second one — a commit later, with the list\n // mounted — is the one that finds the scroller. Upstream solves the same\n // problem with `animatedRef.observe`; re-resolving per commit is the same\n // answer without a second observer protocol, and it is one WeakMap lookup\n // when the answer has not changed.\n useEffect(() => {\n const widget = scrolledWindowOf(animatedRef)\n if (widget === attached.current) {\n return\n }\n detach.current?.()\n detach.current = null\n attached.current = widget\n if (!widget) {\n if (instanceOf(animatedRef) !== null) {\n warnOnce(\n \"useScrollOffset\",\n \"useScrollOffset was given a ref that is not on a scrollable, so the \" +\n \"offset will stay at 0. Pass the ref to a ScrollView, FlatList or \" +\n \"Animated.ScrollView.\",\n )\n }\n return\n }\n\n const hadjustment = widget.getHadjustment()\n const vadjustment = widget.getVadjustment()\n const update = (): void => {\n // Upstream's own rule, mirrored exactly: the horizontal offset when\n // there is one, the vertical otherwise. One shared value cannot carry\n // two axes, and this is the choice every consumer is written against.\n const x = hadjustment.getValue()\n offset.value = x === 0 ? vadjustment.getValue() : x\n }\n update()\n hadjustment.on(\"value-changed\", update)\n vadjustment.on(\"value-changed\", update)\n detach.current = () => {\n hadjustment.off(\"value-changed\", update)\n vadjustment.off(\"value-changed\", update)\n }\n })\n\n useEffect(\n () => () => {\n detach.current?.()\n detach.current = null\n attached.current = null\n },\n [],\n )\n\n return offset\n }\n\n return {\n useScrollOffset,\n /** Upstream's own deprecated alias, kept so library call sites are unchanged. */\n useScrollViewOffset: useScrollOffset,\n }\n}\n"]}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export type StyleValue = number | string;
|
|
2
|
+
export type StyleObject = Record<string, unknown>;
|
|
3
|
+
/** The animated node a style leaf is replaced with. */
|
|
4
|
+
export type StyleNode = {
|
|
5
|
+
addListener(callback: (state: {
|
|
6
|
+
value: StyleValue;
|
|
7
|
+
}) => void): string;
|
|
8
|
+
removeListener(id: string): void;
|
|
9
|
+
__getValue(): StyleValue;
|
|
10
|
+
/** @internal Publishes a new value to the view layer. */
|
|
11
|
+
__push(value: StyleValue): void;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* @internal Shared with `props.ts`: `useAnimatedProps` publishes through the
|
|
15
|
+
* exact same node the style path does, because the receivers duck-type it the
|
|
16
|
+
* same way (`addListener` + `__getValue`).
|
|
17
|
+
*/
|
|
18
|
+
export declare const createStyleNode: (initial: StyleValue) => StyleNode;
|
|
19
|
+
/** @internal Test seam: the warning is once per session by design. */
|
|
20
|
+
export declare const resetUndriveableWarnings: () => void;
|
|
21
|
+
/**
|
|
22
|
+
* Whether a property that finishes an animation has to be published through a
|
|
23
|
+
* React render to land at all.
|
|
24
|
+
*
|
|
25
|
+
* Every warning on the layout path ends with "the new value is applied on the
|
|
26
|
+
* next React render", and until now that was true only by luck: a value that
|
|
27
|
+
* moves solely inside a `useAnimatedStyle` produces no render, so the last
|
|
28
|
+
* frame of the animation was the last place it existed. `@gorhom/bottom-sheet`
|
|
29
|
+
* is exactly that case — it bounds its scrollable with an animated `height`
|
|
30
|
+
* and nothing else re-renders — and `spike/core-exports` is what found it.
|
|
31
|
+
*
|
|
32
|
+
* Layout properties only. `opacity`, `transform` and colours are written to
|
|
33
|
+
* the widget on the frame they change, so a render at the end would be a
|
|
34
|
+
* render for nothing; a `width`/`height` the driven-size rule ACCEPTS is on
|
|
35
|
+
* screen at the right size already, and the render is what puts the same value
|
|
36
|
+
* into Yoga so the committed layout and the override agree.
|
|
37
|
+
*
|
|
38
|
+
* At the end of the animation AND on a cadence while it runs, which is the
|
|
39
|
+
* correction §10 made to §9: the settle alone kept the promise only when the
|
|
40
|
+
* animation was short and never when its target moved every frame, and
|
|
41
|
+
* gorhom's sheet was blank for 1.38 s because of it. Ten renders a second at
|
|
42
|
+
* the very most, against sixty frames — still the whole difference between
|
|
43
|
+
* this and the naive layout write the refusal is about
|
|
44
|
+
* (docs/research/animated-size.md §3, and LANDING_INTERVAL_MS in
|
|
45
|
+
* ./updater-animations.ts for the numbers).
|
|
46
|
+
*/
|
|
47
|
+
export declare const settlesThroughReact: (property: string) => boolean;
|
|
48
|
+
export type AnimatedStyle = {
|
|
49
|
+
/**
|
|
50
|
+
* The object handed to `Animated.View`. Stable while an animation runs — a
|
|
51
|
+
* driven frame is a write into a node, not a new style — and replaced by a
|
|
52
|
+
* shallow copy exactly once per `renew()`, which is how a value that cannot
|
|
53
|
+
* be driven reaches the view layer at all.
|
|
54
|
+
*/
|
|
55
|
+
readonly style: StyleObject;
|
|
56
|
+
/** The nodes behind it, reusable when the style has to be rebuilt. */
|
|
57
|
+
readonly nodes: ReadonlyMap<string, StyleNode>;
|
|
58
|
+
/**
|
|
59
|
+
* Publishes a fresh updater result. Returns false when the SHAPE changed
|
|
60
|
+
* (a transform entry appeared, vanished or swapped kind) and the caller
|
|
61
|
+
* must rebuild — the node set no longer matches, and the bindings in the
|
|
62
|
+
* view layer were made against the old one.
|
|
63
|
+
*/
|
|
64
|
+
apply(next: StyleObject): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Gives the style a new identity, so a render that follows actually reaches
|
|
67
|
+
* the view.
|
|
68
|
+
*
|
|
69
|
+
* A stable object was the right default and it is still what a running
|
|
70
|
+
* animation gets, but it is not enough on its own: `@gorhom/bottom-sheet`
|
|
71
|
+
* wraps the view this style lands on in `memo`, and a re-render of the
|
|
72
|
+
* component that OWNS the `useAnimatedStyle` stops at that boundary with
|
|
73
|
+
* every prop identical. The value then exists in the style and never reaches
|
|
74
|
+
* Yoga — measured, `spike/core-exports`: the sheet's content mask stayed at
|
|
75
|
+
* the height of the animation's first frame while the animation itself ran
|
|
76
|
+
* to 543 px.
|
|
77
|
+
*
|
|
78
|
+
* Called for a property the platform will not drive at frame rate when its
|
|
79
|
+
* animation settles, and at most ten times a second while it runs — never
|
|
80
|
+
* per frame. See LANDING_INTERVAL_MS in ./updater-animations.ts for why the
|
|
81
|
+
* settle on its own was not enough.
|
|
82
|
+
*/
|
|
83
|
+
renew(): void;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Builds the node-backed style. `reuse` carries nodes over from a previous
|
|
87
|
+
* build so a shape change keeps the identity of the leaves that survived it,
|
|
88
|
+
* which is what stops the view layer re-binding the parts that did not move.
|
|
89
|
+
*/
|
|
90
|
+
export declare const createAnimatedStyle: (source: StyleObject, reuse?: ReadonlyMap<string, StyleNode>) => AnimatedStyle;
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
// Turning a `useAnimatedStyle` updater's plain object into a style the view
|
|
2
|
+
// layer can drive without React.
|
|
3
|
+
//
|
|
4
|
+
// The mapper returns ordinary values every frame. On the first run each
|
|
5
|
+
// animatable leaf is replaced by a small animated NODE and the mapping is
|
|
6
|
+
// remembered; later runs only push new values into those nodes, so a running
|
|
7
|
+
// animation costs zero React renders. `src/components/animated.tsx`
|
|
8
|
+
// recognises those nodes structurally and writes them straight to GTK —
|
|
9
|
+
// `widget.setOpacity` for opacity, the rect store plus `queueAllocate` for
|
|
10
|
+
// transforms, and a CSS provider private to the widget for colours.
|
|
11
|
+
//
|
|
12
|
+
// WHERE THE BOUNDARY IS NOW. Opacity, transforms and colours are driven
|
|
13
|
+
// unconditionally. `flex`, every `margin*`/`padding*`, `gap`, `flexBasis` and
|
|
14
|
+
// the `min*`/`max*` family are refused, and that is a refusal on measured
|
|
15
|
+
// grounds rather than a gap waiting to be filled: a layout write has to go
|
|
16
|
+
// through Yoga, and a pass plus its commit walk costs what the CONTAINER
|
|
17
|
+
// costs, not what the animated value costs — 52 µs for a five-child
|
|
18
|
+
// container, 133 µs at sixty, 496 µs at three hundred, per frame. The same
|
|
19
|
+
// frame's transform write is 1.5 µs and does not grow at all.
|
|
20
|
+
//
|
|
21
|
+
// It is a cost argument and ONLY a cost argument, which is narrower than this
|
|
22
|
+
// comment used to claim. docs/research/animated-size.md re-measured the two
|
|
23
|
+
// other things that were said here: GTK re-measuring the ancestors after the
|
|
24
|
+
// resize adds nothing at any tree size, and a size write cannot move the
|
|
25
|
+
// window — the RN root reports a zero size request, so the toplevel never
|
|
26
|
+
// re-negotiates. (An `IntrinsicRoot` mounted directly in GTK chrome is the one
|
|
27
|
+
// exception, and it does change the window's request.)
|
|
28
|
+
//
|
|
29
|
+
// TWO CARVE-OUTS, and neither is a softening of that.
|
|
30
|
+
//
|
|
31
|
+
// - `top`/`left`/`right`/`bottom` on a node whose OWN `position` is
|
|
32
|
+
// `"absolute"`, because an out-of-flow node's inset has an exact transform
|
|
33
|
+
// equivalent: moving it changes nothing but where it is drawn, which is
|
|
34
|
+
// what the transform path already does, so no Yoga pass is needed at all —
|
|
35
|
+
// src/style/absolute-insets.ts, docs/research/absolute-insets.md.
|
|
36
|
+
// - `width`/`height` where the change is CONFINED to the node that owns it.
|
|
37
|
+
// There is no transform that reproduces a size change (a scale grows about
|
|
38
|
+
// the centre and stretches the content instead of re-laying it out —
|
|
39
|
+
// measured), so this one really does run Yoga; it just runs it rooted at
|
|
40
|
+
// the animated node rather than at the tree's root, which is 6.6 µs for a
|
|
41
|
+
// leaf and 23 µs with wrapped text, IDENTICAL at 5, 60 and 300 siblings —
|
|
42
|
+
// src/style/animated-size.ts, docs/research/animated-size.md.
|
|
43
|
+
//
|
|
44
|
+
// Both rules are measured, neither is universal, and the configurations where
|
|
45
|
+
// they fail are refused as loudly as `flex` is.
|
|
46
|
+
//
|
|
47
|
+
// The rule for everything undriveable stays what it was: it must be VISIBLE.
|
|
48
|
+
// A property that changes between mapper runs but cannot be driven says so
|
|
49
|
+
// once, by name — and layout properties say it in their own words, because
|
|
50
|
+
// "use translateX instead of left" is advice and "cannot be written" is not.
|
|
51
|
+
import { INSET_PROPERTIES, insetRefusalReason, insetTranslation, } from "../style/absolute-insets";
|
|
52
|
+
import { SIZE_PROPERTIES } from "../style/animated-size";
|
|
53
|
+
import { DRIVEABLE_COLOR_PROPERTIES } from "../style/imperative-css";
|
|
54
|
+
/**
|
|
55
|
+
* @internal Shared with `props.ts`: `useAnimatedProps` publishes through the
|
|
56
|
+
* exact same node the style path does, because the receivers duck-type it the
|
|
57
|
+
* same way (`addListener` + `__getValue`).
|
|
58
|
+
*/
|
|
59
|
+
export const createStyleNode = (initial) => {
|
|
60
|
+
let current = initial;
|
|
61
|
+
const listeners = new Map();
|
|
62
|
+
let nextListenerId = 1;
|
|
63
|
+
return {
|
|
64
|
+
addListener(callback) {
|
|
65
|
+
const id = String(nextListenerId++);
|
|
66
|
+
listeners.set(id, callback);
|
|
67
|
+
return id;
|
|
68
|
+
},
|
|
69
|
+
removeListener(id) {
|
|
70
|
+
listeners.delete(id);
|
|
71
|
+
},
|
|
72
|
+
__getValue() {
|
|
73
|
+
return current;
|
|
74
|
+
},
|
|
75
|
+
__push(value) {
|
|
76
|
+
if (Object.is(value, current)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
current = value;
|
|
80
|
+
for (const listener of [...listeners.values()]) {
|
|
81
|
+
listener({ value: current });
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
const isStyleValue = (value) => typeof value === "number" || typeof value === "string";
|
|
87
|
+
const OPACITY = "opacity";
|
|
88
|
+
const Z_INDEX = "zIndex";
|
|
89
|
+
const transformLeafKey = (index, part) => `transform.${index}.${part}`;
|
|
90
|
+
const isInsetProperty = (property) => INSET_PROPERTIES.includes(property);
|
|
91
|
+
const isSizeProperty = (property) => SIZE_PROPERTIES.includes(property);
|
|
92
|
+
/**
|
|
93
|
+
* Whether this style's `property` is one of the insets that becomes a
|
|
94
|
+
* transform. A number is required rather than merely a `DimensionValue`: a
|
|
95
|
+
* percentage has no fixed offset from a point base, and switching to one
|
|
96
|
+
* mid-animation changes the leaf signature, which rebuilds the style and puts
|
|
97
|
+
* the property back on the refusal path where it belongs.
|
|
98
|
+
*
|
|
99
|
+
* The updater's object is not the whole style. `style={[styles.row, animated]}`
|
|
100
|
+
* is how most people write this, so `position` and the opposite edge are very
|
|
101
|
+
* often in a SIBLING entry that this module never sees. When the updater says
|
|
102
|
+
* nothing about `position`, the question is simply not answerable here and the
|
|
103
|
+
* leaf is made optimistically — the view layer re-asks it against the
|
|
104
|
+
* FLATTENED style, which is the only place the true answer exists, and warns
|
|
105
|
+
* there if the answer is no (components/animated.tsx). Nothing becomes silent
|
|
106
|
+
* either way; only which channel says it moves.
|
|
107
|
+
*/
|
|
108
|
+
const isDriveableInset = (source, property) => {
|
|
109
|
+
if (!isInsetProperty(property) || typeof source[property] !== "number") {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
return source.position === undefined
|
|
113
|
+
? true
|
|
114
|
+
: insetTranslation(source, property) !== null;
|
|
115
|
+
};
|
|
116
|
+
// RN's layout properties, as `STYLE_PROPERTIES_CONFIG` enumerates them.
|
|
117
|
+
// Enumerated by hand rather than derived by subtracting the driveable set,
|
|
118
|
+
// because the point of the list is to produce a DIFFERENT message: these are
|
|
119
|
+
// refused for a measured reason and have a real alternative, which
|
|
120
|
+
// `borderStyle` does not.
|
|
121
|
+
const LAYOUT_PROPERTIES = new Set([
|
|
122
|
+
"aspectRatio",
|
|
123
|
+
"borderBottomWidth",
|
|
124
|
+
"borderEndWidth",
|
|
125
|
+
"borderLeftWidth",
|
|
126
|
+
"borderRightWidth",
|
|
127
|
+
"borderStartWidth",
|
|
128
|
+
"borderTopWidth",
|
|
129
|
+
"borderWidth",
|
|
130
|
+
"bottom",
|
|
131
|
+
"columnGap",
|
|
132
|
+
"end",
|
|
133
|
+
"flex",
|
|
134
|
+
"flexBasis",
|
|
135
|
+
"flexGrow",
|
|
136
|
+
"flexShrink",
|
|
137
|
+
"gap",
|
|
138
|
+
"height",
|
|
139
|
+
"left",
|
|
140
|
+
"margin",
|
|
141
|
+
"marginBottom",
|
|
142
|
+
"marginEnd",
|
|
143
|
+
"marginHorizontal",
|
|
144
|
+
"marginLeft",
|
|
145
|
+
"marginRight",
|
|
146
|
+
"marginStart",
|
|
147
|
+
"marginTop",
|
|
148
|
+
"marginVertical",
|
|
149
|
+
"maxHeight",
|
|
150
|
+
"maxWidth",
|
|
151
|
+
"minHeight",
|
|
152
|
+
"minWidth",
|
|
153
|
+
"padding",
|
|
154
|
+
"paddingBottom",
|
|
155
|
+
"paddingEnd",
|
|
156
|
+
"paddingHorizontal",
|
|
157
|
+
"paddingLeft",
|
|
158
|
+
"paddingRight",
|
|
159
|
+
"paddingStart",
|
|
160
|
+
"paddingTop",
|
|
161
|
+
"paddingVertical",
|
|
162
|
+
"right",
|
|
163
|
+
"rowGap",
|
|
164
|
+
"start",
|
|
165
|
+
"top",
|
|
166
|
+
"width",
|
|
167
|
+
]);
|
|
168
|
+
// Which transform to reach for instead, in the two kinds it comes in. The
|
|
169
|
+
// distinction is not pedantry — `scaleX` was named as the replacement for
|
|
170
|
+
// `width` here and it is not one, which docs/research/animated-size.md §6
|
|
171
|
+
// measured on a 100×60 box widened to 260:
|
|
172
|
+
//
|
|
173
|
+
// - a scale grows about the view's CENTRE, so the box MOVES: x went 500 →
|
|
174
|
+
// 420, where the width change kept it at 500;
|
|
175
|
+
// - a scale scales the CONTENT with the box instead of re-laying it out: the
|
|
176
|
+
// label inside kept its three-line, 45 px-tall layout and was drawn 2.6×
|
|
177
|
+
// wide — stretched glyphs — where the width change re-wrapped it to one
|
|
178
|
+
// line, 15 px tall.
|
|
179
|
+
//
|
|
180
|
+
// So an inset really does have a transform that reproduces it, and a size does
|
|
181
|
+
// not. Offering `scaleX` as if it did sends people to a different behaviour
|
|
182
|
+
// with the same name on it — which is also why a numeric `width`/`height` is
|
|
183
|
+
// driven for real now and only reaches this table as a PERCENTAGE, where
|
|
184
|
+
// there is no point base to lay out at.
|
|
185
|
+
const EXACT_ALTERNATIVE = {
|
|
186
|
+
bottom: "translateY",
|
|
187
|
+
end: "translateX",
|
|
188
|
+
left: "translateX",
|
|
189
|
+
right: "translateX",
|
|
190
|
+
start: "translateX",
|
|
191
|
+
top: "translateY",
|
|
192
|
+
};
|
|
193
|
+
const APPROXIMATE_ALTERNATIVE = {
|
|
194
|
+
height: "scaleY",
|
|
195
|
+
width: "scaleX",
|
|
196
|
+
};
|
|
197
|
+
// One warning per property name per session, matching the platform's
|
|
198
|
+
// warnNativeDriverIgnored policy: a 60 Hz mapper must not produce 60 lines a
|
|
199
|
+
// second, and the name is the actionable part.
|
|
200
|
+
const warned = new Set();
|
|
201
|
+
const warnUndriveable = (property, source) => {
|
|
202
|
+
if (warned.has(property)) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
warned.add(property);
|
|
206
|
+
const isProduction = typeof process !== "undefined" && process.env.NODE_ENV === "production";
|
|
207
|
+
if (isProduction) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const insetReason = isInsetProperty(property)
|
|
211
|
+
? insetRefusalReason(source, property)
|
|
212
|
+
: null;
|
|
213
|
+
if (insetReason) {
|
|
214
|
+
console.warn(`react-native-reanimated: useAnimatedStyle changed \`${property}\`. This node IS absolutely ` +
|
|
215
|
+
`positioned — which is normally driven at frame rate here — but ${insetReason}, so no ` +
|
|
216
|
+
"translation reproduces it and it would need a Yoga pass. Give the axis a single edge (or a " +
|
|
217
|
+
`definite size) and \`${property}\` animates; otherwise animate ` +
|
|
218
|
+
`\`transform: [{ ${EXACT_ALTERNATIVE[property]}: … }]\`. The new value is applied on the next ` +
|
|
219
|
+
"React render. See docs/api.md.");
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (LAYOUT_PROPERTIES.has(property)) {
|
|
223
|
+
const exact = EXACT_ALTERNATIVE[property];
|
|
224
|
+
const approximate = APPROXIMATE_ALTERNATIVE[property];
|
|
225
|
+
console.warn(`react-native-reanimated: useAnimatedStyle changed \`${property}\`, a LAYOUT property. ` +
|
|
226
|
+
"react-native-gtkx does not drive layout at frame rate on purpose: a layout write costs a Yoga " +
|
|
227
|
+
"pass over the container plus the commit walk that follows it, and that cost grows with the " +
|
|
228
|
+
"CONTAINER rather than with the number of animated values — measured at 52 µs for a five-child " +
|
|
229
|
+
"container and 496 µs at three hundred, against a transform's 1.5 µs. " +
|
|
230
|
+
(exact
|
|
231
|
+
? `Animate \`transform: [{ ${exact}: … }]\` instead — it reproduces the move exactly, it is ` +
|
|
232
|
+
"paint-only, it costs the same at any tree size, and it is what RN's own native driver " +
|
|
233
|
+
"restricts you to. "
|
|
234
|
+
: approximate
|
|
235
|
+
? `The closest transform is \`transform: [{ ${approximate}: … }]\`, but it is NOT the same ` +
|
|
236
|
+
"thing and it is worth knowing how: a scale grows about the view's CENTRE, so the box " +
|
|
237
|
+
"moves as it grows, and it scales the CONTENT with the box instead of re-laying it out — " +
|
|
238
|
+
"text stretches rather than re-wrapping. Reach for it when the content can take being " +
|
|
239
|
+
"stretched (a plain box, an image), not as a replacement. "
|
|
240
|
+
: "Transforms are paint-only and cost the same at any tree size. ") +
|
|
241
|
+
(isInsetProperty(property)
|
|
242
|
+
? `(\`${property}\` IS driven at frame rate on a node whose own \`position\` is "absolute", ` +
|
|
243
|
+
"where moving it is exactly a translation and touches no sibling.) "
|
|
244
|
+
: isSizeProperty(property)
|
|
245
|
+
? `(A NUMERIC \`${property}\` IS driven at frame rate where the change is confined to the node ` +
|
|
246
|
+
"that owns it. This one is a percentage, which has no point base to re-lay the subtree out " +
|
|
247
|
+
"at.) "
|
|
248
|
+
: "") +
|
|
249
|
+
"The new value is applied on the next React render. See docs/api.md.");
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
console.warn(`react-native-reanimated: useAnimatedStyle changed \`${property}\`, which react-native-gtkx cannot ` +
|
|
253
|
+
"write to a mounted widget without a React render. `opacity`, `transform` and colours animate at " +
|
|
254
|
+
"frame rate here; the new value is applied on the next render instead. See docs/api.md.");
|
|
255
|
+
};
|
|
256
|
+
/** @internal Test seam: the warning is once per session by design. */
|
|
257
|
+
export const resetUndriveableWarnings = () => {
|
|
258
|
+
warned.clear();
|
|
259
|
+
};
|
|
260
|
+
/**
|
|
261
|
+
* Whether a property that finishes an animation has to be published through a
|
|
262
|
+
* React render to land at all.
|
|
263
|
+
*
|
|
264
|
+
* Every warning on the layout path ends with "the new value is applied on the
|
|
265
|
+
* next React render", and until now that was true only by luck: a value that
|
|
266
|
+
* moves solely inside a `useAnimatedStyle` produces no render, so the last
|
|
267
|
+
* frame of the animation was the last place it existed. `@gorhom/bottom-sheet`
|
|
268
|
+
* is exactly that case — it bounds its scrollable with an animated `height`
|
|
269
|
+
* and nothing else re-renders — and `spike/core-exports` is what found it.
|
|
270
|
+
*
|
|
271
|
+
* Layout properties only. `opacity`, `transform` and colours are written to
|
|
272
|
+
* the widget on the frame they change, so a render at the end would be a
|
|
273
|
+
* render for nothing; a `width`/`height` the driven-size rule ACCEPTS is on
|
|
274
|
+
* screen at the right size already, and the render is what puts the same value
|
|
275
|
+
* into Yoga so the committed layout and the override agree.
|
|
276
|
+
*
|
|
277
|
+
* At the end of the animation AND on a cadence while it runs, which is the
|
|
278
|
+
* correction §10 made to §9: the settle alone kept the promise only when the
|
|
279
|
+
* animation was short and never when its target moved every frame, and
|
|
280
|
+
* gorhom's sheet was blank for 1.38 s because of it. Ten renders a second at
|
|
281
|
+
* the very most, against sixty frames — still the whole difference between
|
|
282
|
+
* this and the naive layout write the refusal is about
|
|
283
|
+
* (docs/research/animated-size.md §3, and LANDING_INTERVAL_MS in
|
|
284
|
+
* ./updater-animations.ts for the numbers).
|
|
285
|
+
*/
|
|
286
|
+
export const settlesThroughReact = (property) => LAYOUT_PROPERTIES.has(property);
|
|
287
|
+
/**
|
|
288
|
+
* Collects the animatable leaves of a style, in the order the view layer
|
|
289
|
+
* composes them (the transform array's order IS the composition order in RN).
|
|
290
|
+
*/
|
|
291
|
+
const leavesOf = (source) => {
|
|
292
|
+
const leaves = [];
|
|
293
|
+
if (typeof source[OPACITY] === "number") {
|
|
294
|
+
leaves.push({ key: OPACITY, value: source[OPACITY] });
|
|
295
|
+
}
|
|
296
|
+
// Driven for real, and by the same kind of write opacity uses: the paint
|
|
297
|
+
// and pick order of the container (gtkx/bridge/view-box.ts). It has to be
|
|
298
|
+
// driveable rather than merely accepted — `useSortable` puts it in the
|
|
299
|
+
// style object on every frame, so a refusal here is a refusal of the whole
|
|
300
|
+
// sortable-list shape.
|
|
301
|
+
if (typeof source[Z_INDEX] === "number") {
|
|
302
|
+
leaves.push({ key: Z_INDEX, value: source[Z_INDEX] });
|
|
303
|
+
}
|
|
304
|
+
for (const property of DRIVEABLE_COLOR_PROPERTIES) {
|
|
305
|
+
const value = source[property];
|
|
306
|
+
// Strings only. A colour is a string on this platform (RN's packed
|
|
307
|
+
// integers never reach a GTK stylesheet), so a number here is not a
|
|
308
|
+
// colour we can drive and belongs on the warn path with everything else.
|
|
309
|
+
if (typeof value === "string") {
|
|
310
|
+
leaves.push({ key: property, value });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
for (const property of INSET_PROPERTIES) {
|
|
314
|
+
if (isDriveableInset(source, property)) {
|
|
315
|
+
leaves.push({ key: property, value: source[property] });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// Sizes: a number is the whole of what can be decided HERE. Whether the
|
|
319
|
+
// change is confined to the node depends on the container's
|
|
320
|
+
// `flexDirection`, its `alignItems` and where its own size comes from —
|
|
321
|
+
// none of which is in the updater's object, and none of which is even in
|
|
322
|
+
// the animated component's props. So the leaf is made optimistically and
|
|
323
|
+
// the view layer, which has the layout tree, answers and warns
|
|
324
|
+
// (src/style/animated-size.ts, components/animated.tsx). A percentage stays
|
|
325
|
+
// on the refusal path: it has no point base to lay out at, and switching to
|
|
326
|
+
// one mid-animation changes the leaf signature, which rebuilds the style.
|
|
327
|
+
for (const property of SIZE_PROPERTIES) {
|
|
328
|
+
if (typeof source[property] === "number") {
|
|
329
|
+
leaves.push({ key: property, value: source[property] });
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (Array.isArray(source.transform)) {
|
|
333
|
+
source.transform.forEach((entry, index) => {
|
|
334
|
+
if (typeof entry !== "object" || entry === null) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const part = Object.keys(entry)[0];
|
|
338
|
+
if (part === undefined) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const value = entry[part];
|
|
342
|
+
if (isStyleValue(value)) {
|
|
343
|
+
leaves.push({ key: transformLeafKey(index, part), value });
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
return leaves;
|
|
348
|
+
};
|
|
349
|
+
const signatureOf = (leaves) => leaves.map((leaf) => leaf.key).join("|");
|
|
350
|
+
/**
|
|
351
|
+
* Builds the node-backed style. `reuse` carries nodes over from a previous
|
|
352
|
+
* build so a shape change keeps the identity of the leaves that survived it,
|
|
353
|
+
* which is what stops the view layer re-binding the parts that did not move.
|
|
354
|
+
*/
|
|
355
|
+
export const createAnimatedStyle = (source, reuse) => {
|
|
356
|
+
const leaves = leavesOf(source);
|
|
357
|
+
const signature = signatureOf(leaves);
|
|
358
|
+
const nodes = new Map();
|
|
359
|
+
for (const leaf of leaves) {
|
|
360
|
+
const existing = reuse?.get(leaf.key);
|
|
361
|
+
if (existing) {
|
|
362
|
+
existing.__push(leaf.value);
|
|
363
|
+
nodes.set(leaf.key, existing);
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
nodes.set(leaf.key, createStyleNode(leaf.value));
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// Reassigned by `renew()` only. Every other write below — the node
|
|
370
|
+
// substitutions here, and `apply`'s update of a value it cannot drive — goes
|
|
371
|
+
// INTO the object currently published, so a renewed copy carries the latest
|
|
372
|
+
// of everything rather than a snapshot taken when it was made.
|
|
373
|
+
let style = { ...source };
|
|
374
|
+
const opacityNode = nodes.get(OPACITY);
|
|
375
|
+
if (opacityNode) {
|
|
376
|
+
style[OPACITY] = opacityNode;
|
|
377
|
+
}
|
|
378
|
+
const zIndexNode = nodes.get(Z_INDEX);
|
|
379
|
+
if (zIndexNode) {
|
|
380
|
+
style[Z_INDEX] = zIndexNode;
|
|
381
|
+
}
|
|
382
|
+
for (const property of DRIVEABLE_COLOR_PROPERTIES) {
|
|
383
|
+
const colorNode = nodes.get(property);
|
|
384
|
+
if (colorNode) {
|
|
385
|
+
style[property] = colorNode;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// The view layer duck-types these exactly as it does opacity and colours,
|
|
389
|
+
// and turns each into a translate measured from the position Yoga committed
|
|
390
|
+
// (components/animated.tsx, splitAnimated).
|
|
391
|
+
for (const property of INSET_PROPERTIES) {
|
|
392
|
+
const insetNode = nodes.get(property);
|
|
393
|
+
if (insetNode) {
|
|
394
|
+
style[property] = insetNode;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
for (const property of SIZE_PROPERTIES) {
|
|
398
|
+
const sizeNode = nodes.get(property);
|
|
399
|
+
if (sizeNode) {
|
|
400
|
+
style[property] = sizeNode;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (Array.isArray(source.transform)) {
|
|
404
|
+
style.transform = source.transform.map((entry, index) => {
|
|
405
|
+
if (typeof entry !== "object" || entry === null) {
|
|
406
|
+
return entry;
|
|
407
|
+
}
|
|
408
|
+
const part = Object.keys(entry)[0];
|
|
409
|
+
if (part === undefined) {
|
|
410
|
+
return entry;
|
|
411
|
+
}
|
|
412
|
+
const node = nodes.get(transformLeafKey(index, part));
|
|
413
|
+
return node ? { [part]: node } : entry;
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
// The last values seen for everything that is NOT node-backed, so a change
|
|
417
|
+
// can be reported rather than silently dropped.
|
|
418
|
+
let staticSnapshot = source;
|
|
419
|
+
return {
|
|
420
|
+
get style() {
|
|
421
|
+
return style;
|
|
422
|
+
},
|
|
423
|
+
nodes,
|
|
424
|
+
renew() {
|
|
425
|
+
style = { ...style };
|
|
426
|
+
},
|
|
427
|
+
apply(next) {
|
|
428
|
+
const nextLeaves = leavesOf(next);
|
|
429
|
+
if (signatureOf(nextLeaves) !== signature) {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
for (const leaf of nextLeaves) {
|
|
433
|
+
nodes.get(leaf.key)?.__push(leaf.value);
|
|
434
|
+
}
|
|
435
|
+
for (const key of Object.keys(next)) {
|
|
436
|
+
if (key === OPACITY || key === "transform" || nodes.has(key)) {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
if (!Object.is(next[key], staticSnapshot[key])) {
|
|
440
|
+
warnUndriveable(key, next);
|
|
441
|
+
// Kept up to date anyway: the next React render — whenever it
|
|
442
|
+
// happens, for whatever reason — then applies the current value
|
|
443
|
+
// rather than the one from mount.
|
|
444
|
+
style[key] = next[key];
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
staticSnapshot = next;
|
|
448
|
+
return true;
|
|
449
|
+
},
|
|
450
|
+
};
|
|
451
|
+
};
|
|
452
|
+
//# sourceMappingURL=style.js.map
|