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
@@ -0,0 +1,273 @@
1
+ import { animationSignature, buildAnimation, isAnimationSpec, targetOf, } from "./animation";
2
+ // How far behind its own animation a property this platform will not drive at
3
+ // frame rate is allowed to fall.
4
+ //
5
+ // The settle alone is not an answer, and `@gorhom/bottom-sheet` MOUNTING is
6
+ // the measurement that says so. Its content mask is bounded by an animated
7
+ // `height` whose target is derived from the sheet's own POSITION, so every
8
+ // frame of the opening spring re-aims it — 37 re-aims over 673 ms, each
9
+ // cancelling the last, so not one of them a settle — and then a further
10
+ // 677 ms for the final spring to converge. Measured on the gallery's
11
+ // `upstream-bottom-sheet` screen: the seed was 95.9 px (gorhom's
12
+ // `animatedContentHeightMax` was still 0 on the mapper run that first
13
+ // produced a `height` at all, and the real 832 px arrived 27 ms later, by
14
+ // which time the key already had an entry and a moving target is an animation
15
+ // rather than a seed), and `height` reached Yoga exactly TWICE in the whole
16
+ // mount: 95.9 px at once, 954.6 px 1378 ms later. For 1.38 s the mask was a
17
+ // tenth of its size, the list inside it had no bounded parent, and it mounted
18
+ // zero of its 18 rows. Correct by the settle rule and plainly wrong to
19
+ // anybody opening the screen.
20
+ //
21
+ // So the value lands on a cadence as well. 100 ms because that is the bound
22
+ // under which a change still reads as immediate, and because it bounds the
23
+ // cost by the CLOCK rather than by the animation: at most ten renders a
24
+ // second per animated key, against sixty frames. The same mount now lands the
25
+ // mask at 266 → 546 → 649 → 731 → 792 → 832 → 896 → 939 → 951 → 954 px before
26
+ // settling at 954.6 — the shape the animation has, at a tenth of its rate —
27
+ // with the first at ~100 ms rather than 1.38 s.
28
+ //
29
+ // What it costs, both halves measured in one `spike/core-exports` run on the
30
+ // same machine: 4 settles and 4 renders against 290 refused-property frames
31
+ // before, 4 settles + 38 landings and 42 renders against 294 after. Still an
32
+ // order of magnitude under the per-frame layout write the refusal exists to
33
+ // avoid, and no longer a promise that may never come due.
34
+ // docs/research/animated-size.md §10.
35
+ const LANDING_INTERVAL_MS = 100;
36
+ // A landing that cannot move a widget is a render for nothing: GTK allocates
37
+ // whole pixels, so a sub-pixel step changes no committed geometry. It is what
38
+ // keeps a long animation over a short distance from spending ten renders a
39
+ // second to travel one pixel — gorhom's `paddingBottom` crosses 26 px over the
40
+ // same 1.38 s and lands 8 times, not 13.
41
+ const LANDING_EPSILON = 1;
42
+ /**
43
+ * @param publish Receives the updater's object with every animated key
44
+ * replaced by a number. Called once per mapper run and once per animation
45
+ * frame.
46
+ * @param onSettled Called when an animation on `key` reaches its target on its
47
+ * own (never when it is cancelled or replaced). The style layer uses it for
48
+ * the properties it refuses to drive at frame rate, whose contract is that
49
+ * the value lands on the next React render — without it, that promise is
50
+ * only kept when something else happens to re-render.
51
+ * @param onLanding Called when `key`'s current value has to reach React
52
+ * without its animation having reached a target. Two occasions: while an
53
+ * animation runs, at most once per `LANDING_INTERVAL_MS` and only when the
54
+ * value has moved far enough to change a committed layout (the cadence); and
55
+ * when a plain number REPLACES a running animation, where upstream cancels
56
+ * and snaps and a refused property cannot snap without a render. The snap is
57
+ * deliberately not rate-limited — it is a state change rather than a step of
58
+ * one, it happens once per mapper run that flips the key rather than once per
59
+ * frame, and upstream pushes exactly the same number of `updateProps` for it.
60
+ * Same job as `onSettled` and the same handling at the call site; it is a
61
+ * separate callback because a settle and a value that has merely got to be
62
+ * published are different claims, and only the first one is a promise kept.
63
+ * Omit it and both cost nothing at all — a caller with no refused properties
64
+ * (`useAnimatedProps`) does.
65
+ */
66
+ export const createUpdaterAnimations = (engine, publish, onSettled, onLanding) => {
67
+ const entries = new Map();
68
+ // The clock the animations themselves run on, never `Date` — the whole
69
+ // layer is driven by one injected scheduler so a test can take it
70
+ // (components/frame-scheduler.ts). A driver that only has frame stamps and
71
+ // no off-frame `now` cannot have a cadence, and falls back to the settle.
72
+ const hasClock = engine.scheduler.now !== undefined;
73
+ const now = () => engine.scheduler.now?.() ?? 0;
74
+ let lastSource = null;
75
+ // A frame of one key's animation republishes the WHOLE object, which
76
+ // re-enters `resolve`. The guard keeps that from restarting anything: the
77
+ // run in progress publishes the final state itself.
78
+ let resolving = false;
79
+ /**
80
+ * @param replacement The plain value that took the key over, or `undefined`
81
+ * when the key left the updater's result altogether.
82
+ *
83
+ * A number here owes the caller a render, and `landedValue` is what decides
84
+ * it: that field is this module's whole model of what REACT holds for the
85
+ * key, and everything since the last landing or settle went to the widget
86
+ * without passing through React. So a plain number that differs from it has
87
+ * to be published or it never arrives — on a refused property the snap IS
88
+ * a render, and neither the cadence nor a settle is ever going to come for
89
+ * it. Compared without the cadence's one-pixel epsilon and for the same
90
+ * reason a settle ignores it: this is a resting value rather than a step
91
+ * towards one, and there is nothing after it to correct a skipped render.
92
+ *
93
+ * Not asked for otherwise. A key that VANISHED, or one replaced by a
94
+ * percentage or a colour, changes the style's SHAPE, and the caller already
95
+ * pays exactly one render for that (hooks.ts); a value equal to the one
96
+ * React already has is a render for nothing.
97
+ */
98
+ const drop = (key, replacement) => {
99
+ const entry = entries.get(key);
100
+ if (entry === undefined) {
101
+ return;
102
+ }
103
+ entries.delete(key);
104
+ entry.running?.stop();
105
+ entry.driver.removeListener(entry.listenerId);
106
+ if (typeof replacement === "number" &&
107
+ !Object.is(replacement, entry.landedValue)) {
108
+ onLanding?.(key);
109
+ }
110
+ };
111
+ const republish = () => {
112
+ if (resolving || lastSource === null) {
113
+ return;
114
+ }
115
+ // No previous result on a frame: nothing can START an animation here — the
116
+ // source has not changed — so there is nothing to look a starting point up
117
+ // for.
118
+ publish(resolve(lastSource, null));
119
+ };
120
+ /**
121
+ * Records that `key`'s current value is the one React now holds. Called
122
+ * wherever a render is asked for, so the cadence is measured from the last
123
+ * value React was actually given rather than from the last frame.
124
+ */
125
+ const markLanded = (entry) => {
126
+ entry.landedValue = entry.driver.__getValue();
127
+ entry.landedAt = now();
128
+ };
129
+ /** The cadence: see LANDING_INTERVAL_MS. */
130
+ const considerLanding = (key, value) => {
131
+ if (onLanding === undefined || !hasClock) {
132
+ return;
133
+ }
134
+ const entry = entries.get(key);
135
+ if (entry === undefined) {
136
+ return;
137
+ }
138
+ if (now() - entry.landedAt < LANDING_INTERVAL_MS) {
139
+ return;
140
+ }
141
+ if (Math.abs(value - entry.landedValue) < LANDING_EPSILON) {
142
+ return;
143
+ }
144
+ markLanded(entry);
145
+ onLanding(key);
146
+ };
147
+ /** Aims `entry`'s driver at `spec`, replacing whatever it was running. */
148
+ const start = (key, entry, spec, signature) => {
149
+ entry.signature = signature;
150
+ entry.running?.stop();
151
+ const animation = buildAnimation(engine, entry.driver, spec);
152
+ entry.running = animation;
153
+ animation.start((result) => {
154
+ if (entry.running === animation) {
155
+ entry.running = null;
156
+ }
157
+ // `finished` only: a restart stops the previous animation, and reporting
158
+ // that as a settle would publish through React on every frame the target
159
+ // moves, which is the cost the refusal exists to avoid. A restart is
160
+ // still covered — the cadence above is what carries a target that keeps
161
+ // moving, and it does not care whose animation the frames belong to.
162
+ if (result.finished) {
163
+ markLanded(entry);
164
+ onSettled?.(key);
165
+ }
166
+ });
167
+ return entry;
168
+ };
169
+ /**
170
+ * Where an animation that is starting has to start FROM, for a key that was
171
+ * not animating a moment ago: the value the PREVIOUS updater result held for
172
+ * it, and only when that is a real number.
173
+ *
174
+ * This is `prepareAnimation(…, oldValues[key])` and nothing more —
175
+ * `oldValues` is `state.last`, upstream's copy of the whole previous updater
176
+ * result — so the second of the header's three rules costs no bookkeeping at
177
+ * all. The previous result is an object this module was already holding for
178
+ * `republish`; reading one key out of it is the entire mechanism, and the
179
+ * entry does not have to carry a value across runs or outlive its animation.
180
+ *
181
+ * Numbers only, and `undefined` for everything else, which lines up with
182
+ * upstream at both ends: a key that was absent has no starting point (its
183
+ * `oldValues[key]` is `undefined`, and the animation's own `current` — the
184
+ * target — stands), and a percentage string or a colour is not something the
185
+ * numeric drivers here can start at.
186
+ */
187
+ const startingPoint = (previous, key) => {
188
+ const value = previous?.[key];
189
+ return typeof value === "number" && Number.isFinite(value)
190
+ ? value
191
+ : undefined;
192
+ };
193
+ const ensure = (key, spec, previous) => {
194
+ const signature = animationSignature(spec);
195
+ const existing = entries.get(key);
196
+ if (existing !== undefined) {
197
+ return existing.signature === signature
198
+ ? existing
199
+ : start(key, existing, spec, signature);
200
+ }
201
+ const from = startingPoint(previous, key);
202
+ // The driver is seeded before anything is built on purpose: a spring reads
203
+ // its origin off the value it is given (animation.ts), so this IS where
204
+ // "animate from the old number" happens.
205
+ const driver = new engine.api.Value(from ?? targetOf(spec) ?? 0);
206
+ const entry = {
207
+ driver,
208
+ listenerId: driver.addListener(({ value }) => {
209
+ republish();
210
+ considerLanding(key, value);
211
+ }),
212
+ signature,
213
+ running: null,
214
+ // Whichever branch follows, React's copy of this key is the value the
215
+ // driver starts at: a seed changes the style's SHAPE and the caller
216
+ // already pays one render for that (hooks.ts), and a starting point that
217
+ // came out of the previous result is a number React was already handed.
218
+ // So the cadence starts here either way.
219
+ landedValue: driver.__getValue(),
220
+ landedAt: now(),
221
+ };
222
+ entries.set(key, entry);
223
+ // Nothing to animate from: seeded at the target and not started at all,
224
+ // which is the first of the header's three rules.
225
+ return from === undefined ? entry : start(key, entry, spec, signature);
226
+ };
227
+ const resolve = (source, previous) => {
228
+ resolving = true;
229
+ try {
230
+ let resolved = null;
231
+ for (const key of Object.keys(source)) {
232
+ const value = source[key];
233
+ if (!isAnimationSpec(value)) {
234
+ // A plain value replacing an animation cancels it and snaps, exactly
235
+ // as an assignment to a shared value does and exactly as upstream's
236
+ // `delete animations[key]` + `updateProps` in the same run does. The
237
+ // number itself is published below, by identity.
238
+ drop(key, value);
239
+ continue;
240
+ }
241
+ resolved ??= { ...source };
242
+ resolved[key] = ensure(key, value, previous).driver.__getValue();
243
+ }
244
+ for (const key of [...entries.keys()]) {
245
+ if (!(key in source)) {
246
+ drop(key, undefined);
247
+ }
248
+ }
249
+ return resolved ?? source;
250
+ }
251
+ finally {
252
+ resolving = false;
253
+ }
254
+ };
255
+ return {
256
+ run(source) {
257
+ // `state.last`, upstream's name for it: the result this run's animations
258
+ // measure themselves against. Held for one run only, and it was already
259
+ // being held.
260
+ const previous = lastSource;
261
+ lastSource = source;
262
+ publish(resolve(source, previous));
263
+ },
264
+ dispose() {
265
+ for (const key of [...entries.keys()]) {
266
+ // Nothing is owed a render: the component is going away.
267
+ drop(key, undefined);
268
+ }
269
+ lastSource = null;
270
+ },
271
+ };
272
+ };
273
+ //# sourceMappingURL=updater-animations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"updater-animations.js","sourceRoot":"","sources":["../../src/reanimated-compat/updater-animations.ts"],"names":[],"mappings":"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","sourcesContent":["// An animation returned FROM a style or props updater, which is the shape the\n// documentation of `useAnimatedStyle` is written in:\n//\n// const style = useAnimatedStyle(() => ({\n// height: withSpring(open.value ? 320 : 0),\n// }))\n//\n// Assigning an animation to a shared value has always worked here\n// (mutable.ts). Returning one from an updater did not, and it failed the way\n// this repo likes least: silently. `with*` builders return a MARKED DESCRIPTOR\n// outside the initial run (animation.ts), the style layer's leaf test is\n// `typeof value === \"number\"`, and a descriptor is an object — so the property\n// was neither driven nor written nor warned about. It simply sat in the style\n// as `{kind: \"spring\", toValue: 543.4, …}`.\n//\n// Found by `spike/core-exports`, not by reading: `@gorhom/bottom-sheet` bounds\n// its scrollable with `height: animate({point: …})` from `useAnimatedStyle`,\n// and the probe's report that the height \"is not reaching the Yoga node\" was\n// true for a reason one layer earlier than layout — the height was never a\n// number on this platform at all. docs/research/animated-size.md §9.\n//\n// WHAT THIS MODULE IS. One running animation per key, on the platform's own\n// `Animated.Value` and the platform's one frame scheduler — the same\n// `buildAnimation` a shared value uses, so `withTiming`, `withSpring`,\n// `withDelay`, `withSequence`, `withRepeat`, `withDecay` and `withClamp` all\n// arrive here already implemented. The updater's object is republished with\n// each animated key replaced by the number its animation is currently at, and\n// a frame of a running animation costs no React render — it goes down exactly\n// the path a shared value written every frame goes down.\n//\n// THREE RULES, and all three are upstream's — every one of them read out of\n// `hook/useAnimatedStyle.ts`'s `styleUpdater`/`prepareAnimation` rather than\n// inferred from behaviour:\n//\n// - A key APPEARING for the first time is seeded at the animation's target\n// rather than animated to it. There is nothing to animate from, and it is\n// the same collapse `initialUpdaterRun` performs on the updater's first\n// run. Upstream reaches the same place from the other direction: its\n// `prepareAnimation` starts at `oldValues[key]`, which for a key that was\n// not in the last result is `undefined` and leaves the animation's own\n// `current` — its target — standing.\n// - A key whose previous result held a PLAIN NUMBER animates from that\n// number. `oldValues` is `state.last`, the whole previous updater result\n// kept raw, and `prepareAnimation`'s last branch is one line with the\n// comment already on it: `// previously it was a plain value, just set it\n// as starting point`. So `height: open.value ? withTiming(200) : 100` runs\n// 100 → 200, and the idiom people actually write — snap shut, open\n// smoothly — animates. It did not here: the key was dropped when it held a\n// number and re-seeded when it next held an animation, so the seed went\n// straight to 200 and the animation never played at all. Silently, which\n// is the failure this repo ranks worst.\n// - A re-run that produces an EQUIVALENT animation does not restart it. A\n// mapper re-runs whenever anything it read changed, which for a real app is\n// many times a second, and rebuilding the spring each time would leave it\n// crawling. Equivalence is the descriptor's target and shape\n// (`animationSignature`), not object identity — every run builds a fresh\n// object.\n//\n// AND THE REVERSE DIRECTION, animation → plain number, which is upstream's\n// too and is not the mirror image of the second rule: `styleUpdater`'s\n// non-animated branch does `delete animations[key]` and pushes the value\n// through `updateProps` in the SAME run, so the animation is cancelled — no\n// callback, no settle — and the number lands at once. It does not ease back.\n// The only thing this platform has to add is the render a refused property\n// needs to land at all, because \"at once\" through React is still a render (see\n// `onLanding`).\n//\n// WHERE THIS DIFFERS FROM UPSTREAM, said out loud: a restart picks the\n// animation up at the value it is currently at, with the velocity the new\n// descriptor asks for, where upstream also carries the previous animation's\n// VELOCITY across. For a target that moves once that is the same animation;\n// for a target that moves every frame ours is slightly more damped.\n//\n// AND THE CADENCE, the one rule here that is ours rather than upstream's — see\n// LANDING_INTERVAL_MS. It exists because none of the above is enough for a\n// property whose frames this platform refuses to write: for those, the value\n// only exists on screen at whatever the last React render committed, and \"the\n// render at the settle\" turned out to be arbitrarily far away.\nimport type { AnimatedValue, CompositeAnimation } from \"../animated/index\"\nimport {\n animationSignature,\n buildAnimation,\n isAnimationSpec,\n targetOf,\n type AnimationEngine,\n type AnimationSpec,\n} from \"./animation\"\n\n/** What an updater returns: a style object, or a props object. */\nexport type UpdaterObject = Record<string, unknown>\n\n// How far behind its own animation a property this platform will not drive at\n// frame rate is allowed to fall.\n//\n// The settle alone is not an answer, and `@gorhom/bottom-sheet` MOUNTING is\n// the measurement that says so. Its content mask is bounded by an animated\n// `height` whose target is derived from the sheet's own POSITION, so every\n// frame of the opening spring re-aims it — 37 re-aims over 673 ms, each\n// cancelling the last, so not one of them a settle — and then a further\n// 677 ms for the final spring to converge. Measured on the gallery's\n// `upstream-bottom-sheet` screen: the seed was 95.9 px (gorhom's\n// `animatedContentHeightMax` was still 0 on the mapper run that first\n// produced a `height` at all, and the real 832 px arrived 27 ms later, by\n// which time the key already had an entry and a moving target is an animation\n// rather than a seed), and `height` reached Yoga exactly TWICE in the whole\n// mount: 95.9 px at once, 954.6 px 1378 ms later. For 1.38 s the mask was a\n// tenth of its size, the list inside it had no bounded parent, and it mounted\n// zero of its 18 rows. Correct by the settle rule and plainly wrong to\n// anybody opening the screen.\n//\n// So the value lands on a cadence as well. 100 ms because that is the bound\n// under which a change still reads as immediate, and because it bounds the\n// cost by the CLOCK rather than by the animation: at most ten renders a\n// second per animated key, against sixty frames. The same mount now lands the\n// mask at 266 → 546 → 649 → 731 → 792 → 832 → 896 → 939 → 951 → 954 px before\n// settling at 954.6 — the shape the animation has, at a tenth of its rate —\n// with the first at ~100 ms rather than 1.38 s.\n//\n// What it costs, both halves measured in one `spike/core-exports` run on the\n// same machine: 4 settles and 4 renders against 290 refused-property frames\n// before, 4 settles + 38 landings and 42 renders against 294 after. Still an\n// order of magnitude under the per-frame layout write the refusal exists to\n// avoid, and no longer a promise that may never come due.\n// docs/research/animated-size.md §10.\nconst LANDING_INTERVAL_MS = 100\n\n// A landing that cannot move a widget is a render for nothing: GTK allocates\n// whole pixels, so a sub-pixel step changes no committed geometry. It is what\n// keeps a long animation over a short distance from spending ten renders a\n// second to travel one pixel — gorhom's `paddingBottom` crosses 26 px over the\n// same 1.38 s and lands 8 times, not 13.\nconst LANDING_EPSILON = 1\n\ntype Entry = {\n driver: AnimatedValue\n listenerId: string\n signature: string\n running: CompositeAnimation | null\n // The value React was last given for this key, and the moment it was given\n // — the two the cadence is measured against. They survive a restart on\n // purpose: what matters is how stale REACT's copy is, not how far the\n // animation currently in flight has come.\n landedValue: number\n landedAt: number\n}\n\nexport type UpdaterAnimations = {\n /**\n * Resolves `source`'s animations and publishes the result. Called from the\n * mapper, so `source` is a fresh object every time.\n */\n run(source: UpdaterObject): void\n dispose(): void\n}\n\n/**\n * @param publish Receives the updater's object with every animated key\n * replaced by a number. Called once per mapper run and once per animation\n * frame.\n * @param onSettled Called when an animation on `key` reaches its target on its\n * own (never when it is cancelled or replaced). The style layer uses it for\n * the properties it refuses to drive at frame rate, whose contract is that\n * the value lands on the next React render — without it, that promise is\n * only kept when something else happens to re-render.\n * @param onLanding Called when `key`'s current value has to reach React\n * without its animation having reached a target. Two occasions: while an\n * animation runs, at most once per `LANDING_INTERVAL_MS` and only when the\n * value has moved far enough to change a committed layout (the cadence); and\n * when a plain number REPLACES a running animation, where upstream cancels\n * and snaps and a refused property cannot snap without a render. The snap is\n * deliberately not rate-limited — it is a state change rather than a step of\n * one, it happens once per mapper run that flips the key rather than once per\n * frame, and upstream pushes exactly the same number of `updateProps` for it.\n * Same job as `onSettled` and the same handling at the call site; it is a\n * separate callback because a settle and a value that has merely got to be\n * published are different claims, and only the first one is a promise kept.\n * Omit it and both cost nothing at all — a caller with no refused properties\n * (`useAnimatedProps`) does.\n */\nexport const createUpdaterAnimations = (\n engine: AnimationEngine,\n publish: (resolved: UpdaterObject) => void,\n onSettled?: (key: string) => void,\n onLanding?: (key: string) => void,\n): UpdaterAnimations => {\n const entries = new Map<string, Entry>()\n // The clock the animations themselves run on, never `Date` — the whole\n // layer is driven by one injected scheduler so a test can take it\n // (components/frame-scheduler.ts). A driver that only has frame stamps and\n // no off-frame `now` cannot have a cadence, and falls back to the settle.\n const hasClock = engine.scheduler.now !== undefined\n const now = (): number => engine.scheduler.now?.() ?? 0\n let lastSource: UpdaterObject | null = null\n // A frame of one key's animation republishes the WHOLE object, which\n // re-enters `resolve`. The guard keeps that from restarting anything: the\n // run in progress publishes the final state itself.\n let resolving = false\n\n /**\n * @param replacement The plain value that took the key over, or `undefined`\n * when the key left the updater's result altogether.\n *\n * A number here owes the caller a render, and `landedValue` is what decides\n * it: that field is this module's whole model of what REACT holds for the\n * key, and everything since the last landing or settle went to the widget\n * without passing through React. So a plain number that differs from it has\n * to be published or it never arrives — on a refused property the snap IS\n * a render, and neither the cadence nor a settle is ever going to come for\n * it. Compared without the cadence's one-pixel epsilon and for the same\n * reason a settle ignores it: this is a resting value rather than a step\n * towards one, and there is nothing after it to correct a skipped render.\n *\n * Not asked for otherwise. A key that VANISHED, or one replaced by a\n * percentage or a colour, changes the style's SHAPE, and the caller already\n * pays exactly one render for that (hooks.ts); a value equal to the one\n * React already has is a render for nothing.\n */\n const drop = (key: string, replacement: unknown): void => {\n const entry = entries.get(key)\n if (entry === undefined) {\n return\n }\n entries.delete(key)\n entry.running?.stop()\n entry.driver.removeListener(entry.listenerId)\n if (\n typeof replacement === \"number\" &&\n !Object.is(replacement, entry.landedValue)\n ) {\n onLanding?.(key)\n }\n }\n\n const republish = (): void => {\n if (resolving || lastSource === null) {\n return\n }\n // No previous result on a frame: nothing can START an animation here — the\n // source has not changed — so there is nothing to look a starting point up\n // for.\n publish(resolve(lastSource, null))\n }\n\n /**\n * Records that `key`'s current value is the one React now holds. Called\n * wherever a render is asked for, so the cadence is measured from the last\n * value React was actually given rather than from the last frame.\n */\n const markLanded = (entry: Entry): void => {\n entry.landedValue = entry.driver.__getValue()\n entry.landedAt = now()\n }\n\n /** The cadence: see LANDING_INTERVAL_MS. */\n const considerLanding = (key: string, value: number): void => {\n if (onLanding === undefined || !hasClock) {\n return\n }\n const entry = entries.get(key)\n if (entry === undefined) {\n return\n }\n if (now() - entry.landedAt < LANDING_INTERVAL_MS) {\n return\n }\n if (Math.abs(value - entry.landedValue) < LANDING_EPSILON) {\n return\n }\n markLanded(entry)\n onLanding(key)\n }\n\n /** Aims `entry`'s driver at `spec`, replacing whatever it was running. */\n const start = (\n key: string,\n entry: Entry,\n spec: AnimationSpec,\n signature: string,\n ): Entry => {\n entry.signature = signature\n entry.running?.stop()\n const animation = buildAnimation(engine, entry.driver, spec)\n entry.running = animation\n animation.start((result) => {\n if (entry.running === animation) {\n entry.running = null\n }\n // `finished` only: a restart stops the previous animation, and reporting\n // that as a settle would publish through React on every frame the target\n // moves, which is the cost the refusal exists to avoid. A restart is\n // still covered — the cadence above is what carries a target that keeps\n // moving, and it does not care whose animation the frames belong to.\n if (result.finished) {\n markLanded(entry)\n onSettled?.(key)\n }\n })\n return entry\n }\n\n /**\n * Where an animation that is starting has to start FROM, for a key that was\n * not animating a moment ago: the value the PREVIOUS updater result held for\n * it, and only when that is a real number.\n *\n * This is `prepareAnimation(…, oldValues[key])` and nothing more —\n * `oldValues` is `state.last`, upstream's copy of the whole previous updater\n * result — so the second of the header's three rules costs no bookkeeping at\n * all. The previous result is an object this module was already holding for\n * `republish`; reading one key out of it is the entire mechanism, and the\n * entry does not have to carry a value across runs or outlive its animation.\n *\n * Numbers only, and `undefined` for everything else, which lines up with\n * upstream at both ends: a key that was absent has no starting point (its\n * `oldValues[key]` is `undefined`, and the animation's own `current` — the\n * target — stands), and a percentage string or a colour is not something the\n * numeric drivers here can start at.\n */\n const startingPoint = (\n previous: UpdaterObject | null,\n key: string,\n ): number | undefined => {\n const value = previous?.[key]\n return typeof value === \"number\" && Number.isFinite(value)\n ? value\n : undefined\n }\n\n const ensure = (\n key: string,\n spec: AnimationSpec,\n previous: UpdaterObject | null,\n ): Entry => {\n const signature = animationSignature(spec)\n const existing = entries.get(key)\n if (existing !== undefined) {\n return existing.signature === signature\n ? existing\n : start(key, existing, spec, signature)\n }\n const from = startingPoint(previous, key)\n // The driver is seeded before anything is built on purpose: a spring reads\n // its origin off the value it is given (animation.ts), so this IS where\n // \"animate from the old number\" happens.\n const driver = new engine.api.Value(from ?? targetOf(spec) ?? 0)\n const entry: Entry = {\n driver,\n listenerId: driver.addListener(({ value }) => {\n republish()\n considerLanding(key, value)\n }),\n signature,\n running: null,\n // Whichever branch follows, React's copy of this key is the value the\n // driver starts at: a seed changes the style's SHAPE and the caller\n // already pays one render for that (hooks.ts), and a starting point that\n // came out of the previous result is a number React was already handed.\n // So the cadence starts here either way.\n landedValue: driver.__getValue(),\n landedAt: now(),\n }\n entries.set(key, entry)\n // Nothing to animate from: seeded at the target and not started at all,\n // which is the first of the header's three rules.\n return from === undefined ? entry : start(key, entry, spec, signature)\n }\n\n const resolve = (\n source: UpdaterObject,\n previous: UpdaterObject | null,\n ): UpdaterObject => {\n resolving = true\n try {\n let resolved: UpdaterObject | null = null\n for (const key of Object.keys(source)) {\n const value = source[key]\n if (!isAnimationSpec(value)) {\n // A plain value replacing an animation cancels it and snaps, exactly\n // as an assignment to a shared value does and exactly as upstream's\n // `delete animations[key]` + `updateProps` in the same run does. The\n // number itself is published below, by identity.\n drop(key, value)\n continue\n }\n resolved ??= { ...source }\n resolved[key] = ensure(key, value, previous).driver.__getValue()\n }\n for (const key of [...entries.keys()]) {\n if (!(key in source)) {\n drop(key, undefined)\n }\n }\n return resolved ?? source\n } finally {\n resolving = false\n }\n }\n\n return {\n run(source) {\n // `state.last`, upstream's name for it: the result this run's animations\n // measure themselves against. Held for one run only, and it was already\n // being held.\n const previous = lastSource\n lastSource = source\n publish(resolve(source, previous))\n },\n dispose() {\n for (const key of [...entries.keys()]) {\n // Nothing is owed a render: the component is going away.\n drop(key, undefined)\n }\n lastSource = null\n },\n }\n}\n"]}
@@ -2,18 +2,49 @@ import type { NativeTouch } from "../components/press-event";
2
2
  import type { ResponderProps } from "./types";
