react-native-gtkx 0.1.0-alpha.4 → 0.2.0-alpha.2

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 (257) hide show
  1. package/README.md +22 -9
  2. package/dist/adw/index.d.ts +3 -0
  3. package/dist/adw/index.js +41 -0
  4. package/dist/adw/index.js.map +1 -0
  5. package/dist/adw/widgets.generated.d.ts +63 -0
  6. package/dist/adw/widgets.generated.js +125 -0
  7. package/dist/adw/widgets.generated.js.map +1 -0
  8. package/dist/animated/create-animated.d.ts +2 -0
  9. package/dist/animated/create-animated.js +2 -0
  10. package/dist/animated/create-animated.js.map +1 -1
  11. package/dist/animated/index.d.ts +2 -0
  12. package/dist/animated/index.js +1 -0
  13. package/dist/animated/index.js.map +1 -1
  14. package/dist/animated/value-xy.d.ts +41 -0
  15. package/dist/animated/value-xy.js +90 -0
  16. package/dist/animated/value-xy.js.map +1 -0
  17. package/dist/animated/value.d.ts +9 -0
  18. package/dist/animated/value.js +30 -2
  19. package/dist/animated/value.js.map +1 -1
  20. package/dist/common/content.d.ts +69 -0
  21. package/dist/common/content.js +84 -0
  22. package/dist/common/content.js.map +1 -0
  23. package/dist/common/icon.d.ts +23 -0
  24. package/dist/common/icon.js +14 -0
  25. package/dist/common/icon.js.map +1 -0
  26. package/dist/common/index.d.ts +5 -0
  27. package/dist/common/index.js +21 -0
  28. package/dist/common/index.js.map +1 -0
  29. package/dist/common/list.d.ts +82 -0
  30. package/dist/common/list.js +166 -0
  31. package/dist/common/list.js.map +1 -0
  32. package/dist/common/navigation-stack.d.ts +84 -0
  33. package/dist/common/navigation-stack.js +313 -0
  34. package/dist/common/navigation-stack.js.map +1 -0
  35. package/dist/common/widget.d.ts +62 -0
  36. package/dist/common/widget.js +209 -0
  37. package/dist/common/widget.js.map +1 -0
  38. package/dist/components/animated.d.ts +7 -4
  39. package/dist/components/animated.js +88 -67
  40. package/dist/components/animated.js.map +1 -1
  41. package/dist/components/app-registry.d.ts +36 -1
  42. package/dist/components/app-registry.js +8 -3
  43. package/dist/components/app-registry.js.map +1 -1
  44. package/dist/components/host-node.d.ts +7 -0
  45. package/dist/components/host-node.js +26 -1
  46. package/dist/components/host-node.js.map +1 -1
  47. package/dist/components/index.d.ts +6 -2
  48. package/dist/components/index.js +2 -1
  49. package/dist/components/index.js.map +1 -1
  50. package/dist/components/measure.d.ts +13 -0
  51. package/dist/components/measure.js +51 -0
  52. package/dist/components/measure.js.map +1 -0
  53. package/dist/components/modal.d.ts +1 -1
  54. package/dist/components/press-event.d.ts +28 -0
  55. package/dist/components/press-event.js +46 -0
  56. package/dist/components/press-event.js.map +1 -0
  57. package/dist/components/pressable.d.ts +2 -6
  58. package/dist/components/pressable.js +119 -20
  59. package/dist/components/pressable.js.map +1 -1
  60. package/dist/components/rect-store.d.ts +11 -1
  61. package/dist/components/rect-store.js +27 -3
  62. package/dist/components/rect-store.js.map +1 -1
  63. package/dist/components/scroll-view.d.ts +4 -0
  64. package/dist/components/scroll-view.js +8 -2
  65. package/dist/components/scroll-view.js.map +1 -1
  66. package/dist/components/svg/animated-support.d.ts +19 -0
  67. package/dist/components/svg/animated-support.js +64 -0
  68. package/dist/components/svg/animated-support.js.map +1 -0
  69. package/dist/components/svg/context.d.ts +8 -0
  70. package/dist/components/svg/context.js +15 -0
  71. package/dist/components/svg/context.js.map +1 -0
  72. package/dist/components/svg/gradients.d.ts +31 -0
  73. package/dist/components/svg/gradients.js +93 -0
  74. package/dist/components/svg/gradients.js.map +1 -0
  75. package/dist/components/svg/group.d.ts +8 -0
  76. package/dist/components/svg/group.js +30 -0
  77. package/dist/components/svg/group.js.map +1 -0
  78. package/dist/components/svg/index.d.ts +6 -0
  79. package/dist/components/svg/index.js +5 -0
  80. package/dist/components/svg/index.js.map +1 -0
  81. package/dist/components/svg/node.d.ts +7 -0
  82. package/dist/components/svg/node.js +7 -0
  83. package/dist/components/svg/node.js.map +1 -0
  84. package/dist/components/svg/paint.d.ts +24 -0
  85. package/dist/components/svg/paint.js +57 -0
  86. package/dist/components/svg/paint.js.map +1 -0
  87. package/dist/components/svg/shapes.d.ts +47 -0
  88. package/dist/components/svg/shapes.js +51 -0
  89. package/dist/components/svg/shapes.js.map +1 -0
  90. package/dist/components/svg/svg.d.ts +14 -0
  91. package/dist/components/svg/svg.js +48 -0
  92. package/dist/components/svg/svg.js.map +1 -0
  93. package/dist/components/svg/use-shape-node.d.ts +12 -0
  94. package/dist/components/svg/use-shape-node.js +52 -0
  95. package/dist/components/svg/use-shape-node.js.map +1 -0
  96. package/dist/components/text-input.js +1 -1
  97. package/dist/components/text-input.js.map +1 -1
  98. package/dist/components/text.js +36 -3
  99. package/dist/components/text.js.map +1 -1
  100. package/dist/components/use-layout-child.js +50 -4
  101. package/dist/components/use-layout-child.js.map +1 -1
  102. package/dist/components/view.d.ts +9 -4
  103. package/dist/components/view.js +8 -2
  104. package/dist/components/view.js.map +1 -1
  105. package/dist/contracts.d.ts +31 -4
  106. package/dist/contracts.js.map +1 -1
  107. package/dist/gtk/index.d.ts +7 -0
  108. package/dist/gtk/index.js +46 -0
  109. package/dist/gtk/index.js.map +1 -0
  110. package/dist/gtk/widgets.generated.d.ts +103 -0
  111. package/dist/gtk/widgets.generated.js +205 -0
  112. package/dist/gtk/widgets.generated.js.map +1 -0
  113. package/dist/gtk/window-actions.d.ts +77 -0
  114. package/dist/gtk/window-actions.js +193 -0
  115. package/dist/gtk/window-actions.js.map +1 -0
  116. package/dist/gtkx/bridge/geometry.d.ts +15 -0
  117. package/dist/gtkx/bridge/geometry.js +38 -0
  118. package/dist/gtkx/bridge/geometry.js.map +1 -0
  119. package/dist/gtkx/bridge/index.d.ts +11 -4
  120. package/dist/gtkx/bridge/index.js +47 -6
  121. package/dist/gtkx/bridge/index.js.map +1 -1
  122. package/dist/gtkx/bridge/layout-manager.d.ts +2 -1
  123. package/dist/gtkx/bridge/layout-manager.js +31 -9
  124. package/dist/gtkx/bridge/layout-manager.js.map +1 -1
  125. package/dist/gtkx/bridge/slot-portal.d.ts +12 -0
  126. package/dist/gtkx/bridge/slot-portal.js +17 -0
  127. package/dist/gtkx/bridge/slot-portal.js.map +1 -0
  128. package/dist/gtkx/bridge/svg-node.d.ts +79 -0
  129. package/dist/gtkx/bridge/svg-node.js +323 -0
  130. package/dist/gtkx/bridge/svg-node.js.map +1 -0
  131. package/dist/gtkx/bridge/use-signal.js +19 -9
  132. package/dist/gtkx/bridge/use-signal.js.map +1 -1
  133. package/dist/gtkx/bridge/widgets.generated.d.ts +2 -0
  134. package/dist/gtkx/bridge/widgets.generated.js +14 -0
  135. package/dist/gtkx/bridge/widgets.generated.js.map +1 -0
  136. package/dist/index.d.ts +2 -2
  137. package/dist/index.js +1 -1
  138. package/dist/index.js.map +1 -1
  139. package/dist/mcp/bin.d.ts +2 -0
  140. package/dist/mcp/bin.js +17 -0
  141. package/dist/mcp/bin.js.map +1 -0
  142. package/dist/mcp/data/generated.d.ts +1304 -0
  143. package/dist/mcp/data/generated.js +1179 -0
  144. package/dist/mcp/data/generated.js.map +1 -0
  145. package/dist/mcp/index.d.ts +5 -0
  146. package/dist/mcp/index.js +9 -0
  147. package/dist/mcp/index.js.map +1 -0
  148. package/dist/mcp/list.d.ts +4 -0
  149. package/dist/mcp/list.js +56 -0
  150. package/dist/mcp/list.js.map +1 -0
  151. package/dist/mcp/resolve.d.ts +55 -0
  152. package/dist/mcp/resolve.js +151 -0
  153. package/dist/mcp/resolve.js.map +1 -0
  154. package/dist/mcp/search.d.ts +13 -0
  155. package/dist/mcp/search.js +48 -0
  156. package/dist/mcp/search.js.map +1 -0
  157. package/dist/mcp/server.d.ts +7 -0
  158. package/dist/mcp/server.js +135 -0
  159. package/dist/mcp/server.js.map +1 -0
  160. package/dist/mcp/tool.d.ts +21 -0
  161. package/dist/mcp/tool.js +36 -0
  162. package/dist/mcp/tool.js.map +1 -0
  163. package/dist/metro/index.js +10 -0
  164. package/dist/metro/index.js.map +1 -1
  165. package/dist/navigation/index.d.ts +50 -13
  166. package/dist/navigation/index.js +183 -148
  167. package/dist/navigation/index.js.map +1 -1
  168. package/dist/navigation/option-warnings.js +9 -1
  169. package/dist/navigation/option-warnings.js.map +1 -1
  170. package/dist/navigation/sidebar.d.ts +207 -9
  171. package/dist/navigation/sidebar.js +294 -10
  172. package/dist/navigation/sidebar.js.map +1 -1
  173. package/dist/responder/system.d.ts +27 -0
  174. package/dist/responder/system.js +184 -0
  175. package/dist/responder/system.js.map +1 -0
  176. package/dist/responder/touch-history.d.ts +10 -0
  177. package/dist/responder/touch-history.js +78 -0
  178. package/dist/responder/touch-history.js.map +1 -0
  179. package/dist/responder/types.d.ts +63 -0
  180. package/dist/responder/types.js +29 -0
  181. package/dist/responder/types.js.map +1 -0
  182. package/dist/responder/use-responder.d.ts +12 -0
  183. package/dist/responder/use-responder.js +98 -0
  184. package/dist/responder/use-responder.js.map +1 -0
  185. package/dist/runner/host-dev.js +3 -1
  186. package/dist/runner/host-dev.js.map +1 -1
  187. package/dist/runner/host.js +5 -1
  188. package/dist/runner/host.js.map +1 -1
  189. package/dist/runner/index.d.ts +22 -2
  190. package/dist/runner/index.js +163 -13
  191. package/dist/runner/index.js.map +1 -1
  192. package/dist/sea/assemble.d.ts +29 -0
  193. package/dist/sea/assemble.js +129 -0
  194. package/dist/sea/assemble.js.map +1 -0
  195. package/dist/sea/bundle.d.ts +61 -0
  196. package/dist/sea/bundle.js +202 -0
  197. package/dist/sea/bundle.js.map +1 -0
  198. package/dist/sea/gtkx-config-module.d.ts +18 -0
  199. package/dist/sea/gtkx-config-module.js +67 -0
  200. package/dist/sea/gtkx-config-module.js.map +1 -0
  201. package/dist/sea/native-shim.d.ts +40 -0
  202. package/dist/sea/native-shim.js +121 -0
  203. package/dist/sea/native-shim.js.map +1 -0
  204. package/dist/sea/react-anchor-plugin.d.ts +2 -0
  205. package/dist/sea/react-anchor-plugin.js +30 -0
  206. package/dist/sea/react-anchor-plugin.js.map +1 -0
  207. package/dist/style/box-shadow.d.ts +7 -0
  208. package/dist/style/box-shadow.js +197 -0
  209. package/dist/style/box-shadow.js.map +1 -0
  210. package/dist/style/index.d.ts +3 -0
  211. package/dist/style/index.js +3 -0
  212. package/dist/style/index.js.map +1 -1
  213. package/dist/style/split-style.d.ts +5 -2
  214. package/dist/style/split-style.js +33 -5
  215. package/dist/style/split-style.js.map +1 -1
  216. package/dist/style/text-decoration.d.ts +15 -0
  217. package/dist/style/text-decoration.js +26 -0
  218. package/dist/style/text-decoration.js.map +1 -0
  219. package/dist/style/transform.d.ts +22 -0
  220. package/dist/style/transform.js +137 -0
  221. package/dist/style/transform.js.map +1 -0
  222. package/dist/style/visual-css.js +40 -3
  223. package/dist/style/visual-css.js.map +1 -1
  224. package/dist/svg/geometry.d.ts +19 -0
  225. package/dist/svg/geometry.js +80 -0
  226. package/dist/svg/geometry.js.map +1 -0
  227. package/dist/svg/gradient-geometry.d.ts +15 -0
  228. package/dist/svg/gradient-geometry.js +15 -0
  229. package/dist/svg/gradient-geometry.js.map +1 -0
  230. package/dist/svg/transform.d.ts +22 -0
  231. package/dist/svg/transform.js +74 -0
  232. package/dist/svg/transform.js.map +1 -0
  233. package/dist/svg/view-box.d.ts +34 -0
  234. package/dist/svg/view-box.js +108 -0
  235. package/dist/svg/view-box.js.map +1 -0
  236. package/dist/svg-compat/index.d.ts +1 -0
  237. package/dist/svg-compat/index.js +16 -0
  238. package/dist/svg-compat/index.js.map +1 -0
  239. package/dist/testing/index.d.ts +14 -0
  240. package/dist/testing/index.js +14 -0
  241. package/dist/testing/index.js.map +1 -0
  242. package/dist/vendor/react-native/pan-responder.d.ts +102 -0
  243. package/dist/vendor/react-native/pan-responder.js +196 -0
  244. package/dist/vendor/react-native/pan-responder.js.map +1 -0
  245. package/dist/vendor/react-native/touch-history-math.d.ts +18 -0
  246. package/dist/vendor/react-native/touch-history-math.js +102 -0
  247. package/dist/vendor/react-native/touch-history-math.js.map +1 -0
  248. package/dist/vite/index.d.ts +7 -3
  249. package/dist/vite/index.js +16 -4
  250. package/dist/vite/index.js.map +1 -1
  251. package/dist/vitest/index.d.ts +52 -0
  252. package/dist/vitest/index.js +58 -0
  253. package/dist/vitest/index.js.map +1 -0
  254. package/dist/vitest/setup.d.ts +4 -0
  255. package/dist/vitest/setup.js +22 -0
  256. package/dist/vitest/setup.js.map +1 -0
  257. package/package.json +52 -13
