@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,38 @@
1
+ "use strict";
2
+ // REQ-GESTURE-028 (R15 D2) — the VIEWPORT constraints computation, the pin's
3
+ // `calcViewportAxisConstraints` verbatim (framer-motion `drag/utils/constraints.ts` at the §B
4
+ // pin, motion@12.42.2 / 61833240): bounds for the drag value relative to the draggable's
5
+ // CURRENT layout box at grab — `min = container.min − layout.min`,
6
+ // `max = container.max − layout.max` — then FLIPPED per axis when the container is shorter
7
+ // than the child (the negative-range case: the child fits past both edges). Host-agnostic
8
+ // (REQ-CORE-003); both engines compute identical bounds from the same measured boxes.
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.resolveViewportConstraints = resolveViewportConstraints;
11
+ exports.progressWithinSpan = progressWithinSpan;
12
+ // One axis of viewport constraints. `layout` is the draggable's CURRENT layout span at grab;
13
+ // `container` is the measured constraint element's span (already corrected into the same
14
+ // coordinate space).
15
+ function resolveViewportConstraints(layout, container) {
16
+ let min = container.min - layout.min;
17
+ let max = container.max - layout.max;
18
+ // The pin's flip: when the container is SHORTER than the child, swap the pair (the child
19
+ // can only rubber-band past both edges — the range is negative before the swap).
20
+ if (container.max - container.min < layout.max - layout.min) {
21
+ ;
22
+ [min, max] = [max, min];
23
+ }
24
+ return { min, max };
25
+ }
26
+ // The pin's `calcOrigin` for a point source (VEDC scalePositionWithinConstraints, review
27
+ // major 3): the progress of a value within a constraint span, clamped to [0, 1]. A degenerate
28
+ // (zero-length) span reports 0.5 — the pin's neither-branch case exactly. TOTAL (review r4
29
+ // major 3): a non-finite span (an open-sided hook replacement, legal) yields no NaN — an
30
+ // open-ended progress is unrepresentable, reported as the degenerate 0.5; callers that can
31
+ // keep the live value should (the wiring's rescale does).
32
+ function progressWithinSpan(value, span) {
33
+ const length = span.max - span.min;
34
+ if (!Number.isFinite(length) || length <= 0)
35
+ return 0.5;
36
+ const progress = (value - span.min) / length;
37
+ return progress < 0 ? 0 : progress > 1 ? 1 : progress;
38
+ }
@@ -0,0 +1,6 @@
1
+ export interface AxisSpan {
2
+ readonly min: number;
3
+ readonly max: number;
4
+ }
5
+ export declare function resolveViewportConstraints(layout: AxisSpan, container: AxisSpan): AxisSpan;
6
+ export declare function progressWithinSpan(value: number, span: AxisSpan): number;
@@ -0,0 +1,6 @@
1
+ export interface AxisSpan {
2
+ readonly min: number;
3
+ readonly max: number;
4
+ }
5
+ export declare function resolveViewportConstraints(layout: AxisSpan, container: AxisSpan): AxisSpan;
6
+ export declare function progressWithinSpan(value: number, span: AxisSpan): number;
@@ -0,0 +1,34 @@
1
+ // REQ-GESTURE-028 (R15 D2) — the VIEWPORT constraints computation, the pin's
2
+ // `calcViewportAxisConstraints` verbatim (framer-motion `drag/utils/constraints.ts` at the §B
3
+ // pin, motion@12.42.2 / 61833240): bounds for the drag value relative to the draggable's
4
+ // CURRENT layout box at grab — `min = container.min − layout.min`,
5
+ // `max = container.max − layout.max` — then FLIPPED per axis when the container is shorter
6
+ // than the child (the negative-range case: the child fits past both edges). Host-agnostic
7
+ // (REQ-CORE-003); both engines compute identical bounds from the same measured boxes.
8
+ // One axis of viewport constraints. `layout` is the draggable's CURRENT layout span at grab;
9
+ // `container` is the measured constraint element's span (already corrected into the same
10
+ // coordinate space).
11
+ export function resolveViewportConstraints(layout, container) {
12
+ let min = container.min - layout.min;
13
+ let max = container.max - layout.max;
14
+ // The pin's flip: when the container is SHORTER than the child, swap the pair (the child
15
+ // can only rubber-band past both edges — the range is negative before the swap).
16
+ if (container.max - container.min < layout.max - layout.min) {
17
+ ;
18
+ [min, max] = [max, min];
19
+ }
20
+ return { min, max };
21
+ }
22
+ // The pin's `calcOrigin` for a point source (VEDC scalePositionWithinConstraints, review
23
+ // major 3): the progress of a value within a constraint span, clamped to [0, 1]. A degenerate
24
+ // (zero-length) span reports 0.5 — the pin's neither-branch case exactly. TOTAL (review r4
25
+ // major 3): a non-finite span (an open-sided hook replacement, legal) yields no NaN — an
26
+ // open-ended progress is unrepresentable, reported as the degenerate 0.5; callers that can
27
+ // keep the live value should (the wiring's rescale does).
28
+ export function progressWithinSpan(value, span) {
29
+ const length = span.max - span.min;
30
+ if (!Number.isFinite(length) || length <= 0)
31
+ return 0.5;
32
+ const progress = (value - span.min) / length;
33
+ return progress < 0 ? 0 : progress > 1 ? 1 : progress;
34
+ }
package/dist/graph.cjs ADDED
@@ -0,0 +1,226 @@
1
+ "use strict";
2
+ // The motion graph: value factory, derived graph, animation registry, and settle ledger. Owns the
3
+ // injected clock/scheduler and registers a single per-frame step. Each frame advances active
4
+ // animations (by accumulated, dt-clamped elapsed), flushes dirty derived nodes in topological order,
5
+ // then reconciles settle. Zero host dependencies (REQ-CORE-003).
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.createMotionGraph = createMotionGraph;
8
+ const motion_value_1 = require("./motion-value.cjs");
9
+ const derived_1 = require("./derived.cjs");
10
+ const constants_1 = require("./config/constants.cjs");
11
+ /** Handle to one in-flight animation. `done` is true once it completes or is stopped/interrupted. */
12
+ class AnimationHandle {
13
+ #finished = false;
14
+ #onStop;
15
+ constructor(onStop) {
16
+ this.#onStop = onStop;
17
+ }
18
+ get done() {
19
+ return this.#finished;
20
+ }
21
+ /** @internal Mark finished without running the stop hook (completion / interruption path). */
22
+ markFinished() {
23
+ this.#finished = true;
24
+ }
25
+ stop() {
26
+ if (this.#finished)
27
+ return;
28
+ this.#finished = true;
29
+ this.#onStop();
30
+ }
31
+ }
32
+ class MotionGraphImpl {
33
+ #clock;
34
+ // Dirty derived nodes live in a reusable creation-index-sorted slot list, not a Set
35
+ // (REQ-DRIVER-021, packet §6 ordinary local-JS repair): mark dedupes and inserts by indexed
36
+ // binary-search/shift, and the flush drains the front by index — no Set growth or iterator on
37
+ // the frame path. The sort keeps the drain exactly the old min-first topological order.
38
+ #dirtySlots = [];
39
+ #animations = new Map();
40
+ // Command-edge-grown completion slots: the frame step writes finished records by index and the
41
+ // completion pass drains by count — no fresh completion array, no Map iterator, no push.
42
+ #completedSlots = [];
43
+ #completedCount = 0;
44
+ #frameClampedDt = 0;
45
+ #creationCounter = 0;
46
+ #openActivities = 0;
47
+ #wasSettled = true;
48
+ // One retained settle-resolver list, drained by index and reset by length — never replaced
49
+ // with a fresh array on the settle edge.
50
+ #settleResolvers = [];
51
+ constructor(options) {
52
+ this.#clock = options.clock;
53
+ options.scheduler.onFrame((dtMs) => this.#frameStep(dtMs));
54
+ }
55
+ motionValue(initial) {
56
+ return new motion_value_1.MotionValueImpl(initial, this.#clock);
57
+ }
58
+ derived(inputs, compute) {
59
+ // Both public call shapes erase to this unknown[] implementation; the graph passes each
60
+ // input's live get() positionally, so the runtime match is by construction.
61
+ return new derived_1.DerivedValue(this.#creationCounter++, inputs, compute, (node) => this.#markDirtyIntoReusableSlot(node));
62
+ }
63
+ animate(value, factory) {
64
+ if (!(value instanceof motion_value_1.MotionValueImpl)) {
65
+ throw new Error('native-motion-core: animate() requires a MotionValue created by this graph, not a derived/read-only value');
66
+ }
67
+ // Interruption (REQ-CORE-002): stop the prior animation on this value and seed the new generator
68
+ // with the value's current value+velocity so motion continues from the present visual state.
69
+ // Replacing the map entry keeps the active count net-unchanged across the swap.
70
+ const prior = this.#animations.get(value);
71
+ if (prior) {
72
+ prior.handle.markFinished();
73
+ // Pinned lifecycle: the interrupted animation never completed — it was cancelled.
74
+ value.emitEvent('animationCancel');
75
+ }
76
+ const seed = { from: value.get(), velocity: value.getVelocity() };
77
+ const generator = factory(seed);
78
+ const handle = new AnimationHandle(() => this.#removeAnimation(value, handle));
79
+ this.#animations.set(value, { value, generator, handle, elapsedMs: 0 });
80
+ value.attachAnimationStopper(() => handle.stop());
81
+ value.emitEvent('animationStart');
82
+ this.#reconcileSettleFromPrefilledSlots();
83
+ return handle;
84
+ }
85
+ activeAnimationCount() {
86
+ return this.#animations.size;
87
+ }
88
+ registerActivity() {
89
+ this.#openActivities += 1;
90
+ this.#reconcileSettleFromPrefilledSlots();
91
+ let released = false;
92
+ return {
93
+ release: () => {
94
+ if (released)
95
+ return;
96
+ released = true;
97
+ this.#openActivities -= 1;
98
+ this.#reconcileSettleFromPrefilledSlots();
99
+ },
100
+ };
101
+ }
102
+ isSettled() {
103
+ return this.#animations.size === 0 && this.#openActivities === 0;
104
+ }
105
+ whenSettled() {
106
+ if (this.isSettled())
107
+ return Promise.resolve();
108
+ return new Promise((resolve) => {
109
+ this.#settleResolvers.push(resolve);
110
+ });
111
+ }
112
+ #markDirtyIntoReusableSlot(node) {
113
+ // Sorted insertion by creationIndex (unique per node): the membership check IS the binary
114
+ // search — an already-dirty node is found and skipped, exactly like the old Set dedupe.
115
+ const slots = this.#dirtySlots;
116
+ let lo = 0;
117
+ let hi = slots.length;
118
+ while (lo < hi) {
119
+ const mid = (lo + hi) >> 1;
120
+ const midIndex = slots[mid].creationIndex;
121
+ if (midIndex === node.creationIndex)
122
+ return;
123
+ if (midIndex < node.creationIndex)
124
+ lo = mid + 1;
125
+ else
126
+ hi = mid;
127
+ }
128
+ // Indexed tail shift; capacity grows only to the frame's high-water mark, then is reused.
129
+ for (let i = slots.length; i > lo; i--)
130
+ slots[i] = slots[i - 1];
131
+ slots[lo] = node;
132
+ }
133
+ #removeAnimation(value, handle) {
134
+ const record = this.#animations.get(value);
135
+ if (record && record.handle === handle) {
136
+ this.#animations.delete(value);
137
+ record.value.endAnimation();
138
+ this.#reconcileSettleFromPrefilledSlots();
139
+ }
140
+ }
141
+ // Stable per-instance frame callback for the active-animation scan: Map.prototype.forEach walks
142
+ // the map's internal slots (no per-frame iterator object) with the same live-iteration semantics
143
+ // as the old for..of — entries appended mid-scan are visited, deleted entries are skipped.
144
+ #sampleActiveAnimation = (record) => {
145
+ record.elapsedMs += this.#frameClampedDt;
146
+ const sample = record.generator.sample(record.elapsedMs);
147
+ record.value.applyAnimationSample(sample.value, sample.velocity);
148
+ if (sample.done) {
149
+ this.#completedSlots[this.#completedCount] = record;
150
+ this.#completedCount += 1;
151
+ }
152
+ };
153
+ #frameStep(dtMs) {
154
+ // REQ-CLOCK-003: clamp per-frame elapsed so a large gap after backgrounding does not jump the
155
+ // graph forward unbounded. Animations advance by accumulated clamped elapsed, which is also what
156
+ // makes the trajectory a pure function of elapsed time (dt-robust) when no clamp triggers.
157
+ this.#frameClampedDt = Math.min(dtMs, constants_1.DT_CLAMP_MS);
158
+ this.#completedCount = 0;
159
+ this.#animations.forEach(this.#sampleActiveAnimation);
160
+ this.#completeFinishedAnimationsFromPrefilledSlots();
161
+ // Animations write their target values before the derived flush, so derived values observe
162
+ // post-animation inputs within the same frame (glitch-free).
163
+ this.#flushDirtyDerivedFromPrefilledSlots();
164
+ this.#reconcileSettleFromPrefilledSlots();
165
+ }
166
+ #completeFinishedAnimationsFromPrefilledSlots() {
167
+ const completed = this.#completedSlots;
168
+ const count = this.#completedCount;
169
+ for (let i = 0; i < count; i++) {
170
+ const record = completed[i];
171
+ completed[i] = undefined;
172
+ record.handle.markFinished();
173
+ this.#animations.delete(record.value);
174
+ record.value.endAnimation();
175
+ record.value.emitEvent('animationComplete');
176
+ }
177
+ }
178
+ // Flush dirty derived nodes in ascending creation index. A node depends only on earlier-created
179
+ // nodes, so lowest-index-first is a valid topological order: recomputing a node may notify and
180
+ // re-dirty a higher-index dependent, which this same drain then processes — glitch-free within one
181
+ // frame. The graph is acyclic by construction; the iteration guard is a loud backstop (REQ-VALUE-013).
182
+ // The slot list is kept sorted by #markDirtyIntoReusableSlot, so the front IS the minimum — the
183
+ // drain pops it by indexed shift (no Set iterator per frame, REQ-DRIVER-021).
184
+ #flushDirtyDerivedFromPrefilledSlots() {
185
+ let guard = 0;
186
+ const maxIterations = 1_000_000;
187
+ const slots = this.#dirtySlots;
188
+ while (slots.length > 0) {
189
+ if (++guard > maxIterations) {
190
+ throw new Error('native-motion-core: derived graph flush did not converge (cycle detected)');
191
+ }
192
+ const next = slots[0];
193
+ for (let i = 1; i < slots.length; i++)
194
+ slots[i - 1] = slots[i];
195
+ slots.length -= 1;
196
+ next.flush();
197
+ }
198
+ }
199
+ // Settle ledger (REQ-SETTLE-001/002): the graph is settled iff no active animations and no open
200
+ // activity handles. Resolve pending `whenSettled` promises exactly on the transition into settled;
201
+ // re-arm on the transition out. Fail-closed: while any source is open, resolvers stay pending.
202
+ // The resolver list is retained and drained by index — never replaced by a fresh array.
203
+ #reconcileSettleFromPrefilledSlots() {
204
+ const settled = this.isSettled();
205
+ if (settled && !this.#wasSettled) {
206
+ this.#wasSettled = true;
207
+ const resolvers = this.#settleResolvers;
208
+ const count = resolvers.length;
209
+ // Invoke BEFORE clearing: truncating first would delete the very prefix being drained. A
210
+ // resolver-time whenSettled() appends past `count` and survives the drain, exactly like
211
+ // the old replacement array.
212
+ for (let i = 0; i < count; i++)
213
+ resolvers[i]();
214
+ let tail = 0;
215
+ for (let i = count; i < resolvers.length; i++)
216
+ resolvers[tail++] = resolvers[i];
217
+ resolvers.length = tail;
218
+ }
219
+ else if (!settled && this.#wasSettled) {
220
+ this.#wasSettled = false;
221
+ }
222
+ }
223
+ }
224
+ function createMotionGraph(options) {
225
+ return new MotionGraphImpl(options);
226
+ }
@@ -0,0 +1,2 @@
1
+ import type { MotionGraph, MotionGraphOptions } from "./types.cjs";
2
+ export declare function createMotionGraph(options: MotionGraphOptions): MotionGraph;
@@ -0,0 +1,2 @@
1
+ import type { MotionGraph, MotionGraphOptions } from "./types.js";
2
+ export declare function createMotionGraph(options: MotionGraphOptions): MotionGraph;
package/dist/graph.js ADDED
@@ -0,0 +1,223 @@
1
+ // The motion graph: value factory, derived graph, animation registry, and settle ledger. Owns the
2
+ // injected clock/scheduler and registers a single per-frame step. Each frame advances active
3
+ // animations (by accumulated, dt-clamped elapsed), flushes dirty derived nodes in topological order,
4
+ // then reconciles settle. Zero host dependencies (REQ-CORE-003).
5
+ import { MotionValueImpl } from "./motion-value.js";
6
+ import { DerivedValue } from "./derived.js";
7
+ import { DT_CLAMP_MS } from "./config/constants.js";
8
+ /** Handle to one in-flight animation. `done` is true once it completes or is stopped/interrupted. */
9
+ class AnimationHandle {
10
+ #finished = false;
11
+ #onStop;
12
+ constructor(onStop) {
13
+ this.#onStop = onStop;
14
+ }
15
+ get done() {
16
+ return this.#finished;
17
+ }
18
+ /** @internal Mark finished without running the stop hook (completion / interruption path). */
19
+ markFinished() {
20
+ this.#finished = true;
21
+ }
22
+ stop() {
23
+ if (this.#finished)
24
+ return;
25
+ this.#finished = true;
26
+ this.#onStop();
27
+ }
28
+ }
29
+ class MotionGraphImpl {
30
+ #clock;
31
+ // Dirty derived nodes live in a reusable creation-index-sorted slot list, not a Set
32
+ // (REQ-DRIVER-021, packet §6 ordinary local-JS repair): mark dedupes and inserts by indexed
33
+ // binary-search/shift, and the flush drains the front by index — no Set growth or iterator on
34
+ // the frame path. The sort keeps the drain exactly the old min-first topological order.
35
+ #dirtySlots = [];
36
+ #animations = new Map();
37
+ // Command-edge-grown completion slots: the frame step writes finished records by index and the
38
+ // completion pass drains by count — no fresh completion array, no Map iterator, no push.
39
+ #completedSlots = [];
40
+ #completedCount = 0;
41
+ #frameClampedDt = 0;
42
+ #creationCounter = 0;
43
+ #openActivities = 0;
44
+ #wasSettled = true;
45
+ // One retained settle-resolver list, drained by index and reset by length — never replaced
46
+ // with a fresh array on the settle edge.
47
+ #settleResolvers = [];
48
+ constructor(options) {
49
+ this.#clock = options.clock;
50
+ options.scheduler.onFrame((dtMs) => this.#frameStep(dtMs));
51
+ }
52
+ motionValue(initial) {
53
+ return new MotionValueImpl(initial, this.#clock);
54
+ }
55
+ derived(inputs, compute) {
56
+ // Both public call shapes erase to this unknown[] implementation; the graph passes each
57
+ // input's live get() positionally, so the runtime match is by construction.
58
+ return new DerivedValue(this.#creationCounter++, inputs, compute, (node) => this.#markDirtyIntoReusableSlot(node));
59
+ }
60
+ animate(value, factory) {
61
+ if (!(value instanceof MotionValueImpl)) {
62
+ throw new Error('native-motion-core: animate() requires a MotionValue created by this graph, not a derived/read-only value');
63
+ }
64
+ // Interruption (REQ-CORE-002): stop the prior animation on this value and seed the new generator
65
+ // with the value's current value+velocity so motion continues from the present visual state.
66
+ // Replacing the map entry keeps the active count net-unchanged across the swap.
67
+ const prior = this.#animations.get(value);
68
+ if (prior) {
69
+ prior.handle.markFinished();
70
+ // Pinned lifecycle: the interrupted animation never completed — it was cancelled.
71
+ value.emitEvent('animationCancel');
72
+ }
73
+ const seed = { from: value.get(), velocity: value.getVelocity() };
74
+ const generator = factory(seed);
75
+ const handle = new AnimationHandle(() => this.#removeAnimation(value, handle));
76
+ this.#animations.set(value, { value, generator, handle, elapsedMs: 0 });
77
+ value.attachAnimationStopper(() => handle.stop());
78
+ value.emitEvent('animationStart');
79
+ this.#reconcileSettleFromPrefilledSlots();
80
+ return handle;
81
+ }
82
+ activeAnimationCount() {
83
+ return this.#animations.size;
84
+ }
85
+ registerActivity() {
86
+ this.#openActivities += 1;
87
+ this.#reconcileSettleFromPrefilledSlots();
88
+ let released = false;
89
+ return {
90
+ release: () => {
91
+ if (released)
92
+ return;
93
+ released = true;
94
+ this.#openActivities -= 1;
95
+ this.#reconcileSettleFromPrefilledSlots();
96
+ },
97
+ };
98
+ }
99
+ isSettled() {
100
+ return this.#animations.size === 0 && this.#openActivities === 0;
101
+ }
102
+ whenSettled() {
103
+ if (this.isSettled())
104
+ return Promise.resolve();
105
+ return new Promise((resolve) => {
106
+ this.#settleResolvers.push(resolve);
107
+ });
108
+ }
109
+ #markDirtyIntoReusableSlot(node) {
110
+ // Sorted insertion by creationIndex (unique per node): the membership check IS the binary
111
+ // search — an already-dirty node is found and skipped, exactly like the old Set dedupe.
112
+ const slots = this.#dirtySlots;
113
+ let lo = 0;
114
+ let hi = slots.length;
115
+ while (lo < hi) {
116
+ const mid = (lo + hi) >> 1;
117
+ const midIndex = slots[mid].creationIndex;
118
+ if (midIndex === node.creationIndex)
119
+ return;
120
+ if (midIndex < node.creationIndex)
121
+ lo = mid + 1;
122
+ else
123
+ hi = mid;
124
+ }
125
+ // Indexed tail shift; capacity grows only to the frame's high-water mark, then is reused.
126
+ for (let i = slots.length; i > lo; i--)
127
+ slots[i] = slots[i - 1];
128
+ slots[lo] = node;
129
+ }
130
+ #removeAnimation(value, handle) {
131
+ const record = this.#animations.get(value);
132
+ if (record && record.handle === handle) {
133
+ this.#animations.delete(value);
134
+ record.value.endAnimation();
135
+ this.#reconcileSettleFromPrefilledSlots();
136
+ }
137
+ }
138
+ // Stable per-instance frame callback for the active-animation scan: Map.prototype.forEach walks
139
+ // the map's internal slots (no per-frame iterator object) with the same live-iteration semantics
140
+ // as the old for..of — entries appended mid-scan are visited, deleted entries are skipped.
141
+ #sampleActiveAnimation = (record) => {
142
+ record.elapsedMs += this.#frameClampedDt;
143
+ const sample = record.generator.sample(record.elapsedMs);
144
+ record.value.applyAnimationSample(sample.value, sample.velocity);
145
+ if (sample.done) {
146
+ this.#completedSlots[this.#completedCount] = record;
147
+ this.#completedCount += 1;
148
+ }
149
+ };
150
+ #frameStep(dtMs) {
151
+ // REQ-CLOCK-003: clamp per-frame elapsed so a large gap after backgrounding does not jump the
152
+ // graph forward unbounded. Animations advance by accumulated clamped elapsed, which is also what
153
+ // makes the trajectory a pure function of elapsed time (dt-robust) when no clamp triggers.
154
+ this.#frameClampedDt = Math.min(dtMs, DT_CLAMP_MS);
155
+ this.#completedCount = 0;
156
+ this.#animations.forEach(this.#sampleActiveAnimation);
157
+ this.#completeFinishedAnimationsFromPrefilledSlots();
158
+ // Animations write their target values before the derived flush, so derived values observe
159
+ // post-animation inputs within the same frame (glitch-free).
160
+ this.#flushDirtyDerivedFromPrefilledSlots();
161
+ this.#reconcileSettleFromPrefilledSlots();
162
+ }
163
+ #completeFinishedAnimationsFromPrefilledSlots() {
164
+ const completed = this.#completedSlots;
165
+ const count = this.#completedCount;
166
+ for (let i = 0; i < count; i++) {
167
+ const record = completed[i];
168
+ completed[i] = undefined;
169
+ record.handle.markFinished();
170
+ this.#animations.delete(record.value);
171
+ record.value.endAnimation();
172
+ record.value.emitEvent('animationComplete');
173
+ }
174
+ }
175
+ // Flush dirty derived nodes in ascending creation index. A node depends only on earlier-created
176
+ // nodes, so lowest-index-first is a valid topological order: recomputing a node may notify and
177
+ // re-dirty a higher-index dependent, which this same drain then processes — glitch-free within one
178
+ // frame. The graph is acyclic by construction; the iteration guard is a loud backstop (REQ-VALUE-013).
179
+ // The slot list is kept sorted by #markDirtyIntoReusableSlot, so the front IS the minimum — the
180
+ // drain pops it by indexed shift (no Set iterator per frame, REQ-DRIVER-021).
181
+ #flushDirtyDerivedFromPrefilledSlots() {
182
+ let guard = 0;
183
+ const maxIterations = 1_000_000;
184
+ const slots = this.#dirtySlots;
185
+ while (slots.length > 0) {
186
+ if (++guard > maxIterations) {
187
+ throw new Error('native-motion-core: derived graph flush did not converge (cycle detected)');
188
+ }
189
+ const next = slots[0];
190
+ for (let i = 1; i < slots.length; i++)
191
+ slots[i - 1] = slots[i];
192
+ slots.length -= 1;
193
+ next.flush();
194
+ }
195
+ }
196
+ // Settle ledger (REQ-SETTLE-001/002): the graph is settled iff no active animations and no open
197
+ // activity handles. Resolve pending `whenSettled` promises exactly on the transition into settled;
198
+ // re-arm on the transition out. Fail-closed: while any source is open, resolvers stay pending.
199
+ // The resolver list is retained and drained by index — never replaced by a fresh array.
200
+ #reconcileSettleFromPrefilledSlots() {
201
+ const settled = this.isSettled();
202
+ if (settled && !this.#wasSettled) {
203
+ this.#wasSettled = true;
204
+ const resolvers = this.#settleResolvers;
205
+ const count = resolvers.length;
206
+ // Invoke BEFORE clearing: truncating first would delete the very prefix being drained. A
207
+ // resolver-time whenSettled() appends past `count` and survives the drain, exactly like
208
+ // the old replacement array.
209
+ for (let i = 0; i < count; i++)
210
+ resolvers[i]();
211
+ let tail = 0;
212
+ for (let i = count; i < resolvers.length; i++)
213
+ resolvers[tail++] = resolvers[i];
214
+ resolvers.length = tail;
215
+ }
216
+ else if (!settled && this.#wasSettled) {
217
+ this.#wasSettled = false;
218
+ }
219
+ }
220
+ }
221
+ export function createMotionGraph(options) {
222
+ return new MotionGraphImpl(options);
223
+ }