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.
Files changed (340) hide show
  1. package/README.md +5 -2
  2. package/dist/aliases/index.d.ts +84 -0
  3. package/dist/aliases/index.js +208 -0
  4. package/dist/aliases/index.js.map +1 -0
  5. package/dist/animated/index.d.ts +2 -0
  6. package/dist/animated/index.js +5 -0
  7. package/dist/animated/index.js.map +1 -1
  8. package/dist/animated/types.d.ts +1 -0
  9. package/dist/animated/types.js.map +1 -1
  10. package/dist/animated/value-animation.js +17 -4
  11. package/dist/animated/value-animation.js.map +1 -1
  12. package/dist/apis/host.gtkx.js +9 -12
  13. package/dist/apis/host.gtkx.js.map +1 -1
  14. package/dist/apis/index.d.ts +4 -2
  15. package/dist/apis/index.js +2 -0
  16. package/dist/apis/index.js.map +1 -1
  17. package/dist/apis/keyboard.d.ts +60 -0
  18. package/dist/apis/keyboard.js +63 -0
  19. package/dist/apis/keyboard.js.map +1 -0
  20. package/dist/apis/log-box.d.ts +15 -0
  21. package/dist/apis/log-box.js +46 -0
  22. package/dist/apis/log-box.js.map +1 -0
  23. package/dist/apis/platform.d.ts +18 -1
  24. package/dist/apis/platform.js.map +1 -1
  25. package/dist/common/index.d.ts +0 -1
  26. package/dist/common/index.js +24 -1
  27. package/dist/common/index.js.map +1 -1
  28. package/dist/components/animated.d.ts +70 -6
  29. package/dist/components/animated.js +559 -46
  30. package/dist/components/animated.js.map +1 -1
  31. package/dist/components/driven-size.d.ts +27 -0
  32. package/dist/components/driven-size.js +85 -0
  33. package/dist/components/driven-size.js.map +1 -0
  34. package/dist/components/find-node-handle.d.ts +11 -0
  35. package/dist/components/find-node-handle.js +53 -0
  36. package/dist/components/find-node-handle.js.map +1 -0
  37. package/dist/components/flat-list.d.ts +3 -2
  38. package/dist/components/flat-list.js.map +1 -1
  39. package/dist/components/frame-scheduler.d.ts +16 -0
  40. package/dist/components/frame-scheduler.js +37 -0
  41. package/dist/components/frame-scheduler.js.map +1 -0
  42. package/dist/components/image.d.ts +6 -1
  43. package/dist/components/image.js +15 -4
  44. package/dist/components/image.js.map +1 -1
  45. package/dist/components/index.d.ts +7 -5
  46. package/dist/components/index.js +5 -3
  47. package/dist/components/index.js.map +1 -1
  48. package/dist/components/measure.d.ts +51 -1
  49. package/dist/components/measure.js +101 -2
  50. package/dist/components/measure.js.map +1 -1
  51. package/dist/components/pressable.d.ts +61 -1
  52. package/dist/components/pressable.js +151 -4
  53. package/dist/components/pressable.js.map +1 -1
  54. package/dist/components/rect-store.d.ts +58 -0
  55. package/dist/components/rect-store.js +124 -3
  56. package/dist/components/rect-store.js.map +1 -1
  57. package/dist/components/root.js +6 -1
  58. package/dist/components/root.js.map +1 -1
  59. package/dist/components/scroll-phase.d.ts +35 -0
  60. package/dist/components/scroll-phase.js +26 -0
  61. package/dist/components/scroll-phase.js.map +1 -0
  62. package/dist/components/scroll-view.d.ts +39 -3
  63. package/dist/components/scroll-view.js +378 -43
  64. package/dist/components/scroll-view.js.map +1 -1
  65. package/dist/components/text-input.js +9 -25
  66. package/dist/components/text-input.js.map +1 -1
  67. package/dist/components/text.d.ts +6 -2
  68. package/dist/components/text.js +43 -8
  69. package/dist/components/text.js.map +1 -1
  70. package/dist/components/use-focus.d.ts +27 -0
  71. package/dist/components/use-focus.js +116 -0
  72. package/dist/components/use-focus.js.map +1 -0
  73. package/dist/components/use-layout-child.d.ts +13 -1
  74. package/dist/components/use-layout-child.js +188 -5
  75. package/dist/components/use-layout-child.js.map +1 -1
  76. package/dist/components/view.d.ts +5 -2
  77. package/dist/components/view.js +18 -13
  78. package/dist/components/view.js.map +1 -1
  79. package/dist/components/virtualized-list.d.ts +45 -5
  80. package/dist/components/virtualized-list.js +90 -24
  81. package/dist/components/virtualized-list.js.map +1 -1
  82. package/dist/components/wheel-scroll-session.d.ts +7 -0
  83. package/dist/components/wheel-scroll-session.js +53 -0
  84. package/dist/components/wheel-scroll-session.js.map +1 -0
  85. package/dist/components/widget-retention.d.ts +35 -0
  86. package/dist/components/widget-retention.js +169 -0
  87. package/dist/components/widget-retention.js.map +1 -0
  88. package/dist/contracts.d.ts +4 -0
  89. package/dist/contracts.js.map +1 -1
  90. package/dist/dnd/context.d.ts +69 -0
  91. package/dist/dnd/context.js +149 -0
  92. package/dist/dnd/context.js.map +1 -0
  93. package/dist/dnd/draggable.d.ts +59 -0
  94. package/dist/dnd/draggable.js +149 -0
  95. package/dist/dnd/draggable.js.map +1 -0
  96. package/dist/dnd/droppable.d.ts +24 -0
  97. package/dist/dnd/droppable.js +74 -0
  98. package/dist/dnd/droppable.js.map +1 -0
  99. package/dist/dnd/gtk-controllers.d.ts +39 -0
  100. package/dist/dnd/gtk-controllers.js +110 -0
  101. package/dist/dnd/gtk-controllers.js.map +1 -0
  102. package/dist/dnd/index.d.ts +6 -0
  103. package/dist/dnd/index.js +31 -0
  104. package/dist/dnd/index.js.map +1 -0
  105. package/dist/dnd/order.d.ts +7 -0
  106. package/dist/dnd/order.js +20 -0
  107. package/dist/dnd/order.js.map +1 -0
  108. package/dist/dnd/payload.d.ts +14 -0
  109. package/dist/dnd/payload.js +41 -0
  110. package/dist/dnd/payload.js.map +1 -0
  111. package/dist/dnd/sortable.d.ts +49 -0
  112. package/dist/dnd/sortable.js +307 -0
  113. package/dist/dnd/sortable.js.map +1 -0
  114. package/dist/dnd/types.d.ts +290 -0
  115. package/dist/dnd/types.js +25 -0
  116. package/dist/dnd/types.js.map +1 -0
  117. package/dist/gesture-handler-compat/builder.d.ts +251 -0
  118. package/dist/gesture-handler-compat/builder.js +513 -0
  119. package/dist/gesture-handler-compat/builder.js.map +1 -0
  120. package/dist/gesture-handler-compat/composition.d.ts +23 -0
  121. package/dist/gesture-handler-compat/composition.js +111 -0
  122. package/dist/gesture-handler-compat/composition.js.map +1 -0
  123. package/dist/gesture-handler-compat/deciders.d.ts +3 -0
  124. package/dist/gesture-handler-compat/deciders.js +47 -0
  125. package/dist/gesture-handler-compat/deciders.js.map +1 -0
  126. package/dist/gesture-handler-compat/detector-runtime.d.ts +44 -0
  127. package/dist/gesture-handler-compat/detector-runtime.js +374 -0
  128. package/dist/gesture-handler-compat/detector-runtime.js.map +1 -0
  129. package/dist/gesture-handler-compat/detector.d.ts +22 -0
  130. package/dist/gesture-handler-compat/detector.js +104 -0
  131. package/dist/gesture-handler-compat/detector.js.map +1 -0
  132. package/dist/gesture-handler-compat/fling.d.ts +24 -0
  133. package/dist/gesture-handler-compat/fling.js +123 -0
  134. package/dist/gesture-handler-compat/fling.js.map +1 -0
  135. package/dist/gesture-handler-compat/force-touch.d.ts +13 -0
  136. package/dist/gesture-handler-compat/force-touch.js +40 -0
  137. package/dist/gesture-handler-compat/force-touch.js.map +1 -0
  138. package/dist/gesture-handler-compat/hooks.d.ts +157 -0
  139. package/dist/gesture-handler-compat/hooks.js +268 -0
  140. package/dist/gesture-handler-compat/hooks.js.map +1 -0
  141. package/dist/gesture-handler-compat/hover.d.ts +2 -0
  142. package/dist/gesture-handler-compat/hover.js +22 -0
  143. package/dist/gesture-handler-compat/hover.js.map +1 -0
  144. package/dist/gesture-handler-compat/index.d.ts +98 -0
  145. package/dist/gesture-handler-compat/index.js +255 -0
  146. package/dist/gesture-handler-compat/index.js.map +1 -0
  147. package/dist/gesture-handler-compat/long-press.d.ts +6 -0
  148. package/dist/gesture-handler-compat/long-press.js +21 -0
  149. package/dist/gesture-handler-compat/long-press.js.map +1 -0
  150. package/dist/gesture-handler-compat/manual.d.ts +2 -0
  151. package/dist/gesture-handler-compat/manual.js +18 -0
  152. package/dist/gesture-handler-compat/manual.js.map +1 -0
  153. package/dist/gesture-handler-compat/native.d.ts +7 -0
  154. package/dist/gesture-handler-compat/native.js +34 -0
  155. package/dist/gesture-handler-compat/native.js.map +1 -0
  156. package/dist/gesture-handler-compat/orchestrator.d.ts +77 -0
  157. package/dist/gesture-handler-compat/orchestrator.js +284 -0
  158. package/dist/gesture-handler-compat/orchestrator.js.map +1 -0
  159. package/dist/gesture-handler-compat/pan.d.ts +23 -0
  160. package/dist/gesture-handler-compat/pan.js +116 -0
  161. package/dist/gesture-handler-compat/pan.js.map +1 -0
  162. package/dist/gesture-handler-compat/recognizer.d.ts +268 -0
  163. package/dist/gesture-handler-compat/recognizer.js +1056 -0
  164. package/dist/gesture-handler-compat/recognizer.js.map +1 -0
  165. package/dist/gesture-handler-compat/relations.d.ts +43 -0
  166. package/dist/gesture-handler-compat/relations.js +128 -0
  167. package/dist/gesture-handler-compat/relations.js.map +1 -0
  168. package/dist/gesture-handler-compat/tap.d.ts +6 -0
  169. package/dist/gesture-handler-compat/tap.js +29 -0
  170. package/dist/gesture-handler-compat/tap.js.map +1 -0
  171. package/dist/gesture-handler-compat/touchpad.d.ts +41 -0
  172. package/dist/gesture-handler-compat/touchpad.js +64 -0
  173. package/dist/gesture-handler-compat/touchpad.js.map +1 -0
  174. package/dist/gesture-handler-compat/types.d.ts +441 -0
  175. package/dist/gesture-handler-compat/types.js +167 -0
  176. package/dist/gesture-handler-compat/types.js.map +1 -0
  177. package/dist/gtk/controllers.d.ts +39 -0
  178. package/dist/gtk/controllers.js +92 -0
  179. package/dist/gtk/controllers.js.map +1 -0
  180. package/dist/gtk/index.d.ts +2 -1
  181. package/dist/gtk/index.js +7 -1
  182. package/dist/gtk/index.js.map +1 -1
  183. package/dist/gtkx/bridge/geometry.js +6 -4
  184. package/dist/gtkx/bridge/geometry.js.map +1 -1
  185. package/dist/gtkx/bridge/index.d.ts +5 -3
  186. package/dist/gtkx/bridge/index.js +6 -3
  187. package/dist/gtkx/bridge/index.js.map +1 -1
  188. package/dist/gtkx/bridge/slot-portal.js.map +1 -1
  189. package/dist/gtkx/bridge/use-signal.js +1 -1
  190. package/dist/gtkx/bridge/use-signal.js.map +1 -1
  191. package/dist/gtkx/bridge/view-box.d.ts +56 -0
  192. package/dist/gtkx/bridge/view-box.js +394 -9
  193. package/dist/gtkx/bridge/view-box.js.map +1 -1
  194. package/dist/gtkx/bridge/widget-css.d.ts +17 -0
  195. package/dist/gtkx/bridge/widget-css.js +72 -0
  196. package/dist/gtkx/bridge/widget-css.js.map +1 -0
  197. package/dist/index.d.ts +3 -3
  198. package/dist/index.js +2 -2
  199. package/dist/index.js.map +1 -1
  200. package/dist/layout/driven-size.d.ts +12 -0
  201. package/dist/layout/driven-size.js +70 -0
  202. package/dist/layout/driven-size.js.map +1 -0
  203. package/dist/layout/engine.d.ts +17 -1
  204. package/dist/layout/engine.js +3 -20
  205. package/dist/layout/engine.js.map +1 -1
  206. package/dist/layout/node.d.ts +13 -0
  207. package/dist/layout/node.js +14 -0
  208. package/dist/layout/node.js.map +1 -1
  209. package/dist/layout/yoga.d.ts +3 -2
  210. package/dist/layout/yoga.js +5 -2
  211. package/dist/layout/yoga.js.map +1 -1
  212. package/dist/mcp/data/generated.d.ts +257 -60
  213. package/dist/mcp/data/generated.js +306 -62
  214. package/dist/mcp/data/generated.js.map +1 -1
  215. package/dist/metro/index.d.ts +10 -0
  216. package/dist/metro/index.js +21 -12
  217. package/dist/metro/index.js.map +1 -1
  218. package/dist/navigation/sidebar.d.ts +16 -0
  219. package/dist/navigation/sidebar.js +65 -1
  220. package/dist/navigation/sidebar.js.map +1 -1
  221. package/dist/reanimated-compat/animated-ref.d.ts +32 -0
  222. package/dist/reanimated-compat/animated-ref.js +57 -0
  223. package/dist/reanimated-compat/animated-ref.js.map +1 -0
  224. package/dist/reanimated-compat/animation.d.ts +177 -0
  225. package/dist/reanimated-compat/animation.js +540 -0
  226. package/dist/reanimated-compat/animation.js.map +1 -0
  227. package/dist/reanimated-compat/color.d.ts +28 -0
  228. package/dist/reanimated-compat/color.js +249 -0
  229. package/dist/reanimated-compat/color.js.map +1 -0
  230. package/dist/reanimated-compat/decay.d.ts +39 -0
  231. package/dist/reanimated-compat/decay.js +96 -0
  232. package/dist/reanimated-compat/decay.js.map +1 -0
  233. package/dist/reanimated-compat/easing.d.ts +30 -0
  234. package/dist/reanimated-compat/easing.js +76 -0
  235. package/dist/reanimated-compat/easing.js.map +1 -0
  236. package/dist/reanimated-compat/hooks.d.ts +14 -0
  237. package/dist/reanimated-compat/hooks.js +217 -0
  238. package/dist/reanimated-compat/hooks.js.map +1 -0
  239. package/dist/reanimated-compat/index.d.ts +258 -0
  240. package/dist/reanimated-compat/index.js +464 -0
  241. package/dist/reanimated-compat/index.js.map +1 -0
  242. package/dist/reanimated-compat/interpolation.d.ts +18 -0
  243. package/dist/reanimated-compat/interpolation.js +122 -0
  244. package/dist/reanimated-compat/interpolation.js.map +1 -0
  245. package/dist/reanimated-compat/layout-animation-config.d.ts +43 -0
  246. package/dist/reanimated-compat/layout-animation-config.js +78 -0
  247. package/dist/reanimated-compat/layout-animation-config.js.map +1 -0
  248. package/dist/reanimated-compat/layout-animation-presets.d.ts +117 -0
  249. package/dist/reanimated-compat/layout-animation-presets.js +498 -0
  250. package/dist/reanimated-compat/layout-animation-presets.js.map +1 -0
  251. package/dist/reanimated-compat/layout-animation-runtime.d.ts +21 -0
  252. package/dist/reanimated-compat/layout-animation-runtime.js +311 -0
  253. package/dist/reanimated-compat/layout-animation-runtime.js.map +1 -0
  254. package/dist/reanimated-compat/layout-animation-view.d.ts +27 -0
  255. package/dist/reanimated-compat/layout-animation-view.js +266 -0
  256. package/dist/reanimated-compat/layout-animation-view.js.map +1 -0
  257. package/dist/reanimated-compat/layout-animation.d.ts +171 -0
  258. package/dist/reanimated-compat/layout-animation.js +469 -0
  259. package/dist/reanimated-compat/layout-animation.js.map +1 -0
  260. package/dist/reanimated-compat/layout-transitions.d.ts +83 -0
  261. package/dist/reanimated-compat/layout-transitions.js +350 -0
  262. package/dist/reanimated-compat/layout-transitions.js.map +1 -0
  263. package/dist/reanimated-compat/mutable.d.ts +29 -0
  264. package/dist/reanimated-compat/mutable.js +139 -0
  265. package/dist/reanimated-compat/mutable.js.map +1 -0
  266. package/dist/reanimated-compat/props.d.ts +18 -0
  267. package/dist/reanimated-compat/props.js +111 -0
  268. package/dist/reanimated-compat/props.js.map +1 -0
  269. package/dist/reanimated-compat/scroll-handler.d.ts +119 -0
  270. package/dist/reanimated-compat/scroll-handler.js +274 -0
  271. package/dist/reanimated-compat/scroll-handler.js.map +1 -0
  272. package/dist/reanimated-compat/scroll-offset.d.ts +12 -0
  273. package/dist/reanimated-compat/scroll-offset.js +104 -0
  274. package/dist/reanimated-compat/scroll-offset.js.map +1 -0
  275. package/dist/reanimated-compat/style.d.ts +90 -0
  276. package/dist/reanimated-compat/style.js +452 -0
  277. package/dist/reanimated-compat/style.js.map +1 -0
  278. package/dist/reanimated-compat/test-timers.d.ts +28 -0
  279. package/dist/reanimated-compat/test-timers.js +136 -0
  280. package/dist/reanimated-compat/test-timers.js.map +1 -0
  281. package/dist/reanimated-compat/threads.d.ts +13 -0
  282. package/dist/reanimated-compat/threads.js +41 -0
  283. package/dist/reanimated-compat/threads.js.map +1 -0
  284. package/dist/reanimated-compat/tracking.d.ts +41 -0
  285. package/dist/reanimated-compat/tracking.js +106 -0
  286. package/dist/reanimated-compat/tracking.js.map +1 -0
  287. package/dist/reanimated-compat/updater-animations.d.ts +36 -0
  288. package/dist/reanimated-compat/updater-animations.js +273 -0
  289. package/dist/reanimated-compat/updater-animations.js.map +1 -0
  290. package/dist/responder/system.d.ts +80 -7
  291. package/dist/responder/system.js +143 -23
  292. package/dist/responder/system.js.map +1 -1
  293. package/dist/responder/use-responder.d.ts +11 -0
  294. package/dist/responder/use-responder.js +225 -6
  295. package/dist/responder/use-responder.js.map +1 -1
  296. package/dist/runner/host-dev.js +1 -1
  297. package/dist/runner/host-dev.js.map +1 -1
  298. package/dist/runner/host.js +1 -1
  299. package/dist/runner/host.js.map +1 -1
  300. package/dist/runner/index.js +3 -3
  301. package/dist/runner/index.js.map +1 -1
  302. package/dist/sea/gtkx-config-module.js +1 -1
  303. package/dist/sea/gtkx-config-module.js.map +1 -1
  304. package/dist/style/absolute-insets.d.ts +26 -0
  305. package/dist/style/absolute-insets.js +113 -0
  306. package/dist/style/absolute-insets.js.map +1 -0
  307. package/dist/style/animated-size.d.ts +25 -0
  308. package/dist/style/animated-size.js +225 -0
  309. package/dist/style/animated-size.js.map +1 -0
  310. package/dist/style/colors.d.ts +17 -0
  311. package/dist/style/colors.js +25 -6
  312. package/dist/style/colors.js.map +1 -1
  313. package/dist/style/imperative-css.d.ts +21 -0
  314. package/dist/style/imperative-css.js +63 -0
  315. package/dist/style/imperative-css.js.map +1 -0
  316. package/dist/style/index.d.ts +2 -1
  317. package/dist/style/index.js +2 -1
  318. package/dist/style/index.js.map +1 -1
  319. package/dist/style/split-style.js +5 -0
  320. package/dist/style/split-style.js.map +1 -1
  321. package/dist/testing/index.js.map +1 -1
  322. package/dist/unsupported-export.d.ts +8 -0
  323. package/dist/unsupported-export.js +66 -0
  324. package/dist/unsupported-export.js.map +1 -0
  325. package/dist/vite/index.d.ts +21 -8
  326. package/dist/vite/index.js +127 -82
  327. package/dist/vite/index.js.map +1 -1
  328. package/dist/vitest/index.d.ts +2 -2
  329. package/dist/vitest/index.js.map +1 -1
  330. package/dist/worklets-compat/index.d.ts +4 -0
  331. package/dist/worklets-compat/index.js +28 -0
  332. package/dist/worklets-compat/index.js.map +1 -0
  333. package/dist/worklets-compat/surface.d.ts +110 -0
  334. package/dist/worklets-compat/surface.js +153 -0
  335. package/dist/worklets-compat/surface.js.map +1 -0
  336. package/package.json +25 -9
  337. package/types.d.ts +9 -0
  338. package/dist/common/list.d.ts +0 -82
  339. package/dist/common/list.js +0 -166
  340. package/dist/common/list.js.map +0 -1