3
3
  /** An opaque platform handle. A Gtk.Widget in practice; never treated as one. */
4
4
  export type ResponderHost = object;
5
+ /**
6
+ * Why the responder was taken away by something that is not a pointer event.
7
+ *
8
+ * react-native-web's list is longer — cancel-ish events, context menu,
9
+ * window blur, ancestor scroll, selection change — and measuring GTK
10
+ * collapsed most of it. GTK cancels a single-button gesture the instant a
11
+ * second button goes down, and a widget that steals a sequence (a
12
+ * selectable label, a `GtkDragSource` attached through `Controllers`)
13
+ * cancels it too, so "context menu" and "selection change" both arrive as
14
+ * an ordinary `::cancel` on the event source and need no trigger of their
15
+ * own. What is left needs asking for:
16
+ *
17
+ * - `"blur"`: the toplevel stopped being the active window. Not a question,
18
+ * in RN or here — the window is gone either way.
19
+ * - `"scroll"`: an ancestor scroller moved under the gesture. The one
20
+ * termination a holder may refuse, and therefore the only place
21
+ * `onResponderTerminationRequest` is consulted outside a transfer (PRD
22
+ * crux 2: GTK's `CLAIMED` is irrevocable, so anything GTK has already
23
+ * taken cannot be given back no matter what the holder answers).
24
+ */
25
+ export type TerminationReason = "blur" | "scroll";
5
26
  export type ResponderSystemOptions = {
6
27
  /** The platform's parent link. Returning null ends the path. */
7
28
  parentOf: (host: ResponderHost) => ResponderHost | null;
8
29
  /**
9
- * Fired after a node is granted the responder where the platform makes
10
- * its GTK `CLAIMED` declaration. One-way by construction: GTK has no way
11
- * to hand a claimed sequence back, so this is only ever called once JS has
12
- * already decided (docs/research/gestures.md, "no voluntary release").
30
+ * Fired at most ONCE per interaction, the first time any node is granted
31
+ * the responder — where the platform makes its GTK `CLAIMED` declaration.
32
+ *
33
+ * Its argument is the node whose event source is carrying the interaction,
34
+ * NOT the node that won the responder. Those differ whenever an ancestor
35
+ * claims (the common `onMoveShouldSetPanResponder` shape), and claiming on
36
+ * the ancestor is actively wrong: GTK denies the sequence on every gesture
37
+ * below the one that claims, which kills the very source the interaction
38
+ * is arriving through. What GTK is being told is "this interaction belongs
39
+ * to React Native, native widgets keep off" — a statement about the
40
+ * island, not about which view inside it currently holds the lock, which
41
+ * is a JS matter GTK has no way to represent.
42
+ *
43
+ * One-way by construction: GTK has no way to hand a claimed sequence back,
44
+ * so this only ever runs once JS has already decided (see
45
+ * docs/research/gestures.md, "no voluntary release").
13
46
  */
14
- onGrant?: (host: ResponderHost) => void;
15
- /** Fired when the responder is released or terminated. */
16
- onRelease?: (host: ResponderHost) => void;
47
+ onClaim?: (source: ResponderHost) => void;
17
48
  };
