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
package/dist/responder/system.js
CHANGED
|
@@ -63,13 +63,68 @@ export const createResponderSystem = (options) => {
|
|
|
63
63
|
propsOf(path[i])?.[bubbleName]?.(event);
|
|
64
64
|
}
|
|
65
65
|
};
|
|
66
|
-
const grant = (host, event
|
|
66
|
+
const grant = (host, event) => {
|
|
67
67
|
responder = host;
|
|
68
68
|
notify(host, "onResponderGrant", event);
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
// The GTK claim is per INTERACTION, not per grant: a transfer moves a JS
|
|
70
|
+
// lock, and there is nothing left to tell GTK once the sequence is
|
|
71
|
+
// already ours. Claiming again on the new holder's own gesture would
|
|
72
|
+
// deny the sequence on the source below it and silently end the drag.
|
|
73
|
+
if (session && !session.claimed) {
|
|
74
|
+
session.claimed = true;
|
|
75
|
+
options.onClaim?.(session.host);
|
|
71
76
|
}
|
|
72
|
-
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* The negotiation path when someone already holds the responder: RN prunes
|
|
80
|
+
* it to the lowest common ancestor of the holder and the event target, and
|
|
81
|
+
* then skips the holder itself so it is never asked whether it wants what
|
|
82
|
+
* it already has. Pruning is what makes the lock transferable only
|
|
83
|
+
* UPWARDS — a descendant of the holder is not on the pruned path and
|
|
84
|
+
* cannot take it, which is RN's documented rule.
|
|
85
|
+
*
|
|
86
|
+
* Returns null when the two paths are disjoint (RNW's `lowestCommonAncestor
|
|
87
|
+
* == null`): nothing may take the responder in that case.
|
|
88
|
+
*/
|
|
89
|
+
const pruneToAncestors = (path, holder) => {
|
|
90
|
+
const holderPath = pathOf(holder);
|
|
91
|
+
let common = -1;
|
|
92
|
+
const shared = Math.min(path.length, holderPath.length);
|
|
93
|
+
for (let i = 0; i < shared && path[i] === holderPath[i]; i += 1) {
|
|
94
|
+
common = i;
|
|
95
|
+
}
|
|
96
|
+
if (common === -1) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
return path.slice(0, path[common] === holder ? common : common + 1);
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* RN's handoff, in react-native-web's order: ask the holder, then
|
|
103
|
+
* terminate it, then grant. (Upstream's ResponderEventPlugin dispatches
|
|
104
|
+
* the grant BEFORE asking, so a rejected candidate still sees
|
|
105
|
+
* `onResponderGrant` — a quirk of the pooled-event implementation rather
|
|
106
|
+
* than of the model. RN's own documentation diagram, which RNW reproduces
|
|
107
|
+
* verbatim, shows TerminationRequest -> GRANT, and that is what we do.)
|
|
108
|
+
*
|
|
109
|
+
* A holder with no `onResponderTerminationRequest` always yields: absent
|
|
110
|
+
* means "sure", in RN and in RNW alike.
|
|
111
|
+
*/
|
|
112
|
+
const attemptTransfer = (winner, event) => {
|
|
113
|
+
const holder = responder;
|
|
114
|
+
if (holder === null) {
|
|
115
|
+
grant(winner, event);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (holder === winner) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (propsOf(holder)?.onResponderTerminationRequest?.(event) === false) {
|
|
122
|
+
notify(winner, "onResponderReject", event);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
notify(holder, "onResponderTerminate", event);
|
|
126
|
+
responder = null;
|
|
127
|
+
grant(winner, event);
|
|
73
128
|
};
|
|
74
129
|
/**
|
|
75
130
|
* Capture root-to-target, then bubble target-to-root — RN's two-phase
|
|
@@ -90,6 +145,23 @@ export const createResponderSystem = (options) => {
|
|
|
90
145
|
}
|
|
91
146
|
return firstToClaim([...path].reverse(), bubble, event);
|
|
92
147
|
};
|
|
148
|
+
/**
|
|
149
|
+
* The whole negotiation for one event: prune the path if the lock is held,
|
|
150
|
+
* ask who wants it, hand it over. Returns nothing — the caller reads
|
|
151
|
+
* `responder` afterwards, because RN dispatches `onResponderStart`/`Move`
|
|
152
|
+
* to whoever holds the lock AFTER the handoff, not before it.
|
|
153
|
+
*/
|
|
154
|
+
const negotiateAndTransfer = (path, event, phase) => {
|
|
155
|
+
const holder = responder;
|
|
156
|
+
const candidates = holder === null ? path : pruneToAncestors(path, holder);
|
|
157
|
+
if (candidates === null || candidates.length === 0) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const winner = negotiate(candidates, event, phase);
|
|
161
|
+
if (winner) {
|
|
162
|
+
attemptTransfer(winner, event);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
93
165
|
const finish = (touch, terminated, touchProps) => {
|
|
94
166
|
const current = session;
|
|
95
167
|
if (!current) {
|
|
@@ -105,7 +177,6 @@ export const createResponderSystem = (options) => {
|
|
|
105
177
|
if (holder) {
|
|
106
178
|
notify(holder, "onResponderEnd", event);
|
|
107
179
|
notify(holder, terminated ? "onResponderTerminate" : "onResponderRelease", event);
|
|
108
|
-
options.onRelease?.(holder);
|
|
109
180
|
}
|
|
110
181
|
};
|
|
111
182
|
return {
|
|
@@ -129,42 +200,45 @@ export const createResponderSystem = (options) => {
|
|
|
129
200
|
if (session !== null) {
|
|
130
201
|
return;
|
|
131
202
|
}
|
|
132
|
-
session = {
|
|
203
|
+
session = {
|
|
204
|
+
host,
|
|
205
|
+
identifier: touch.identifier,
|
|
206
|
+
lastTouch: touch,
|
|
207
|
+
claimed: false,
|
|
208
|
+
};
|
|
133
209
|
recordTouchStart(history, touch);
|
|
134
210
|
refreshActiveTouches(history);
|
|
135
211
|
const path = pathOf(host);
|
|
136
212
|
const event = createEvent(touch, true);
|
|
137
213
|
dispatchTouch(path, "onTouchStartCapture", "onTouchStart", event);
|
|
214
|
+
negotiateAndTransfer(path, event, "start");
|
|
215
|
+
// RN dispatches this to whoever holds the lock once the handoff is
|
|
216
|
+
// over, which is why it is not folded into grant(): a view granted on
|
|
217
|
+
// a MOVE must not be told the interaction just started.
|
|
138
218
|
if (responder !== null) {
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
const winner = negotiate(path, event, "start");
|
|
142
|
-
if (winner) {
|
|
143
|
-
grant(winner, event, true);
|
|
219
|
+
notify(responder, "onResponderStart", event);
|
|
144
220
|
}
|
|
145
221
|
},
|
|
146
222
|
touchMove(host, touch) {
|
|
147
223
|
if (session === null || session.host !== host) {
|
|
148
224
|
return;
|
|
149
225
|
}
|
|
226
|
+
session.lastTouch = touch;
|
|
150
227
|
recordTouchMove(history, touch);
|
|
151
228
|
refreshActiveTouches(history);
|
|
152
229
|
const path = pathOf(host);
|
|
153
230
|
const event = createEvent(touch, true);
|
|
154
231
|
dispatchTouch(path, "onTouchMoveCapture", "onTouchMove", event);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
232
|
+
// Two shapes go through here. Nobody holds the lock yet: the common
|
|
233
|
+
// PanResponder one, where the pan starts once
|
|
234
|
+
// onMoveShouldSetPanResponder sees enough movement. Somebody does: an
|
|
235
|
+
// ancestor may still take it over, and the move is then delivered to
|
|
236
|
+
// whoever ends up holding it — including in the same event as the
|
|
237
|
+
// handoff, exactly as RN's own state diagram shows.
|
|
238
|
+
negotiateAndTransfer(path, event, "move");
|
|
239
|
+
if (responder !== null) {
|
|
240
|
+
notify(responder, "onResponderMove", event);
|
|
163
241
|
}
|
|
164
|
-
// v1 deliberately does not renegotiate while a responder holds: GTK
|
|
165
|
-
// cannot revoke a claim, so a transfer we cannot enforce natively would
|
|
166
|
-
// be a lie at the boundary with native widgets. Tracked as task 002b.
|
|
167
|
-
notify(responder, "onResponderMove", event);
|
|
168
242
|
},
|
|
169
243
|
touchEnd(host, touch) {
|
|
170
244
|
if (session === null || session.host !== host) {
|
|
@@ -178,7 +252,53 @@ export const createResponderSystem = (options) => {
|
|
|
178
252
|
}
|
|
179
253
|
finish(touch, true, ["onTouchCancelCapture", "onTouchCancel"]);
|
|
180
254
|
},
|
|
255
|
+
terminate(reason) {
|
|
256
|
+
const current = session;
|
|
257
|
+
const holder = responder;
|
|
258
|
+
if (current === null || holder === null) {
|
|
259
|
+
// Nothing holds the lock: an ancestor scrolling or a menu opening is
|
|
260
|
+
// then simply an ancestor scrolling or a menu opening. RN terminates
|
|
261
|
+
// a RESPONDER; it has nothing to say when there is not one.
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
const event = createEvent(current.lastTouch, false);
|
|
265
|
+
if (reason === "scroll" &&
|
|
266
|
+
propsOf(holder)?.onResponderTerminationRequest?.(event) === false) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
// No onResponderEnd here, and that is RN's shape rather than an
|
|
270
|
+
// oversight: End pairs with a touch actually ending. A termination
|
|
271
|
+
// that is not a pointer event goes straight to Terminate.
|
|
272
|
+
recordTouchEnd(history, current.lastTouch);
|
|
273
|
+
refreshActiveTouches(history);
|
|
274
|
+
session = null;
|
|
275
|
+
responder = null;
|
|
276
|
+
notify(holder, "onResponderTerminate", event);
|
|
277
|
+
return true;
|
|
278
|
+
},
|
|
279
|
+
requestResponder(host) {
|
|
280
|
+
const current = session;
|
|
281
|
+
if (current === null) {
|
|
282
|
+
// No interaction to take. A timer that fires after the pointer is up
|
|
283
|
+
// is a timer that lost, and there is nothing to negotiate over.
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
if (responder === host) {
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
const path = pathOf(current.host);
|
|
290
|
+
// A node off the interaction's path is not a candidate — the same rule
|
|
291
|
+
// the touch paths enforce by construction, stated here because this
|
|
292
|
+
// entry point is reached from a timer rather than from an event that
|
|
293
|
+
// already travelled the tree.
|
|
294
|
+
if (!path.includes(host)) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
negotiateAndTransfer(path, createEvent(current.lastTouch, true), "move");
|
|
298
|
+
return responder === host;
|
|
299
|
+
},
|
|
181
300
|
getResponder: () => responder,
|
|
301
|
+
getSource: () => session?.host ?? null,
|
|
182
302
|
};
|
|
183
303
|
};
|
|
184
304
|
//# sourceMappingURL=system.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system.js","sourceRoot":"","sources":["../../src/responder/system.ts"],"names":[],"mappings":"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","sourcesContent":["// RN's Gesture Responder System: the negotiation, in pure JS.\n//\n// Zero @gtkx imports on purpose. Everything platform-specific enters through\n// two seams — `parentOf`, which turns a host handle into its parent, and the\n// grant/release notifications the platform uses to make its one-way GTK\n// claim. That keeps the algorithm unit-testable off Linux, which matters\n// because this is the part that is subtle and the part every platform\n// historically gets wrong.\n//\n// Why reimplemented rather than reused: every other RN platform feeds\n// `topTouchStart/Move/End/Cancel` to the stock renderer's\n// ResponderEventPlugin, which lives in `facebook/react` inside\n// `react-native-renderer`. We render through `@gtkx/react` on\n// `react-reconciler`, so there is no plugin seam to feed —\n// react-native-web is in the same position and reimplemented for the same\n// reason (docs/research/gestures.md).\nimport type { NativeTouch } from \"../components/press-event\"\nimport {\n createTouchHistory,\n recordTouchEnd,\n recordTouchMove,\n recordTouchStart,\n refreshActiveTouches,\n} from \"./touch-history\"\nimport type { GestureResponderEvent, ResponderProps } from \"./types\"\n\n/** An opaque platform handle. A Gtk.Widget in practice; never treated as one. */\nexport type ResponderHost = object\n\nexport type ResponderSystemOptions = {\n /** The platform's parent link. Returning null ends the path. */\n parentOf: (host: ResponderHost) => ResponderHost | null\n /**\n * Fired after a node is granted the responder — where the platform makes\n * its GTK `CLAIMED` declaration. One-way by construction: GTK has no way\n * to hand a claimed sequence back, so this is only ever called once JS has\n * already decided (docs/research/gestures.md, \"no voluntary release\").\n */\n onGrant?: (host: ResponderHost) => void\n /** Fired when the responder is released or terminated. */\n onRelease?: (host: ResponderHost) => void\n}\n\ntype Registration = { getProps: () => ResponderProps }\n\n// One interaction, from press to release. v1 is single-pointer: a mouse is\n// one fabricated touch, so at most one session exists at a time. It also\n// does the deduplicating — GTK delivers a press to every ancestor's gesture\n// in the bubble chain, so the second and later reports for the same press\n// find a session already open and return.\ntype Session = { host: ResponderHost; identifier: number }\n\nexport type ResponderSystem = {\n register(host: ResponderHost, getProps: () => ResponderProps): () => void\n touchStart(host: ResponderHost, touch: NativeTouch): void\n touchMove(host: ResponderHost, touch: NativeTouch): void\n touchEnd(host: ResponderHost, touch: NativeTouch): void\n touchCancel(host: ResponderHost, touch: NativeTouch): void\n /** The node currently holding the responder, or null. */\n getResponder(): ResponderHost | null\n}\n\ntype ShouldSetName =\n | \"onStartShouldSetResponder\"\n | \"onStartShouldSetResponderCapture\"\n | \"onMoveShouldSetResponder\"\n | \"onMoveShouldSetResponderCapture\"\n\ntype NotifyName =\n | \"onResponderGrant\"\n | \"onResponderStart\"\n | \"onResponderMove\"\n | \"onResponderEnd\"\n | \"onResponderRelease\"\n | \"onResponderTerminate\"\n | \"onResponderReject\"\n\ntype TouchName =\n | \"onTouchStart\"\n | \"onTouchStartCapture\"\n | \"onTouchMove\"\n | \"onTouchMoveCapture\"\n | \"onTouchEnd\"\n | \"onTouchEndCapture\"\n | \"onTouchCancel\"\n | \"onTouchCancelCapture\"\n\nexport const createResponderSystem = (\n options: ResponderSystemOptions,\n): ResponderSystem => {\n const registry = new Map<ResponderHost, Registration>()\n const history = createTouchHistory()\n let responder: ResponderHost | null = null\n let session: Session | null = null\n\n /**\n * The registered nodes from the toplevel down to `host`, root first — RN's\n * capture order, reversed for bubble.\n *\n * The walk stops wherever `parentOf` stops, which on this platform is the\n * layout Root: an RN tree here is an island inside a native GTK widget\n * tree (NestedRoot/IntrinsicRoot mount engines into arbitrary GTK slots),\n * so there is no single app-wide view hierarchy to traverse. Native\n * widgets on the way up are simply not registered and take no part.\n */\n const pathOf = (host: ResponderHost): ResponderHost[] => {\n const path: ResponderHost[] = []\n let current: ResponderHost | null = host\n while (current) {\n if (registry.has(current)) {\n path.unshift(current)\n }\n current = options.parentOf(current)\n }\n return path\n }\n\n const propsOf = (host: ResponderHost): ResponderProps | null =>\n registry.get(host)?.getProps() ?? null\n\n const createEvent = (\n touch: NativeTouch,\n stillDown: boolean,\n ): GestureResponderEvent => ({\n nativeEvent: {\n ...touch,\n // Single-pointer: `touches` is the still-active set (empty once the\n // pointer is up), `changedTouches` is always this one.\n touches: stillDown ? [touch] : [],\n changedTouches: [touch],\n },\n touchHistory: history,\n })\n\n /**\n * Runs a should-set handler over `path` in order, stopping at the first\n * that returns true — RN's executeDispatchesInOrderStopAtTrue. Handlers\n * before the winner all run, which PanResponder depends on: its capture\n * handlers are where gestureState is initialised and where dx/dy are\n * accumulated on move.\n */\n const firstToClaim = (\n path: readonly ResponderHost[],\n name: ShouldSetName,\n event: GestureResponderEvent,\n ): ResponderHost | null => {\n for (const host of path) {\n const handler = propsOf(host)?.[name]\n if (handler?.(event) === true) {\n return host\n }\n }\n return null\n }\n\n const notify = (\n host: ResponderHost,\n name: NotifyName,\n event: GestureResponderEvent,\n ): void => {\n propsOf(host)?.[name]?.(event)\n }\n\n const dispatchTouch = (\n path: readonly ResponderHost[],\n captureName: TouchName,\n bubbleName: TouchName,\n event: GestureResponderEvent,\n ): void => {\n for (const host of path) {\n propsOf(host)?.[captureName]?.(event)\n }\n for (let i = path.length - 1; i >= 0; i -= 1) {\n propsOf(path[i]!)?.[bubbleName]?.(event)\n }\n }\n\n const grant = (\n host: ResponderHost,\n event: GestureResponderEvent,\n fromStart: boolean,\n ): void => {\n responder = host\n notify(host, \"onResponderGrant\", event)\n if (fromStart) {\n notify(host, \"onResponderStart\", event)\n }\n options.onGrant?.(host)\n }\n\n /**\n * Capture root-to-target, then bubble target-to-root — RN's two-phase\n * negotiation, and the one place GTK's model and RN's agree, since GTK's\n * CAPTURE/TARGET/BUBBLE phases run in exactly this order. We still do the\n * walk ourselves so a single GTK event source can drive the whole path.\n */\n const negotiate = (\n path: readonly ResponderHost[],\n event: GestureResponderEvent,\n phase: \"start\" | \"move\",\n ): ResponderHost | null => {\n const capture =\n phase === \"start\"\n ? \"onStartShouldSetResponderCapture\"\n : \"onMoveShouldSetResponderCapture\"\n const bubble =\n phase === \"start\"\n ? \"onStartShouldSetResponder\"\n : \"onMoveShouldSetResponder\"\n const captured = firstToClaim(path, capture, event)\n if (captured) {\n return captured\n }\n return firstToClaim([...path].reverse(), bubble, event)\n }\n\n const finish = (\n touch: NativeTouch,\n terminated: boolean,\n touchProps: [TouchName, TouchName],\n ): void => {\n const current = session\n if (!current) {\n return\n }\n recordTouchEnd(history, touch)\n refreshActiveTouches(history)\n const event = createEvent(touch, false)\n dispatchTouch(pathOf(current.host), touchProps[0], touchProps[1], event)\n\n const holder = responder\n session = null\n responder = null\n if (holder) {\n notify(holder, \"onResponderEnd\", event)\n notify(\n holder,\n terminated ? \"onResponderTerminate\" : \"onResponderRelease\",\n event,\n )\n options.onRelease?.(holder)\n }\n }\n\n return {\n register(host, getProps) {\n registry.set(host, { getProps })\n return () => {\n registry.delete(host)\n // A responder that unmounts mid-gesture cannot be asked anything\n // ever again; drop the lock rather than stranding it.\n if (responder === host) {\n responder = null\n }\n if (session?.host === host) {\n session = null\n }\n }\n },\n\n touchStart(host, touch) {\n // Already tracking this press: this is an ancestor's gesture seeing the\n // same event through GTK's bubble chain.\n if (session !== null) {\n return\n }\n session = { host, identifier: touch.identifier }\n recordTouchStart(history, touch)\n refreshActiveTouches(history)\n\n const path = pathOf(host)\n const event = createEvent(touch, true)\n dispatchTouch(path, \"onTouchStartCapture\", \"onTouchStart\", event)\n\n if (responder !== null) {\n return\n }\n const winner = negotiate(path, event, \"start\")\n if (winner) {\n grant(winner, event, true)\n }\n },\n\n touchMove(host, touch) {\n if (session === null || session.host !== host) {\n return\n }\n recordTouchMove(history, touch)\n refreshActiveTouches(history)\n\n const path = pathOf(host)\n const event = createEvent(touch, true)\n dispatchTouch(path, \"onTouchMoveCapture\", \"onTouchMove\", event)\n\n if (responder === null) {\n // The common PanResponder shape: nothing claims on press, and the\n // pan starts once onMoveShouldSetPanResponder sees enough movement.\n const winner = negotiate(path, event, \"move\")\n if (winner) {\n grant(winner, event, false)\n }\n return\n }\n // v1 deliberately does not renegotiate while a responder holds: GTK\n // cannot revoke a claim, so a transfer we cannot enforce natively would\n // be a lie at the boundary with native widgets. Tracked as task 002b.\n notify(responder, \"onResponderMove\", event)\n },\n\n touchEnd(host, touch) {\n if (session === null || session.host !== host) {\n return\n }\n finish(touch, false, [\"onTouchEndCapture\", \"onTouchEnd\"])\n },\n\n touchCancel(host, touch) {\n if (session === null || session.host !== host) {\n return\n }\n finish(touch, true, [\"onTouchCancelCapture\", \"onTouchCancel\"])\n },\n\n getResponder: () => responder,\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"system.js","sourceRoot":"","sources":["../../src/responder/system.ts"],"names":[],"mappings":"AAiBA,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,iBAAiB,CAAA;AAmJxB,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACnC,OAA+B,EACd,EAAE;IACnB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA+B,CAAA;IACvD,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAA;IACpC,IAAI,SAAS,GAAyB,IAAI,CAAA;IAC1C,IAAI,OAAO,GAAmB,IAAI,CAAA;IAElC;;;;;;;;;OASG;IACH,MAAM,MAAM,GAAG,CAAC,IAAmB,EAAmB,EAAE;QACtD,MAAM,IAAI,GAAoB,EAAE,CAAA;QAChC,IAAI,OAAO,GAAyB,IAAI,CAAA;QACxC,OAAO,OAAO,EAAE,CAAC;YACf,IAAI,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC1B,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;YACvB,CAAC;YACD,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;QACrC,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC,CAAA;IAED,MAAM,OAAO,GAAG,CAAC,IAAmB,EAAyB,EAAE,CAC7D,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,IAAI,CAAA;IAExC,MAAM,WAAW,GAAG,CAClB,KAAkB,EAClB,SAAkB,EACK,EAAE,CAAC,CAAC;QAC3B,WAAW,EAAE;YACX,GAAG,KAAK;YACR,oEAAoE;YACpE,uDAAuD;YACvD,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE;YACjC,cAAc,EAAE,CAAC,KAAK,CAAC;SACxB;QACD,YAAY,EAAE,OAAO;KACtB,CAAC,CAAA;IAEF;;;;;;OAMG;IACH,MAAM,YAAY,GAAG,CACnB,IAA8B,EAC9B,IAAmB,EACnB,KAA4B,EACN,EAAE;QACxB,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;YACxB,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAA;YACrC,IAAI,OAAO,EAAE,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC;gBAC9B,OAAO,IAAI,CAAA;YACb,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC,CAAA;IAED,MAAM,MAAM,GAAG,CACb,IAAmB,EACnB,IAAgB,EAChB,KAA4B,EACtB,EAAE;QACR,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,CAAA;IAChC,CAAC,CAAA;IAED,MAAM,aAAa,GAAG,CACpB,IAA8B,EAC9B,WAAsB,EACtB,UAAqB,EACrB,KAA4B,EACtB,EAAE;QACR,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;YACxB,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,CAAC,CAAA;QACvC,CAAC;QACD,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,KAAK,CAAC,CAAA;QAC1C,CAAC;IACH,CAAC,CAAA;IAED,MAAM,KAAK,GAAG,CAAC,IAAmB,EAAE,KAA4B,EAAQ,EAAE;QACxE,SAAS,GAAG,IAAI,CAAA;QAChB,MAAM,CAAC,IAAI,EAAE,kBAAkB,EAAE,KAAK,CAAC,CAAA;QACvC,yEAAyE;QACzE,mEAAmE;QACnE,qEAAqE;QACrE,sEAAsE;QACtE,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YAChC,OAAO,CAAC,OAAO,GAAG,IAAI,CAAA;YACtB,OAAO,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACjC,CAAC;IACH,CAAC,CAAA;IAED;;;;;;;;;;OAUG;IACH,MAAM,gBAAgB,GAAG,CACvB,IAA8B,EAC9B,MAAqB,EACG,EAAE;QAC1B,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAA;QACjC,IAAI,MAAM,GAAG,CAAC,CAAC,CAAA;QACf,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,CAAA;QACvD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAChE,MAAM,GAAG,CAAC,CAAA;QACZ,CAAC;QACD,IAAI,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;YAClB,OAAO,IAAI,CAAA;QACb,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IACrE,CAAC,CAAA;IAED;;;;;;;;;;OAUG;IACH,MAAM,eAAe,GAAG,CACtB,MAAqB,EACrB,KAA4B,EACtB,EAAE;QACR,MAAM,MAAM,GAAG,SAAS,CAAA;QACxB,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;YACpB,OAAM;QACR,CAAC;QACD,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YACtB,OAAM;QACR,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,6BAA6B,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,EAAE,CAAC;YACtE,MAAM,CAAC,MAAM,EAAE,mBAAmB,EAAE,KAAK,CAAC,CAAA;YAC1C,OAAM;QACR,CAAC;QACD,MAAM,CAAC,MAAM,EAAE,sBAAsB,EAAE,KAAK,CAAC,CAAA;QAC7C,SAAS,GAAG,IAAI,CAAA;QAChB,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IACtB,CAAC,CAAA;IAED;;;;;OAKG;IACH,MAAM,SAAS,GAAG,CAChB,IAA8B,EAC9B,KAA4B,EAC5B,KAAuB,EACD,EAAE;QACxB,MAAM,OAAO,GACX,KAAK,KAAK,OAAO;YACf,CAAC,CAAC,kCAAkC;YACpC,CAAC,CAAC,iCAAiC,CAAA;QACvC,MAAM,MAAM,GACV,KAAK,KAAK,OAAO;YACf,CAAC,CAAC,2BAA2B;YAC7B,CAAC,CAAC,0BAA0B,CAAA;QAChC,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;QACnD,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,QAAQ,CAAA;QACjB,CAAC;QACD,OAAO,YAAY,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,CAAA;IACzD,CAAC,CAAA;IAED;;;;;OAKG;IACH,MAAM,oBAAoB,GAAG,CAC3B,IAA8B,EAC9B,KAA4B,EAC5B,KAAuB,EACjB,EAAE;QACR,MAAM,MAAM,GAAG,SAAS,CAAA;QACxB,MAAM,UAAU,GAAG,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QAC1E,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACnD,OAAM;QACR,CAAC;QACD,MAAM,MAAM,GAAG,SAAS,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,CAAC,CAAA;QAClD,IAAI,MAAM,EAAE,CAAC;YACX,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;QAChC,CAAC;IACH,CAAC,CAAA;IAED,MAAM,MAAM,GAAG,CACb,KAAkB,EAClB,UAAmB,EACnB,UAAkC,EAC5B,EAAE;QACR,MAAM,OAAO,GAAG,OAAO,CAAA;QACvB,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAM;QACR,CAAC;QACD,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;QAC9B,oBAAoB,CAAC,OAAO,CAAC,CAAA;QAC7B,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;QACvC,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;QAExE,MAAM,MAAM,GAAG,SAAS,CAAA;QACxB,OAAO,GAAG,IAAI,CAAA;QACd,SAAS,GAAG,IAAI,CAAA;QAChB,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,CAAC,MAAM,EAAE,gBAAgB,EAAE,KAAK,CAAC,CAAA;YACvC,MAAM,CACJ,MAAM,EACN,UAAU,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,oBAAoB,EAC1D,KAAK,CACN,CAAA;QACH,CAAC;IACH,CAAC,CAAA;IAED,OAAO;QACL,QAAQ,CAAC,IAAI,EAAE,QAAQ;YACrB,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAA;YAChC,OAAO,GAAG,EAAE;gBACV,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;gBACrB,iEAAiE;gBACjE,sDAAsD;gBACtD,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;oBACvB,SAAS,GAAG,IAAI,CAAA;gBAClB,CAAC;gBACD,IAAI,OAAO,EAAE,IAAI,KAAK,IAAI,EAAE,CAAC;oBAC3B,OAAO,GAAG,IAAI,CAAA;gBAChB,CAAC;YACH,CAAC,CAAA;QACH,CAAC;QAED,UAAU,CAAC,IAAI,EAAE,KAAK;YACpB,wEAAwE;YACxE,yCAAyC;YACzC,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACrB,OAAM;YACR,CAAC;YACD,OAAO,GAAG;gBACR,IAAI;gBACJ,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,SAAS,EAAE,KAAK;gBAChB,OAAO,EAAE,KAAK;aACf,CAAA;YACD,gBAAgB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;YAChC,oBAAoB,CAAC,OAAO,CAAC,CAAA;YAE7B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;YACzB,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;YACtC,aAAa,CAAC,IAAI,EAAE,qBAAqB,EAAE,cAAc,EAAE,KAAK,CAAC,CAAA;YAEjE,oBAAoB,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAA;YAC1C,mEAAmE;YACnE,sEAAsE;YACtE,wDAAwD;YACxD,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;gBACvB,MAAM,CAAC,SAAS,EAAE,kBAAkB,EAAE,KAAK,CAAC,CAAA;YAC9C,CAAC;QACH,CAAC;QAED,SAAS,CAAC,IAAI,EAAE,KAAK;YACnB,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;gBAC9C,OAAM;YACR,CAAC;YACD,OAAO,CAAC,SAAS,GAAG,KAAK,CAAA;YACzB,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;YAC/B,oBAAoB,CAAC,OAAO,CAAC,CAAA;YAE7B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;YACzB,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;YACtC,aAAa,CAAC,IAAI,EAAE,oBAAoB,EAAE,aAAa,EAAE,KAAK,CAAC,CAAA;YAE/D,oEAAoE;YACpE,8CAA8C;YAC9C,sEAAsE;YACtE,qEAAqE;YACrE,kEAAkE;YAClE,oDAAoD;YACpD,oBAAoB,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;YACzC,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;gBACvB,MAAM,CAAC,SAAS,EAAE,iBAAiB,EAAE,KAAK,CAAC,CAAA;YAC7C,CAAC;QACH,CAAC;QAED,QAAQ,CAAC,IAAI,EAAE,KAAK;YAClB,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;gBAC9C,OAAM;YACR,CAAC;YACD,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,mBAAmB,EAAE,YAAY,CAAC,CAAC,CAAA;QAC3D,CAAC;QAED,WAAW,CAAC,IAAI,EAAE,KAAK;YACrB,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;gBAC9C,OAAM;YACR,CAAC;YACD,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,sBAAsB,EAAE,eAAe,CAAC,CAAC,CAAA;QAChE,CAAC;QAED,SAAS,CAAC,MAAM;YACd,MAAM,OAAO,GAAG,OAAO,CAAA;YACvB,MAAM,MAAM,GAAG,SAAS,CAAA;YACxB,IAAI,OAAO,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;gBACxC,qEAAqE;gBACrE,qEAAqE;gBACrE,4DAA4D;gBAC5D,OAAO,KAAK,CAAA;YACd,CAAC;YACD,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;YACnD,IACE,MAAM,KAAK,QAAQ;gBACnB,OAAO,CAAC,MAAM,CAAC,EAAE,6BAA6B,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,EACjE,CAAC;gBACD,OAAO,KAAK,CAAA;YACd,CAAC;YAED,gEAAgE;YAChE,mEAAmE;YACnE,0DAA0D;YAC1D,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,CAAA;YAC1C,oBAAoB,CAAC,OAAO,CAAC,CAAA;YAC7B,OAAO,GAAG,IAAI,CAAA;YACd,SAAS,GAAG,IAAI,CAAA;YAChB,MAAM,CAAC,MAAM,EAAE,sBAAsB,EAAE,KAAK,CAAC,CAAA;YAC7C,OAAO,IAAI,CAAA;QACb,CAAC;QAED,gBAAgB,CAAC,IAAI;YACnB,MAAM,OAAO,GAAG,OAAO,CAAA;YACvB,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACrB,qEAAqE;gBACrE,gEAAgE;gBAChE,OAAO,KAAK,CAAA;YACd,CAAC;YACD,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;gBACvB,OAAO,IAAI,CAAA;YACb,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;YACjC,uEAAuE;YACvE,oEAAoE;YACpE,qEAAqE;YACrE,8BAA8B;YAC9B,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzB,OAAO,KAAK,CAAA;YACd,CAAC;YACD,oBAAoB,CAAC,IAAI,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAA;YACxE,OAAO,SAAS,KAAK,IAAI,CAAA;QAC3B,CAAC;QAED,YAAY,EAAE,GAAG,EAAE,CAAC,SAAS;QAC7B,SAAS,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,IAAI,IAAI,IAAI;KACvC,CAAA;AACH,CAAC,CAAA","sourcesContent":["// RN's Gesture Responder System: the negotiation, in pure JS.\n//\n// Zero @gtkx imports on purpose. Everything platform-specific enters through\n// two seams — `parentOf`, which turns a host handle into its parent, and\n// `onClaim`, the one-way GTK declaration the platform makes once per\n// interaction. That keeps the algorithm unit-testable off Linux, which\n// matters because this is the part that is subtle and the part every\n// platform historically gets wrong.\n//\n// Why reimplemented rather than reused: every other RN platform feeds\n// `topTouchStart/Move/End/Cancel` to the stock renderer's\n// ResponderEventPlugin, which lives in `facebook/react` inside\n// `react-native-renderer`. We render through `@gtkx/react` on\n// `react-reconciler`, so there is no plugin seam to feed —\n// react-native-web is in the same position and reimplemented for the same\n// reason (docs/research/gestures.md).\nimport type { NativeTouch } from \"../components/press-event\"\nimport {\n createTouchHistory,\n recordTouchEnd,\n recordTouchMove,\n recordTouchStart,\n refreshActiveTouches,\n} from \"./touch-history\"\nimport type { GestureResponderEvent, ResponderProps } from \"./types\"\n\n/** An opaque platform handle. A Gtk.Widget in practice; never treated as one. */\nexport type ResponderHost = object\n\n/**\n * Why the responder was taken away by something that is not a pointer event.\n *\n * react-native-web's list is longer — cancel-ish events, context menu,\n * window blur, ancestor scroll, selection change — and measuring GTK\n * collapsed most of it. GTK cancels a single-button gesture the instant a\n * second button goes down, and a widget that steals a sequence (a\n * selectable label, a `GtkDragSource` attached through `Controllers`)\n * cancels it too, so \"context menu\" and \"selection change\" both arrive as\n * an ordinary `::cancel` on the event source and need no trigger of their\n * own. What is left needs asking for:\n *\n * - `\"blur\"`: the toplevel stopped being the active window. Not a question,\n * in RN or here — the window is gone either way.\n * - `\"scroll\"`: an ancestor scroller moved under the gesture. The one\n * termination a holder may refuse, and therefore the only place\n * `onResponderTerminationRequest` is consulted outside a transfer (PRD\n * crux 2: GTK's `CLAIMED` is irrevocable, so anything GTK has already\n * taken cannot be given back no matter what the holder answers).\n */\nexport type TerminationReason = \"blur\" | \"scroll\"\n\nexport type ResponderSystemOptions = {\n /** The platform's parent link. Returning null ends the path. */\n parentOf: (host: ResponderHost) => ResponderHost | null\n /**\n * Fired at most ONCE per interaction, the first time any node is granted\n * the responder — where the platform makes its GTK `CLAIMED` declaration.\n *\n * Its argument is the node whose event source is carrying the interaction,\n * NOT the node that won the responder. Those differ whenever an ancestor\n * claims (the common `onMoveShouldSetPanResponder` shape), and claiming on\n * the ancestor is actively wrong: GTK denies the sequence on every gesture\n * below the one that claims, which kills the very source the interaction\n * is arriving through. What GTK is being told is \"this interaction belongs\n * to React Native, native widgets keep off\" — a statement about the\n * island, not about which view inside it currently holds the lock, which\n * is a JS matter GTK has no way to represent.\n *\n * One-way by construction: GTK has no way to hand a claimed sequence back,\n * so this only ever runs once JS has already decided (see\n * docs/research/gestures.md, \"no voluntary release\").\n */\n onClaim?: (source: ResponderHost) => void\n}\n\ntype Registration = { getProps: () => ResponderProps }\n\n// One interaction, from press to release. v1 is single-pointer: a mouse is\n// one fabricated touch, so at most one session exists at a time. It also\n// does the deduplicating — GTK delivers a press to every ancestor's gesture\n// in the bubble chain, so the second and later reports for the same press\n// find a session already open and return.\n//\n// `lastTouch` exists for termination: a window losing focus is not a pointer\n// event, and `onResponderTerminate` still has to be handed an RN-shaped\n// event. The last known position is what RN reports there too.\ntype Session = {\n host: ResponderHost\n identifier: number\n lastTouch: NativeTouch\n claimed: boolean\n}\n\nexport type ResponderSystem = {\n register(host: ResponderHost, getProps: () => ResponderProps): () => void\n touchStart(host: ResponderHost, touch: NativeTouch): void\n touchMove(host: ResponderHost, touch: NativeTouch): void\n touchEnd(host: ResponderHost, touch: NativeTouch): void\n touchCancel(host: ResponderHost, touch: NativeTouch): void\n /**\n * Something that is not a pointer event took the interaction. Returns\n * whether the responder was actually given up — `false` means either\n * nothing held it or the holder refused a reason that allows refusal.\n */\n terminate(reason: TerminationReason): boolean\n /**\n * THE OUT-OF-EVENT GRANT CHANNEL. A registered node asks for the responder\n * between touch events, and the ordinary negotiation runs against a\n * synthesized event at the last known pointer position.\n *\n * RN negotiates inside touch events only, so a recognizer that changes its\n * mind on a TIMER has no way to take the interaction until the pointer next\n * moves. `activateAfterLongPress` is exactly that recognizer: the press\n * matures while the pointer is still, and waiting for the next move puts\n * the \"lifted\" visual one frame late — or, for a press-and-hold that never\n * moves again, never.\n *\n * The precedent is react-native-web, which added\n * `onScrollShouldSetResponder` and `onSelectionChangeShouldSetResponder`\n * for its own platform's realities; extending the model where the platform\n * demands it is recorded as acceptable in docs/research/gestures.md, and\n * this channel is documented there with its reason.\n *\n * Deliberately NOT a new prop and not a new grant path: it reuses\n * `negotiateAndTransfer` unchanged, so capture still beats bubble, the LCA\n * pruning still applies, and an ancestor that wants the interaction still\n * wins it. The only thing that is new is WHEN the question gets asked. The\n * synthesized event is the same one `terminate()` already builds from\n * `session.lastTouch`, for the same reason.\n *\n * No `onResponderMove` is dispatched afterwards: the pointer did not move,\n * and inventing a move to announce a grant would put a fabricated\n * coordinate into every consumer's translation arithmetic.\n *\n * Returns whether `host` holds the responder once the negotiation is over\n * — `false` when there is no interaction, when `host` is not on the path\n * the interaction is travelling, or when somebody else won.\n */\n requestResponder(host: ResponderHost): boolean\n /** The node currently holding the responder, or null. */\n getResponder(): ResponderHost | null\n /** The node whose event source is carrying the interaction, or null. */\n getSource(): ResponderHost | null\n}\n\ntype ShouldSetName =\n | \"onStartShouldSetResponder\"\n | \"onStartShouldSetResponderCapture\"\n | \"onMoveShouldSetResponder\"\n | \"onMoveShouldSetResponderCapture\"\n\ntype NotifyName =\n | \"onResponderGrant\"\n | \"onResponderStart\"\n | \"onResponderMove\"\n | \"onResponderEnd\"\n | \"onResponderRelease\"\n | \"onResponderTerminate\"\n | \"onResponderReject\"\n\ntype TouchName =\n | \"onTouchStart\"\n | \"onTouchStartCapture\"\n | \"onTouchMove\"\n | \"onTouchMoveCapture\"\n | \"onTouchEnd\"\n | \"onTouchEndCapture\"\n | \"onTouchCancel\"\n | \"onTouchCancelCapture\"\n\nexport const createResponderSystem = (\n options: ResponderSystemOptions,\n): ResponderSystem => {\n const registry = new Map<ResponderHost, Registration>()\n const history = createTouchHistory()\n let responder: ResponderHost | null = null\n let session: Session | null = null\n\n /**\n * The registered nodes from the toplevel down to `host`, root first — RN's\n * capture order, reversed for bubble.\n *\n * The walk stops wherever `parentOf` stops, which on this platform is the\n * layout Root: an RN tree here is an island inside a native GTK widget\n * tree (NestedRoot/IntrinsicRoot mount engines into arbitrary GTK slots),\n * so there is no single app-wide view hierarchy to traverse. Native\n * widgets on the way up are simply not registered and take no part.\n */\n const pathOf = (host: ResponderHost): ResponderHost[] => {\n const path: ResponderHost[] = []\n let current: ResponderHost | null = host\n while (current) {\n if (registry.has(current)) {\n path.unshift(current)\n }\n current = options.parentOf(current)\n }\n return path\n }\n\n const propsOf = (host: ResponderHost): ResponderProps | null =>\n registry.get(host)?.getProps() ?? null\n\n const createEvent = (\n touch: NativeTouch,\n stillDown: boolean,\n ): GestureResponderEvent => ({\n nativeEvent: {\n ...touch,\n // Single-pointer: `touches` is the still-active set (empty once the\n // pointer is up), `changedTouches` is always this one.\n touches: stillDown ? [touch] : [],\n changedTouches: [touch],\n },\n touchHistory: history,\n })\n\n /**\n * Runs a should-set handler over `path` in order, stopping at the first\n * that returns true — RN's executeDispatchesInOrderStopAtTrue. Handlers\n * before the winner all run, which PanResponder depends on: its capture\n * handlers are where gestureState is initialised and where dx/dy are\n * accumulated on move.\n */\n const firstToClaim = (\n path: readonly ResponderHost[],\n name: ShouldSetName,\n event: GestureResponderEvent,\n ): ResponderHost | null => {\n for (const host of path) {\n const handler = propsOf(host)?.[name]\n if (handler?.(event) === true) {\n return host\n }\n }\n return null\n }\n\n const notify = (\n host: ResponderHost,\n name: NotifyName,\n event: GestureResponderEvent,\n ): void => {\n propsOf(host)?.[name]?.(event)\n }\n\n const dispatchTouch = (\n path: readonly ResponderHost[],\n captureName: TouchName,\n bubbleName: TouchName,\n event: GestureResponderEvent,\n ): void => {\n for (const host of path) {\n propsOf(host)?.[captureName]?.(event)\n }\n for (let i = path.length - 1; i >= 0; i -= 1) {\n propsOf(path[i]!)?.[bubbleName]?.(event)\n }\n }\n\n const grant = (host: ResponderHost, event: GestureResponderEvent): void => {\n responder = host\n notify(host, \"onResponderGrant\", event)\n // The GTK claim is per INTERACTION, not per grant: a transfer moves a JS\n // lock, and there is nothing left to tell GTK once the sequence is\n // already ours. Claiming again on the new holder's own gesture would\n // deny the sequence on the source below it and silently end the drag.\n if (session && !session.claimed) {\n session.claimed = true\n options.onClaim?.(session.host)\n }\n }\n\n /**\n * The negotiation path when someone already holds the responder: RN prunes\n * it to the lowest common ancestor of the holder and the event target, and\n * then skips the holder itself so it is never asked whether it wants what\n * it already has. Pruning is what makes the lock transferable only\n * UPWARDS — a descendant of the holder is not on the pruned path and\n * cannot take it, which is RN's documented rule.\n *\n * Returns null when the two paths are disjoint (RNW's `lowestCommonAncestor\n * == null`): nothing may take the responder in that case.\n */\n const pruneToAncestors = (\n path: readonly ResponderHost[],\n holder: ResponderHost,\n ): ResponderHost[] | null => {\n const holderPath = pathOf(holder)\n let common = -1\n const shared = Math.min(path.length, holderPath.length)\n for (let i = 0; i < shared && path[i] === holderPath[i]; i += 1) {\n common = i\n }\n if (common === -1) {\n return null\n }\n return path.slice(0, path[common] === holder ? common : common + 1)\n }\n\n /**\n * RN's handoff, in react-native-web's order: ask the holder, then\n * terminate it, then grant. (Upstream's ResponderEventPlugin dispatches\n * the grant BEFORE asking, so a rejected candidate still sees\n * `onResponderGrant` — a quirk of the pooled-event implementation rather\n * than of the model. RN's own documentation diagram, which RNW reproduces\n * verbatim, shows TerminationRequest -> GRANT, and that is what we do.)\n *\n * A holder with no `onResponderTerminationRequest` always yields: absent\n * means \"sure\", in RN and in RNW alike.\n */\n const attemptTransfer = (\n winner: ResponderHost,\n event: GestureResponderEvent,\n ): void => {\n const holder = responder\n if (holder === null) {\n grant(winner, event)\n return\n }\n if (holder === winner) {\n return\n }\n if (propsOf(holder)?.onResponderTerminationRequest?.(event) === false) {\n notify(winner, \"onResponderReject\", event)\n return\n }\n notify(holder, \"onResponderTerminate\", event)\n responder = null\n grant(winner, event)\n }\n\n /**\n * Capture root-to-target, then bubble target-to-root — RN's two-phase\n * negotiation, and the one place GTK's model and RN's agree, since GTK's\n * CAPTURE/TARGET/BUBBLE phases run in exactly this order. We still do the\n * walk ourselves so a single GTK event source can drive the whole path.\n */\n const negotiate = (\n path: readonly ResponderHost[],\n event: GestureResponderEvent,\n phase: \"start\" | \"move\",\n ): ResponderHost | null => {\n const capture =\n phase === \"start\"\n ? \"onStartShouldSetResponderCapture\"\n : \"onMoveShouldSetResponderCapture\"\n const bubble =\n phase === \"start\"\n ? \"onStartShouldSetResponder\"\n : \"onMoveShouldSetResponder\"\n const captured = firstToClaim(path, capture, event)\n if (captured) {\n return captured\n }\n return firstToClaim([...path].reverse(), bubble, event)\n }\n\n /**\n * The whole negotiation for one event: prune the path if the lock is held,\n * ask who wants it, hand it over. Returns nothing — the caller reads\n * `responder` afterwards, because RN dispatches `onResponderStart`/`Move`\n * to whoever holds the lock AFTER the handoff, not before it.\n */\n const negotiateAndTransfer = (\n path: readonly ResponderHost[],\n event: GestureResponderEvent,\n phase: \"start\" | \"move\",\n ): void => {\n const holder = responder\n const candidates = holder === null ? path : pruneToAncestors(path, holder)\n if (candidates === null || candidates.length === 0) {\n return\n }\n const winner = negotiate(candidates, event, phase)\n if (winner) {\n attemptTransfer(winner, event)\n }\n }\n\n const finish = (\n touch: NativeTouch,\n terminated: boolean,\n touchProps: [TouchName, TouchName],\n ): void => {\n const current = session\n if (!current) {\n return\n }\n recordTouchEnd(history, touch)\n refreshActiveTouches(history)\n const event = createEvent(touch, false)\n dispatchTouch(pathOf(current.host), touchProps[0], touchProps[1], event)\n\n const holder = responder\n session = null\n responder = null\n if (holder) {\n notify(holder, \"onResponderEnd\", event)\n notify(\n holder,\n terminated ? \"onResponderTerminate\" : \"onResponderRelease\",\n event,\n )\n }\n }\n\n return {\n register(host, getProps) {\n registry.set(host, { getProps })\n return () => {\n registry.delete(host)\n // A responder that unmounts mid-gesture cannot be asked anything\n // ever again; drop the lock rather than stranding it.\n if (responder === host) {\n responder = null\n }\n if (session?.host === host) {\n session = null\n }\n }\n },\n\n touchStart(host, touch) {\n // Already tracking this press: this is an ancestor's gesture seeing the\n // same event through GTK's bubble chain.\n if (session !== null) {\n return\n }\n session = {\n host,\n identifier: touch.identifier,\n lastTouch: touch,\n claimed: false,\n }\n recordTouchStart(history, touch)\n refreshActiveTouches(history)\n\n const path = pathOf(host)\n const event = createEvent(touch, true)\n dispatchTouch(path, \"onTouchStartCapture\", \"onTouchStart\", event)\n\n negotiateAndTransfer(path, event, \"start\")\n // RN dispatches this to whoever holds the lock once the handoff is\n // over, which is why it is not folded into grant(): a view granted on\n // a MOVE must not be told the interaction just started.\n if (responder !== null) {\n notify(responder, \"onResponderStart\", event)\n }\n },\n\n touchMove(host, touch) {\n if (session === null || session.host !== host) {\n return\n }\n session.lastTouch = touch\n recordTouchMove(history, touch)\n refreshActiveTouches(history)\n\n const path = pathOf(host)\n const event = createEvent(touch, true)\n dispatchTouch(path, \"onTouchMoveCapture\", \"onTouchMove\", event)\n\n // Two shapes go through here. Nobody holds the lock yet: the common\n // PanResponder one, where the pan starts once\n // onMoveShouldSetPanResponder sees enough movement. Somebody does: an\n // ancestor may still take it over, and the move is then delivered to\n // whoever ends up holding it — including in the same event as the\n // handoff, exactly as RN's own state diagram shows.\n negotiateAndTransfer(path, event, \"move\")\n if (responder !== null) {\n notify(responder, \"onResponderMove\", event)\n }\n },\n\n touchEnd(host, touch) {\n if (session === null || session.host !== host) {\n return\n }\n finish(touch, false, [\"onTouchEndCapture\", \"onTouchEnd\"])\n },\n\n touchCancel(host, touch) {\n if (session === null || session.host !== host) {\n return\n }\n finish(touch, true, [\"onTouchCancelCapture\", \"onTouchCancel\"])\n },\n\n terminate(reason) {\n const current = session\n const holder = responder\n if (current === null || holder === null) {\n // Nothing holds the lock: an ancestor scrolling or a menu opening is\n // then simply an ancestor scrolling or a menu opening. RN terminates\n // a RESPONDER; it has nothing to say when there is not one.\n return false\n }\n const event = createEvent(current.lastTouch, false)\n if (\n reason === \"scroll\" &&\n propsOf(holder)?.onResponderTerminationRequest?.(event) === false\n ) {\n return false\n }\n\n // No onResponderEnd here, and that is RN's shape rather than an\n // oversight: End pairs with a touch actually ending. A termination\n // that is not a pointer event goes straight to Terminate.\n recordTouchEnd(history, current.lastTouch)\n refreshActiveTouches(history)\n session = null\n responder = null\n notify(holder, \"onResponderTerminate\", event)\n return true\n },\n\n requestResponder(host) {\n const current = session\n if (current === null) {\n // No interaction to take. A timer that fires after the pointer is up\n // is a timer that lost, and there is nothing to negotiate over.\n return false\n }\n if (responder === host) {\n return true\n }\n const path = pathOf(current.host)\n // A node off the interaction's path is not a candidate — the same rule\n // the touch paths enforce by construction, stated here because this\n // entry point is reached from a timer rather than from an event that\n // already travelled the tree.\n if (!path.includes(host)) {\n return false\n }\n negotiateAndTransfer(path, createEvent(current.lastTouch, true), \"move\")\n return responder === host\n },\n\n getResponder: () => responder,\n getSource: () => session?.host ?? null,\n }\n}\n"]}
|
|
@@ -4,6 +4,17 @@ import { type ResponderHost } from "./system";
|
|
|
4
4
|
import { type ResponderProps } from "./types";
|
|
5
5
|
/** Exposed for tests and for future ScrollView arbitration. */
|
|
6
6
|
export declare const getCurrentResponder: () => ResponderHost | null;
|
|
7
|
+
/**
|
|
8
|
+
* The out-of-event grant channel, for a recognizer whose mind was changed by
|
|
9
|
+
* a timer rather than by a pointer (`activateAfterLongPress`). See
|
|
10
|
+
* `ResponderSystem.requestResponder` for what it does and
|
|
11
|
+
* docs/research/gestures.md for why the model is extended at all.
|
|
12
|
+
*
|
|
13
|
+
* @internal Not part of the RN surface: an app writes responder props, and
|
|
14
|
+
* nothing in RN asks for the responder from outside an event. This exists for
|
|
15
|
+
* `GestureDetector`, whose recognizers do.
|
|
16
|
+
*/
|
|
17
|
+
export declare const requestResponder: (widget: Gtk.Widget) => boolean;
|
|
7
18
|
/**
|
|
8
19
|
* Attaches the responder event source to a component's widget, but only when
|
|
9
20
|
* it actually declares responder props — RN never asks a view without
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
// drag-begin on press with no threshold of its own, which is exactly what
|
|
16
16
|
// RN wants — the responder system has no threshold either, and PanResponder
|
|
17
17
|
// users apply their own inside onMoveShouldSetPanResponder.
|
|
18
|
+
//
|
|
19
|
+
// The terminations that are not pointer events (watchTerminations, below)
|
|
20
|
+
// were expected to need that raw tap after all. Measurement said otherwise.
|
|
18
21
|
import { useLayoutEffect, useRef } from "react";
|
|
19
22
|
import { createTouch } from "../components/press-event";
|
|
20
23
|
import { Gtk } from "../gtkx/bridge/index";
|
|
@@ -23,6 +26,79 @@ import { hasResponderProps } from "./types";
|
|
|
23
26
|
// The gesture that reported each registered widget, so a grant can make the
|
|
24
27
|
// GTK claim on the right one.
|
|
25
28
|
const gestures = new WeakMap();
|
|
29
|
+
/** Whether `widget` is `ancestor` or sits underneath it. */
|
|
30
|
+
const contains = (ancestor, widget) => {
|
|
31
|
+
for (let current = widget; current !== null; current = current.getParent()) {
|
|
32
|
+
if (current === ancestor) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Every `GtkScrolledWindow` above `source`, innermost first. Nested
|
|
40
|
+
* scrollers are rare but real, and stopping only the innermost would let an
|
|
41
|
+
* outer one take the drag instead.
|
|
42
|
+
*/
|
|
43
|
+
const enclosingScrollers = (source) => {
|
|
44
|
+
const found = [];
|
|
45
|
+
for (let widget = source.getParent(); widget !== null; widget = widget.getParent()) {
|
|
46
|
+
if (widget instanceof Gtk.ScrolledWindow) {
|
|
47
|
+
found.push(widget);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return found;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* `setIsJSResponder`, which is what every RN platform calls the message
|
|
54
|
+
* "JavaScript has taken this interaction, native scroller stop stealing the
|
|
55
|
+
* drag". iOS cancels the `UIScrollView` pan when the responder is a
|
|
56
|
+
* descendant; Android has a `JSResponderHandler`; react-native-windows
|
|
57
|
+
* punted on it in 2017, shipped a `manipulationModes` prop instead, and its
|
|
58
|
+
* tracking issues are still open.
|
|
59
|
+
*
|
|
60
|
+
* On GTK the message is `kinetic-scrolling`. Measured on GTK 4 rather than
|
|
61
|
+
* inferred (tests/gtk/components/scroll-arbitration.gtk.test.tsx):
|
|
62
|
+
* `GtkScrolledWindow` installs exactly four gestures of its own —
|
|
63
|
+
* `GestureLongPress`, `GestureSwipe`, `GesturePan`, `GestureDrag`, all
|
|
64
|
+
* touch-only, all in the CAPTURE phase — and turning kinetic scrolling off
|
|
65
|
+
* puts those four, and only those four, into `GTK_PHASE_NONE`. Setting a
|
|
66
|
+
* controller's phase to NONE resets it, so a scroll that had already begun
|
|
67
|
+
* stops there; the wheel and the motion controllers are untouched, so
|
|
68
|
+
* scrolling with a mouse keeps working while a child pans.
|
|
69
|
+
*
|
|
70
|
+
* Why this is needed at all when the `CLAIMED` declaration above already
|
|
71
|
+
* denies ancestor gestures: it is a race, and only sometimes ours.
|
|
72
|
+
* `GtkScrolledWindow` does not claim on press — it claims in `drag-update`,
|
|
73
|
+
* once movement passes `gtk-dnd-drag-threshold` (8 px). A view that claims
|
|
74
|
+
* on press beats it. A view that claims on a MOVE — which is what
|
|
75
|
+
* `onMoveShouldSetPanResponder` does, and it is the commonest shape there
|
|
76
|
+
* is — usually does not, and `CLAIMED` cannot be taken back once the
|
|
77
|
+
* scroller has it. So the claim is not enough on its own, and this is the
|
|
78
|
+
* part that makes a child pan reachable inside a scrolling list.
|
|
79
|
+
*
|
|
80
|
+
* Restores the previous value rather than assuming `true`: an app that
|
|
81
|
+
* turned kinetic scrolling off itself must not have it turned back on.
|
|
82
|
+
*/
|
|
83
|
+
const suspendEnclosingScrollers = (source) => {
|
|
84
|
+
const suspended = enclosingScrollers(source).filter((scroller) => scroller.getKineticScrolling());
|
|
85
|
+
for (const scroller of suspended) {
|
|
86
|
+
scroller.setKineticScrolling(false);
|
|
87
|
+
}
|
|
88
|
+
return () => {
|
|
89
|
+
for (const scroller of suspended) {
|
|
90
|
+
scroller.setKineticScrolling(true);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* The scroller suspension for the interaction currently under way. Module
|
|
96
|
+
* scope because it is set when the responder is granted and undone when the
|
|
97
|
+
* interaction ends, which are two different events on two different
|
|
98
|
+
* objects; there is only ever one interaction, which is the same assumption
|
|
99
|
+
* the responder system itself is built on (one pointer, one session).
|
|
100
|
+
*/
|
|
101
|
+
let restoreScrollers = null;
|
|
26
102
|
/**
|
|
27
103
|
* One lock for the process, as in RN. What is island-scoped is the
|
|
28
104
|
* negotiation PATH, not the lock: `parentOf` walks the GTK widget tree and
|
|
@@ -31,15 +107,100 @@ const gestures = new WeakMap();
|
|
|
31
107
|
*/
|
|
32
108
|
const responderSystem = createResponderSystem({
|
|
33
109
|
parentOf: (host) => host.getParent(),
|
|
34
|
-
|
|
110
|
+
onClaim: (source) => {
|
|
35
111
|
// GTK's only arbitration primitive, used the only way it can honestly be
|
|
36
112
|
// used: a final one-way declaration AFTER JS has decided. There is no
|
|
37
113
|
// un-claim, so nothing may depend on taking it back.
|
|
38
|
-
|
|
114
|
+
//
|
|
115
|
+
// On the SOURCE's gesture — the one the interaction is arriving through
|
|
116
|
+
// — never on the granted view's. Claiming denies the sequence on every
|
|
117
|
+
// gesture above the claimer and cancels it on everything below, so
|
|
118
|
+
// claiming on an ancestor kills the source and the drag goes silent
|
|
119
|
+
// after the grant. Slice 2 did exactly that, and the common
|
|
120
|
+
// onMoveShouldSetPanResponder shape never received a single move.
|
|
121
|
+
gestures.get(source)?.setState(Gtk.EventSequenceState.CLAIMED);
|
|
122
|
+
// A ResponderHost is deliberately opaque to the pure module; on this
|
|
123
|
+
// platform it is always the Gtk.Widget that registered it.
|
|
124
|
+
restoreScrollers = suspendEnclosingScrollers(source);
|
|
39
125
|
},
|
|
40
126
|
});
|
|
127
|
+
/**
|
|
128
|
+
* Everything that ends a gesture without being a pointer event, connected
|
|
129
|
+
* for the length of one interaction and torn down with it.
|
|
130
|
+
*
|
|
131
|
+
* The plan for this task called for a single `GtkEventControllerLegacy` on
|
|
132
|
+
* the toplevel in the capture phase — react-native-web's shape, on the
|
|
133
|
+
* reasoning that a raw event tap is the only way to see terminations that
|
|
134
|
+
* are not pointer events. Measured against GTK, it buys nothing here. Each
|
|
135
|
+
* of the two triggers that survive has a first-class signal that says
|
|
136
|
+
* exactly what we need to know and nothing else, and the rest of RNW's list
|
|
137
|
+
* turned out to arrive as an ordinary gesture `::cancel` (see
|
|
138
|
+
* `TerminationReason`). A legacy controller would have re-derived all of it
|
|
139
|
+
* from a stream of raw events, and its one advantage — hearing events on a
|
|
140
|
+
* sequence GTK has already denied us — matters only for triggers that no
|
|
141
|
+
* longer exist.
|
|
142
|
+
*/
|
|
143
|
+
const watchTerminations = (source) => {
|
|
144
|
+
const disposers = [];
|
|
145
|
+
// Window blur. GTK's `is-active` on the toplevel is the same thing the DOM
|
|
146
|
+
// reports as a window `blur` — the window stopped being the active one,
|
|
147
|
+
// which on a desktop usually means the user alt-tabbed mid-drag.
|
|
148
|
+
const root = source.getRoot();
|
|
149
|
+
if (root instanceof Gtk.Window) {
|
|
150
|
+
const onActive = () => {
|
|
151
|
+
if (!root.isActive()) {
|
|
152
|
+
responderSystem.terminate("blur");
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
root.on("notify::is-active", onActive);
|
|
156
|
+
disposers.push(() => {
|
|
157
|
+
root.off("notify::is-active", onActive);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
// An ancestor scrolling. RNW's rule exactly: the scroller has to CONTAIN
|
|
161
|
+
// the responder and not BE it — a ScrollView that holds the responder
|
|
162
|
+
// itself keeps it, and a sibling list scrolling is none of our business.
|
|
163
|
+
// Watching the adjustments rather than a scroll controller is what makes
|
|
164
|
+
// that precise: it fires for a wheel, for a keyboard scroll and for
|
|
165
|
+
// kinetic deceleration alike, and only for this scroller.
|
|
166
|
+
for (const scroller of enclosingScrollers(source)) {
|
|
167
|
+
const onScrolled = () => {
|
|
168
|
+
const holder = responderSystem.getResponder();
|
|
169
|
+
if (holder !== null &&
|
|
170
|
+
holder !== scroller &&
|
|
171
|
+
contains(scroller, holder)) {
|
|
172
|
+
responderSystem.terminate("scroll");
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
for (const adjustment of [
|
|
176
|
+
scroller.getHadjustment(),
|
|
177
|
+
scroller.getVadjustment(),
|
|
178
|
+
]) {
|
|
179
|
+
adjustment.on("value-changed", onScrolled);
|
|
180
|
+
disposers.push(() => {
|
|
181
|
+
adjustment.off("value-changed", onScrolled);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return () => {
|
|
186
|
+
for (const dispose of disposers) {
|
|
187
|
+
dispose();
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
};
|
|
41
191
|
/** Exposed for tests and for future ScrollView arbitration. */
|
|
42
192
|
export const getCurrentResponder = () => responderSystem.getResponder();
|
|
193
|
+
/**
|
|
194
|
+
* The out-of-event grant channel, for a recognizer whose mind was changed by
|
|
195
|
+
* a timer rather than by a pointer (`activateAfterLongPress`). See
|
|
196
|
+
* `ResponderSystem.requestResponder` for what it does and
|
|
197
|
+
* docs/research/gestures.md for why the model is extended at all.
|
|
198
|
+
*
|
|
199
|
+
* @internal Not part of the RN surface: an app writes responder props, and
|
|
200
|
+
* nothing in RN asks for the responder from outside an event. This exists for
|
|
201
|
+
* `GestureDetector`, whose recognizers do.
|
|
202
|
+
*/
|
|
203
|
+
export const requestResponder = (widget) => responderSystem.requestResponder(widget);
|
|
43
204
|
/**
|
|
44
205
|
* Attaches the responder event source to a component's widget, but only when
|
|
45
206
|
* it actually declares responder props — RN never asks a view without
|
|
@@ -69,25 +230,83 @@ export const useResponder = (widgetRef, props) => {
|
|
|
69
230
|
// coordinates — the start has to be carried.
|
|
70
231
|
let startX = 0;
|
|
71
232
|
let startY = 0;
|
|
233
|
+
let stopWatching = null;
|
|
234
|
+
// Whether this sequence was taken from us by something above. See the
|
|
235
|
+
// `sequence-state-changed` handler below.
|
|
236
|
+
let denied = false;
|
|
237
|
+
const endInteraction = () => {
|
|
238
|
+
stopWatching?.();
|
|
239
|
+
stopWatching = null;
|
|
240
|
+
restoreScrollers?.();
|
|
241
|
+
restoreScrollers = null;
|
|
242
|
+
};
|
|
243
|
+
// THE ONE SIGNAL THAT TELLS A THEFT FROM AN ENDING.
|
|
244
|
+
//
|
|
245
|
+
// GTK's claim propagation is asymmetric, and measuring it on 4.22.4
|
|
246
|
+
// corrected `docs/research/gestures.md`, which had it backwards. A claim
|
|
247
|
+
// by a DESCENDANT cancels the ancestor's gesture — `::cancel` then
|
|
248
|
+
// `::end`, and no state transition at all, which the `cancel` handler
|
|
249
|
+
// below already turns into a termination. A claim by an ANCESTOR is the
|
|
250
|
+
// dangerous direction: it DENIES the descendant's sequence and then ends
|
|
251
|
+
// it with an ordinary `drag-end`, exactly the signal a finger lifting
|
|
252
|
+
// produces. Without this handler a native ancestor stealing the drag
|
|
253
|
+
// mid-gesture reaches JS as `onResponderRelease` — a CLEAN RELEASE,
|
|
254
|
+
// indistinguishable from the user letting go, so a drag would commit
|
|
255
|
+
// itself at whatever position the theft happened at.
|
|
256
|
+
//
|
|
257
|
+
// `->DENIED` is the only thing that separates the two, which is why the
|
|
258
|
+
// sequence state has to be watched rather than just the drag signals.
|
|
259
|
+
// Nothing else here can produce it: this module never sets DENIED, and
|
|
260
|
+
// our own CLAIMED is always made on the source, which cancels ancestors
|
|
261
|
+
// rather than denying them.
|
|
262
|
+
drag.on("sequence-state-changed", (_sequence, state) => {
|
|
263
|
+
if (state === Gtk.EventSequenceState.DENIED) {
|
|
264
|
+
denied = true;
|
|
265
|
+
}
|
|
266
|
+
});
|
|
72
267
|
drag.on("drag-begin", (x, y) => {
|
|
73
268
|
startX = x;
|
|
74
269
|
startY = y;
|
|
270
|
+
// A `GtkGestureSingle` denies its own sequence when a press arrives on
|
|
271
|
+
// the wrong button and nothing is down yet, so the flag can survive from
|
|
272
|
+
// before this press. Every interaction starts clean.
|
|
273
|
+
denied = false;
|
|
75
274
|
responderSystem.touchStart(widget, createTouch(widget, x, y));
|
|
275
|
+
// Every ancestor's gesture reports the same press; only the one that
|
|
276
|
+
// opened the interaction owns it, and asking the system afterwards is
|
|
277
|
+
// the cheapest way to find out which that was.
|
|
278
|
+
if (responderSystem.getSource() === widget) {
|
|
279
|
+
stopWatching = watchTerminations(widget);
|
|
280
|
+
}
|
|
76
281
|
});
|
|
77
282
|
drag.on("drag-update", (offsetX, offsetY) => {
|
|
78
283
|
responderSystem.touchMove(widget, createTouch(widget, startX + offsetX, startY + offsetY));
|
|
79
284
|
});
|
|
80
285
|
drag.on("drag-end", (offsetX, offsetY) => {
|
|
81
|
-
|
|
286
|
+
const touch = createTouch(widget, startX + offsetX, startY + offsetY);
|
|
287
|
+
// A denied sequence ends with this same signal, and it is not an
|
|
288
|
+
// ending: the interaction was taken away. RN's word for that is a
|
|
289
|
+
// termination, so it goes down the cancel path.
|
|
290
|
+
if (denied) {
|
|
291
|
+
responderSystem.touchCancel(widget, touch);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
responderSystem.touchEnd(widget, touch);
|
|
295
|
+
}
|
|
296
|
+
endInteraction();
|
|
82
297
|
});
|
|
83
|
-
// GTK cancels a sequence when something
|
|
84
|
-
// ancestor
|
|
85
|
-
//
|
|
298
|
+
// GTK cancels a sequence when something takes it away from us: a native
|
|
299
|
+
// ancestor claiming it, a `GtkDragSource` attached through `Controllers`
|
|
300
|
+
// reaching its threshold, a second mouse button going down for a context
|
|
301
|
+
// menu. RN calls all of that a termination, and unlike RNW we never get
|
|
302
|
+
// to ask first — by the time GTK tells us, the sequence is gone.
|
|
86
303
|
drag.on("cancel", () => {
|
|
87
304
|
responderSystem.touchCancel(widget, createTouch(widget, startX, startY));
|
|
305
|
+
endInteraction();
|
|
88
306
|
});
|
|
89
307
|
widget.addController(drag);
|
|
90
308
|
return () => {
|
|
309
|
+
endInteraction();
|
|
91
310
|
widget.removeController(drag);
|
|
92
311
|
gestures.delete(widget);
|
|
93
312
|
unregister();
|