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,1056 @@
|
|
|
1
|
+
import { GESTURE_STATE, POINTER_TYPE, TOUCH_EVENT_TYPE, } from "./types";
|
|
2
|
+
const newRuntime = () => ({
|
|
3
|
+
state: GESTURE_STATE.UNDETERMINED,
|
|
4
|
+
oldState: GESTURE_STATE.UNDETERMINED,
|
|
5
|
+
pressTime: 0,
|
|
6
|
+
originX: 0,
|
|
7
|
+
originY: 0,
|
|
8
|
+
pressX: 0,
|
|
9
|
+
pressY: 0,
|
|
10
|
+
lastX: 0,
|
|
11
|
+
lastY: 0,
|
|
12
|
+
lastTime: 0,
|
|
13
|
+
velocityX: 0,
|
|
14
|
+
velocityY: 0,
|
|
15
|
+
changeFromX: 0,
|
|
16
|
+
changeFromY: 0,
|
|
17
|
+
hasEmittedUpdate: false,
|
|
18
|
+
hasActivated: false,
|
|
19
|
+
activationTimer: null,
|
|
20
|
+
timerElapsed: false,
|
|
21
|
+
pointerCount: 0,
|
|
22
|
+
maxPointerCount: 0,
|
|
23
|
+
taps: 0,
|
|
24
|
+
delayTimer: null,
|
|
25
|
+
authorized: false,
|
|
26
|
+
hasResponder: false,
|
|
27
|
+
forcedFailure: false,
|
|
28
|
+
scale: 1,
|
|
29
|
+
rotation: 0,
|
|
30
|
+
scaleVelocity: 0,
|
|
31
|
+
rotationVelocity: 0,
|
|
32
|
+
changeFromScale: 1,
|
|
33
|
+
changeFromRotation: 0,
|
|
34
|
+
force: 0,
|
|
35
|
+
changeFromForce: 0,
|
|
36
|
+
lastSampleTime: 0,
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* RNGH's `checkHitSlop`, restated: build the acceptable box in the view's own
|
|
40
|
+
* coordinates, then test the pointer's offset into the view against it.
|
|
41
|
+
*
|
|
42
|
+
* Every side is "outward is positive", so a NEGATIVE value shrinks the box —
|
|
43
|
+
* the capability RN's own View `hitSlop` does not have. Explicit sides
|
|
44
|
+
* override `horizontal`/`vertical`, and `width`/`height` then anchor the
|
|
45
|
+
* opposite edge to whichever side was named.
|
|
46
|
+
*
|
|
47
|
+
* The plain-number spelling is normalised into the four sides up front.
|
|
48
|
+
* Upstream's web path does not do this and silently ignores a number, which
|
|
49
|
+
* is a bug rather than a semantic worth reproducing — its own native paths
|
|
50
|
+
* normalise exactly this way.
|
|
51
|
+
*/
|
|
52
|
+
export const hitSlopRect = (bounds, hitSlop) => {
|
|
53
|
+
if (hitSlop === undefined || hitSlop === null) {
|
|
54
|
+
return bounds;
|
|
55
|
+
}
|
|
56
|
+
const slop = typeof hitSlop === "number"
|
|
57
|
+
? { left: hitSlop, right: hitSlop, top: hitSlop, bottom: hitSlop }
|
|
58
|
+
: hitSlop;
|
|
59
|
+
let left = 0;
|
|
60
|
+
let top = 0;
|
|
61
|
+
let right = bounds.width;
|
|
62
|
+
let bottom = bounds.height;
|
|
63
|
+
if (slop.horizontal !== undefined) {
|
|
64
|
+
left -= slop.horizontal;
|
|
65
|
+
right += slop.horizontal;
|
|
66
|
+
}
|
|
67
|
+
if (slop.vertical !== undefined) {
|
|
68
|
+
top -= slop.vertical;
|
|
69
|
+
bottom += slop.vertical;
|
|
70
|
+
}
|
|
71
|
+
if (slop.left !== undefined) {
|
|
72
|
+
left = -slop.left;
|
|
73
|
+
}
|
|
74
|
+
if (slop.right !== undefined) {
|
|
75
|
+
right = bounds.width + slop.right;
|
|
76
|
+
}
|
|
77
|
+
if (slop.top !== undefined) {
|
|
78
|
+
top = -slop.top;
|
|
79
|
+
}
|
|
80
|
+
if (slop.bottom !== undefined) {
|
|
81
|
+
bottom = bounds.height + slop.bottom;
|
|
82
|
+
}
|
|
83
|
+
if (slop.width !== undefined) {
|
|
84
|
+
if (slop.left !== undefined) {
|
|
85
|
+
right = left + slop.width;
|
|
86
|
+
}
|
|
87
|
+
else if (slop.right !== undefined) {
|
|
88
|
+
left = right - slop.width;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (slop.height !== undefined) {
|
|
92
|
+
if (slop.top !== undefined) {
|
|
93
|
+
bottom = top + slop.height;
|
|
94
|
+
}
|
|
95
|
+
else if (slop.bottom !== undefined) {
|
|
96
|
+
top = bottom - slop.height;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
x: bounds.x + left,
|
|
101
|
+
y: bounds.y + top,
|
|
102
|
+
width: right - left,
|
|
103
|
+
height: bottom - top,
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
const contains = (rect, x, y) => x >= rect.x &&
|
|
107
|
+
x <= rect.x + rect.width &&
|
|
108
|
+
y >= rect.y &&
|
|
109
|
+
y <= rect.y + rect.height;
|
|
110
|
+
/**
|
|
111
|
+
* Builds the machine. `readConfig` is called on every event rather than
|
|
112
|
+
* captured, so a re-render that hands the detector a fresh gesture object —
|
|
113
|
+
* which is what both spellings produce, every render — takes effect without
|
|
114
|
+
* swapping the handler set mid-drag.
|
|
115
|
+
*/
|
|
116
|
+
export const createRecognizer = (handlerTag, decider, readConfig, env) => {
|
|
117
|
+
let runtime = newRuntime();
|
|
118
|
+
const isEnabled = () => readConfig().enabled !== false;
|
|
119
|
+
/**
|
|
120
|
+
* What kind of device this gesture's events come from.
|
|
121
|
+
*
|
|
122
|
+
* `MOUSE` for everything except `ForceTouch`, and that is not a default with
|
|
123
|
+
* a hole in it: this platform has one pointer and no touch injection, so
|
|
124
|
+
* every other kind really is a mouse. A pressure reading can only have come
|
|
125
|
+
* from a tablet tool, so the one kind that reads pressure reports `STYLUS`.
|
|
126
|
+
*/
|
|
127
|
+
const pointerType = decider.source === "stylus" ? POINTER_TYPE.STYLUS : POINTER_TYPE.MOUSE;
|
|
128
|
+
const setState = (next) => {
|
|
129
|
+
runtime.oldState = runtime.state;
|
|
130
|
+
runtime.state = next;
|
|
131
|
+
};
|
|
132
|
+
const viewOf = () => ({
|
|
133
|
+
translationX: runtime.lastX - runtime.originX,
|
|
134
|
+
translationY: runtime.lastY - runtime.originY,
|
|
135
|
+
distanceFromPress: Math.hypot(runtime.lastX - runtime.pressX, runtime.lastY - runtime.pressY),
|
|
136
|
+
velocityX: runtime.velocityX,
|
|
137
|
+
velocityY: runtime.velocityY,
|
|
138
|
+
timerElapsed: runtime.timerElapsed,
|
|
139
|
+
pointerCount: runtime.pointerCount,
|
|
140
|
+
maxPointerCount: runtime.maxPointerCount,
|
|
141
|
+
taps: runtime.taps,
|
|
142
|
+
scale: runtime.scale,
|
|
143
|
+
rotation: runtime.rotation,
|
|
144
|
+
force: runtime.force,
|
|
145
|
+
});
|
|
146
|
+
/**
|
|
147
|
+
* One payload, from a point rather than from an event.
|
|
148
|
+
*
|
|
149
|
+
* A timer has no event to build one from and every one of the three kinds
|
|
150
|
+
* has a timer that can end the gesture — `Tap`'s `maxDuration` and
|
|
151
|
+
* `maxDelay` both finalize with no pointer in flight. Fabricating a
|
|
152
|
+
* `GestureResponderEvent` to feed an event-shaped builder would have put an
|
|
153
|
+
* invented event into the responder system's own vocabulary; taking the
|
|
154
|
+
* three numbers the payload actually needs does not.
|
|
155
|
+
*/
|
|
156
|
+
const payloadAt = (pageX, pageY, pointerCount, fallback) => {
|
|
157
|
+
const bounds = env.boundsInWindow();
|
|
158
|
+
const translationX = pageX - runtime.originX;
|
|
159
|
+
const translationY = pageY - runtime.originY;
|
|
160
|
+
return {
|
|
161
|
+
handlerTag,
|
|
162
|
+
numberOfPointers: pointerCount,
|
|
163
|
+
pointerType,
|
|
164
|
+
state: runtime.state,
|
|
165
|
+
oldState: runtime.oldState,
|
|
166
|
+
// RNGH's `x`/`y` are relative to the GESTURE's view. The responder
|
|
167
|
+
// event's own `locationX` is relative to whichever widget carried the
|
|
168
|
+
// event, which is the deepest one with a controller and not always this
|
|
169
|
+
// one — so it is recomputed rather than passed through.
|
|
170
|
+
x: bounds ? pageX - bounds.x : (fallback?.locationX ?? pageX),
|
|
171
|
+
y: bounds ? pageY - bounds.y : (fallback?.locationY ?? pageY),
|
|
172
|
+
absoluteX: pageX,
|
|
173
|
+
absoluteY: pageY,
|
|
174
|
+
translationX,
|
|
175
|
+
translationY,
|
|
176
|
+
velocityX: runtime.velocityX,
|
|
177
|
+
velocityY: runtime.velocityY,
|
|
178
|
+
// On the first update the change IS the translation, which is
|
|
179
|
+
// upstream's rule rather than an approximation of it.
|
|
180
|
+
changeX: runtime.hasEmittedUpdate
|
|
181
|
+
? translationX - runtime.changeFromX
|
|
182
|
+
: translationX,
|
|
183
|
+
changeY: runtime.hasEmittedUpdate
|
|
184
|
+
? translationY - runtime.changeFromY
|
|
185
|
+
: translationY,
|
|
186
|
+
duration: Date.now() - runtime.pressTime,
|
|
187
|
+
scale: runtime.scale,
|
|
188
|
+
// A RATIO, not a difference — scale composes by multiplication, and
|
|
189
|
+
// upstream's `changeEventCalculator` for `Pinch` divides where the one
|
|
190
|
+
// for `Rotation` subtracts. On the first update it is the scale itself,
|
|
191
|
+
// which is upstream's rule for both and the same rule `changeX` follows.
|
|
192
|
+
scaleChange: runtime.hasEmittedUpdate
|
|
193
|
+
? runtime.scale / runtime.changeFromScale
|
|
194
|
+
: runtime.scale,
|
|
195
|
+
// The focal point IS the position for these two kinds: the touchpad
|
|
196
|
+
// channel writes the gesture's bounding-box centre into the same
|
|
197
|
+
// last-position fields the pointer kinds write a touch into, so `x`/`y`
|
|
198
|
+
// and `focalX`/`focalY` are the same number arrived at the same way.
|
|
199
|
+
focalX: bounds ? pageX - bounds.x : (fallback?.locationX ?? pageX),
|
|
200
|
+
focalY: bounds ? pageY - bounds.y : (fallback?.locationY ?? pageY),
|
|
201
|
+
rotation: runtime.rotation,
|
|
202
|
+
rotationChange: runtime.hasEmittedUpdate
|
|
203
|
+
? runtime.rotation - runtime.changeFromRotation
|
|
204
|
+
: runtime.rotation,
|
|
205
|
+
anchorX: bounds ? pageX - bounds.x : (fallback?.locationX ?? pageX),
|
|
206
|
+
anchorY: bounds ? pageY - bounds.y : (fallback?.locationY ?? pageY),
|
|
207
|
+
force: runtime.force,
|
|
208
|
+
// A DIFFERENCE, like `rotationChange` and unlike `scaleChange`, because
|
|
209
|
+
// upstream's `changeEventCalculator` for `ForceTouch` subtracts. On the
|
|
210
|
+
// first update it is the force itself, which is upstream's rule there
|
|
211
|
+
// and the same rule `changeX` follows.
|
|
212
|
+
forceChange: runtime.hasEmittedUpdate
|
|
213
|
+
? runtime.force - runtime.changeFromForce
|
|
214
|
+
: runtime.force,
|
|
215
|
+
velocity: decider.kind === "rotation"
|
|
216
|
+
? runtime.rotationVelocity
|
|
217
|
+
: runtime.scaleVelocity,
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
const payloadOf = (event) => {
|
|
221
|
+
const { pageX, pageY, locationX, locationY } = event.nativeEvent;
|
|
222
|
+
return payloadAt(pageX, pageY, event.nativeEvent.touches.length, {
|
|
223
|
+
locationX,
|
|
224
|
+
locationY,
|
|
225
|
+
});
|
|
226
|
+
};
|
|
227
|
+
/** The payload for a decision a timer made, from the last known position. */
|
|
228
|
+
const payloadNow = () => payloadAt(runtime.lastX, runtime.lastY, runtime.pointerCount);
|
|
229
|
+
const touchEventOf = (event, eventType) => {
|
|
230
|
+
const bounds = env.boundsInWindow();
|
|
231
|
+
const toData = (touch) => ({
|
|
232
|
+
id: touch.identifier,
|
|
233
|
+
x: bounds ? touch.pageX - bounds.x : touch.pageX,
|
|
234
|
+
y: bounds ? touch.pageY - bounds.y : touch.pageY,
|
|
235
|
+
absoluteX: touch.pageX,
|
|
236
|
+
absoluteY: touch.pageY,
|
|
237
|
+
});
|
|
238
|
+
return {
|
|
239
|
+
handlerTag,
|
|
240
|
+
numberOfTouches: event.nativeEvent.touches.length,
|
|
241
|
+
eventType,
|
|
242
|
+
allTouches: event.nativeEvent.touches.map(toData),
|
|
243
|
+
changedTouches: event.nativeEvent.changedTouches.map(toData),
|
|
244
|
+
state: runtime.state,
|
|
245
|
+
pointerType,
|
|
246
|
+
};
|
|
247
|
+
};
|
|
248
|
+
const clearActivationTimer = () => {
|
|
249
|
+
if (runtime.activationTimer !== null) {
|
|
250
|
+
clearTimeout(runtime.activationTimer);
|
|
251
|
+
runtime.activationTimer = null;
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
const clearDelayTimer = () => {
|
|
255
|
+
if (runtime.delayTimer !== null) {
|
|
256
|
+
clearTimeout(runtime.delayTimer);
|
|
257
|
+
runtime.delayTimer = null;
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
const track = (event) => {
|
|
261
|
+
const { pageX, pageY, timestamp } = event.nativeEvent;
|
|
262
|
+
const elapsed = timestamp - runtime.lastTime;
|
|
263
|
+
if (elapsed > 0) {
|
|
264
|
+
runtime.velocityX = ((pageX - runtime.lastX) / elapsed) * 1000;
|
|
265
|
+
runtime.velocityY = ((pageY - runtime.lastY) / elapsed) * 1000;
|
|
266
|
+
}
|
|
267
|
+
runtime.lastX = pageX;
|
|
268
|
+
runtime.lastY = pageY;
|
|
269
|
+
runtime.lastTime = timestamp;
|
|
270
|
+
countPointers(event);
|
|
271
|
+
};
|
|
272
|
+
const countPointers = (event) => {
|
|
273
|
+
runtime.pointerCount = event.nativeEvent.touches.length;
|
|
274
|
+
runtime.maxPointerCount = Math.max(runtime.maxPointerCount, runtime.pointerCount);
|
|
275
|
+
};
|
|
276
|
+
/**
|
|
277
|
+
* Every exit from a live gesture goes through here exactly once, and this is
|
|
278
|
+
* where the tap sequence dies: `newRuntime()` drops `taps` along with
|
|
279
|
+
* everything else, so a finalized multi-tap starts counting from zero.
|
|
280
|
+
*/
|
|
281
|
+
const finalize = (payloadFor, success) => {
|
|
282
|
+
if (runtime.state === GESTURE_STATE.UNDETERMINED) {
|
|
283
|
+
// Already over. The responder system dispatches `onResponderRelease` to
|
|
284
|
+
// whoever holds the lock at the end of the interaction, and a gesture
|
|
285
|
+
// can have cancelled itself while still holding it — a `LongPress` that
|
|
286
|
+
// wandered past `maxDistance`, or one the orchestrator cancelled. One
|
|
287
|
+
// exit per gesture, and this is where the second one stops.
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const config = readConfig();
|
|
291
|
+
clearActivationTimer();
|
|
292
|
+
clearDelayTimer();
|
|
293
|
+
// Read the flag, not the state: a cancellation sets the state to
|
|
294
|
+
// CANCELLED on its way in, and an ACTIVE gesture that gets cancelled must
|
|
295
|
+
// still be told it deactivated.
|
|
296
|
+
const wasActive = runtime.hasActivated;
|
|
297
|
+
if (success) {
|
|
298
|
+
setState(GESTURE_STATE.END);
|
|
299
|
+
}
|
|
300
|
+
// Built AFTER the transition, so `event.state` is the state the gesture
|
|
301
|
+
// ended in — which is what a consumer comparing it against `State` reads.
|
|
302
|
+
const payload = payloadFor();
|
|
303
|
+
// The deactivation callback is the ACTIVE gesture's ending. A gesture
|
|
304
|
+
// that never activated gets `onFinalize` and nothing else, which is what
|
|
305
|
+
// lets a consumer distinguish a drag that finished from one that never
|
|
306
|
+
// happened — `react-native-reanimated-dnd` relies on exactly that.
|
|
307
|
+
if (wasActive) {
|
|
308
|
+
config.onDeactivate?.(payload, success);
|
|
309
|
+
}
|
|
310
|
+
config.onFinalize?.(payload, success);
|
|
311
|
+
// Told AFTER the callbacks and BEFORE the reset. After, so a gesture this
|
|
312
|
+
// failure releases from `awaiting` starts where the previous one left off
|
|
313
|
+
// rather than in the middle of it; before, so the loop is handed the state
|
|
314
|
+
// the gesture actually ended in — END releases nobody and cancels the
|
|
315
|
+
// waiters, FAILED and CANCELLED release them.
|
|
316
|
+
env.orchestrator.finished(participant, runtime.state);
|
|
317
|
+
runtime = newRuntime();
|
|
318
|
+
};
|
|
319
|
+
const finalizeAt = (event, success) => {
|
|
320
|
+
finalize(() => payloadOf(event), success);
|
|
321
|
+
};
|
|
322
|
+
const fail = (event) => {
|
|
323
|
+
clearActivationTimer();
|
|
324
|
+
setState(GESTURE_STATE.FAILED);
|
|
325
|
+
finalize(() => payloadOf(event), false);
|
|
326
|
+
};
|
|
327
|
+
/** A timer decided this, so there is no event and the position is the last one. */
|
|
328
|
+
const failNow = () => {
|
|
329
|
+
clearActivationTimer();
|
|
330
|
+
setState(GESTURE_STATE.FAILED);
|
|
331
|
+
finalize(payloadNow, false);
|
|
332
|
+
};
|
|
333
|
+
/** Whether this kind takes the interaction when it activates. */
|
|
334
|
+
const claimsResponder = decider.claimsResponder !== false;
|
|
335
|
+
/**
|
|
336
|
+
* Everything becoming ACTIVE means, from a position rather than an event.
|
|
337
|
+
*
|
|
338
|
+
* Three ways in now, and the third is the whole of slice 3: `onResponderGrant`
|
|
339
|
+
* for a recognizer that took the lock, `authorize()` for one that never will
|
|
340
|
+
* (`claimsResponder: false`), and `authorize()` again for one that WOULD have
|
|
341
|
+
* but was told the interaction is already held by a gesture it is
|
|
342
|
+
* simultaneous with. Everything after the transition is identical, because
|
|
343
|
+
* being ACTIVE is a fact about the gesture and not about the lock.
|
|
344
|
+
*/
|
|
345
|
+
const enterActive = (pageX, pageY) => {
|
|
346
|
+
setState(GESTURE_STATE.ACTIVE);
|
|
347
|
+
runtime.authorized = false;
|
|
348
|
+
clearActivationTimer();
|
|
349
|
+
clearDelayTimer();
|
|
350
|
+
// Translation is measured from where the gesture BECAME ACTIVE, not
|
|
351
|
+
// from the press: a pan with `activeOffsetY([-10, 10])` that reported
|
|
352
|
+
// 10px of travel the moment it started would jump the content by the
|
|
353
|
+
// threshold on every drag. With `activateAfterLongPress` the pointer
|
|
354
|
+
// has not moved when the timer grants, so the two origins coincide —
|
|
355
|
+
// which is only true because the grant no longer waits for a move.
|
|
356
|
+
runtime.originX = pageX;
|
|
357
|
+
runtime.originY = pageY;
|
|
358
|
+
runtime.lastX = pageX;
|
|
359
|
+
runtime.lastY = pageY;
|
|
360
|
+
runtime.changeFromX = 0;
|
|
361
|
+
runtime.changeFromY = 0;
|
|
362
|
+
runtime.hasEmittedUpdate = false;
|
|
363
|
+
runtime.hasActivated = true;
|
|
364
|
+
};
|
|
365
|
+
/**
|
|
366
|
+
* `Fling`'s ending, which is the same instant as its activation.
|
|
367
|
+
*
|
|
368
|
+
* Called at the tail of every path into ACTIVE, so the one-shot kind behaves
|
|
369
|
+
* identically however it got there — a grant, an authorization without one,
|
|
370
|
+
* or a controller channel. Upstream reaches this by overriding `activate()`
|
|
371
|
+
* to call `end()`; a flag on the decider is the same statement without a
|
|
372
|
+
* subclass to put it on.
|
|
373
|
+
*/
|
|
374
|
+
const endIfOneShot = () => {
|
|
375
|
+
if (decider.endsOnActivate === true) {
|
|
376
|
+
finalize(payloadNow, true);
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
/** Becoming ACTIVE without a grant to carry the position or the payload. */
|
|
380
|
+
const activateHere = () => {
|
|
381
|
+
enterActive(runtime.lastX, runtime.lastY);
|
|
382
|
+
const payload = payloadNow();
|
|
383
|
+
// Mutual exclusion is enforced here rather than at the moment permission
|
|
384
|
+
// was given, because permission is not always the same instant: a gesture
|
|
385
|
+
// that has to take the responder becomes ACTIVE only if the negotiation
|
|
386
|
+
// grants it, and an ancestor can still win.
|
|
387
|
+
env.orchestrator.activated(participant);
|
|
388
|
+
readConfig().onActivate?.(payload);
|
|
389
|
+
endIfOneShot();
|
|
390
|
+
};
|
|
391
|
+
/**
|
|
392
|
+
* The orchestrator's answer, and the only place the two locks touch.
|
|
393
|
+
*
|
|
394
|
+
* `needsResponder` false means another gesture already holds the interaction
|
|
395
|
+
* and this one was cleared to run alongside it. It does NOT ask for the
|
|
396
|
+
* responder: the lock is single-holder by design, GTK has already been told
|
|
397
|
+
* everything it can be told, and winning the lock would take the interaction
|
|
398
|
+
* away from the gesture this one was written to accompany.
|
|
399
|
+
*
|
|
400
|
+
* A recognizer that never claims takes the same path for its own reason —
|
|
401
|
+
* it is not asking for the interaction, it is reporting that the widget
|
|
402
|
+
* underneath it has taken one.
|
|
403
|
+
*/
|
|
404
|
+
const authorize = (needsResponder) => {
|
|
405
|
+
if (runtime.state !== GESTURE_STATE.BEGAN) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
runtime.authorized = true;
|
|
409
|
+
if (!claimsResponder || !needsResponder) {
|
|
410
|
+
activateHere();
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
// The out-of-event channel, asked from inside a touch event as well as
|
|
414
|
+
// from a timer, and that uniformity is not laziness. Slice 1 could leave
|
|
415
|
+
// an in-event activation to the negotiation the responder system runs
|
|
416
|
+
// after the touch props, because there was only ever one gesture asking.
|
|
417
|
+
// With two, both would defer into the same negotiation, exactly one would
|
|
418
|
+
// win it, and the loser would sit authorized and never activate — so
|
|
419
|
+
// `Simultaneous` would silently be a race. Asking here settles the lock
|
|
420
|
+
// before the second gesture is even consulted, which is what lets it be
|
|
421
|
+
// told the interaction is already taken.
|
|
422
|
+
//
|
|
423
|
+
// It is the same negotiation either way: `requestResponder` reuses
|
|
424
|
+
// `negotiateAndTransfer` unchanged, so capture still beats bubble and an
|
|
425
|
+
// ancestor can still win.
|
|
426
|
+
env.requestResponder();
|
|
427
|
+
};
|
|
428
|
+
/**
|
|
429
|
+
* The one place a recognizer asks to become active, and therefore the seam
|
|
430
|
+
* the orchestrator sits in.
|
|
431
|
+
*
|
|
432
|
+
* Every path that used to take the responder now goes through here first:
|
|
433
|
+
* the gesture's own criteria being met is a request, not a decision. What
|
|
434
|
+
* comes back is `authorize()` (with or without the lock), a parking in
|
|
435
|
+
* `awaiting` that leaves the gesture BEGAN holding nothing, or a
|
|
436
|
+
* cancellation. `Native` goes through it too — a gesture that never takes
|
|
437
|
+
* the lock still takes part in arbitration, and it is the one kind that can
|
|
438
|
+
* cancel an already-active gesture.
|
|
439
|
+
*/
|
|
440
|
+
const decide = () => {
|
|
441
|
+
if (runtime.authorized || runtime.state !== GESTURE_STATE.BEGAN) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
env.orchestrator.tryActivate(participant);
|
|
445
|
+
};
|
|
446
|
+
/** Cancelled by the loop: another gesture won, or the one it waited for ended. */
|
|
447
|
+
const cancelFromOrchestrator = () => {
|
|
448
|
+
if (runtime.state !== GESTURE_STATE.BEGAN &&
|
|
449
|
+
runtime.state !== GESTURE_STATE.ACTIVE) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
setState(GESTURE_STATE.CANCELLED);
|
|
453
|
+
finalize(payloadNow, false);
|
|
454
|
+
};
|
|
455
|
+
const participant = {
|
|
456
|
+
tag: handlerTag,
|
|
457
|
+
kind: decider.kind,
|
|
458
|
+
authorize,
|
|
459
|
+
holdsResponder: () => runtime.hasResponder,
|
|
460
|
+
cancel: cancelFromOrchestrator,
|
|
461
|
+
};
|
|
462
|
+
/**
|
|
463
|
+
* The state, read through a call.
|
|
464
|
+
*
|
|
465
|
+
* Not decoration: `decide()` can change the state before it returns — the
|
|
466
|
+
* orchestrator may activate the gesture, or cancel it, synchronously — and a
|
|
467
|
+
* narrowed `runtime.state` read straight afterwards is a fact the compiler
|
|
468
|
+
* believes and the runtime does not.
|
|
469
|
+
*/
|
|
470
|
+
const stateNow = () => runtime.state;
|
|
471
|
+
/**
|
|
472
|
+
* The four transitions an app may drive by hand — upstream's
|
|
473
|
+
* `GestureStateManager`, and the whole of what `Gesture.Manual()` is.
|
|
474
|
+
*
|
|
475
|
+
* These used to be two real transitions and two deferrals: `fail()` and
|
|
476
|
+
* `end()` both set a flag that the next `onTouchMove` noticed, which was
|
|
477
|
+
* enough while the only caller was a `Pan` narrowing its own criteria, and
|
|
478
|
+
* is not enough for a gesture that has NO criteria. `Manual` is driven
|
|
479
|
+
* entirely from here, so each of the four has to mean what upstream's means,
|
|
480
|
+
* at the moment it is called:
|
|
481
|
+
*
|
|
482
|
+
* begin UNDETERMINED -> BEGAN
|
|
483
|
+
* activate BEGAN -> ACTIVE, through the orchestrator like every other
|
|
484
|
+
* activation — a manual gesture takes part in arbitration
|
|
485
|
+
* rather than bypassing it, which is the point of having it
|
|
486
|
+
* end BEGAN or ACTIVE -> END, successfully
|
|
487
|
+
* fail BEGAN or ACTIVE -> FAILED
|
|
488
|
+
*
|
|
489
|
+
* `activate()` is FORCED past `manualActivation`, exactly as upstream's web
|
|
490
|
+
* state manager calls `handler.activate(true)`: that flag exists to stop the
|
|
491
|
+
* gesture's own predicates activating it, and this is not them.
|
|
492
|
+
*/
|
|
493
|
+
const stateManager = {
|
|
494
|
+
begin: () => {
|
|
495
|
+
if (runtime.state === GESTURE_STATE.UNDETERMINED) {
|
|
496
|
+
setState(GESTURE_STATE.BEGAN);
|
|
497
|
+
readConfig().onBegin?.(payloadNow());
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
activate: () => {
|
|
501
|
+
// Upstream forces the BEGAN step first when asked to activate a gesture
|
|
502
|
+
// that never began, "to preserve the correct state transition flow" —
|
|
503
|
+
// and here it also matters that the orchestrator has been told about the
|
|
504
|
+
// gesture, which `record()` on the press is what does.
|
|
505
|
+
if (runtime.state === GESTURE_STATE.UNDETERMINED) {
|
|
506
|
+
setState(GESTURE_STATE.BEGAN);
|
|
507
|
+
env.orchestrator.record(participant);
|
|
508
|
+
readConfig().onBegin?.(payloadNow());
|
|
509
|
+
}
|
|
510
|
+
if (runtime.state === GESTURE_STATE.BEGAN) {
|
|
511
|
+
decide();
|
|
512
|
+
}
|
|
513
|
+
},
|
|
514
|
+
fail: () => {
|
|
515
|
+
if (runtime.state === GESTURE_STATE.BEGAN ||
|
|
516
|
+
runtime.state === GESTURE_STATE.ACTIVE) {
|
|
517
|
+
// Kept as well as acted on: a gesture failed from inside
|
|
518
|
+
// `onTouchesDown` has not reached the move that would have consulted
|
|
519
|
+
// its predicates, and the flag is what stops a later move reviving it.
|
|
520
|
+
runtime.forcedFailure = true;
|
|
521
|
+
failNow();
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
end: () => {
|
|
525
|
+
if (runtime.state === GESTURE_STATE.BEGAN ||
|
|
526
|
+
runtime.state === GESTURE_STATE.ACTIVE) {
|
|
527
|
+
runtime.forcedFailure = true;
|
|
528
|
+
finalize(payloadNow, true);
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
};
|
|
532
|
+
const outsideBounds = (event) => {
|
|
533
|
+
const bounds = env.boundsInWindow();
|
|
534
|
+
if (bounds === null) {
|
|
535
|
+
// Before the first layout there is no truth to test against, and
|
|
536
|
+
// refusing every press would be worse than accepting them.
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
539
|
+
const { pageX, pageY } = event.nativeEvent;
|
|
540
|
+
return !contains(hitSlopRect(bounds, readConfig().hitSlop), pageX, pageY);
|
|
541
|
+
};
|
|
542
|
+
const pointerCountAllowed = (event) => {
|
|
543
|
+
const config = readConfig();
|
|
544
|
+
const count = event.nativeEvent.touches.length;
|
|
545
|
+
return (count >= (config.minPointers ?? 1) && count <= (config.maxPointers ?? 10));
|
|
546
|
+
};
|
|
547
|
+
/**
|
|
548
|
+
* One ACTIVE gesture's reaction to the pointer moving, from whichever
|
|
549
|
+
* channel reached it: `onResponderMove` for the lock's holder,
|
|
550
|
+
* `onTouchMove` for a recognizer that never takes it.
|
|
551
|
+
*
|
|
552
|
+
* Returns nothing; the gesture may have finalized itself on the way out.
|
|
553
|
+
*/
|
|
554
|
+
const advance = (event) => {
|
|
555
|
+
track(event);
|
|
556
|
+
const config = readConfig();
|
|
557
|
+
if ((config.shouldCancelWhenOutside === true && outsideBounds(event)) ||
|
|
558
|
+
// `LongPress` is the one kind that can be cancelled by movement it has
|
|
559
|
+
// already accepted: `maxDistance` keeps applying once the press has
|
|
560
|
+
// matured, and travelling past it while ACTIVE is a cancellation
|
|
561
|
+
// rather than a failure.
|
|
562
|
+
decider.shouldCancelWhileActive?.(viewOf(), config) === true) {
|
|
563
|
+
setState(GESTURE_STATE.CANCELLED);
|
|
564
|
+
finalizeAt(event, false);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const payload = payloadOf(event);
|
|
568
|
+
// The event that GRANTED the responder reaches here too — the system
|
|
569
|
+
// dispatches the move to whoever holds the lock after the handoff, in
|
|
570
|
+
// the same event. Emitting it would be an update of zero travel that
|
|
571
|
+
// `onActivate` has just reported the position of, and it would make
|
|
572
|
+
// upstream's rule that the FIRST change equals the translation vacuous:
|
|
573
|
+
// that first delta would always be zero. So the activation event is not
|
|
574
|
+
// an update, and the first real move is.
|
|
575
|
+
if (!runtime.hasEmittedUpdate &&
|
|
576
|
+
payload.translationX === 0 &&
|
|
577
|
+
payload.translationY === 0) {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
config.onUpdate?.(payload);
|
|
581
|
+
config.onChange?.(payload);
|
|
582
|
+
runtime.changeFromX = payload.translationX;
|
|
583
|
+
runtime.changeFromY = payload.translationY;
|
|
584
|
+
runtime.hasEmittedUpdate = true;
|
|
585
|
+
};
|
|
586
|
+
const handlers = {
|
|
587
|
+
// Always false. A pan that takes the interaction on press cannot honour
|
|
588
|
+
// an `activeOffset`, and letting a sibling or an ancestor win instead is
|
|
589
|
+
// the behaviour every offset knob exists to produce.
|
|
590
|
+
onStartShouldSetResponder: () => false,
|
|
591
|
+
onTouchStart: (event) => {
|
|
592
|
+
const config = readConfig();
|
|
593
|
+
if (!isEnabled() || outsideBounds(event)) {
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
// A tap sequence in progress carries its count across the press. The
|
|
597
|
+
// delay timer's existence IS the sequence — while it is armed the
|
|
598
|
+
// gesture is between taps, still BEGAN, and this press is the next one
|
|
599
|
+
// rather than a fresh gesture.
|
|
600
|
+
const continuing = runtime.delayTimer !== null && runtime.state === GESTURE_STATE.BEGAN;
|
|
601
|
+
const carriedTaps = continuing ? runtime.taps : 0;
|
|
602
|
+
clearDelayTimer();
|
|
603
|
+
runtime = newRuntime();
|
|
604
|
+
runtime.taps = carriedTaps;
|
|
605
|
+
const { pageX, pageY, timestamp } = event.nativeEvent;
|
|
606
|
+
runtime.pressTime = Date.now();
|
|
607
|
+
runtime.originX = pageX;
|
|
608
|
+
runtime.originY = pageY;
|
|
609
|
+
runtime.pressX = pageX;
|
|
610
|
+
runtime.pressY = pageY;
|
|
611
|
+
runtime.lastX = pageX;
|
|
612
|
+
runtime.lastY = pageY;
|
|
613
|
+
runtime.lastTime = timestamp;
|
|
614
|
+
countPointers(event);
|
|
615
|
+
setState(GESTURE_STATE.BEGAN);
|
|
616
|
+
// Recorded on the press, not on mount. That is the whole islands answer
|
|
617
|
+
// in one line: a gesture takes part in an interaction only when the
|
|
618
|
+
// interaction's pointer reaches it, so a relation naming a gesture in
|
|
619
|
+
// another `Root` — or one the pointer never went near — never has an
|
|
620
|
+
// occasion to apply and can never park anything for ever. Recording on
|
|
621
|
+
// mount would have made `requireExternalGestureToFail` across two
|
|
622
|
+
// islands a permanent deadlock. See docs/api.md.
|
|
623
|
+
env.orchestrator.record(participant);
|
|
624
|
+
// Upstream begins once per SEQUENCE, not once per tap: its `begin()` is
|
|
625
|
+
// reached only from the UNDETERMINED branch, so the second press of a
|
|
626
|
+
// double tap gets no second `onBegin`.
|
|
627
|
+
if (!continuing) {
|
|
628
|
+
config.onBegin?.(payloadOf(event));
|
|
629
|
+
}
|
|
630
|
+
config.onTouchesDown?.(touchEventOf(event, TOUCH_EVENT_TYPE.TOUCHES_DOWN), stateManager);
|
|
631
|
+
const timer = decider.timer?.(config) ?? null;
|
|
632
|
+
if (timer !== null) {
|
|
633
|
+
runtime.activationTimer = setTimeout(() => {
|
|
634
|
+
runtime.activationTimer = null;
|
|
635
|
+
if (runtime.state !== GESTURE_STATE.BEGAN) {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
runtime.timerElapsed = true;
|
|
639
|
+
if (timer.elapsed === "fail") {
|
|
640
|
+
// `Tap`'s `maxDuration`: the pointer is still down and the tap is
|
|
641
|
+
// already too slow. There is no event to fail with.
|
|
642
|
+
failNow();
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
if (readConfig().manualActivation === true) {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
// Asked rather than assumed: the timer maturing is one criterion
|
|
649
|
+
// among the kind's own, and `LongPress` also wants the right number
|
|
650
|
+
// of pointers down before it will take the gesture.
|
|
651
|
+
if (!decider.shouldActivate(viewOf(), readConfig())) {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
// THE OUT-OF-EVENT GRANT. Without it the gesture could not take the
|
|
655
|
+
// interaction until the pointer next moved — one frame late for a
|
|
656
|
+
// drag, and never for a press-and-hold that stays still. This is
|
|
657
|
+
// the single extension slice 1 makes to the responder model;
|
|
658
|
+
// docs/research/gestures.md records it with its reason.
|
|
659
|
+
decide();
|
|
660
|
+
}, timer.delay);
|
|
661
|
+
}
|
|
662
|
+
// A recognizer that never claims may activate on the press itself, and
|
|
663
|
+
// `Gesture.Native().shouldActivateOnStart(true)` is the shape that
|
|
664
|
+
// wants to: a native BUTTON takes the press at once rather than waiting
|
|
665
|
+
// to see whether the pointer travels.
|
|
666
|
+
//
|
|
667
|
+
// Deliberately not asked for the claiming kinds, and that is the same
|
|
668
|
+
// rule `onStartShouldSetResponder` states by always answering false — a
|
|
669
|
+
// gesture that grabbed the interaction on press cannot honour an
|
|
670
|
+
// `activeOffset`, and deciding late is what every offset knob is for.
|
|
671
|
+
// Nothing is being decided late here, because nothing is being taken.
|
|
672
|
+
if (!claimsResponder &&
|
|
673
|
+
config.manualActivation !== true &&
|
|
674
|
+
decider.shouldActivate(viewOf(), config)) {
|
|
675
|
+
decide();
|
|
676
|
+
}
|
|
677
|
+
},
|
|
678
|
+
onTouchMove: (event) => {
|
|
679
|
+
if (runtime.state === GESTURE_STATE.ACTIVE) {
|
|
680
|
+
// A gesture that does not hold the lock has no `onResponderMove`
|
|
681
|
+
// coming, so this is where its updates live. One that does is driven
|
|
682
|
+
// from there instead, and emitting here as well would double every
|
|
683
|
+
// update it reports.
|
|
684
|
+
//
|
|
685
|
+
// Two kinds of gesture end up here and the flag covers both: one that
|
|
686
|
+
// never takes the lock (`claimsResponder: false`), and one that would
|
|
687
|
+
// have but is ACTIVE alongside a gesture it is `Simultaneous` with.
|
|
688
|
+
// Without the second, `Simultaneous` would be a state with no events
|
|
689
|
+
// attached to it.
|
|
690
|
+
if (!runtime.hasResponder) {
|
|
691
|
+
advance(event);
|
|
692
|
+
}
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
if (runtime.state !== GESTURE_STATE.BEGAN) {
|
|
696
|
+
// A finished gesture is not tracked at all.
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
track(event);
|
|
700
|
+
const config = readConfig();
|
|
701
|
+
config.onTouchesMove?.(touchEventOf(event, TOUCH_EVENT_TYPE.TOUCHES_MOVE), stateManager);
|
|
702
|
+
const view = viewOf();
|
|
703
|
+
// Failure is tested before activation, and the comparisons differ —
|
|
704
|
+
// failure is strict, activation is not — so a translation sitting
|
|
705
|
+
// exactly on a bound activates rather than fails. That asymmetry is
|
|
706
|
+
// upstream's and is reproduced rather than tidied.
|
|
707
|
+
if (runtime.forcedFailure ||
|
|
708
|
+
!pointerCountAllowed(event) ||
|
|
709
|
+
// Upstream fails a BEGAN handler that leaves the view, not just an
|
|
710
|
+
// ACTIVE one — `onPointerLeave` splits exactly that way. It is the
|
|
711
|
+
// default for `Tap` and `LongPress`, which is why it surfaces here;
|
|
712
|
+
// `Pan` leaves the flag off unless an app sets it.
|
|
713
|
+
(config.shouldCancelWhenOutside === true && outsideBounds(event)) ||
|
|
714
|
+
decider.shouldFail(view, config)) {
|
|
715
|
+
fail(event);
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (config.manualActivation === true) {
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
if (decider.shouldActivate(view, config)) {
|
|
722
|
+
decide();
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
// The instant of decision. Everything above ran holding nothing.
|
|
726
|
+
onMoveShouldSetResponder: () => {
|
|
727
|
+
// `Native` never answers yes: winning here is what makes the platform
|
|
728
|
+
// claim the GTK sequence and suspend the scroller it is reporting on.
|
|
729
|
+
if (!claimsResponder) {
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
732
|
+
if (runtime.state !== GESTURE_STATE.BEGAN || !isEnabled()) {
|
|
733
|
+
return false;
|
|
734
|
+
}
|
|
735
|
+
// The orchestrator's answer and nothing else. Slice 1 asked the decider
|
|
736
|
+
// a second time from here, on the reasoning that a move arriving while
|
|
737
|
+
// another view held the responder had not reached the decision above —
|
|
738
|
+
// which was never true (system.ts dispatches the touch props BEFORE it
|
|
739
|
+
// negotiates, in both entry points) and is now actively wrong: a gesture
|
|
740
|
+
// parked behind `requireExternalGestureToFail` still meets its own
|
|
741
|
+
// criteria, and answering them here would let it take the lock it was
|
|
742
|
+
// told to wait for.
|
|
743
|
+
return runtime.authorized;
|
|
744
|
+
},
|
|
745
|
+
onResponderGrant: (event) => {
|
|
746
|
+
// The host holds the responder whoever asked for it — the recognizer's
|
|
747
|
+
// props are merged into the child's, so a child with responder props of
|
|
748
|
+
// its own can be the reason this fired.
|
|
749
|
+
runtime.hasResponder = true;
|
|
750
|
+
if (!runtime.authorized) {
|
|
751
|
+
// Not this gesture's grant. Becoming ACTIVE is the orchestrator's to
|
|
752
|
+
// authorize, and it has not.
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
enterActive(event.nativeEvent.pageX, event.nativeEvent.pageY);
|
|
756
|
+
const payload = payloadOf(event);
|
|
757
|
+
env.orchestrator.activated(participant);
|
|
758
|
+
readConfig().onActivate?.(payload);
|
|
759
|
+
endIfOneShot();
|
|
760
|
+
},
|
|
761
|
+
onResponderMove: (event) => {
|
|
762
|
+
if (runtime.state !== GESTURE_STATE.ACTIVE) {
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
advance(event);
|
|
766
|
+
},
|
|
767
|
+
onResponderRelease: (event) => {
|
|
768
|
+
runtime.hasResponder = false;
|
|
769
|
+
finalizeAt(event, true);
|
|
770
|
+
},
|
|
771
|
+
onResponderTerminate: (event) => {
|
|
772
|
+
runtime.hasResponder = false;
|
|
773
|
+
// The interaction went somewhere this registry cannot follow — an
|
|
774
|
+
// ancestor took the responder, the window lost focus, a scroller moved
|
|
775
|
+
// under it. Anything running alongside this gesture was riding on a lock
|
|
776
|
+
// that is gone, and there is no callback of its own coming to tell it.
|
|
777
|
+
env.orchestrator.interactionLost(participant);
|
|
778
|
+
if (runtime.state === GESTURE_STATE.UNDETERMINED) {
|
|
779
|
+
// Already over: this gesture cancelled itself while still holding the
|
|
780
|
+
// lock, and the system is telling the HOST the interaction ended.
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
setState(GESTURE_STATE.CANCELLED);
|
|
784
|
+
finalizeAt(event, false);
|
|
785
|
+
},
|
|
786
|
+
onTouchEnd: (event) => {
|
|
787
|
+
readConfig().onTouchesUp?.(touchEventOf(event, TOUCH_EVENT_TYPE.TOUCHES_UP), stateManager);
|
|
788
|
+
// The responder holder is finalized by `onResponderRelease`, which the
|
|
789
|
+
// system dispatches AFTER the touch props. A gesture that is ACTIVE
|
|
790
|
+
// without holding the lock — a `Native`, or a `Simultaneous` partner
|
|
791
|
+
// that lost the race for it — has no responder callback coming, and
|
|
792
|
+
// this is where its successful ending is.
|
|
793
|
+
if (runtime.state === GESTURE_STATE.ACTIVE) {
|
|
794
|
+
if (!runtime.hasResponder) {
|
|
795
|
+
finalizeAt(event, true);
|
|
796
|
+
}
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
if (runtime.state !== GESTURE_STATE.BEGAN) {
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
const config = readConfig();
|
|
803
|
+
runtime.taps += 1;
|
|
804
|
+
const outcome = decider.onRelease?.(viewOf(), config) ?? { kind: "fail" };
|
|
805
|
+
if (outcome.kind === "hold") {
|
|
806
|
+
// `Manual`: the pointer lifting decides nothing, because nothing about
|
|
807
|
+
// this gesture is decided by the pointer. It stays BEGAN, holding no
|
|
808
|
+
// lock and running no timer, until the app says otherwise — and the
|
|
809
|
+
// `onTouchesUp` above has just given it the state manager to say it
|
|
810
|
+
// with. The next press resets it, as it resets any BEGAN gesture.
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
if (outcome.kind === "await") {
|
|
814
|
+
// Not over. The gesture stays BEGAN with its count intact, holding no
|
|
815
|
+
// lock, and the next press continues it — or this timer fails it.
|
|
816
|
+
clearActivationTimer();
|
|
817
|
+
runtime.delayTimer = setTimeout(() => {
|
|
818
|
+
runtime.delayTimer = null;
|
|
819
|
+
if (runtime.state === GESTURE_STATE.BEGAN) {
|
|
820
|
+
failNow();
|
|
821
|
+
}
|
|
822
|
+
}, outcome.delay);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
if (outcome.kind === "fail") {
|
|
826
|
+
// `fail()` rather than a bare `finalizeAt(..., false)`, because the
|
|
827
|
+
// payload has to carry the state the gesture ENDED in and this path
|
|
828
|
+
// was reporting BEGAN. Upstream's `fail()` moves to FAILED first, and
|
|
829
|
+
// a consumer comparing `event.state` against `State.FAILED` — which is
|
|
830
|
+
// exactly what two of the four target libraries do with `State` — was
|
|
831
|
+
// being told the gesture was still deciding. `Fling` made it visible
|
|
832
|
+
// because failing on the lift is its ordinary ending rather than an
|
|
833
|
+
// edge case, but `Tap` and `Pan` were reporting it too.
|
|
834
|
+
fail(event);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
// `Tap` activating: this is the one kind whose activation criterion is
|
|
838
|
+
// the pointer coming back UP, so the grant is asked for here rather than
|
|
839
|
+
// from a move. The responder system dispatches the touch props before it
|
|
840
|
+
// clears the session, so the out-of-event channel still has an
|
|
841
|
+
// interaction to negotiate over — and the `onResponderRelease` that
|
|
842
|
+
// follows in the same event is what ends the gesture with success.
|
|
843
|
+
clearActivationTimer();
|
|
844
|
+
decide();
|
|
845
|
+
if (stateNow() === GESTURE_STATE.ACTIVE) {
|
|
846
|
+
if (!runtime.hasResponder) {
|
|
847
|
+
// It activated alongside a gesture that holds the lock, so no
|
|
848
|
+
// `onResponderRelease` is coming for it either.
|
|
849
|
+
finalizeAt(event, true);
|
|
850
|
+
}
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
// Nobody granted it — an ancestor holds the interaction, the gesture is
|
|
854
|
+
// not on its path, or the orchestrator parked it behind another. There
|
|
855
|
+
// is no release callback coming, and a discrete gesture whose pointer
|
|
856
|
+
// has lifted cannot be woken later: the interaction it would have taken
|
|
857
|
+
// is over.
|
|
858
|
+
finalizeAt(event, false);
|
|
859
|
+
},
|
|
860
|
+
onTouchCancel: (event) => {
|
|
861
|
+
readConfig().onTouchesCancel?.(touchEventOf(event, TOUCH_EVENT_TYPE.TOUCHES_CANCEL), stateManager);
|
|
862
|
+
// THIS is where a stolen sequence lands, and why the responder system
|
|
863
|
+
// learned to tell `->DENIED` from an ordinary `drag-end`
|
|
864
|
+
// (`responder/use-responder.ts`). A native ancestor claiming mid-drag
|
|
865
|
+
// used to arrive here as `onTouchEnd`, which for an ACTIVE `Native`
|
|
866
|
+
// gesture would have reported a clean, successful ending of a drag that
|
|
867
|
+
// was actually taken away.
|
|
868
|
+
//
|
|
869
|
+
// An ACTIVE gesture that DID hold the lock is cancelled by
|
|
870
|
+
// `onResponderTerminate`, which the system dispatches after these touch
|
|
871
|
+
// props — finalizing here too would end it twice.
|
|
872
|
+
if (runtime.state === GESTURE_STATE.BEGAN ||
|
|
873
|
+
(!runtime.hasResponder && runtime.state === GESTURE_STATE.ACTIVE)) {
|
|
874
|
+
setState(GESTURE_STATE.CANCELLED);
|
|
875
|
+
finalizeAt(event, false);
|
|
876
|
+
}
|
|
877
|
+
},
|
|
878
|
+
};
|
|
879
|
+
/**
|
|
880
|
+
* The second entry surface: a GTK controller, for the kinds the pointer
|
|
881
|
+
* stream cannot carry.
|
|
882
|
+
*
|
|
883
|
+
* THREE sources arrive here and the machine cannot tell them apart, which is
|
|
884
|
+
* the point — `GtkGestureZoom`/`GtkGestureRotate` for `Pinch`/`Rotation`,
|
|
885
|
+
* `GtkEventControllerMotion` for `Hover`, `GtkGestureStylus` for
|
|
886
|
+
* `ForceTouch`. What they have in common is everything that matters: no
|
|
887
|
+
* button goes down, so there is no GTK sequence, no responder session and
|
|
888
|
+
* nothing to claim; and the numbers they carry are already in the shape the
|
|
889
|
+
* payload wants.
|
|
890
|
+
*
|
|
891
|
+
* Everything below the entry point is the machine above, unchanged. `begin`
|
|
892
|
+
* does what `onTouchStart` does (record with the orchestrator, BEGAN,
|
|
893
|
+
* `onBegin`), `update` does what `onTouchMove` and `advance` do between them
|
|
894
|
+
* (test the kind's predicate, `decide()`, emit), and `end` does what
|
|
895
|
+
* `onTouchEnd` does. `decide()` is the same call into the same
|
|
896
|
+
* `tryActivate`, and `finalize` is the same one exit.
|
|
897
|
+
*
|
|
898
|
+
* ONE DIFFERENCE FROM `Pan`, and it is upstream's: `scale`, `rotation` and
|
|
899
|
+
* `force` are NOT re-based when the gesture activates. Translation is,
|
|
900
|
+
* because a pan that reported its `activeOffset` as travel would jump the
|
|
901
|
+
* content by the threshold on every drag. Upstream's `scale` is not —
|
|
902
|
+
* `resetProgress()` resets it only while the handler is not yet ACTIVE, so
|
|
903
|
+
* the value that crossed the activation threshold is the value the first
|
|
904
|
+
* `onUpdate` reports. GTK measures from its own recognition point for the
|
|
905
|
+
* same reason, so the two origins already agree and nothing is re-derived.
|
|
906
|
+
*/
|
|
907
|
+
const controller = {
|
|
908
|
+
begin: (sample) => {
|
|
909
|
+
if (!isEnabled() || runtime.state !== GESTURE_STATE.UNDETERMINED) {
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
const bounds = env.boundsInWindow();
|
|
913
|
+
const pageX = (bounds?.x ?? 0) + sample.x;
|
|
914
|
+
const pageY = (bounds?.y ?? 0) + sample.y;
|
|
915
|
+
if (bounds !== null &&
|
|
916
|
+
!contains(hitSlopRect(bounds, readConfig().hitSlop), pageX, pageY)) {
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
runtime = newRuntime();
|
|
920
|
+
runtime.pressTime = Date.now();
|
|
921
|
+
runtime.lastSampleTime = runtime.pressTime;
|
|
922
|
+
runtime.originX = pageX;
|
|
923
|
+
runtime.originY = pageY;
|
|
924
|
+
runtime.pressX = pageX;
|
|
925
|
+
runtime.pressY = pageY;
|
|
926
|
+
runtime.lastX = pageX;
|
|
927
|
+
runtime.lastY = pageY;
|
|
928
|
+
runtime.scale = sample.scale ?? 1;
|
|
929
|
+
runtime.rotation = sample.rotation ?? 0;
|
|
930
|
+
runtime.force = sample.force ?? 0;
|
|
931
|
+
runtime.pointerCount = sample.pointers;
|
|
932
|
+
runtime.maxPointerCount = sample.pointers;
|
|
933
|
+
setState(GESTURE_STATE.BEGAN);
|
|
934
|
+
// On the gesture, not on mount — the same rule and the same line as
|
|
935
|
+
// `onTouchStart`, and the same islands answer follows from it.
|
|
936
|
+
env.orchestrator.record(participant);
|
|
937
|
+
readConfig().onBegin?.(payloadNow());
|
|
938
|
+
// `Hover` activates on the crossing itself: upstream's
|
|
939
|
+
// `onPointerMoveOver` calls `begin()` and `activate()` one after the
|
|
940
|
+
// other, because a pointer that is over the view IS the gesture and
|
|
941
|
+
// there is nothing further to wait for. Asked through the decider rather
|
|
942
|
+
// than flagged, so it is the same question `onTouchStart` asks of a
|
|
943
|
+
// `Native` that activates on the press.
|
|
944
|
+
if (readConfig().manualActivation !== true &&
|
|
945
|
+
decider.shouldActivate(viewOf(), readConfig())) {
|
|
946
|
+
decide();
|
|
947
|
+
}
|
|
948
|
+
},
|
|
949
|
+
update: (sample) => {
|
|
950
|
+
if (runtime.state !== GESTURE_STATE.BEGAN &&
|
|
951
|
+
runtime.state !== GESTURE_STATE.ACTIVE) {
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
const scale = sample.scale ?? 1;
|
|
955
|
+
const rotation = sample.rotation ?? 0;
|
|
956
|
+
const now = Date.now();
|
|
957
|
+
const elapsed = now - runtime.lastSampleTime;
|
|
958
|
+
if (elapsed > 0) {
|
|
959
|
+
// Per SECOND, which upstream documents and neither of its web paths
|
|
960
|
+
// computes. See `GestureEventPayload.velocity`.
|
|
961
|
+
runtime.scaleVelocity = ((scale - runtime.scale) / elapsed) * 1000;
|
|
962
|
+
runtime.rotationVelocity =
|
|
963
|
+
((rotation - runtime.rotation) / elapsed) * 1000;
|
|
964
|
+
runtime.lastSampleTime = now;
|
|
965
|
+
}
|
|
966
|
+
runtime.scale = scale;
|
|
967
|
+
runtime.rotation = rotation;
|
|
968
|
+
runtime.force = sample.force ?? 0;
|
|
969
|
+
runtime.pointerCount = sample.pointers;
|
|
970
|
+
runtime.maxPointerCount = Math.max(runtime.maxPointerCount, sample.pointers);
|
|
971
|
+
const bounds = env.boundsInWindow();
|
|
972
|
+
runtime.lastX = (bounds?.x ?? 0) + sample.x;
|
|
973
|
+
runtime.lastY = (bounds?.y ?? 0) + sample.y;
|
|
974
|
+
const config = readConfig();
|
|
975
|
+
// A kind that can be cancelled by its own numbers while ACTIVE gets the
|
|
976
|
+
// same treatment `advance` gives `LongPress` — `ForceTouch` is the one
|
|
977
|
+
// that wants it, because `maxForce` keeps applying after activation.
|
|
978
|
+
if (runtime.state === GESTURE_STATE.ACTIVE &&
|
|
979
|
+
decider.shouldCancelWhileActive?.(viewOf(), config) === true) {
|
|
980
|
+
setState(GESTURE_STATE.CANCELLED);
|
|
981
|
+
finalize(payloadNow, false);
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
if (runtime.state === GESTURE_STATE.BEGAN) {
|
|
985
|
+
if (decider.shouldFail(viewOf(), config)) {
|
|
986
|
+
failNow();
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
if (config.manualActivation === true) {
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
if (!decider.shouldActivate(viewOf(), config)) {
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
// The same request into the same loop every other kind makes. It can
|
|
996
|
+
// come back as an activation, as a parking behind
|
|
997
|
+
// `requireExternalGestureToFail`, or as a cancellation.
|
|
998
|
+
decide();
|
|
999
|
+
if (stateNow() !== GESTURE_STATE.ACTIVE) {
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
// `onActivate` has just reported this sample; emitting it again as an
|
|
1003
|
+
// update would double it, exactly as `advance` refuses the event that
|
|
1004
|
+
// granted the responder.
|
|
1005
|
+
runtime.changeFromScale = runtime.scale;
|
|
1006
|
+
runtime.changeFromRotation = runtime.rotation;
|
|
1007
|
+
runtime.changeFromForce = runtime.force;
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
const payload = payloadNow();
|
|
1011
|
+
config.onUpdate?.(payload);
|
|
1012
|
+
config.onChange?.(payload);
|
|
1013
|
+
runtime.changeFromX = payload.translationX;
|
|
1014
|
+
runtime.changeFromY = payload.translationY;
|
|
1015
|
+
runtime.changeFromScale = runtime.scale;
|
|
1016
|
+
runtime.changeFromRotation = runtime.rotation;
|
|
1017
|
+
runtime.changeFromForce = runtime.force;
|
|
1018
|
+
runtime.hasEmittedUpdate = true;
|
|
1019
|
+
},
|
|
1020
|
+
/** GTK's `end`: a success if it activated, a failure if it never did. */
|
|
1021
|
+
end: () => {
|
|
1022
|
+
if (runtime.state === GESTURE_STATE.ACTIVE) {
|
|
1023
|
+
finalize(payloadNow, true);
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
if (runtime.state === GESTURE_STATE.BEGAN) {
|
|
1027
|
+
setState(GESTURE_STATE.FAILED);
|
|
1028
|
+
finalize(payloadNow, false);
|
|
1029
|
+
}
|
|
1030
|
+
},
|
|
1031
|
+
/** GTK's `cancel`: the sequence was taken away. */
|
|
1032
|
+
cancel: () => {
|
|
1033
|
+
if (runtime.state === GESTURE_STATE.BEGAN ||
|
|
1034
|
+
runtime.state === GESTURE_STATE.ACTIVE) {
|
|
1035
|
+
setState(GESTURE_STATE.CANCELLED);
|
|
1036
|
+
finalize(payloadNow, false);
|
|
1037
|
+
}
|
|
1038
|
+
},
|
|
1039
|
+
};
|
|
1040
|
+
const drivenByController = decider.source !== undefined && decider.source !== "pointer";
|
|
1041
|
+
return {
|
|
1042
|
+
// Exactly one surface is live. A controller kind that also answered the
|
|
1043
|
+
// touch props would begin on a mouse press — `onBegin` on every click, on
|
|
1044
|
+
// a gesture that cannot happen — and a pointer kind with a live controller
|
|
1045
|
+
// channel would pan on a pinch.
|
|
1046
|
+
handlers: drivenByController ? {} : handlers,
|
|
1047
|
+
controller: drivenByController ? controller : null,
|
|
1048
|
+
participant,
|
|
1049
|
+
dispose: () => {
|
|
1050
|
+
clearActivationTimer();
|
|
1051
|
+
clearDelayTimer();
|
|
1052
|
+
env.orchestrator.forget(participant);
|
|
1053
|
+
},
|
|
1054
|
+
};
|
|
1055
|
+
};
|
|
1056
|
+
//# sourceMappingURL=recognizer.js.map
|