18
49
  export type ResponderSystem = {
19
50
  register(host: ResponderHost, getProps: () => ResponderProps): () => void;
@@ -21,7 +52,49 @@ export type ResponderSystem = {
21
52
  touchMove(host: ResponderHost, touch: NativeTouch): void;
22
53
  touchEnd(host: ResponderHost, touch: NativeTouch): void;
23
54
  touchCancel(host: ResponderHost, touch: NativeTouch): void;
55
+ /**
56
+ * Something that is not a pointer event took the interaction. Returns
57
+ * whether the responder was actually given up — `false` means either
58
+ * nothing held it or the holder refused a reason that allows refusal.
59
+ */
60
+ terminate(reason: TerminationReason): boolean;
61
+ /**
62
+ * THE OUT-OF-EVENT GRANT CHANNEL. A registered node asks for the responder
63
+ * between touch events, and the ordinary negotiation runs against a
64
+ * synthesized event at the last known pointer position.
65
+ *
66
+ * RN negotiates inside touch events only, so a recognizer that changes its
67
+ * mind on a TIMER has no way to take the interaction until the pointer next
68
+ * moves. `activateAfterLongPress` is exactly that recognizer: the press
69
+ * matures while the pointer is still, and waiting for the next move puts
70
+ * the "lifted" visual one frame late — or, for a press-and-hold that never
71
+ * moves again, never.
72
+ *
73
+ * The precedent is react-native-web, which added
74
+ * `onScrollShouldSetResponder` and `onSelectionChangeShouldSetResponder`
75
+ * for its own platform's realities; extending the model where the platform
76
+ * demands it is recorded as acceptable in docs/research/gestures.md, and
77
+ * this channel is documented there with its reason.
78
+ *
79
+ * Deliberately NOT a new prop and not a new grant path: it reuses
80
+ * `negotiateAndTransfer` unchanged, so capture still beats bubble, the LCA
81
+ * pruning still applies, and an ancestor that wants the interaction still
82
+ * wins it. The only thing that is new is WHEN the question gets asked. The
83
+ * synthesized event is the same one `terminate()` already builds from
84
+ * `session.lastTouch`, for the same reason.
85
+ *
86
+ * No `onResponderMove` is dispatched afterwards: the pointer did not move,
87
+ * and inventing a move to announce a grant would put a fabricated
88
+ * coordinate into every consumer's translation arithmetic.
89
+ *
90
+ * Returns whether `host` holds the responder once the negotiation is over
91
+ * — `false` when there is no interaction, when `host` is not on the path
92
+ * the interaction is travelling, or when somebody else won.
93
+ */
94
+ requestResponder(host: ResponderHost): boolean;
24
95
  /** The node currently holding the responder, or null. */
25
96
  getResponder(): ResponderHost | null;
97
+ /** The node whose event source is carrying the interaction, or null. */
98
+ getSource(): ResponderHost | null;
26
99
  };
27
100
  export declare const createResponderSystem: (options: ResponderSystemOptions) => ResponderSystem;