@unrulysystems/native-motion-core 0.1.0-alpha.0

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 (326) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +21 -0
  3. package/README.md +48 -0
  4. package/dist/clock.cjs +71 -0
  5. package/dist/clock.d.cts +23 -0
  6. package/dist/clock.d.ts +23 -0
  7. package/dist/clock.js +66 -0
  8. package/dist/collect-reads.cjs +68 -0
  9. package/dist/collect-reads.d.cts +23 -0
  10. package/dist/collect-reads.d.ts +23 -0
  11. package/dist/collect-reads.js +62 -0
  12. package/dist/component/boundedArray.cjs +74 -0
  13. package/dist/component/boundedArray.d.cts +25 -0
  14. package/dist/component/boundedArray.d.ts +25 -0
  15. package/dist/component/boundedArray.js +68 -0
  16. package/dist/component/index.cjs +57 -0
  17. package/dist/component/index.d.cts +12 -0
  18. package/dist/component/index.d.ts +12 -0
  19. package/dist/component/index.js +22 -0
  20. package/dist/component/orchestration.cjs +129 -0
  21. package/dist/component/orchestration.d.cts +66 -0
  22. package/dist/component/orchestration.d.ts +66 -0
  23. package/dist/component/orchestration.js +124 -0
  24. package/dist/component/resolve.cjs +104 -0
  25. package/dist/component/resolve.d.cts +11 -0
  26. package/dist/component/resolve.d.ts +11 -0
  27. package/dist/component/resolve.js +97 -0
  28. package/dist/component/transition.cjs +352 -0
  29. package/dist/component/transition.d.cts +33 -0
  30. package/dist/component/transition.d.ts +33 -0
  31. package/dist/component/transition.js +339 -0
  32. package/dist/component/types.cjs +114 -0
  33. package/dist/component/types.d.cts +60 -0
  34. package/dist/component/types.d.ts +60 -0
  35. package/dist/component/types.js +111 -0
  36. package/dist/component/validate.cjs +1015 -0
  37. package/dist/component/validate.d.cts +37 -0
  38. package/dist/component/validate.d.ts +37 -0
  39. package/dist/component/validate.js +1002 -0
  40. package/dist/component/variants.cjs +333 -0
  41. package/dist/component/variants.d.cts +106 -0
  42. package/dist/component/variants.d.ts +106 -0
  43. package/dist/component/variants.js +321 -0
  44. package/dist/config/constants.cjs +41 -0
  45. package/dist/config/constants.d.cts +29 -0
  46. package/dist/config/constants.d.ts +29 -0
  47. package/dist/config/constants.js +38 -0
  48. package/dist/delay.cjs +72 -0
  49. package/dist/delay.d.cts +26 -0
  50. package/dist/delay.d.ts +26 -0
  51. package/dist/delay.js +70 -0
  52. package/dist/derived.cjs +143 -0
  53. package/dist/derived.d.cts +41 -0
  54. package/dist/derived.d.ts +41 -0
  55. package/dist/derived.js +139 -0
  56. package/dist/driver/index.cjs +22 -0
  57. package/dist/driver/index.d.cts +7 -0
  58. package/dist/driver/index.d.ts +7 -0
  59. package/dist/driver/index.js +14 -0
  60. package/dist/driver/keyframeTiming.cjs +130 -0
  61. package/dist/driver/keyframeTiming.d.cts +18 -0
  62. package/dist/driver/keyframeTiming.d.ts +18 -0
  63. package/dist/driver/keyframeTiming.js +124 -0
  64. package/dist/driver/keyframeTimingConfig.cjs +24 -0
  65. package/dist/driver/keyframeTimingConfig.d.cts +5 -0
  66. package/dist/driver/keyframeTimingConfig.d.ts +5 -0
  67. package/dist/driver/keyframeTimingConfig.js +22 -0
  68. package/dist/driver/prepare.cjs +459 -0
  69. package/dist/driver/prepare.d.cts +4 -0
  70. package/dist/driver/prepare.d.ts +4 -0
  71. package/dist/driver/prepare.js +454 -0
  72. package/dist/driver/reference.cjs +762 -0
  73. package/dist/driver/reference.d.cts +53 -0
  74. package/dist/driver/reference.d.ts +53 -0
  75. package/dist/driver/reference.js +757 -0
  76. package/dist/driver/step.cjs +55 -0
  77. package/dist/driver/step.d.cts +9 -0
  78. package/dist/driver/step.d.ts +9 -0
  79. package/dist/driver/step.js +52 -0
  80. package/dist/driver/tiers.cjs +40 -0
  81. package/dist/driver/tiers.d.cts +2 -0
  82. package/dist/driver/tiers.d.ts +2 -0
  83. package/dist/driver/tiers.js +37 -0
  84. package/dist/driver/types.cjs +9 -0
  85. package/dist/driver/types.d.cts +68 -0
  86. package/dist/driver/types.d.ts +68 -0
  87. package/dist/driver/types.js +8 -0
  88. package/dist/external-animation-ledger.cjs +1250 -0
  89. package/dist/external-animation-ledger.d.cts +416 -0
  90. package/dist/external-animation-ledger.d.ts +416 -0
  91. package/dist/external-animation-ledger.js +1243 -0
  92. package/dist/gesture/directionLock.cjs +25 -0
  93. package/dist/gesture/directionLock.d.cts +8 -0
  94. package/dist/gesture/directionLock.d.ts +8 -0
  95. package/dist/gesture/directionLock.js +21 -0
  96. package/dist/gesture/dragConfig.cjs +634 -0
  97. package/dist/gesture/dragConfig.d.cts +298 -0
  98. package/dist/gesture/dragConfig.d.ts +298 -0
  99. package/dist/gesture/dragConfig.js +624 -0
  100. package/dist/gesture/elastic.cjs +44 -0
  101. package/dist/gesture/elastic.d.cts +4 -0
  102. package/dist/gesture/elastic.d.ts +4 -0
  103. package/dist/gesture/elastic.js +39 -0
  104. package/dist/gesture/handoffSession.cjs +161 -0
  105. package/dist/gesture/handoffSession.d.cts +35 -0
  106. package/dist/gesture/handoffSession.d.ts +35 -0
  107. package/dist/gesture/handoffSession.js +158 -0
  108. package/dist/gesture/index.cjs +37 -0
  109. package/dist/gesture/index.d.cts +12 -0
  110. package/dist/gesture/index.d.ts +12 -0
  111. package/dist/gesture/index.js +11 -0
  112. package/dist/gesture/projection.cjs +95 -0
  113. package/dist/gesture/projection.d.cts +7 -0
  114. package/dist/gesture/projection.d.ts +7 -0
  115. package/dist/gesture/projection.js +87 -0
  116. package/dist/gesture/session.cjs +162 -0
  117. package/dist/gesture/session.d.cts +29 -0
  118. package/dist/gesture/session.d.ts +29 -0
  119. package/dist/gesture/session.js +158 -0
  120. package/dist/gesture/types.cjs +5 -0
  121. package/dist/gesture/types.d.cts +13 -0
  122. package/dist/gesture/types.d.ts +13 -0
  123. package/dist/gesture/types.js +4 -0
  124. package/dist/gesture/viewportConstraints.cjs +38 -0
  125. package/dist/gesture/viewportConstraints.d.cts +6 -0
  126. package/dist/gesture/viewportConstraints.d.ts +6 -0
  127. package/dist/gesture/viewportConstraints.js +34 -0
  128. package/dist/graph.cjs +226 -0
  129. package/dist/graph.d.cts +2 -0
  130. package/dist/graph.d.ts +2 -0
  131. package/dist/graph.js +223 -0
  132. package/dist/index.cjs +140 -0
  133. package/dist/index.d.cts +33 -0
  134. package/dist/index.d.ts +33 -0
  135. package/dist/index.js +70 -0
  136. package/dist/inertia.cjs +214 -0
  137. package/dist/inertia.d.cts +53 -0
  138. package/dist/inertia.d.ts +53 -0
  139. package/dist/inertia.js +212 -0
  140. package/dist/instant.cjs +66 -0
  141. package/dist/instant.d.cts +16 -0
  142. package/dist/instant.d.ts +16 -0
  143. package/dist/instant.js +63 -0
  144. package/dist/internal-driver.cjs +81 -0
  145. package/dist/internal-driver.d.cts +19 -0
  146. package/dist/internal-driver.d.ts +19 -0
  147. package/dist/internal-driver.js +35 -0
  148. package/dist/keyframes.cjs +191 -0
  149. package/dist/keyframes.d.cts +13 -0
  150. package/dist/keyframes.d.ts +13 -0
  151. package/dist/keyframes.js +188 -0
  152. package/dist/layout/commitDetector.cjs +67 -0
  153. package/dist/layout/commitDetector.d.cts +21 -0
  154. package/dist/layout/commitDetector.d.ts +21 -0
  155. package/dist/layout/commitDetector.js +64 -0
  156. package/dist/layout/compose.cjs +67 -0
  157. package/dist/layout/compose.d.cts +30 -0
  158. package/dist/layout/compose.d.ts +30 -0
  159. package/dist/layout/compose.js +65 -0
  160. package/dist/layout/constants.cjs +13 -0
  161. package/dist/layout/constants.d.cts +6 -0
  162. package/dist/layout/constants.d.ts +6 -0
  163. package/dist/layout/constants.js +10 -0
  164. package/dist/layout/identity.cjs +700 -0
  165. package/dist/layout/identity.d.cts +67 -0
  166. package/dist/layout/identity.d.ts +67 -0
  167. package/dist/layout/identity.js +698 -0
  168. package/dist/layout/index.cjs +36 -0
  169. package/dist/layout/index.d.cts +18 -0
  170. package/dist/layout/index.d.ts +18 -0
  171. package/dist/layout/index.js +13 -0
  172. package/dist/layout/measure.cjs +81 -0
  173. package/dist/layout/measure.d.cts +43 -0
  174. package/dist/layout/measure.d.ts +43 -0
  175. package/dist/layout/measure.js +78 -0
  176. package/dist/layout/projection.cjs +99 -0
  177. package/dist/layout/projection.d.cts +23 -0
  178. package/dist/layout/projection.d.ts +23 -0
  179. package/dist/layout/projection.js +97 -0
  180. package/dist/layout/scroll.cjs +27 -0
  181. package/dist/layout/scroll.d.cts +13 -0
  182. package/dist/layout/scroll.d.ts +13 -0
  183. package/dist/layout/scroll.js +24 -0
  184. package/dist/layout/session.cjs +207 -0
  185. package/dist/layout/session.d.cts +73 -0
  186. package/dist/layout/session.d.ts +73 -0
  187. package/dist/layout/session.js +205 -0
  188. package/dist/layout/tree.cjs +826 -0
  189. package/dist/layout/tree.d.cts +70 -0
  190. package/dist/layout/tree.d.ts +70 -0
  191. package/dist/layout/tree.js +823 -0
  192. package/dist/layout/types.cjs +36 -0
  193. package/dist/layout/types.d.cts +31 -0
  194. package/dist/layout/types.d.ts +31 -0
  195. package/dist/layout/types.js +35 -0
  196. package/dist/motion-arc.cjs +184 -0
  197. package/dist/motion-arc.d.cts +78 -0
  198. package/dist/motion-arc.d.ts +78 -0
  199. package/dist/motion-arc.js +183 -0
  200. package/dist/motion-mix.cjs +205 -0
  201. package/dist/motion-mix.d.cts +3 -0
  202. package/dist/motion-mix.d.ts +3 -0
  203. package/dist/motion-mix.js +202 -0
  204. package/dist/motion-value-driver-port.cjs +636 -0
  205. package/dist/motion-value-driver-port.d.cts +406 -0
  206. package/dist/motion-value-driver-port.d.ts +406 -0
  207. package/dist/motion-value-driver-port.js +627 -0
  208. package/dist/motion-value.cjs +189 -0
  209. package/dist/motion-value.d.cts +51 -0
  210. package/dist/motion-value.d.ts +51 -0
  211. package/dist/motion-value.js +185 -0
  212. package/dist/presence/controller.cjs +657 -0
  213. package/dist/presence/controller.d.cts +6 -0
  214. package/dist/presence/controller.d.ts +6 -0
  215. package/dist/presence/controller.js +652 -0
  216. package/dist/presence/index.cjs +19 -0
  217. package/dist/presence/index.d.cts +4 -0
  218. package/dist/presence/index.d.ts +4 -0
  219. package/dist/presence/index.js +12 -0
  220. package/dist/presence/machine.cjs +50 -0
  221. package/dist/presence/machine.d.cts +10 -0
  222. package/dist/presence/machine.d.ts +10 -0
  223. package/dist/presence/machine.js +46 -0
  224. package/dist/presence/types.cjs +6 -0
  225. package/dist/presence/types.d.cts +32 -0
  226. package/dist/presence/types.d.ts +32 -0
  227. package/dist/presence/types.js +5 -0
  228. package/dist/repeat.cjs +311 -0
  229. package/dist/repeat.d.cts +174 -0
  230. package/dist/repeat.d.ts +174 -0
  231. package/dist/repeat.js +300 -0
  232. package/dist/spring.cjs +128 -0
  233. package/dist/spring.d.cts +18 -0
  234. package/dist/spring.d.ts +18 -0
  235. package/dist/spring.js +125 -0
  236. package/dist/subscriptions.cjs +74 -0
  237. package/dist/subscriptions.d.cts +19 -0
  238. package/dist/subscriptions.d.ts +19 -0
  239. package/dist/subscriptions.js +69 -0
  240. package/dist/subset/index.cjs +23 -0
  241. package/dist/subset/index.d.cts +6 -0
  242. package/dist/subset/index.d.ts +6 -0
  243. package/dist/subset/index.js +13 -0
  244. package/dist/subset/normalize.cjs +90 -0
  245. package/dist/subset/normalize.d.cts +10 -0
  246. package/dist/subset/normalize.d.ts +10 -0
  247. package/dist/subset/normalize.js +85 -0
  248. package/dist/subset/registry.cjs +259 -0
  249. package/dist/subset/registry.d.cts +25 -0
  250. package/dist/subset/registry.d.ts +25 -0
  251. package/dist/subset/registry.js +256 -0
  252. package/dist/subset/resolve.cjs +98 -0
  253. package/dist/subset/resolve.d.cts +24 -0
  254. package/dist/subset/resolve.d.ts +24 -0
  255. package/dist/subset/resolve.js +90 -0
  256. package/dist/timing.cjs +206 -0
  257. package/dist/timing.d.cts +17 -0
  258. package/dist/timing.d.ts +17 -0
  259. package/dist/timing.js +201 -0
  260. package/dist/transformTemplate.cjs +219 -0
  261. package/dist/transformTemplate.d.cts +29 -0
  262. package/dist/transformTemplate.d.ts +29 -0
  263. package/dist/transformTemplate.js +215 -0
  264. package/dist/transition.cjs +231 -0
  265. package/dist/transition.d.cts +89 -0
  266. package/dist/transition.d.ts +89 -0
  267. package/dist/transition.js +223 -0
  268. package/dist/types.cjs +4 -0
  269. package/dist/types.d.cts +175 -0
  270. package/dist/types.d.ts +175 -0
  271. package/dist/types.js +3 -0
  272. package/dist/value-types/color.cjs +220 -0
  273. package/dist/value-types/color.d.cts +19 -0
  274. package/dist/value-types/color.d.ts +19 -0
  275. package/dist/value-types/color.js +218 -0
  276. package/dist/value-types/complex.cjs +160 -0
  277. package/dist/value-types/complex.d.cts +19 -0
  278. package/dist/value-types/complex.d.ts +19 -0
  279. package/dist/value-types/complex.js +155 -0
  280. package/dist/value-types/constants.cjs +15 -0
  281. package/dist/value-types/constants.d.cts +6 -0
  282. package/dist/value-types/constants.d.ts +6 -0
  283. package/dist/value-types/constants.js +12 -0
  284. package/dist/value-types/discrete.cjs +62 -0
  285. package/dist/value-types/discrete.d.cts +4 -0
  286. package/dist/value-types/discrete.d.ts +4 -0
  287. package/dist/value-types/discrete.js +56 -0
  288. package/dist/value-types/index.cjs +59 -0
  289. package/dist/value-types/index.d.cts +14 -0
  290. package/dist/value-types/index.d.ts +14 -0
  291. package/dist/value-types/index.js +24 -0
  292. package/dist/value-types/measure-resolve.cjs +325 -0
  293. package/dist/value-types/measure-resolve.d.cts +91 -0
  294. package/dist/value-types/measure-resolve.d.ts +91 -0
  295. package/dist/value-types/measure-resolve.js +313 -0
  296. package/dist/value-types/mix.cjs +90 -0
  297. package/dist/value-types/mix.d.cts +27 -0
  298. package/dist/value-types/mix.d.ts +27 -0
  299. package/dist/value-types/mix.js +85 -0
  300. package/dist/value-types/named-colors.cjs +61 -0
  301. package/dist/value-types/named-colors.d.cts +2 -0
  302. package/dist/value-types/named-colors.d.ts +2 -0
  303. package/dist/value-types/named-colors.js +58 -0
  304. package/dist/value-types/numeric.cjs +86 -0
  305. package/dist/value-types/numeric.d.cts +16 -0
  306. package/dist/value-types/numeric.d.ts +16 -0
  307. package/dist/value-types/numeric.js +79 -0
  308. package/dist/worklet-layout/config/constants.js +39 -0
  309. package/dist/worklet-layout/layout/constants.js +11 -0
  310. package/dist/worklet-layout/layout/identity.js +699 -0
  311. package/dist/worklet-layout/layout/projection.js +98 -0
  312. package/dist/worklet-layout/layout/session.js +206 -0
  313. package/dist/worklet-layout/layout/tree.js +824 -0
  314. package/dist/worklet-layout/layout/types.js +36 -0
  315. package/dist/worklet-layout/spring.js +125 -0
  316. package/dist/worklet-layout/timing.js +201 -0
  317. package/dist/worklet-layout/transition.js +223 -0
  318. package/dist/worklet-layout.cjs +47 -0
  319. package/dist/worklet-layout.d.cts +15 -0
  320. package/dist/worklet-layout.d.ts +15 -0
  321. package/dist/worklet-layout.js +28 -0
  322. package/dist/wrap.cjs +7 -0
  323. package/dist/wrap.d.cts +1 -0
  324. package/dist/wrap.d.ts +1 -0
  325. package/dist/wrap.js +4 -0
  326. package/package.json +45 -0