@@ -63,13 +63,68 @@ export const createResponderSystem = (options) => {
63
63
  propsOf(path[i])?.[bubbleName]?.(event);
64
64
  }
65
65
  };
66
- const grant = (host, event, fromStart) => {
66
+ const grant = (host, event) => {
67
67
  responder = host;
68
68
  notify(host, "onResponderGrant", event);
69
- if (fromStart) {
70
- notify(host, "onResponderStart", event);
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
- options.onGrant?.(host);
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 = { host, identifier: touch.identifier };
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
- return;
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
- if (responder === null) {
156
- // The common PanResponder shape: nothing claims on press, and the
157
- // pan starts once onMoveShouldSetPanResponder sees enough movement.
158
- const winner = negotiate(path, event, "move");
159
- if (winner) {
160
- grant(winner, event, false);
161
- }
162
- return;
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":"AAiBA,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,iBAAiB,CAAA;AAgExB,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,CACZ,IAAmB,EACnB,KAA4B,EAC5B,SAAkB,EACZ,EAAE;QACR,SAAS,GAAG,IAAI,CAAA;QAChB,MAAM,CAAC,IAAI,EAAE,kBAAkB,EAAE,KAAK,CAAC,CAAA;QACvC,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,CAAC,IAAI,EAAE,kBAAkB,EAAE,KAAK,CAAC,CAAA;QACzC,CAAC;QACD,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAA;IACzB,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,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;YACD,OAAO,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,CAAA;QAC7B,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,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAA;YAChD,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,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;gBACvB,OAAM;YACR,CAAC;YACD,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAA;YAC9C,IAAI,MAAM,EAAE,CAAC;gBACX,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAA;YAC5B,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,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,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;gBACvB,kEAAkE;gBAClE,oEAAoE;gBACpE,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;gBAC7C,IAAI,MAAM,EAAE,CAAC;oBACX,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAA;gBAC7B,CAAC;gBACD,OAAM;YACR,CAAC;YACD,oEAAoE;YACpE,wEAAwE;YACxE,sEAAsE;YACtE,MAAM,CAAC,SAAS,EAAE,iBAAiB,EAAE,KAAK,CAAC,CAAA;QAC7C,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,YAAY,EAAE,GAAG,EAAE,CAAC,SAAS;KAC9B,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 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
- onGrant: (host) => {
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
- gestures.get(host)?.setState(Gtk.EventSequenceState.CLAIMED);
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
- responderSystem.touchEnd(widget, createTouch(widget, startX + offsetX, startY + offsetY));
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 upstream claims it a native
84
- // ancestor scrolling, say. RN calls that a termination, and it is the
85
- // one termination trigger we get for free.
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();