@@ -0,0 +1,184 @@
1
+ import { createTouchHistory, recordTouchEnd, recordTouchMove, recordTouchStart, refreshActiveTouches, } from "./touch-history";
2
+ export const createResponderSystem = (options) => {
3
+ const registry = new Map();
4
+ const history = createTouchHistory();
5
+ let responder = null;
6
+ let session = null;
7
+ /**
8
+ * The registered nodes from the toplevel down to `host`, root first — RN's
9
+ * capture order, reversed for bubble.
10
+ *
11
+ * The walk stops wherever `parentOf` stops, which on this platform is the
12
+ * layout Root: an RN tree here is an island inside a native GTK widget
13
+ * tree (NestedRoot/IntrinsicRoot mount engines into arbitrary GTK slots),
14
+ * so there is no single app-wide view hierarchy to traverse. Native
15
+ * widgets on the way up are simply not registered and take no part.
16
+ */
17
+ const pathOf = (host) => {
18
+ const path = [];
19
+ let current = host;
20
+ while (current) {
21
+ if (registry.has(current)) {
22
+ path.unshift(current);
23
+ }
24
+ current = options.parentOf(current);
25
+ }
26
+ return path;
27
+ };
28
+ const propsOf = (host) => registry.get(host)?.getProps() ?? null;
29
+ const createEvent = (touch, stillDown) => ({
30
+ nativeEvent: {
31
+ ...touch,
32
+ // Single-pointer: `touches` is the still-active set (empty once the
33
+ // pointer is up), `changedTouches` is always this one.
34
+ touches: stillDown ? [touch] : [],
35
+ changedTouches: [touch],
36
+ },
37
+ touchHistory: history,
38
+ });
39
+ /**
40
+ * Runs a should-set handler over `path` in order, stopping at the first
41
+ * that returns true — RN's executeDispatchesInOrderStopAtTrue. Handlers
42
+ * before the winner all run, which PanResponder depends on: its capture
43
+ * handlers are where gestureState is initialised and where dx/dy are
44
+ * accumulated on move.
45
+ */
46
+ const firstToClaim = (path, name, event) => {
47
+ for (const host of path) {
48
+ const handler = propsOf(host)?.[name];
49
+ if (handler?.(event) === true) {
50
+ return host;
51
+ }
52
+ }
53
+ return null;
54
+ };
55
+ const notify = (host, name, event) => {
56
+ propsOf(host)?.[name]?.(event);
57
+ };
58
+ const dispatchTouch = (path, captureName, bubbleName, event) => {
59
+ for (const host of path) {
60
+ propsOf(host)?.[captureName]?.(event);
61
+ }
62
+ for (let i = path.length - 1; i >= 0; i -= 1) {
63
+ propsOf(path[i])?.[bubbleName]?.(event);
64
+ }
65
+ };
66
+ const grant = (host, event, fromStart) => {
67
+ responder = host;
68
+ notify(host, "onResponderGrant", event);
69
+ if (fromStart) {
70
+ notify(host, "onResponderStart", event);
71
+ }
72
+ options.onGrant?.(host);
73
+ };
74
+ /**
75
+ * Capture root-to-target, then bubble target-to-root — RN's two-phase
76
+ * negotiation, and the one place GTK's model and RN's agree, since GTK's
77
+ * CAPTURE/TARGET/BUBBLE phases run in exactly this order. We still do the
78
+ * walk ourselves so a single GTK event source can drive the whole path.
79
+ */
80
+ const negotiate = (path, event, phase) => {
81
+ const capture = phase === "start"
82
+ ? "onStartShouldSetResponderCapture"
83
+ : "onMoveShouldSetResponderCapture";
84
+ const bubble = phase === "start"
85
+ ? "onStartShouldSetResponder"
86
+ : "onMoveShouldSetResponder";
87
+ const captured = firstToClaim(path, capture, event);
88
+ if (captured) {
89
+ return captured;
90
+ }
91
+ return firstToClaim([...path].reverse(), bubble, event);
92
+ };
93
+ const finish = (touch, terminated, touchProps) => {
94
+ const current = session;
95
+ if (!current) {
96
+ return;
97
+ }
98
+ recordTouchEnd(history, touch);
99
+ refreshActiveTouches(history);
100
+ const event = createEvent(touch, false);
101
+ dispatchTouch(pathOf(current.host), touchProps[0], touchProps[1], event);
102
+ const holder = responder;
103
+ session = null;
104
+ responder = null;
105
+ if (holder) {
106
+ notify(holder, "onResponderEnd", event);
107
+ notify(holder, terminated ? "onResponderTerminate" : "onResponderRelease", event);
108
+ options.onRelease?.(holder);
109
+ }
110
+ };
111
+ return {
112
+ register(host, getProps) {
113
+ registry.set(host, { getProps });
114
+ return () => {
115
+ registry.delete(host);
116
+ // A responder that unmounts mid-gesture cannot be asked anything
117
+ // ever again; drop the lock rather than stranding it.
118
+ if (responder === host) {
119
+ responder = null;
120
+ }
121
+ if (session?.host === host) {
122
+ session = null;
123
+ }
124
+ };
125
+ },
126
+ touchStart(host, touch) {
127
+ // Already tracking this press: this is an ancestor's gesture seeing the
128
+ // same event through GTK's bubble chain.
129
+ if (session !== null) {
130
+ return;
131
+ }
132
+ session = { host, identifier: touch.identifier };
133
+ recordTouchStart(history, touch);
134
+ refreshActiveTouches(history);
135
+ const path = pathOf(host);
136
+ const event = createEvent(touch, true);
137
+ dispatchTouch(path, "onTouchStartCapture", "onTouchStart", event);
138
+ if (responder !== null) {
139
+ return;
140
+ }
141
+ const winner = negotiate(path, event, "start");
142
+ if (winner) {
143
+ grant(winner, event, true);
144
+ }
145
+ },
146
+ touchMove(host, touch) {
147
+ if (session === null || session.host !== host) {
148
+ return;
149
+ }
150
+ recordTouchMove(history, touch);
151
+ refreshActiveTouches(history);
152
+ const path = pathOf(host);
153
+ const event = createEvent(touch, true);
154
+ 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;
163
+ }
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
+ },
169
+ touchEnd(host, touch) {
170
+ if (session === null || session.host !== host) {
171
+ return;
172
+ }
173
+ finish(touch, false, ["onTouchEndCapture", "onTouchEnd"]);
174
+ },
175
+ touchCancel(host, touch) {
176
+ if (session === null || session.host !== host) {
177
+ return;
178
+ }
179
+ finish(touch, true, ["onTouchCancelCapture", "onTouchCancel"]);
180
+ },
181
+ getResponder: () => responder,
182
+ };
183
+ };
184
+ //# sourceMappingURL=system.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system.js","sourceRoot":"","sources":["../../src/responder/system.ts"],"names":[],"mappings":"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","sourcesContent":["// RN's Gesture Responder System: the negotiation, in pure JS.\n//\n// Zero @gtkx imports on purpose. Everything platform-specific enters through\n// two seams — `parentOf`, which turns a host handle into its parent, and the\n// grant/release notifications the platform uses to make its one-way GTK\n// claim. That keeps the algorithm unit-testable off Linux, which matters\n// because this is the part that is subtle and the part every platform\n// historically gets wrong.\n//\n// Why reimplemented rather than reused: every other RN platform feeds\n// `topTouchStart/Move/End/Cancel` to the stock renderer's\n// ResponderEventPlugin, which lives in `facebook/react` inside\n// `react-native-renderer`. We render through `@gtkx/react` on\n// `react-reconciler`, so there is no plugin seam to feed —\n// react-native-web is in the same position and reimplemented for the same\n// reason (docs/research/gestures.md).\nimport type { NativeTouch } from \"../components/press-event\"\nimport {\n createTouchHistory,\n recordTouchEnd,\n recordTouchMove,\n recordTouchStart,\n refreshActiveTouches,\n} from \"./touch-history\"\nimport type { GestureResponderEvent, ResponderProps } from \"./types\"\n\n/** An opaque platform handle. A Gtk.Widget in practice; never treated as one. */\nexport type ResponderHost = object\n\nexport type ResponderSystemOptions = {\n /** The platform's parent link. Returning null ends the path. */\n parentOf: (host: ResponderHost) => ResponderHost | null\n /**\n * Fired after a node is granted the responder — where the platform makes\n * its GTK `CLAIMED` declaration. One-way by construction: GTK has no way\n * to hand a claimed sequence back, so this is only ever called once JS has\n * already decided (docs/research/gestures.md, \"no voluntary release\").\n */\n onGrant?: (host: ResponderHost) => void\n /** Fired when the responder is released or terminated. */\n onRelease?: (host: ResponderHost) => void\n}\n\ntype Registration = { getProps: () => ResponderProps }\n\n// One interaction, from press to release. v1 is single-pointer: a mouse is\n// one fabricated touch, so at most one session exists at a time. It also\n// does the deduplicating — GTK delivers a press to every ancestor's gesture\n// in the bubble chain, so the second and later reports for the same press\n// find a session already open and return.\ntype Session = { host: ResponderHost; identifier: number }\n\nexport type ResponderSystem = {\n register(host: ResponderHost, getProps: () => ResponderProps): () => void\n touchStart(host: ResponderHost, touch: NativeTouch): void\n touchMove(host: ResponderHost, touch: NativeTouch): void\n touchEnd(host: ResponderHost, touch: NativeTouch): void\n touchCancel(host: ResponderHost, touch: NativeTouch): void\n /** The node currently holding the responder, or null. */\n getResponder(): ResponderHost | null\n}\n\ntype ShouldSetName =\n | \"onStartShouldSetResponder\"\n | \"onStartShouldSetResponderCapture\"\n | \"onMoveShouldSetResponder\"\n | \"onMoveShouldSetResponderCapture\"\n\ntype NotifyName =\n | \"onResponderGrant\"\n | \"onResponderStart\"\n | \"onResponderMove\"\n | \"onResponderEnd\"\n | \"onResponderRelease\"\n | \"onResponderTerminate\"\n | \"onResponderReject\"\n\ntype TouchName =\n | \"onTouchStart\"\n | \"onTouchStartCapture\"\n | \"onTouchMove\"\n | \"onTouchMoveCapture\"\n | \"onTouchEnd\"\n | \"onTouchEndCapture\"\n | \"onTouchCancel\"\n | \"onTouchCancelCapture\"\n\nexport const createResponderSystem = (\n options: ResponderSystemOptions,\n): ResponderSystem => {\n const registry = new Map<ResponderHost, Registration>()\n const history = createTouchHistory()\n let responder: ResponderHost | null = null\n let session: Session | null = null\n\n /**\n * The registered nodes from the toplevel down to `host`, root first — RN's\n * capture order, reversed for bubble.\n *\n * The walk stops wherever `parentOf` stops, which on this platform is the\n * layout Root: an RN tree here is an island inside a native GTK widget\n * tree (NestedRoot/IntrinsicRoot mount engines into arbitrary GTK slots),\n * so there is no single app-wide view hierarchy to traverse. Native\n * widgets on the way up are simply not registered and take no part.\n */\n const pathOf = (host: ResponderHost): ResponderHost[] => {\n const path: ResponderHost[] = []\n let current: ResponderHost | null = host\n while (current) {\n if (registry.has(current)) {\n path.unshift(current)\n }\n current = options.parentOf(current)\n }\n return path\n }\n\n const propsOf = (host: ResponderHost): ResponderProps | null =>\n registry.get(host)?.getProps() ?? null\n\n const createEvent = (\n touch: NativeTouch,\n stillDown: boolean,\n ): GestureResponderEvent => ({\n nativeEvent: {\n ...touch,\n // Single-pointer: `touches` is the still-active set (empty once the\n // pointer is up), `changedTouches` is always this one.\n touches: stillDown ? [touch] : [],\n changedTouches: [touch],\n },\n touchHistory: history,\n })\n\n /**\n * Runs a should-set handler over `path` in order, stopping at the first\n * that returns true — RN's executeDispatchesInOrderStopAtTrue. Handlers\n * before the winner all run, which PanResponder depends on: its capture\n * handlers are where gestureState is initialised and where dx/dy are\n * accumulated on move.\n */\n const firstToClaim = (\n path: readonly ResponderHost[],\n name: ShouldSetName,\n event: GestureResponderEvent,\n ): ResponderHost | null => {\n for (const host of path) {\n const handler = propsOf(host)?.[name]\n if (handler?.(event) === true) {\n return host\n }\n }\n return null\n }\n\n const notify = (\n host: ResponderHost,\n name: NotifyName,\n event: GestureResponderEvent,\n ): void => {\n propsOf(host)?.[name]?.(event)\n }\n\n const dispatchTouch = (\n path: readonly ResponderHost[],\n captureName: TouchName,\n bubbleName: TouchName,\n event: GestureResponderEvent,\n ): void => {\n for (const host of path) {\n propsOf(host)?.[captureName]?.(event)\n }\n for (let i = path.length - 1; i >= 0; i -= 1) {\n propsOf(path[i]!)?.[bubbleName]?.(event)\n }\n }\n\n const grant = (\n host: ResponderHost,\n event: GestureResponderEvent,\n fromStart: boolean,\n ): void => {\n responder = host\n notify(host, \"onResponderGrant\", event)\n if (fromStart) {\n notify(host, \"onResponderStart\", event)\n }\n options.onGrant?.(host)\n }\n\n /**\n * Capture root-to-target, then bubble target-to-root — RN's two-phase\n * negotiation, and the one place GTK's model and RN's agree, since GTK's\n * CAPTURE/TARGET/BUBBLE phases run in exactly this order. We still do the\n * walk ourselves so a single GTK event source can drive the whole path.\n */\n const negotiate = (\n path: readonly ResponderHost[],\n event: GestureResponderEvent,\n phase: \"start\" | \"move\",\n ): ResponderHost | null => {\n const capture =\n phase === \"start\"\n ? \"onStartShouldSetResponderCapture\"\n : \"onMoveShouldSetResponderCapture\"\n const bubble =\n phase === \"start\"\n ? \"onStartShouldSetResponder\"\n : \"onMoveShouldSetResponder\"\n const captured = firstToClaim(path, capture, event)\n if (captured) {\n return captured\n }\n return firstToClaim([...path].reverse(), bubble, event)\n }\n\n const finish = (\n touch: NativeTouch,\n terminated: boolean,\n touchProps: [TouchName, TouchName],\n ): void => {\n const current = session\n if (!current) {\n return\n }\n recordTouchEnd(history, touch)\n refreshActiveTouches(history)\n const event = createEvent(touch, false)\n dispatchTouch(pathOf(current.host), touchProps[0], touchProps[1], event)\n\n const holder = responder\n session = null\n responder = null\n if (holder) {\n notify(holder, \"onResponderEnd\", event)\n notify(\n holder,\n terminated ? \"onResponderTerminate\" : \"onResponderRelease\",\n event,\n )\n options.onRelease?.(holder)\n }\n }\n\n return {\n register(host, getProps) {\n registry.set(host, { getProps })\n return () => {\n registry.delete(host)\n // A responder that unmounts mid-gesture cannot be asked anything\n // ever again; drop the lock rather than stranding it.\n if (responder === host) {\n responder = null\n }\n if (session?.host === host) {\n session = null\n }\n }\n },\n\n touchStart(host, touch) {\n // Already tracking this press: this is an ancestor's gesture seeing the\n // same event through GTK's bubble chain.\n if (session !== null) {\n return\n }\n session = { host, identifier: touch.identifier }\n recordTouchStart(history, touch)\n refreshActiveTouches(history)\n\n const path = pathOf(host)\n const event = createEvent(touch, true)\n dispatchTouch(path, \"onTouchStartCapture\", \"onTouchStart\", event)\n\n if (responder !== null) {\n return\n }\n const winner = negotiate(path, event, \"start\")\n if (winner) {\n grant(winner, event, true)\n }\n },\n\n touchMove(host, touch) {\n if (session === null || session.host !== host) {\n return\n }\n recordTouchMove(history, touch)\n refreshActiveTouches(history)\n\n const path = pathOf(host)\n const event = createEvent(touch, true)\n dispatchTouch(path, \"onTouchMoveCapture\", \"onTouchMove\", event)\n\n if (responder === null) {\n // The common PanResponder shape: nothing claims on press, and the\n // pan starts once onMoveShouldSetPanResponder sees enough movement.\n const winner = negotiate(path, event, \"move\")\n if (winner) {\n grant(winner, event, false)\n }\n return\n }\n // v1 deliberately does not renegotiate while a responder holds: GTK\n // cannot revoke a claim, so a transfer we cannot enforce natively would\n // be a lie at the boundary with native widgets. Tracked as task 002b.\n notify(responder, \"onResponderMove\", event)\n },\n\n touchEnd(host, touch) {\n if (session === null || session.host !== host) {\n return\n }\n finish(touch, false, [\"onTouchEndCapture\", \"onTouchEnd\"])\n },\n\n touchCancel(host, touch) {\n if (session === null || session.host !== host) {\n return\n }\n finish(touch, true, [\"onTouchCancelCapture\", \"onTouchCancel\"])\n },\n\n getResponder: () => responder,\n }\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import type { NativeTouch, TouchHistory } from "./types";
2
+ export declare const createTouchHistory: () => TouchHistory;
3
+ export declare const recordTouchStart: (history: TouchHistory, touch: NativeTouch) => void;
4
+ export declare const recordTouchMove: (history: TouchHistory, touch: NativeTouch) => void;
5
+ export declare const recordTouchEnd: (history: TouchHistory, touch: NativeTouch) => void;
6
+ /**
7
+ * Recomputes `numberActiveTouches` and `indexOfSingleActiveTouch` — the two
8
+ * derived fields TouchHistoryMath's single-touch fast path reads.
9
+ */
10
+ export declare const refreshActiveTouches: (history: TouchHistory) => void;
@@ -0,0 +1,78 @@
1
+ export const createTouchHistory = () => ({
2
+ touchBank: [],
3
+ numberActiveTouches: 0,
4
+ indexOfSingleActiveTouch: -1,
5
+ mostRecentTimeStamp: 0,
6
+ });
7
+ export const recordTouchStart = (history, touch) => {
8
+ const identifier = touch.identifier;
9
+ const existing = history.touchBank[identifier];
10
+ if (existing) {
11
+ existing.touchActive = true;
12
+ existing.startPageX = touch.pageX;
13
+ existing.startPageY = touch.pageY;
14
+ existing.startTimeStamp = touch.timestamp;
15
+ existing.currentPageX = touch.pageX;
16
+ existing.currentPageY = touch.pageY;
17
+ existing.currentTimeStamp = touch.timestamp;
18
+ existing.previousPageX = touch.pageX;
19
+ existing.previousPageY = touch.pageY;
20
+ existing.previousTimeStamp = touch.timestamp;
21
+ }
22
+ else {
23
+ const record = {
24
+ touchActive: true,
25
+ startPageX: touch.pageX,
26
+ startPageY: touch.pageY,
27
+ startTimeStamp: touch.timestamp,
28
+ currentPageX: touch.pageX,
29
+ currentPageY: touch.pageY,
30
+ currentTimeStamp: touch.timestamp,
31
+ previousPageX: touch.pageX,
32
+ previousPageY: touch.pageY,
33
+ previousTimeStamp: touch.timestamp,
34
+ };
35
+ history.touchBank[identifier] = record;
36
+ }
37
+ history.mostRecentTimeStamp = touch.timestamp;
38
+ };
39
+ const recordTouchMoveOrEnd = (history, touch, stillActive) => {
40
+ const record = history.touchBank[touch.identifier];
41
+ if (!record) {
42
+ // Upstream warns here. A move without a start means the event source and
43
+ // the store disagree, which is a bug in the caller, not a user error.
44
+ return;
45
+ }
46
+ record.touchActive = stillActive;
47
+ record.previousPageX = record.currentPageX;
48
+ record.previousPageY = record.currentPageY;
49
+ record.previousTimeStamp = record.currentTimeStamp;
50
+ record.currentPageX = touch.pageX;
51
+ record.currentPageY = touch.pageY;
52
+ record.currentTimeStamp = touch.timestamp;
53
+ history.mostRecentTimeStamp = touch.timestamp;
54
+ };
55
+ export const recordTouchMove = (history, touch) => {
56
+ recordTouchMoveOrEnd(history, touch, true);
57
+ };
58
+ export const recordTouchEnd = (history, touch) => {
59
+ recordTouchMoveOrEnd(history, touch, false);
60
+ };
61
+ /**
62
+ * Recomputes `numberActiveTouches` and `indexOfSingleActiveTouch` — the two
63
+ * derived fields TouchHistoryMath's single-touch fast path reads.
64
+ */
65
+ export const refreshActiveTouches = (history) => {
66
+ let active = 0;
67
+ let singleIndex = -1;
68
+ for (let i = 0; i < history.touchBank.length; i += 1) {
69
+ const record = history.touchBank[i];
70
+ if (record?.touchActive) {
71
+ active += 1;
72
+ singleIndex = i;
73
+ }
74
+ }
75
+ history.numberActiveTouches = active;
76
+ history.indexOfSingleActiveTouch = active === 1 ? singleIndex : -1;
77
+ };
78
+ //# sourceMappingURL=touch-history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"touch-history.js","sourceRoot":"","sources":["../../src/responder/touch-history.ts"],"names":[],"mappings":"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","sourcesContent":["// The touch-history store, transcribed from RN's ResponderTouchHistoryStore\n// so the vendored PanResponder runs against it unmodified.\n//\n// Everything here is deliberately mechanical. The three-way split — start /\n// previous / current — is what makes velocity and clustered multi-touch\n// accumulation computable at all, and \"previous\" specifically means the\n// value before THIS event, not one frame ago. Collapsing any of it would\n// break TouchHistoryMath in ways that only show up as a subtly wrong vx.\nimport type { NativeTouch, TouchHistory, TouchRecord } from \"./types\"\n\nexport const createTouchHistory = (): TouchHistory => ({\n touchBank: [],\n numberActiveTouches: 0,\n indexOfSingleActiveTouch: -1,\n mostRecentTimeStamp: 0,\n})\n\nexport const recordTouchStart = (\n history: TouchHistory,\n touch: NativeTouch,\n): void => {\n const identifier = touch.identifier\n const existing = history.touchBank[identifier]\n if (existing) {\n existing.touchActive = true\n existing.startPageX = touch.pageX\n existing.startPageY = touch.pageY\n existing.startTimeStamp = touch.timestamp\n existing.currentPageX = touch.pageX\n existing.currentPageY = touch.pageY\n existing.currentTimeStamp = touch.timestamp\n existing.previousPageX = touch.pageX\n existing.previousPageY = touch.pageY\n existing.previousTimeStamp = touch.timestamp\n } else {\n const record: TouchRecord = {\n touchActive: true,\n startPageX: touch.pageX,\n startPageY: touch.pageY,\n startTimeStamp: touch.timestamp,\n currentPageX: touch.pageX,\n currentPageY: touch.pageY,\n currentTimeStamp: touch.timestamp,\n previousPageX: touch.pageX,\n previousPageY: touch.pageY,\n previousTimeStamp: touch.timestamp,\n }\n history.touchBank[identifier] = record\n }\n history.mostRecentTimeStamp = touch.timestamp\n}\n\nconst recordTouchMoveOrEnd = (\n history: TouchHistory,\n touch: NativeTouch,\n stillActive: boolean,\n): void => {\n const record = history.touchBank[touch.identifier]\n if (!record) {\n // Upstream warns here. A move without a start means the event source and\n // the store disagree, which is a bug in the caller, not a user error.\n return\n }\n record.touchActive = stillActive\n record.previousPageX = record.currentPageX\n record.previousPageY = record.currentPageY\n record.previousTimeStamp = record.currentTimeStamp\n record.currentPageX = touch.pageX\n record.currentPageY = touch.pageY\n record.currentTimeStamp = touch.timestamp\n history.mostRecentTimeStamp = touch.timestamp\n}\n\nexport const recordTouchMove = (\n history: TouchHistory,\n touch: NativeTouch,\n): void => {\n recordTouchMoveOrEnd(history, touch, true)\n}\n\nexport const recordTouchEnd = (\n history: TouchHistory,\n touch: NativeTouch,\n): void => {\n recordTouchMoveOrEnd(history, touch, false)\n}\n\n/**\n * Recomputes `numberActiveTouches` and `indexOfSingleActiveTouch` — the two\n * derived fields TouchHistoryMath's single-touch fast path reads.\n */\nexport const refreshActiveTouches = (history: TouchHistory): void => {\n let active = 0\n let singleIndex = -1\n for (let i = 0; i < history.touchBank.length; i += 1) {\n const record = history.touchBank[i]\n if (record?.touchActive) {\n active += 1\n singleIndex = i\n }\n }\n history.numberActiveTouches = active\n history.indexOfSingleActiveTouch = active === 1 ? singleIndex : -1\n}\n"]}
@@ -0,0 +1,63 @@
1
+ import type { NativeTouch, PressEvent } from "../components/press-event";
2
+ /** One tracked touch. Field names are RN's; do not "tidy" them. */
3
+ export type TouchRecord = {
4
+ touchActive: boolean;
5
+ startPageX: number;
6
+ startPageY: number;
7
+ startTimeStamp: number;
8
+ currentPageX: number;
9
+ currentPageY: number;
10
+ currentTimeStamp: number;
11
+ previousPageX: number;
12
+ previousPageY: number;
13
+ previousTimeStamp: number;
14
+ };
15
+ export type TouchHistory = {
16
+ touchBank: TouchRecord[];
17
+ numberActiveTouches: number;
18
+ indexOfSingleActiveTouch: number;
19
+ mostRecentTimeStamp: number;
20
+ };
21
+ /**
22
+ * What every responder callback receives. PanResponder reads `touchHistory`
23
+ * off the EVENT, not off `nativeEvent` — an easy detail to get wrong, and it
24
+ * silently zeroes every gesture if you do.
25
+ */
26
+ export type GestureResponderEvent = PressEvent & {
27
+ touchHistory: TouchHistory;
28
+ };
29
+ export type ShouldSetResponder = (event: GestureResponderEvent) => boolean;
30
+ /**
31
+ * RN's responder and touch props. The touch props fire regardless of who
32
+ * holds the responder; the rest are the negotiation.
33
+ */
34
+ export type ResponderProps = {
35
+ onStartShouldSetResponder?: ShouldSetResponder;
36
+ onStartShouldSetResponderCapture?: ShouldSetResponder;
37
+ onMoveShouldSetResponder?: ShouldSetResponder;
38
+ onMoveShouldSetResponderCapture?: ShouldSetResponder;
39
+ onResponderGrant?: (event: GestureResponderEvent) => void | boolean;
40
+ onResponderStart?: (event: GestureResponderEvent) => void;
41
+ onResponderMove?: (event: GestureResponderEvent) => void;
42
+ onResponderEnd?: (event: GestureResponderEvent) => void;
43
+ onResponderRelease?: (event: GestureResponderEvent) => void;
44
+ onResponderTerminate?: (event: GestureResponderEvent) => void;
45
+ onResponderTerminationRequest?: ShouldSetResponder;
46
+ onResponderReject?: (event: GestureResponderEvent) => void;
47
+ onTouchStart?: (event: GestureResponderEvent) => void;
48
+ onTouchStartCapture?: (event: GestureResponderEvent) => void;
49
+ onTouchMove?: (event: GestureResponderEvent) => void;
50
+ onTouchMoveCapture?: (event: GestureResponderEvent) => void;
51
+ onTouchEnd?: (event: GestureResponderEvent) => void;
52
+ onTouchEndCapture?: (event: GestureResponderEvent) => void;
53
+ onTouchCancel?: (event: GestureResponderEvent) => void;
54
+ onTouchCancelCapture?: (event: GestureResponderEvent) => void;
55
+ };
56
+ /**
57
+ * Whether a component needs an event source at all. A View with no responder
58
+ * props gets no GTK controller: RN only ever asks views that declare
59
+ * handlers, so attaching one everywhere would be pure cost.
60
+ */
61
+ export declare const hasResponderProps: (props: ResponderProps) => boolean;
62
+ /** A single touch as PanResponder's `touches`/`changedTouches` see it. */
63
+ export type { NativeTouch };
@@ -0,0 +1,29 @@
1
+ const RESPONDER_PROP_NAMES = [
2
+ "onStartShouldSetResponder",
3
+ "onStartShouldSetResponderCapture",
4
+ "onMoveShouldSetResponder",
5
+ "onMoveShouldSetResponderCapture",
6
+ "onResponderGrant",
7
+ "onResponderStart",
8
+ "onResponderMove",
9
+ "onResponderEnd",
10
+ "onResponderRelease",
11
+ "onResponderTerminate",
12
+ "onResponderTerminationRequest",
13
+ "onResponderReject",
14
+ "onTouchStart",
15
+ "onTouchStartCapture",
16
+ "onTouchMove",
17
+ "onTouchMoveCapture",
18
+ "onTouchEnd",
19
+ "onTouchEndCapture",
20
+ "onTouchCancel",
21
+ "onTouchCancelCapture",
22
+ ];
23
+ /**
24
+ * Whether a component needs an event source at all. A View with no responder
25
+ * props gets no GTK controller: RN only ever asks views that declare
26
+ * handlers, so attaching one everywhere would be pure cost.
27
+ */
28
+ export const hasResponderProps = (props) => RESPONDER_PROP_NAMES.some((name) => props[name] !== undefined);
29
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/responder/types.ts"],"names":[],"mappings":"AA2EA,MAAM,oBAAoB,GAAG;IAC3B,2BAA2B;IAC3B,kCAAkC;IAClC,0BAA0B;IAC1B,iCAAiC;IACjC,kBAAkB;IAClB,kBAAkB;IAClB,iBAAiB;IACjB,gBAAgB;IAChB,oBAAoB;IACpB,sBAAsB;IACtB,+BAA+B;IAC/B,mBAAmB;IACnB,cAAc;IACd,qBAAqB;IACrB,aAAa;IACb,oBAAoB;IACpB,YAAY;IACZ,mBAAmB;IACnB,eAAe;IACf,sBAAsB;CAC8B,CAAA;AAEtD;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAAqB,EAAW,EAAE,CAClE,oBAAoB,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC,CAAA","sourcesContent":["// The responder system's public vocabulary: the touch-history shape RN's\n// PanResponder expects, and the props a View accepts.\n//\n// The touch-history shape is not ours to design. react-native-web proved\n// that RN's own PanResponder runs unmodified on a foreign platform provided\n// this structure is reproduced exactly — field for field, including the\n// three-way current/previous/start split and the millisecond timestamps —\n// so it is transcribed from ResponderTouchHistoryStore rather than invented.\nimport type { NativeTouch, PressEvent } from \"../components/press-event\"\n\n/** One tracked touch. Field names are RN's; do not \"tidy\" them. */\nexport type TouchRecord = {\n touchActive: boolean\n startPageX: number\n startPageY: number\n startTimeStamp: number\n currentPageX: number\n currentPageY: number\n currentTimeStamp: number\n previousPageX: number\n previousPageY: number\n previousTimeStamp: number\n}\n\nexport type TouchHistory = {\n // Sparse: indexed by touch identifier, exactly as RN does it.\n touchBank: TouchRecord[]\n numberActiveTouches: number\n // Only meaningful when numberActiveTouches === 1; TouchHistoryMath's fast\n // path reads it directly.\n indexOfSingleActiveTouch: number\n mostRecentTimeStamp: number\n}\n\n/**\n * What every responder callback receives. PanResponder reads `touchHistory`\n * off the EVENT, not off `nativeEvent` — an easy detail to get wrong, and it\n * silently zeroes every gesture if you do.\n */\nexport type GestureResponderEvent = PressEvent & {\n touchHistory: TouchHistory\n}\n\nexport type ShouldSetResponder = (event: GestureResponderEvent) => boolean\n\n/**\n * RN's responder and touch props. The touch props fire regardless of who\n * holds the responder; the rest are the negotiation.\n */\nexport type ResponderProps = {\n onStartShouldSetResponder?: ShouldSetResponder\n onStartShouldSetResponderCapture?: ShouldSetResponder\n onMoveShouldSetResponder?: ShouldSetResponder\n onMoveShouldSetResponderCapture?: ShouldSetResponder\n // RN lets onResponderGrant return a boolean (onShouldBlockNativeResponder);\n // PanResponder's does. Nothing consumes it here yet — see docs/api.md.\n onResponderGrant?: (event: GestureResponderEvent) => void | boolean\n onResponderStart?: (event: GestureResponderEvent) => void\n onResponderMove?: (event: GestureResponderEvent) => void\n onResponderEnd?: (event: GestureResponderEvent) => void\n onResponderRelease?: (event: GestureResponderEvent) => void\n onResponderTerminate?: (event: GestureResponderEvent) => void\n onResponderTerminationRequest?: ShouldSetResponder\n onResponderReject?: (event: GestureResponderEvent) => void\n\n onTouchStart?: (event: GestureResponderEvent) => void\n onTouchStartCapture?: (event: GestureResponderEvent) => void\n onTouchMove?: (event: GestureResponderEvent) => void\n onTouchMoveCapture?: (event: GestureResponderEvent) => void\n onTouchEnd?: (event: GestureResponderEvent) => void\n onTouchEndCapture?: (event: GestureResponderEvent) => void\n onTouchCancel?: (event: GestureResponderEvent) => void\n onTouchCancelCapture?: (event: GestureResponderEvent) => void\n}\n\nconst RESPONDER_PROP_NAMES = [\n \"onStartShouldSetResponder\",\n \"onStartShouldSetResponderCapture\",\n \"onMoveShouldSetResponder\",\n \"onMoveShouldSetResponderCapture\",\n \"onResponderGrant\",\n \"onResponderStart\",\n \"onResponderMove\",\n \"onResponderEnd\",\n \"onResponderRelease\",\n \"onResponderTerminate\",\n \"onResponderTerminationRequest\",\n \"onResponderReject\",\n \"onTouchStart\",\n \"onTouchStartCapture\",\n \"onTouchMove\",\n \"onTouchMoveCapture\",\n \"onTouchEnd\",\n \"onTouchEndCapture\",\n \"onTouchCancel\",\n \"onTouchCancelCapture\",\n] as const satisfies readonly (keyof ResponderProps)[]\n\n/**\n * Whether a component needs an event source at all. A View with no responder\n * props gets no GTK controller: RN only ever asks views that declare\n * handlers, so attaching one everywhere would be pure cost.\n */\nexport const hasResponderProps = (props: ResponderProps): boolean =>\n RESPONDER_PROP_NAMES.some((name) => props[name] !== undefined)\n\n/** A single touch as PanResponder's `touches`/`changedTouches` see it. */\nexport type { NativeTouch }\n"]}
@@ -0,0 +1,12 @@
1
+ import { type RefObject } from "react";
2
+ import { Gtk } from "../gtkx/bridge/index";
3
+ import { type ResponderHost } from "./system";
4
+ import { type ResponderProps } from "./types";
5
+ /** Exposed for tests and for future ScrollView arbitration. */
6
+ export declare const getCurrentResponder: () => ResponderHost | null;
7
+ /**
8
+ * Attaches the responder event source to a component's widget, but only when
9
+ * it actually declares responder props — RN never asks a view without
10
+ * handlers, so a controller on every View would be pure cost.
11
+ */
12
+ export declare const useResponder: (widgetRef: RefObject<Gtk.Widget | null>, props: ResponderProps) => void;
@@ -0,0 +1,98 @@
1
+ // The platform half of the responder system: GTK carries the events, the
2
+ // pure module in ./system owns the algorithm.
3
+ //
4
+ // Shape and why (docs/research/gestures.md, "Verifiability decides the
5
+ // implementation shape"): the architecturally cleanest source would be one
6
+ // GtkEventControllerLegacy on the toplevel doing its own hit-testing, which
7
+ // is react-native-web's design. It is also untestable here —
8
+ // @gtkx/testing's userEvent emits GtkGesture SIGNALS on the widget you name
9
+ // and never produces a GdkEvent, so a raw tap could not be driven in CI at
10
+ // all. A gesture per responder-declaring View is drivable today
11
+ // (`userEvent.drag`), and GTK's own bubble chain still delivers a press to
12
+ // every ancestor's gesture, so the path is not lost.
13
+ //
14
+ // One GtkGestureDrag per View covers press, move and release: it emits
15
+ // drag-begin on press with no threshold of its own, which is exactly what
16
+ // RN wants — the responder system has no threshold either, and PanResponder
17
+ // users apply their own inside onMoveShouldSetPanResponder.
18
+ import { useLayoutEffect, useRef } from "react";
19
+ import { createTouch } from "../components/press-event";
20
+ import { Gtk } from "../gtkx/bridge/index";
21
+ import { createResponderSystem } from "./system";
22
+ import { hasResponderProps } from "./types";
23
+ // The gesture that reported each registered widget, so a grant can make the
24
+ // GTK claim on the right one.
25
+ const gestures = new WeakMap();
26
+ /**
27
+ * One lock for the process, as in RN. What is island-scoped is the
28
+ * negotiation PATH, not the lock: `parentOf` walks the GTK widget tree and
29
+ * simply finds nothing registered above a layout Root, so an RN island
30
+ * inside a native widget tree negotiates among its own views and no others.
31
+ */
32
+ const responderSystem = createResponderSystem({
33
+ parentOf: (host) => host.getParent(),
34
+ onGrant: (host) => {
35
+ // GTK's only arbitration primitive, used the only way it can honestly be
36
+ // used: a final one-way declaration AFTER JS has decided. There is no
37
+ // un-claim, so nothing may depend on taking it back.
38
+ gestures.get(host)?.setState(Gtk.EventSequenceState.CLAIMED);
39
+ },
40
+ });
41
+ /** Exposed for tests and for future ScrollView arbitration. */
42
+ export const getCurrentResponder = () => responderSystem.getResponder();
43
+ /**
44
+ * Attaches the responder event source to a component's widget, but only when
45
+ * it actually declares responder props — RN never asks a view without
46
+ * handlers, so a controller on every View would be pure cost.
47
+ */
48
+ export const useResponder = (widgetRef, props) => {
49
+ // Handlers are read through a ref so a re-render never detaches and
50
+ // re-adds the gesture mid-drag (the same reasoning as Pressable's). The
51
+ // ref is refreshed in an effect rather than during render: a render-phase
52
+ // write is not safe under concurrent rendering, and this effect is
53
+ // declared BEFORE the registration effect so the handlers are already
54
+ // current the first time the gesture can fire.
55
+ const propsRef = useRef(props);
56
+ useLayoutEffect(() => {
57
+ propsRef.current = props;
58
+ });
59
+ const active = hasResponderProps(props);
60
+ useLayoutEffect(() => {
61
+ const widget = widgetRef.current;
62
+ if (!widget || !active) {
63
+ return;
64
+ }
65
+ const unregister = responderSystem.register(widget, () => propsRef.current);
66
+ const drag = new Gtk.GestureDrag();
67
+ gestures.set(widget, drag);
68
+ // drag-update/end report offsets FROM the start point, not absolute
69
+ // coordinates — the start has to be carried.
70
+ let startX = 0;
71
+ let startY = 0;
72
+ drag.on("drag-begin", (x, y) => {
73
+ startX = x;
74
+ startY = y;
75
+ responderSystem.touchStart(widget, createTouch(widget, x, y));
76
+ });
77
+ drag.on("drag-update", (offsetX, offsetY) => {
78
+ responderSystem.touchMove(widget, createTouch(widget, startX + offsetX, startY + offsetY));
79
+ });
80
+ drag.on("drag-end", (offsetX, offsetY) => {
81
+ responderSystem.touchEnd(widget, createTouch(widget, startX + offsetX, startY + offsetY));
82
+ });
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.
86
+ drag.on("cancel", () => {
87
+ responderSystem.touchCancel(widget, createTouch(widget, startX, startY));
88
+ });
89
+ widget.addController(drag);
90
+ return () => {
91
+ widget.removeController(drag);
92
+ gestures.delete(widget);
93
+ unregister();
94
+ };
95
+ // eslint-disable-next-line react-hooks/exhaustive-deps
96
+ }, [active]);
97
+ };
98
+ //# sourceMappingURL=use-responder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-responder.js","sourceRoot":"","sources":["../../src/responder/use-responder.ts"],"names":[],"mappings":"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","sourcesContent":["// The platform half of the responder system: GTK carries the events, the\n// pure module in ./system owns the algorithm.\n//\n// Shape and why (docs/research/gestures.md, \"Verifiability decides the\n// implementation shape\"): the architecturally cleanest source would be one\n// GtkEventControllerLegacy on the toplevel doing its own hit-testing, which\n// is react-native-web's design. It is also untestable here —\n// @gtkx/testing's userEvent emits GtkGesture SIGNALS on the widget you name\n// and never produces a GdkEvent, so a raw tap could not be driven in CI at\n// all. A gesture per responder-declaring View is drivable today\n// (`userEvent.drag`), and GTK's own bubble chain still delivers a press to\n// every ancestor's gesture, so the path is not lost.\n//\n// One GtkGestureDrag per View covers press, move and release: it emits\n// drag-begin on press with no threshold of its own, which is exactly what\n// RN wants — the responder system has no threshold either, and PanResponder\n// users apply their own inside onMoveShouldSetPanResponder.\nimport { useLayoutEffect, useRef, type RefObject } from \"react\"\nimport { createTouch } from \"../components/press-event\"\nimport { Gtk } from \"../gtkx/bridge/index\"\nimport { createResponderSystem, type ResponderHost } from \"./system\"\nimport { hasResponderProps, type ResponderProps } from \"./types\"\n\n// The gesture that reported each registered widget, so a grant can make the\n// GTK claim on the right one.\nconst gestures = new WeakMap<ResponderHost, Gtk.Gesture>()\n\n/**\n * One lock for the process, as in RN. What is island-scoped is the\n * negotiation PATH, not the lock: `parentOf` walks the GTK widget tree and\n * simply finds nothing registered above a layout Root, so an RN island\n * inside a native widget tree negotiates among its own views and no others.\n */\nconst responderSystem = createResponderSystem({\n parentOf: (host) => (host as Gtk.Widget).getParent(),\n onGrant: (host) => {\n // GTK's only arbitration primitive, used the only way it can honestly be\n // used: a final one-way declaration AFTER JS has decided. There is no\n // un-claim, so nothing may depend on taking it back.\n gestures.get(host)?.setState(Gtk.EventSequenceState.CLAIMED)\n },\n})\n\n/** Exposed for tests and for future ScrollView arbitration. */\nexport const getCurrentResponder = (): ResponderHost | null =>\n responderSystem.getResponder()\n\n/**\n * Attaches the responder event source to a component's widget, but only when\n * it actually declares responder props — RN never asks a view without\n * handlers, so a controller on every View would be pure cost.\n */\nexport const useResponder = (\n widgetRef: RefObject<Gtk.Widget | null>,\n props: ResponderProps,\n): void => {\n // Handlers are read through a ref so a re-render never detaches and\n // re-adds the gesture mid-drag (the same reasoning as Pressable's). The\n // ref is refreshed in an effect rather than during render: a render-phase\n // write is not safe under concurrent rendering, and this effect is\n // declared BEFORE the registration effect so the handlers are already\n // current the first time the gesture can fire.\n const propsRef = useRef(props)\n useLayoutEffect(() => {\n propsRef.current = props\n })\n const active = hasResponderProps(props)\n\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget || !active) {\n return\n }\n\n const unregister = responderSystem.register(widget, () => propsRef.current)\n\n const drag = new Gtk.GestureDrag()\n gestures.set(widget, drag)\n\n // drag-update/end report offsets FROM the start point, not absolute\n // coordinates — the start has to be carried.\n let startX = 0\n let startY = 0\n\n drag.on(\"drag-begin\", (x: number, y: number) => {\n startX = x\n startY = y\n responderSystem.touchStart(widget, createTouch(widget, x, y))\n })\n drag.on(\"drag-update\", (offsetX: number, offsetY: number) => {\n responderSystem.touchMove(\n widget,\n createTouch(widget, startX + offsetX, startY + offsetY),\n )\n })\n drag.on(\"drag-end\", (offsetX: number, offsetY: number) => {\n responderSystem.touchEnd(\n widget,\n createTouch(widget, startX + offsetX, startY + offsetY),\n )\n })\n // GTK cancels a sequence when something upstream claims it — a native\n // ancestor scrolling, say. RN calls that a termination, and it is the\n // one termination trigger we get for free.\n drag.on(\"cancel\", () => {\n responderSystem.touchCancel(widget, createTouch(widget, startX, startY))\n })\n\n widget.addController(drag)\n\n return () => {\n widget.removeController(drag)\n gestures.delete(widget)\n unregister()\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [active])\n}\n"]}
@@ -54,7 +54,9 @@ globalThis.__ReactRefresh = {
54
54
  const CONFIG_REQUIRED = "gtkx.config.ts with an applicationId is required in the app root " +
55
55
  '(export default defineConfig({ applicationId: "...", libraries: [...] })).';
56
56
  const { createConfigLoader } = await import(pathToFileURL(fromPackage.resolve("@gtkx/config/internal")).href);
57
- const gtkxConfig = await createConfigLoader()(process.cwd()).catch((error) => fail(`${CONFIG_REQUIRED}\n${String(error)}`));
57
+ const gtkxConfig = await createConfigLoader()
58
+ .resolve(process.cwd())
59
+ .catch((error) => fail(`${CONFIG_REQUIRED}\n${String(error)}`));
58
60
  if (!gtkxConfig.applicationId) {
59
61
  fail(CONFIG_REQUIRED);
60
62
  }