@@ -0,0 +1,24 @@
1
+ // SPEC-LAYOUT REQ-LAYOUT-022 — pure scroll-offset accounting for layout measurement.
2
+ // Pin removeElementScroll (motion@12.42.2 create-projection-node): a viewport/window-space
3
+ // layout box is recovered into content space by ADDING each layoutScroll ancestor's scroll
4
+ // offset (HTMLProjectionNode measureScroll = instance.scrollLeft/scrollTop container scroll;
5
+ // DocumentProjectionNode is the always-on window root). Host-agnostic pure math: the binding
6
+ // supplies offsets when available; closed RN hosts that are not scroll containers contribute
7
+ // {0,0} this rung (stack+math only — REQ-LAYOUT-022(c)). Worklet-safe: plain number math only.
8
+ /**
9
+ * Convert a viewport/window-space rect into content-space by adding every layoutScroll
10
+ * ancestor's offset (pin removeElementScroll). Zero offsets leave the rect unchanged.
11
+ * Offsets apply in path order (root → leaf); cumulative add is commutative for pure
12
+ * translation so order is documented, not load-bearing for scalar sums.
13
+ */
14
+ export function accountForLayoutScroll(rect, offsets) {
15
+ 'worklet';
16
+ let x = rect.x;
17
+ let y = rect.y;
18
+ for (let i = 0; i < offsets.length; i++) {
19
+ const offset = offsets[i];
20
+ x += offset.x;
21
+ y += offset.y;
22
+ }
23
+ return { x, y, width: rect.width, height: rect.height };
24
+ }
@@ -0,0 +1,207 @@
1
+ "use strict";
2
+ // @frame-path — executes inside the UI-runtime frame step; allocation-gated (REQ-DRIVER-021).
3
+ // SPEC-LAYOUT §2/§4a — the projection SESSION (REQ-LAYOUT-002/011/018/019). Drives an element's visual rect
4
+ // to its host-computed layout through the reused SPRING, exposing the transform-only correction the substrate
5
+ // executes. A mid-flight host layout change retargets from the current visual position AND velocity (C0/C1);
6
+ // a null read fails closed to identity + unsettled; settle is measurement-availability. Host-agnostic — no
7
+ // driver/React/Fabric import; drives the injected MotionGraph + built SPRING generators, no forked math.
8
+ //
9
+ // Modeling decision (deviates from the build packet's single-progress sketch): the visual rect is animated
10
+ // PER COMPONENT — four motion values (x, y, width, height) each springing toward the after-rect. A single
11
+ // scalar progress cannot preserve velocity continuity across ALL axes on a retarget (the progress→visual-
12
+ // velocity ratio differs per component), whereas per-component springs get C0/C1 for free from the graph's
13
+ // seed-from-live (REQ-API-003) — exactly as GESTURE's owned retarget does. transform() is then the FLIP
14
+ // invert of the live visual rect against the after rect, which is identity at settle (REQ-LAYOUT-002).
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.createProjectionSession = createProjectionSession;
17
+ const transition_1 = require("../transition.cjs");
18
+ const timing_1 = require("../timing.cjs");
19
+ const projection_1 = require("./projection.cjs");
20
+ // Near-critical projection spring (physics ⇒ carries the seeded velocity, REQ-SPRING-007) — minimal overshoot
21
+ // for a layout settle. Config constant, not an inline literal; refinable — the exact feel binds cross-engine
22
+ // parity at M2, not the M1 continuity/identity/settle invariants (§7 vetoable).
23
+ const LAYOUT_PROJECTION_SPRING = { stiffness: 550, damping: 45 };
24
+ // alloc-ok: construction — one session per layout-identity pairing; the per-frame surface is
25
+ // sync()/settledAgainst()/writeVisualInto(), which allocate nothing.
26
+ function createProjectionSession(options) {
27
+ const { graph } = options;
28
+ const spring = options.spring ?? LAYOUT_PROJECTION_SPRING;
29
+ const timing = options.timing;
30
+ // The visual rect, tracked as four independently-animated motion values (see the modeling note above).
31
+ const vx = graph.motionValue(0);
32
+ const vy = graph.motionValue(0);
33
+ const vw = graph.motionValue(0);
34
+ const vh = graph.motionValue(0);
35
+ let state = 'idle';
36
+ let measured = false;
37
+ let currentAfter = null;
38
+ let anims = [];
39
+ // Held while unmeasured (a null read): there is no in-flight animation to keep the graph unsettled, yet the
40
+ // element is not settled — it is waiting for a valid measurement (REQ-LAYOUT-019 / G-INV-7 analog).
41
+ let measureHandle;
42
+ function acquireMeasureHandle() {
43
+ if (measureHandle === undefined)
44
+ measureHandle = graph.registerActivity();
45
+ }
46
+ function releaseMeasureHandle() {
47
+ measureHandle?.release();
48
+ measureHandle = undefined;
49
+ }
50
+ function stopAnims() {
51
+ // Indexed + length=0: sync() reaches this on the settle frame, so a for..of iterator and a
52
+ // fresh array are frame-executed garbage (round-7 major 3; REQ-DRIVER-021).
53
+ for (let i = 0; i < anims.length; i++)
54
+ anims[i].stop();
55
+ anims.length = 0;
56
+ }
57
+ // Drive each visual channel toward the after-rect. Spring path: graph seeds {from, velocity} for
58
+ // C0/C1 retarget (REQ-CORE-002 / REQ-API-003). Timing path (family-3): duration/ease tween — seed
59
+ // velocity is ignored by timingGenerator (REQ-TIMING-001); retarget reseeds from live value.
60
+ // Do NOT stop the prior animation first — stop() clears animated velocity and breaks spring C1.
61
+ // alloc-ok: lifecycle-edge — flight start/retarget only (begin/retarget), never per frame.
62
+ function driveTo(after) {
63
+ if (timing !== undefined) {
64
+ anims = [
65
+ graph.animate(vx, (0, timing_1.timingGenerator)(after.x, timing)),
66
+ graph.animate(vy, (0, timing_1.timingGenerator)(after.y, timing)),
67
+ graph.animate(vw, (0, timing_1.timingGenerator)(after.width, timing)),
68
+ graph.animate(vh, (0, timing_1.timingGenerator)(after.height, timing)),
69
+ ];
70
+ return;
71
+ }
72
+ anims = [
73
+ graph.animate(vx, (0, transition_1.resolveSpringGenerator)(after.x, spring)),
74
+ graph.animate(vy, (0, transition_1.resolveSpringGenerator)(after.y, spring)),
75
+ graph.animate(vw, (0, transition_1.resolveSpringGenerator)(after.width, spring)),
76
+ graph.animate(vh, (0, transition_1.resolveSpringGenerator)(after.height, spring)),
77
+ ];
78
+ }
79
+ // alloc-ok: js-thread — record-building read for the JS-side API (transform/visualRect); the
80
+ // frame path uses writeVisualInto/settledAgainst.
81
+ function visual() {
82
+ return { x: vx.get(), y: vy.get(), width: vw.get(), height: vh.get() };
83
+ }
84
+ // Reconcile the internal state to the graph: once the projection springs settle (graph settled) while
85
+ // measured, the session returns to idle. Every state-dependent entry point reconciles first — the getter
86
+ // AND the mutators — so post-settle reads and a subsequent retarget agree on the state (the GESTURE review
87
+ // lesson: a getter-only reconcile leaves the mutators acting on a stale field).
88
+ function sync() {
89
+ if (state === 'projecting' && measured && graph.isSettled()) {
90
+ stopAnims();
91
+ state = 'idle';
92
+ }
93
+ }
94
+ function failClosed(after) {
95
+ // A null read: unmeasured ⇒ identity + unsettled, retried next epoch. Never a garbage delta (REQ-LAYOUT-011).
96
+ stopAnims();
97
+ measured = false;
98
+ currentAfter = after;
99
+ acquireMeasureHandle();
100
+ state = 'projecting';
101
+ }
102
+ return {
103
+ begin(token, before, after) {
104
+ void token; // identity binding is the measure tracker's concern (M3); the session takes resolved rects
105
+ sync();
106
+ if (before === null || after === null) {
107
+ failClosed(after);
108
+ return;
109
+ }
110
+ // Fresh projection: jump the visual rect to `before` (velocity 0) and spring to `after`.
111
+ releaseMeasureHandle();
112
+ vx.jump(before.x);
113
+ vy.jump(before.y);
114
+ vw.jump(before.width);
115
+ vh.jump(before.height);
116
+ measured = true;
117
+ currentAfter = after;
118
+ driveTo(after);
119
+ state = 'projecting';
120
+ },
121
+ retarget(after) {
122
+ sync();
123
+ if (state !== 'projecting' || !measured) {
124
+ // Nothing in flight to retarget. Fail loud, consistent with the total state machine (never a silent
125
+ // no-op) — the caller must begin() a measured projection first.
126
+ throw new Error(`layout: retarget() with no active measured projection (state='${state}', measured=${measured}) — begin() first`);
127
+ }
128
+ if (after === null) {
129
+ failClosed(after);
130
+ return;
131
+ }
132
+ // Continue from the LIVE visual position (+ velocity on spring path) toward the new target —
133
+ // no jump; the graph re-seeds each channel from live state ⇒ C0/C1 on spring (REQ-LAYOUT-018).
134
+ currentAfter = after;
135
+ driveTo(after);
136
+ },
137
+ settleAt(after) {
138
+ // Pin type:false — no multi-frame flight. Jump every channel to the post-layout rect and
139
+ // drop spring work so settledAgainst/isSettled read true on the same edge (REQ-API-035).
140
+ releaseMeasureHandle();
141
+ stopAnims();
142
+ vx.jump(after.x);
143
+ vy.jump(after.y);
144
+ vw.jump(after.width);
145
+ vh.jump(after.height);
146
+ measured = true;
147
+ currentAfter = after;
148
+ state = 'idle';
149
+ },
150
+ transform() {
151
+ sync();
152
+ if (!measured || currentAfter === null)
153
+ return projection_1.IDENTITY_TRANSFORM;
154
+ return (0, projection_1.invert)(visual(), currentAfter);
155
+ },
156
+ visualRect() {
157
+ sync();
158
+ return measured ? visual() : null;
159
+ },
160
+ // alloc-ok: js-thread — record-building read for the JS-side API; the frame path uses
161
+ // settledAgainst's in-place comparison.
162
+ velocity() {
163
+ return {
164
+ x: vx.getVelocity(),
165
+ y: vy.getVelocity(),
166
+ width: vw.getVelocity(),
167
+ height: vh.getVelocity(),
168
+ };
169
+ },
170
+ settledAgainst(target, epsPos, epsVel) {
171
+ sync();
172
+ if (!measured)
173
+ return false;
174
+ return (Math.abs(vx.get() - target.x) < epsPos &&
175
+ Math.abs(vy.get() - target.y) < epsPos &&
176
+ Math.abs(vw.get() - target.width) < epsPos &&
177
+ Math.abs(vh.get() - target.height) < epsPos &&
178
+ Math.abs(vx.getVelocity()) < epsVel &&
179
+ Math.abs(vy.getVelocity()) < epsVel &&
180
+ Math.abs(vw.getVelocity()) < epsVel &&
181
+ Math.abs(vh.getVelocity()) < epsVel);
182
+ },
183
+ writeVisualInto(out) {
184
+ sync();
185
+ if (!measured)
186
+ return false;
187
+ out.x = vx.get();
188
+ out.y = vy.get();
189
+ out.width = vw.get();
190
+ out.height = vh.get();
191
+ return true;
192
+ },
193
+ measured: () => measured,
194
+ isSettled: () => graph.isSettled(),
195
+ state: () => {
196
+ sync();
197
+ return state;
198
+ },
199
+ dispose() {
200
+ stopAnims();
201
+ releaseMeasureHandle();
202
+ measured = false;
203
+ currentAfter = null;
204
+ state = 'idle';
205
+ },
206
+ };
207
+ }
@@ -0,0 +1,73 @@
1
+ import { type SpringConfig } from "../transition.cjs";
2
+ import { type TimingConfig } from "../timing.cjs";
3
+ import type { MotionGraph } from "../types.cjs";
4
+ import type { LayoutIdToken, Rect, Transform } from "./types.cjs";
5
+ export type ProjectionSessionState = 'idle' | 'projecting';
6
+ /** The live velocity of the visual rect, per component (px/s) — for retarget C1 assertions + diagnostics. */
7
+ export interface RectVelocity {
8
+ readonly x: number;
9
+ readonly y: number;
10
+ readonly width: number;
11
+ readonly height: number;
12
+ }
13
+ /** A caller-owned rect the hot path writes into — the allocation-free counterpart of `Rect`. */
14
+ export interface MutableRect {
15
+ x: number;
16
+ y: number;
17
+ width: number;
18
+ height: number;
19
+ }
20
+ export interface ProjectionSessionOptions {
21
+ readonly graph: MotionGraph;
22
+ readonly spring?: SpringConfig;
23
+ /**
24
+ * Optional duration/ease tween flight (family-3 layoutId transition slice).
25
+ * When set, each visual channel uses `timingGenerator` instead of the layout spring —
26
+ * catalog residuals author `transition={{ duration: N }}` on layoutId hosts.
27
+ * Mutually exclusive with relying on `spring` for the same session: timing wins when present.
28
+ */
29
+ readonly timing?: TimingConfig;
30
+ }
31
+ export interface ProjectionSession {
32
+ /** Start a fresh projection from `before` to `after`. A null before/after fails closed (unmeasured). */
33
+ begin(token: LayoutIdToken, before: Rect | null, after: Rect | null): void;
34
+ /** A host layout change mid-projection: continue from the live visual position + velocity toward `after`. */
35
+ retarget(after: Rect | null): void;
36
+ /**
37
+ * Instant settle at `after` (pin `type: false` under shouldReduceMotion / skipAnimations):
38
+ * jump every visual channel to the target, stop springs, idle. Used by layout-identity
39
+ * force-instant edges so the flight never multi-frames under MotionConfig policy (REQ-API-035).
40
+ */
41
+ settleAt(after: Rect): void;
42
+ /** The transform-only correction to apply this frame; identity while unmeasured or at settle. */
43
+ transform(): Transform;
44
+ /** The current visual rect, or null while unmeasured. */
45
+ visualRect(): Rect | null;
46
+ /** The live per-component velocity of the visual rect. */
47
+ velocity(): RectVelocity;
48
+ /**
49
+ * Allocation-free settle test — the hot-path counterpart of comparing visualRect()+velocity()
50
+ * against `target` (REQ-DRIVER-015): measured AND every visual channel within `epsPos` of the
51
+ * target AND every channel velocity within `epsVel` of rest. False while unmeasured
52
+ * (fail-closed, REQ-LAYOUT-019).
53
+ */
54
+ settledAgainst(target: Rect, epsPos: number, epsVel: number): boolean;
55
+ /**
56
+ * Allocation-free visual-rect read: writes the live rect into the caller-owned `out` and
57
+ * returns true, or returns false while unmeasured (out untouched). The hot-path counterpart of
58
+ * visualRect() (REQ-DRIVER-015).
59
+ */
60
+ writeVisualInto(out: MutableRect): boolean;
61
+ /** A non-null read has landed for the current projection. */
62
+ measured(): boolean;
63
+ isSettled(): boolean;
64
+ state(): ProjectionSessionState;
65
+ /**
66
+ * Close the session out: stop the projection springs, release the waiting-for-measurement
67
+ * activity, return to idle. For an owner ABANDONING the session (a reverted or degraded
68
+ * identity pairing, an entry GC) — an unmeasured session parked in fail-closed wait would
69
+ * otherwise hold the graph's settle ledger open forever (L2 review cycle 3).
70
+ */
71
+ dispose(): void;
72
+ }
73
+ export declare function createProjectionSession(options: ProjectionSessionOptions): ProjectionSession;
@@ -0,0 +1,73 @@
1
+ import { type SpringConfig } from "../transition.js";
2
+ import { type TimingConfig } from "../timing.js";
3
+ import type { MotionGraph } from "../types.js";
4
+ import type { LayoutIdToken, Rect, Transform } from "./types.js";
5
+ export type ProjectionSessionState = 'idle' | 'projecting';
6
+ /** The live velocity of the visual rect, per component (px/s) — for retarget C1 assertions + diagnostics. */
7
+ export interface RectVelocity {
8
+ readonly x: number;
9
+ readonly y: number;
10
+ readonly width: number;
11
+ readonly height: number;
12
+ }
13
+ /** A caller-owned rect the hot path writes into — the allocation-free counterpart of `Rect`. */
14
+ export interface MutableRect {
15
+ x: number;
16
+ y: number;
17
+ width: number;
18
+ height: number;
19
+ }
20
+ export interface ProjectionSessionOptions {
21
+ readonly graph: MotionGraph;
22
+ readonly spring?: SpringConfig;
23
+ /**
24
+ * Optional duration/ease tween flight (family-3 layoutId transition slice).
25
+ * When set, each visual channel uses `timingGenerator` instead of the layout spring —
26
+ * catalog residuals author `transition={{ duration: N }}` on layoutId hosts.
27
+ * Mutually exclusive with relying on `spring` for the same session: timing wins when present.
28
+ */
29
+ readonly timing?: TimingConfig;
30
+ }
31
+ export interface ProjectionSession {
32
+ /** Start a fresh projection from `before` to `after`. A null before/after fails closed (unmeasured). */
33
+ begin(token: LayoutIdToken, before: Rect | null, after: Rect | null): void;
34
+ /** A host layout change mid-projection: continue from the live visual position + velocity toward `after`. */
35
+ retarget(after: Rect | null): void;
36
+ /**
37
+ * Instant settle at `after` (pin `type: false` under shouldReduceMotion / skipAnimations):
38
+ * jump every visual channel to the target, stop springs, idle. Used by layout-identity
39
+ * force-instant edges so the flight never multi-frames under MotionConfig policy (REQ-API-035).
40
+ */
41
+ settleAt(after: Rect): void;
42
+ /** The transform-only correction to apply this frame; identity while unmeasured or at settle. */
43
+ transform(): Transform;
44
+ /** The current visual rect, or null while unmeasured. */
45
+ visualRect(): Rect | null;
46
+ /** The live per-component velocity of the visual rect. */
47
+ velocity(): RectVelocity;
48
+ /**
49
+ * Allocation-free settle test — the hot-path counterpart of comparing visualRect()+velocity()
50
+ * against `target` (REQ-DRIVER-015): measured AND every visual channel within `epsPos` of the
51
+ * target AND every channel velocity within `epsVel` of rest. False while unmeasured
52
+ * (fail-closed, REQ-LAYOUT-019).
53
+ */
54
+ settledAgainst(target: Rect, epsPos: number, epsVel: number): boolean;
55
+ /**
56
+ * Allocation-free visual-rect read: writes the live rect into the caller-owned `out` and
57
+ * returns true, or returns false while unmeasured (out untouched). The hot-path counterpart of
58
+ * visualRect() (REQ-DRIVER-015).
59
+ */
60
+ writeVisualInto(out: MutableRect): boolean;
61
+ /** A non-null read has landed for the current projection. */
62
+ measured(): boolean;
63
+ isSettled(): boolean;
64
+ state(): ProjectionSessionState;
65
+ /**
66
+ * Close the session out: stop the projection springs, release the waiting-for-measurement
67
+ * activity, return to idle. For an owner ABANDONING the session (a reverted or degraded
68
+ * identity pairing, an entry GC) — an unmeasured session parked in fail-closed wait would
69
+ * otherwise hold the graph's settle ledger open forever (L2 review cycle 3).
70
+ */
71
+ dispose(): void;
72
+ }
73
+ export declare function createProjectionSession(options: ProjectionSessionOptions): ProjectionSession;
@@ -0,0 +1,205 @@
1
+ // @frame-path — executes inside the UI-runtime frame step; allocation-gated (REQ-DRIVER-021).
2
+ // SPEC-LAYOUT §2/§4a — the projection SESSION (REQ-LAYOUT-002/011/018/019). Drives an element's visual rect
3
+ // to its host-computed layout through the reused SPRING, exposing the transform-only correction the substrate
4
+ // executes. A mid-flight host layout change retargets from the current visual position AND velocity (C0/C1);
5
+ // a null read fails closed to identity + unsettled; settle is measurement-availability. Host-agnostic — no
6
+ // driver/React/Fabric import; drives the injected MotionGraph + built SPRING generators, no forked math.
7
+ //
8
+ // Modeling decision (deviates from the build packet's single-progress sketch): the visual rect is animated
9
+ // PER COMPONENT — four motion values (x, y, width, height) each springing toward the after-rect. A single
10
+ // scalar progress cannot preserve velocity continuity across ALL axes on a retarget (the progress→visual-
11
+ // velocity ratio differs per component), whereas per-component springs get C0/C1 for free from the graph's
12
+ // seed-from-live (REQ-API-003) — exactly as GESTURE's owned retarget does. transform() is then the FLIP
13
+ // invert of the live visual rect against the after rect, which is identity at settle (REQ-LAYOUT-002).
14
+ import { resolveSpringGenerator } from "../transition.js";
15
+ import { timingGenerator } from "../timing.js";
16
+ import { IDENTITY_TRANSFORM, invert } from "./projection.js";
17
+ // Near-critical projection spring (physics ⇒ carries the seeded velocity, REQ-SPRING-007) — minimal overshoot
18
+ // for a layout settle. Config constant, not an inline literal; refinable — the exact feel binds cross-engine
19
+ // parity at M2, not the M1 continuity/identity/settle invariants (§7 vetoable).
20
+ const LAYOUT_PROJECTION_SPRING = { stiffness: 550, damping: 45 };
21
+ // alloc-ok: construction — one session per layout-identity pairing; the per-frame surface is
22
+ // sync()/settledAgainst()/writeVisualInto(), which allocate nothing.
23
+ export function createProjectionSession(options) {
24
+ 'worklet';
25
+ const { graph } = options;
26
+ const spring = options.spring ?? LAYOUT_PROJECTION_SPRING;
27
+ const timing = options.timing;
28
+ // The visual rect, tracked as four independently-animated motion values (see the modeling note above).
29
+ const vx = graph.motionValue(0);
30
+ const vy = graph.motionValue(0);
31
+ const vw = graph.motionValue(0);
32
+ const vh = graph.motionValue(0);
33
+ let state = 'idle';
34
+ let measured = false;
35
+ let currentAfter = null;
36
+ let anims = [];
37
+ // Held while unmeasured (a null read): there is no in-flight animation to keep the graph unsettled, yet the
38
+ // element is not settled — it is waiting for a valid measurement (REQ-LAYOUT-019 / G-INV-7 analog).
39
+ let measureHandle;
40
+ function acquireMeasureHandle() {
41
+ if (measureHandle === undefined)
42
+ measureHandle = graph.registerActivity();
43
+ }
44
+ function releaseMeasureHandle() {
45
+ measureHandle?.release();
46
+ measureHandle = undefined;
47
+ }
48
+ function stopAnims() {
49
+ // Indexed + length=0: sync() reaches this on the settle frame, so a for..of iterator and a
50
+ // fresh array are frame-executed garbage (round-7 major 3; REQ-DRIVER-021).
51
+ for (let i = 0; i < anims.length; i++)
52
+ anims[i].stop();
53
+ anims.length = 0;
54
+ }
55
+ // Drive each visual channel toward the after-rect. Spring path: graph seeds {from, velocity} for
56
+ // C0/C1 retarget (REQ-CORE-002 / REQ-API-003). Timing path (family-3): duration/ease tween — seed
57
+ // velocity is ignored by timingGenerator (REQ-TIMING-001); retarget reseeds from live value.
58
+ // Do NOT stop the prior animation first — stop() clears animated velocity and breaks spring C1.
59
+ // alloc-ok: lifecycle-edge — flight start/retarget only (begin/retarget), never per frame.
60
+ function driveTo(after) {
61
+ if (timing !== undefined) {
62
+ anims = [
63
+ graph.animate(vx, timingGenerator(after.x, timing)),
64
+ graph.animate(vy, timingGenerator(after.y, timing)),
65
+ graph.animate(vw, timingGenerator(after.width, timing)),
66
+ graph.animate(vh, timingGenerator(after.height, timing)),
67
+ ];
68
+ return;
69
+ }
70
+ anims = [
71
+ graph.animate(vx, resolveSpringGenerator(after.x, spring)),
72
+ graph.animate(vy, resolveSpringGenerator(after.y, spring)),
73
+ graph.animate(vw, resolveSpringGenerator(after.width, spring)),
74
+ graph.animate(vh, resolveSpringGenerator(after.height, spring)),
75
+ ];
76
+ }
77
+ // alloc-ok: js-thread — record-building read for the JS-side API (transform/visualRect); the
78
+ // frame path uses writeVisualInto/settledAgainst.
79
+ function visual() {
80
+ return { x: vx.get(), y: vy.get(), width: vw.get(), height: vh.get() };
81
+ }
82
+ // Reconcile the internal state to the graph: once the projection springs settle (graph settled) while
83
+ // measured, the session returns to idle. Every state-dependent entry point reconciles first — the getter
84
+ // AND the mutators — so post-settle reads and a subsequent retarget agree on the state (the GESTURE review
85
+ // lesson: a getter-only reconcile leaves the mutators acting on a stale field).
86
+ function sync() {
87
+ if (state === 'projecting' && measured && graph.isSettled()) {
88
+ stopAnims();
89
+ state = 'idle';
90
+ }
91
+ }
92
+ function failClosed(after) {
93
+ // A null read: unmeasured ⇒ identity + unsettled, retried next epoch. Never a garbage delta (REQ-LAYOUT-011).
94
+ stopAnims();
95
+ measured = false;
96
+ currentAfter = after;
97
+ acquireMeasureHandle();
98
+ state = 'projecting';
99
+ }
100
+ return {
101
+ begin(token, before, after) {
102
+ void token; // identity binding is the measure tracker's concern (M3); the session takes resolved rects
103
+ sync();
104
+ if (before === null || after === null) {
105
+ failClosed(after);
106
+ return;
107
+ }
108
+ // Fresh projection: jump the visual rect to `before` (velocity 0) and spring to `after`.
109
+ releaseMeasureHandle();
110
+ vx.jump(before.x);
111
+ vy.jump(before.y);
112
+ vw.jump(before.width);
113
+ vh.jump(before.height);
114
+ measured = true;
115
+ currentAfter = after;
116
+ driveTo(after);
117
+ state = 'projecting';
118
+ },
119
+ retarget(after) {
120
+ sync();
121
+ if (state !== 'projecting' || !measured) {
122
+ // Nothing in flight to retarget. Fail loud, consistent with the total state machine (never a silent
123
+ // no-op) — the caller must begin() a measured projection first.
124
+ throw new Error(`layout: retarget() with no active measured projection (state='${state}', measured=${measured}) — begin() first`);
125
+ }
126
+ if (after === null) {
127
+ failClosed(after);
128
+ return;
129
+ }
130
+ // Continue from the LIVE visual position (+ velocity on spring path) toward the new target —
131
+ // no jump; the graph re-seeds each channel from live state ⇒ C0/C1 on spring (REQ-LAYOUT-018).
132
+ currentAfter = after;
133
+ driveTo(after);
134
+ },
135
+ settleAt(after) {
136
+ // Pin type:false — no multi-frame flight. Jump every channel to the post-layout rect and
137
+ // drop spring work so settledAgainst/isSettled read true on the same edge (REQ-API-035).
138
+ releaseMeasureHandle();
139
+ stopAnims();
140
+ vx.jump(after.x);
141
+ vy.jump(after.y);
142
+ vw.jump(after.width);
143
+ vh.jump(after.height);
144
+ measured = true;
145
+ currentAfter = after;
146
+ state = 'idle';
147
+ },
148
+ transform() {
149
+ sync();
150
+ if (!measured || currentAfter === null)
151
+ return IDENTITY_TRANSFORM;
152
+ return invert(visual(), currentAfter);
153
+ },
154
+ visualRect() {
155
+ sync();
156
+ return measured ? visual() : null;
157
+ },
158
+ // alloc-ok: js-thread — record-building read for the JS-side API; the frame path uses
159
+ // settledAgainst's in-place comparison.
160
+ velocity() {
161
+ return {
162
+ x: vx.getVelocity(),
163
+ y: vy.getVelocity(),
164
+ width: vw.getVelocity(),
165
+ height: vh.getVelocity(),
166
+ };
167
+ },
168
+ settledAgainst(target, epsPos, epsVel) {
169
+ sync();
170
+ if (!measured)
171
+ return false;
172
+ return (Math.abs(vx.get() - target.x) < epsPos &&
173
+ Math.abs(vy.get() - target.y) < epsPos &&
174
+ Math.abs(vw.get() - target.width) < epsPos &&
175
+ Math.abs(vh.get() - target.height) < epsPos &&
176
+ Math.abs(vx.getVelocity()) < epsVel &&
177
+ Math.abs(vy.getVelocity()) < epsVel &&
178
+ Math.abs(vw.getVelocity()) < epsVel &&
179
+ Math.abs(vh.getVelocity()) < epsVel);
180
+ },
181
+ writeVisualInto(out) {
182
+ sync();
183
+ if (!measured)
184
+ return false;
185
+ out.x = vx.get();
186
+ out.y = vy.get();
187
+ out.width = vw.get();
188
+ out.height = vh.get();
189
+ return true;
190
+ },
191
+ measured: () => measured,
192
+ isSettled: () => graph.isSettled(),
193
+ state: () => {
194
+ sync();
195
+ return state;
196
+ },
197
+ dispose() {
198
+ stopAnims();
199
+ releaseMeasureHandle();
200
+ measured = false;
201
+ currentAfter = null;
202
+ state = 'idle';
203
+ },
204
+ };
205
+ }