@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
package/dist/delay.js ADDED
@@ -0,0 +1,70 @@
1
+ 'worklet';
2
+ // @frame-path — executes inside the UI-runtime frame step; allocation-gated (REQ-DRIVER-021).
3
+ // T18-b / REQ-TIMING-004 — the delay elapsed-time REBASE. `delay` is not a generator feature in
4
+ // the pinned Motion: it is a pure `t → t − delay` shift applied AROUND the repeat fold
5
+ // (motion-dom@12.42.2 `JSAnimation.tick`: `timeWithoutDelay = currentTime − delay`, clamped at 0,
6
+ // computed BEFORE the fold arithmetic). This module is that shift, so every trajectory — bare
7
+ // generator or fold — delays identically and none of them learns about delay. Host-agnostic
8
+ // (REQ-CORE-003): relative imports only. Numerics are pinned by per-sample goldens in
9
+ // delay.test.ts.
10
+ //
11
+ // The hold is never done, which is what keeps `delay` + `duration: 0` from finishing instantly
12
+ // (the pin's `delayState` law). A NEGATIVE delay is an elapsed-time seed, not an error: the
13
+ // rebase formula is the whole law, so the first sample lands mid-trajectory — including
14
+ // mid-iteration when the magnitude exceeds whole iterations of a fold. There is no sign floor
15
+ // (pinned behavior; validate.ts bounds only a POSITIVE delay to the settle horizon and refuses
16
+ // the non-finite ones).
17
+ /**
18
+ * Wrap `base` in the delay rebase (REQ-TIMING-004). The returned generator is a `Generator` like
19
+ * any other — the driver steps it without knowing it delays. Both driver backends call this at
20
+ * BOTH seams (start and retarget), around whatever the transition resolved to — the fold when the
21
+ * transition repeats, the bare generator otherwise — and parity between them is held by the
22
+ * executing floor, `driverParity.differential.test.ts`, not by this call being shared.
23
+ *
24
+ * While `t − delayMs < 0` the sample is the FIRST keyframe with velocity 0 and `done: false`
25
+ * (packet L2): the pin's `delayState` holds `keyframes[0]` and never completes, and our
26
+ * analytic-velocity contract (REQ-SPRING-009) reads 0 during the hold — so a mid-delay retarget
27
+ * reseeds from the held value at rest, there being no motion to be continuous WITH. The first
28
+ * keyframe is captured from `base.sample(0)` at CONSTRUCTION: every core generator is a pure
29
+ * function of elapsed time, so the zero-sample is the held value by definition.
30
+ *
31
+ * After the hold, `done` is the wrapped trajectory's own at `t − delayMs` — the fold reports done
32
+ * against its `totalDuration` (which excludes delay), so a finite animation settles at
33
+ * `delay + totalDuration` exactly (packet L4).
34
+ *
35
+ * `delayMs === 0` is elided to `base` unchanged: the identity rebase adds no behavior, and the
36
+ * drivers never produce a negative elapsed where elision would differ (`stepProp` accumulates
37
+ * non-negative deltas). A NaN elapsed is NOT laundered into the hold: `NaN < delayMs` is false,
38
+ * so the wrapped generator receives NaN and answers NaN — a broken clock stays loud (the repeat
39
+ * fold's `Math.max` law, applied here).
40
+ */
41
+ // alloc-ok: lifecycle-edge — construction captures the first keyframe and builds the ONE reused
42
+ // sample record; sample() below mutates it and allocates nothing (REQ-DRIVER-021).
43
+ export function delayGenerator(base, delayMs) {
44
+ if (delayMs === 0)
45
+ return base;
46
+ const firstKeyframe = base.sample(0).value;
47
+ const out = { value: firstKeyframe, velocity: 0, done: false };
48
+ return {
49
+ // A delay is a pure time SHIFT: it moves WHEN a trajectory ends, never WHERE it rests. So the
50
+ // wrapped trajectory's finish-time commit passes straight through. Dropping it here silently
51
+ // returned `delay` + a fractional `repeat` to the mid-play landing REQ-TIMING-003 exists to
52
+ // fix — on both drivers and at both seams, through a combination validation accepts (H2
53
+ // round-1 review MAJOR 1). Every wrapper of a `Generator` owes this forward.
54
+ finalValue: base.finalValue,
55
+ sample(elapsedMs) {
56
+ const rebasedMs = elapsedMs - delayMs;
57
+ if (rebasedMs < 0) {
58
+ out.value = firstKeyframe;
59
+ out.velocity = 0;
60
+ out.done = false;
61
+ return out;
62
+ }
63
+ const sample = base.sample(rebasedMs);
64
+ out.value = sample.value;
65
+ out.velocity = sample.velocity;
66
+ out.done = sample.done;
67
+ return out;
68
+ },
69
+ };
70
+ }
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ // Derived values (REQ-VALUE-002/013): a read-only MotionValue computed from numeric inputs, off the
3
+ // host render path. Recompute is coalesced to the frame boundary and applied in creation-index
4
+ // (topological) order by the graph, so a derivation-of-derivations observes no intermediate value.
5
+ // The dependency graph is acyclic by construction: inputs must already exist when a derived is
6
+ // created, so a node can only depend on earlier-created nodes.
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.DerivedValue = void 0;
9
+ const collect_reads_1 = require("./collect-reads.cjs");
10
+ const subscriptions_1 = require("./subscriptions.cjs");
11
+ class DerivedValue {
12
+ creationIndex;
13
+ #inputs;
14
+ #compute;
15
+ #detached = false;
16
+ #markDirty;
17
+ #inputUnsubscribers;
18
+ #subs = new subscriptions_1.Subscriptions();
19
+ #value;
20
+ // Construction-edge-sized reusable input buffer (REQ-DRIVER-021, packet §6 ordinary local-JS
21
+ // repair): recompute fills it by indexed writes — no per-flush map array, closure, or fresh
22
+ // spread target on the engine bookkeeping path.
23
+ #inputValues;
24
+ constructor(creationIndex, inputs, compute, markDirty) {
25
+ this.creationIndex = creationIndex;
26
+ this.#inputs = inputs;
27
+ this.#compute = compute;
28
+ this.#markDirty = markDirty;
29
+ this.#inputValues = Array.from({ length: inputs.length });
30
+ this.#value = this.#recompute();
31
+ this.#inputUnsubscribers = this.#subscribeInputs();
32
+ }
33
+ // One stable named input listener per derived value (manifest node
34
+ // DerivedValue.markDirtyFromInput) — not a fresh closure per input per (re)subscription.
35
+ #markDirtyFromInput = () => {
36
+ this.#markDirty(this);
37
+ };
38
+ #subscribeInputs() {
39
+ // Branded so the driver port can classify a transported write's derived-dirty reach
40
+ // (REQ-VALUE-015) without a graph handle; the listener itself is unchanged.
41
+ return this.#inputs.map((input) => input.subscribe((0, subscriptions_1.brandDerivedDependentListener)(this.#markDirtyFromInput)));
42
+ }
43
+ /**
44
+ * Release the input subscriptions WITHOUT destroying the value (REQ-VALUE-002 lifecycle,
45
+ * review r4): the hook layer detaches on effect cleanup and re-attaches the SAME value on a
46
+ * StrictMode replay. Subscribers and the last value survive a detach.
47
+ */
48
+ detachInputs() {
49
+ for (const unsubscribe of this.#inputUnsubscribers)
50
+ unsubscribe();
51
+ this.#inputUnsubscribers = [];
52
+ // Cancel any ALREADY-QUEUED recompute (review r5): a set() before the detach must not
53
+ // apply or notify on the next flush. The dirty-set entry stays; flush() below no-ops it.
54
+ this.#detached = true;
55
+ }
56
+ /** Re-subscribe the inputs and resync from their CURRENT values. Idempotent. */
57
+ attachInputs() {
58
+ if (this.#inputUnsubscribers.length > 0)
59
+ return;
60
+ this.#detached = false;
61
+ this.#inputUnsubscribers = this.#subscribeInputs();
62
+ this.refresh();
63
+ }
64
+ /**
65
+ * Recompute synchronously from the current inputs and notify on change — the render-phase
66
+ * re-parameterization path (a compute whose captured parameters changed resyncs immediately;
67
+ * frame-coalesced flushing remains the INPUT-driven path).
68
+ */
69
+ refresh() {
70
+ this.#applyRecomputeFromReusableInputs();
71
+ }
72
+ get() {
73
+ (0, collect_reads_1.reportMotionValueRead)(this);
74
+ return this.#value;
75
+ }
76
+ set(_value) {
77
+ throw new Error('native-motion-core: cannot set a derived value; it is read-only (REQ-VALUE-002)');
78
+ }
79
+ jump(_value) {
80
+ throw new Error('native-motion-core: cannot jump a derived value; it is read-only (REQ-VALUE-002)');
81
+ }
82
+ subscribe(listener) {
83
+ return this.#subs.subscribe(listener);
84
+ }
85
+ // Open decision (SPEC-CORE §8): derived-value velocity parity with Motion's transform values.
86
+ // Reports 0 until ratified; recorded so it is not a silent choice.
87
+ getVelocity() {
88
+ return 0;
89
+ }
90
+ on(event, listener) {
91
+ // Broad implementation signature for delegation only — not a public overload (r16).
92
+ return this.#subs.on(event, listener);
93
+ }
94
+ stop() {
95
+ // A derived value is not animated directly; nothing to stop.
96
+ }
97
+ isAnimating() {
98
+ // Read-only: inputs animate, the derivation only recomputes — never animated directly (r12).
99
+ return false;
100
+ }
101
+ destroy() {
102
+ for (const unsubscribe of this.#inputUnsubscribers)
103
+ unsubscribe();
104
+ this.#inputUnsubscribers = [];
105
+ // Teardown cancels queued dirt exactly like detach (r9): a set() that preceded the destroy
106
+ // must never recompute a destroyed value at the next flush.
107
+ this.#detached = true;
108
+ // The pinned full event union (REQ-API-027, r8): teardown announces itself to existing
109
+ // listeners before subscriptions drop — same law as MotionValueImpl.destroy.
110
+ this.#subs.emit('destroy');
111
+ this.#subs.clear();
112
+ }
113
+ /**
114
+ * Recompute from inputs; notify subscribers only if the value actually changed (glitch-free).
115
+ * A DETACHED node no-ops here: dirt queued before the detach is cancelled, never applied
116
+ * (review r5). Explicit refresh() bypasses this gate — a deliberate resync is caller intent.
117
+ */
118
+ flush() {
119
+ if (this.#detached)
120
+ return;
121
+ this.#applyRecomputeFromReusableInputs();
122
+ }
123
+ #applyRecomputeFromReusableInputs() {
124
+ const next = this.#recompute();
125
+ if (!Object.is(next, this.#value)) {
126
+ this.#value = next;
127
+ this.#subs.emitChange(this.#value);
128
+ }
129
+ }
130
+ // Indexed fill of the construction-sized reusable buffer — the frame-path replacement for
131
+ // `inputs.map((input) => input.get())` (fresh array + closure per dirty recompute).
132
+ #readInputsIntoReusableBuffer() {
133
+ const inputs = this.#inputs;
134
+ const values = this.#inputValues;
135
+ for (let i = 0; i < inputs.length; i++)
136
+ values[i] = inputs[i].get();
137
+ }
138
+ #recompute() {
139
+ this.#readInputsIntoReusableBuffer();
140
+ return this.#compute(...this.#inputValues);
141
+ }
142
+ }
143
+ exports.DerivedValue = DerivedValue;
@@ -0,0 +1,41 @@
1
+ import type { DerivedSource, MotionValue, MotionValueEvent, ValueChangeListener } from "./types.cjs";
2
+ /** A node the graph flushes on the frame boundary. */
3
+ export interface DirtyNode {
4
+ readonly creationIndex: number;
5
+ flush(): void;
6
+ }
7
+ export declare class DerivedValue<T> implements MotionValue<T>, DirtyNode {
8
+ #private;
9
+ readonly creationIndex: number;
10
+ constructor(creationIndex: number, inputs: readonly DerivedSource[], compute: (...values: unknown[]) => T, markDirty: (node: DirtyNode) => void);
11
+ /**
12
+ * Release the input subscriptions WITHOUT destroying the value (REQ-VALUE-002 lifecycle,
13
+ * review r4): the hook layer detaches on effect cleanup and re-attaches the SAME value on a
14
+ * StrictMode replay. Subscribers and the last value survive a detach.
15
+ */
16
+ detachInputs(): void;
17
+ /** Re-subscribe the inputs and resync from their CURRENT values. Idempotent. */
18
+ attachInputs(): void;
19
+ /**
20
+ * Recompute synchronously from the current inputs and notify on change — the render-phase
21
+ * re-parameterization path (a compute whose captured parameters changed resyncs immediately;
22
+ * frame-coalesced flushing remains the INPUT-driven path).
23
+ */
24
+ refresh(): void;
25
+ get(): T;
26
+ set(_value: T): never;
27
+ jump(_value: T): never;
28
+ subscribe(listener: ValueChangeListener<T>): () => void;
29
+ getVelocity(): number;
30
+ on(event: 'change', listener: (latest: T) => void): () => void;
31
+ on(event: Exclude<MotionValueEvent, 'change'>, listener: () => void): () => void;
32
+ stop(): void;
33
+ isAnimating(): boolean;
34
+ destroy(): void;
35
+ /**
36
+ * Recompute from inputs; notify subscribers only if the value actually changed (glitch-free).
37
+ * A DETACHED node no-ops here: dirt queued before the detach is cancelled, never applied
38
+ * (review r5). Explicit refresh() bypasses this gate — a deliberate resync is caller intent.
39
+ */
40
+ flush(): void;
41
+ }
@@ -0,0 +1,41 @@
1
+ import type { DerivedSource, MotionValue, MotionValueEvent, ValueChangeListener } from "./types.js";
2
+ /** A node the graph flushes on the frame boundary. */
3
+ export interface DirtyNode {
4
+ readonly creationIndex: number;
5
+ flush(): void;
6
+ }
7
+ export declare class DerivedValue<T> implements MotionValue<T>, DirtyNode {
8
+ #private;
9
+ readonly creationIndex: number;
10
+ constructor(creationIndex: number, inputs: readonly DerivedSource[], compute: (...values: unknown[]) => T, markDirty: (node: DirtyNode) => void);
11
+ /**
12
+ * Release the input subscriptions WITHOUT destroying the value (REQ-VALUE-002 lifecycle,
13
+ * review r4): the hook layer detaches on effect cleanup and re-attaches the SAME value on a
14
+ * StrictMode replay. Subscribers and the last value survive a detach.
15
+ */
16
+ detachInputs(): void;
17
+ /** Re-subscribe the inputs and resync from their CURRENT values. Idempotent. */
18
+ attachInputs(): void;
19
+ /**
20
+ * Recompute synchronously from the current inputs and notify on change — the render-phase
21
+ * re-parameterization path (a compute whose captured parameters changed resyncs immediately;
22
+ * frame-coalesced flushing remains the INPUT-driven path).
23
+ */
24
+ refresh(): void;
25
+ get(): T;
26
+ set(_value: T): never;
27
+ jump(_value: T): never;
28
+ subscribe(listener: ValueChangeListener<T>): () => void;
29
+ getVelocity(): number;
30
+ on(event: 'change', listener: (latest: T) => void): () => void;
31
+ on(event: Exclude<MotionValueEvent, 'change'>, listener: () => void): () => void;
32
+ stop(): void;
33
+ isAnimating(): boolean;
34
+ destroy(): void;
35
+ /**
36
+ * Recompute from inputs; notify subscribers only if the value actually changed (glitch-free).
37
+ * A DETACHED node no-ops here: dirt queued before the detach is cancelled, never applied
38
+ * (review r5). Explicit refresh() bypasses this gate — a deliberate resync is caller intent.
39
+ */
40
+ flush(): void;
41
+ }
@@ -0,0 +1,139 @@
1
+ // Derived values (REQ-VALUE-002/013): a read-only MotionValue computed from numeric inputs, off the
2
+ // host render path. Recompute is coalesced to the frame boundary and applied in creation-index
3
+ // (topological) order by the graph, so a derivation-of-derivations observes no intermediate value.
4
+ // The dependency graph is acyclic by construction: inputs must already exist when a derived is
5
+ // created, so a node can only depend on earlier-created nodes.
6
+ import { reportMotionValueRead } from "./collect-reads.js";
7
+ import { brandDerivedDependentListener, Subscriptions } from "./subscriptions.js";
8
+ export class DerivedValue {
9
+ creationIndex;
10
+ #inputs;
11
+ #compute;
12
+ #detached = false;
13
+ #markDirty;
14
+ #inputUnsubscribers;
15
+ #subs = new Subscriptions();
16
+ #value;
17
+ // Construction-edge-sized reusable input buffer (REQ-DRIVER-021, packet §6 ordinary local-JS
18
+ // repair): recompute fills it by indexed writes — no per-flush map array, closure, or fresh
19
+ // spread target on the engine bookkeeping path.
20
+ #inputValues;
21
+ constructor(creationIndex, inputs, compute, markDirty) {
22
+ this.creationIndex = creationIndex;
23
+ this.#inputs = inputs;
24
+ this.#compute = compute;
25
+ this.#markDirty = markDirty;
26
+ this.#inputValues = Array.from({ length: inputs.length });
27
+ this.#value = this.#recompute();
28
+ this.#inputUnsubscribers = this.#subscribeInputs();
29
+ }
30
+ // One stable named input listener per derived value (manifest node
31
+ // DerivedValue.markDirtyFromInput) — not a fresh closure per input per (re)subscription.
32
+ #markDirtyFromInput = () => {
33
+ this.#markDirty(this);
34
+ };
35
+ #subscribeInputs() {
36
+ // Branded so the driver port can classify a transported write's derived-dirty reach
37
+ // (REQ-VALUE-015) without a graph handle; the listener itself is unchanged.
38
+ return this.#inputs.map((input) => input.subscribe(brandDerivedDependentListener(this.#markDirtyFromInput)));
39
+ }
40
+ /**
41
+ * Release the input subscriptions WITHOUT destroying the value (REQ-VALUE-002 lifecycle,
42
+ * review r4): the hook layer detaches on effect cleanup and re-attaches the SAME value on a
43
+ * StrictMode replay. Subscribers and the last value survive a detach.
44
+ */
45
+ detachInputs() {
46
+ for (const unsubscribe of this.#inputUnsubscribers)
47
+ unsubscribe();
48
+ this.#inputUnsubscribers = [];
49
+ // Cancel any ALREADY-QUEUED recompute (review r5): a set() before the detach must not
50
+ // apply or notify on the next flush. The dirty-set entry stays; flush() below no-ops it.
51
+ this.#detached = true;
52
+ }
53
+ /** Re-subscribe the inputs and resync from their CURRENT values. Idempotent. */
54
+ attachInputs() {
55
+ if (this.#inputUnsubscribers.length > 0)
56
+ return;
57
+ this.#detached = false;
58
+ this.#inputUnsubscribers = this.#subscribeInputs();
59
+ this.refresh();
60
+ }
61
+ /**
62
+ * Recompute synchronously from the current inputs and notify on change — the render-phase
63
+ * re-parameterization path (a compute whose captured parameters changed resyncs immediately;
64
+ * frame-coalesced flushing remains the INPUT-driven path).
65
+ */
66
+ refresh() {
67
+ this.#applyRecomputeFromReusableInputs();
68
+ }
69
+ get() {
70
+ reportMotionValueRead(this);
71
+ return this.#value;
72
+ }
73
+ set(_value) {
74
+ throw new Error('native-motion-core: cannot set a derived value; it is read-only (REQ-VALUE-002)');
75
+ }
76
+ jump(_value) {
77
+ throw new Error('native-motion-core: cannot jump a derived value; it is read-only (REQ-VALUE-002)');
78
+ }
79
+ subscribe(listener) {
80
+ return this.#subs.subscribe(listener);
81
+ }
82
+ // Open decision (SPEC-CORE §8): derived-value velocity parity with Motion's transform values.
83
+ // Reports 0 until ratified; recorded so it is not a silent choice.
84
+ getVelocity() {
85
+ return 0;
86
+ }
87
+ on(event, listener) {
88
+ // Broad implementation signature for delegation only — not a public overload (r16).
89
+ return this.#subs.on(event, listener);
90
+ }
91
+ stop() {
92
+ // A derived value is not animated directly; nothing to stop.
93
+ }
94
+ isAnimating() {
95
+ // Read-only: inputs animate, the derivation only recomputes — never animated directly (r12).
96
+ return false;
97
+ }
98
+ destroy() {
99
+ for (const unsubscribe of this.#inputUnsubscribers)
100
+ unsubscribe();
101
+ this.#inputUnsubscribers = [];
102
+ // Teardown cancels queued dirt exactly like detach (r9): a set() that preceded the destroy
103
+ // must never recompute a destroyed value at the next flush.
104
+ this.#detached = true;
105
+ // The pinned full event union (REQ-API-027, r8): teardown announces itself to existing
106
+ // listeners before subscriptions drop — same law as MotionValueImpl.destroy.
107
+ this.#subs.emit('destroy');
108
+ this.#subs.clear();
109
+ }
110
+ /**
111
+ * Recompute from inputs; notify subscribers only if the value actually changed (glitch-free).
112
+ * A DETACHED node no-ops here: dirt queued before the detach is cancelled, never applied
113
+ * (review r5). Explicit refresh() bypasses this gate — a deliberate resync is caller intent.
114
+ */
115
+ flush() {
116
+ if (this.#detached)
117
+ return;
118
+ this.#applyRecomputeFromReusableInputs();
119
+ }
120
+ #applyRecomputeFromReusableInputs() {
121
+ const next = this.#recompute();
122
+ if (!Object.is(next, this.#value)) {
123
+ this.#value = next;
124
+ this.#subs.emitChange(this.#value);
125
+ }
126
+ }
127
+ // Indexed fill of the construction-sized reusable buffer — the frame-path replacement for
128
+ // `inputs.map((input) => input.get())` (fresh array + closure per dirty recompute).
129
+ #readInputsIntoReusableBuffer() {
130
+ const inputs = this.#inputs;
131
+ const values = this.#inputValues;
132
+ for (let i = 0; i < inputs.length; i++)
133
+ values[i] = inputs[i].get();
134
+ }
135
+ #recompute() {
136
+ this.#readInputsIntoReusableBuffer();
137
+ return this.#compute(...this.#inputValues);
138
+ }
139
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ // Public surface of the SPEC-NATIVE-DRIVER subsystem: the host-agnostic Driver seam `core` defines and the
3
+ // native/web drivers implement. Host-agnostic (REQ-DRIVER-010) — imports no driver, no host package. The
4
+ // interface + command types land first (Milestone 1); the pure worklet-safe step, the reference driver,
5
+ // and the prop-tier classification join this surface as they land.
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.tierFor = exports.createReferenceDriver = exports.stepProp = exports.clampElapsed = exports.captureTransition = void 0;
8
+ var prepare_1 = require("./prepare.cjs");
9
+ Object.defineProperty(exports, "captureTransition", { enumerable: true, get: function () { return prepare_1.captureTransition; } });
10
+ // The pure, time-integrated per-frame step (REQ-DRIVER-012/015): the worklet-safe trajectory advance a
11
+ // concrete driver runs each frame. Exposed so the native worklet + web drivers reuse the single step math.
12
+ var step_1 = require("./step.cjs");
13
+ Object.defineProperty(exports, "clampElapsed", { enumerable: true, get: function () { return step_1.clampElapsed; } });
14
+ Object.defineProperty(exports, "stepProp", { enumerable: true, get: function () { return step_1.stepProp; } });
15
+ // The reference in-core Driver (REQ-DRIVER-013/020): the host-agnostic command-protocol + quiesce
16
+ // implementation the fake host + conformance drive; the substrate drivers are its device-bound peers.
17
+ var reference_1 = require("./reference.cjs");
18
+ Object.defineProperty(exports, "createReferenceDriver", { enumerable: true, get: function () { return reference_1.createReferenceDriver; } });
19
+ // Prop-tier classification (REQ-DRIVER-017): routes each animatable prop to its substrate execution path
20
+ // (fast vs commit), registry-derived and fail-loud on an untiered prop.
21
+ var tiers_1 = require("./tiers.cjs");
22
+ Object.defineProperty(exports, "tierFor", { enumerable: true, get: function () { return tiers_1.tierFor; } });
@@ -0,0 +1,7 @@
1
+ export type { Driver, DriverCommand, ElementHandle, LivePropState, PropTarget, PropTargetTo, PropTransition, } from "./types.cjs";
2
+ export { captureTransition } from "./prepare.cjs";
3
+ export { clampElapsed, stepProp } from "./step.cjs";
4
+ export type { PropStep } from "./step.cjs";
5
+ export { createReferenceDriver } from "./reference.cjs";
6
+ export { tierFor } from "./tiers.cjs";
7
+ export type { PropTier } from "./tiers.cjs";
@@ -0,0 +1,7 @@
1
+ export type { Driver, DriverCommand, ElementHandle, LivePropState, PropTarget, PropTargetTo, PropTransition, } from "./types.js";
2
+ export { captureTransition } from "./prepare.js";
3
+ export { clampElapsed, stepProp } from "./step.js";
4
+ export type { PropStep } from "./step.js";
5
+ export { createReferenceDriver } from "./reference.js";
6
+ export { tierFor } from "./tiers.js";
7
+ export type { PropTier } from "./tiers.js";
@@ -0,0 +1,14 @@
1
+ // Public surface of the SPEC-NATIVE-DRIVER subsystem: the host-agnostic Driver seam `core` defines and the
2
+ // native/web drivers implement. Host-agnostic (REQ-DRIVER-010) — imports no driver, no host package. The
3
+ // interface + command types land first (Milestone 1); the pure worklet-safe step, the reference driver,
4
+ // and the prop-tier classification join this surface as they land.
5
+ export { captureTransition } from "./prepare.js";
6
+ // The pure, time-integrated per-frame step (REQ-DRIVER-012/015): the worklet-safe trajectory advance a
7
+ // concrete driver runs each frame. Exposed so the native worklet + web drivers reuse the single step math.
8
+ export { clampElapsed, stepProp } from "./step.js";
9
+ // The reference in-core Driver (REQ-DRIVER-013/020): the host-agnostic command-protocol + quiesce
10
+ // implementation the fake host + conformance drive; the substrate drivers are its device-bound peers.
11
+ export { createReferenceDriver } from "./reference.js";
12
+ // Prop-tier classification (REQ-DRIVER-017): routes each animatable prop to its substrate execution path
13
+ // (fast vs commit), registry-derived and fail-loud on an untiered prop.
14
+ export { tierFor } from "./tiers.js";
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ // The shared target-count-dependent keyframe timing law (REQ-API-033 law d). Component
3
+ // suppliers and raw Driver.command preparation both use this one predicate, so a transition
4
+ // cannot pass one boundary and become inert or fall back at another.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.keyframeTimingRefusal = keyframeTimingRefusal;
7
+ exports.keyframeTransitionRefusal = keyframeTransitionRefusal;
8
+ exports.initialTargetRefusal = initialTargetRefusal;
9
+ exports.keyframeConfigRefusal = keyframeConfigRefusal;
10
+ const validate_1 = require("../component/validate.cjs");
11
+ const timing_1 = require("../timing.cjs");
12
+ const transition_1 = require("../transition.cjs");
13
+ function isPresentArrayIndex(value, index) {
14
+ return Object.hasOwn(value, index);
15
+ }
16
+ function invalidTiming(componentId, key, value, reason) {
17
+ return new validate_1.InvalidTransitionError(componentId, key, value, reason);
18
+ }
19
+ const NAMED_EASING_SET = new Set(timing_1.NAMED_EASING_KEYS);
20
+ function isValidSingleEasing(value) {
21
+ if (typeof value === 'string')
22
+ return NAMED_EASING_SET.has(value);
23
+ if (!Array.isArray(value) || value.length !== 4)
24
+ return false;
25
+ for (let index = 0; index < 4; index++) {
26
+ if (!isPresentArrayIndex(value, index))
27
+ return false;
28
+ const point = value[index];
29
+ if (typeof point !== 'number' || !Number.isFinite(point))
30
+ return false;
31
+ }
32
+ return true;
33
+ }
34
+ function isEasingList(value) {
35
+ return Array.isArray(value) && typeof value[0] !== 'number';
36
+ }
37
+ function keyframeTimingRefusal(componentId, key, value, transition) {
38
+ if (transition === undefined)
39
+ return null;
40
+ const hasTimes = transition.times !== undefined;
41
+ // Family-2 / T20: easings and easing are exclusive aliases of ease (validated upstream).
42
+ const easeField = transition.ease !== undefined
43
+ ? transition.ease
44
+ : transition.easings !== undefined
45
+ ? transition.easings
46
+ : transition.easing;
47
+ const easeKey = transition.ease !== undefined ? 'ease' : transition.easings !== undefined ? 'easings' : 'easing';
48
+ const easeIsList = easeField !== undefined && isEasingList(easeField);
49
+ if (!hasTimes && !easeIsList)
50
+ return null;
51
+ if (hasTimes) {
52
+ const times = transition.times;
53
+ if (!Array.isArray(times)) {
54
+ return invalidTiming(componentId, 'times', times, 'times must be an array of finite offsets in [0, 1] (REQ-API-033 law d)');
55
+ }
56
+ for (let index = 0; index < times.length; index++) {
57
+ if (!isPresentArrayIndex(times, index)) {
58
+ return invalidTiming(componentId, 'times', times, `times is missing index ${index}; sparse timing arrays are invalid (REQ-API-033 law d)`);
59
+ }
60
+ const offset = times[index];
61
+ if (typeof offset !== 'number' || !Number.isFinite(offset) || offset < 0 || offset > 1) {
62
+ return invalidTiming(componentId, 'times', times, 'times must contain only finite offsets in [0, 1] (REQ-API-033 law d)');
63
+ }
64
+ if (index > 0 && offset < times[index - 1]) {
65
+ return invalidTiming(componentId, 'times', times, 'times must be nondecreasing so every keyframe segment has a non-negative duration (REQ-API-033 law d)');
66
+ }
67
+ }
68
+ }
69
+ if (easeIsList) {
70
+ const easeList = easeField;
71
+ for (let index = 0; index < easeList.length; index++) {
72
+ if (!isPresentArrayIndex(easeList, index)) {
73
+ return invalidTiming(componentId, easeKey, easeList, `${easeKey} is missing index ${index}; sparse timing arrays are invalid (REQ-API-033 law d)`);
74
+ }
75
+ if (!isValidSingleEasing(easeList[index])) {
76
+ return invalidTiming(componentId, easeKey, easeList, `each per-segment ${easeKey} entry must be a named easing or one 4-point cubic-bezier (REQ-API-033 law d)`);
77
+ }
78
+ }
79
+ }
80
+ if (!Array.isArray(value)) {
81
+ return invalidTiming(componentId, hasTimes ? 'times' : easeKey, hasTimes ? transition.times : easeField, `${hasTimes ? 'times' : 'a per-segment ease list'} applies only to a KEYFRAME-ARRAY target — ` +
82
+ `'${key}' is a single value, not a keyframe array (REQ-API-033 law d)`);
83
+ }
84
+ if (transition.type === 'spring') {
85
+ return invalidTiming(componentId, hasTimes ? 'times' : easeKey, hasTimes ? transition.times : easeField, `an explicit spring ignores ${hasTimes ? 'times' : 'a per-segment ease list'} for '${key}'; ` +
86
+ 'keyframe timing is not executable on an explicit spring (REQ-API-033 law d)');
87
+ }
88
+ return null;
89
+ }
90
+ /** The complete target/effective-transition keyframe law shared by every supplier. */
91
+ function keyframeTransitionRefusal(componentId, key, value, transition) {
92
+ if (Array.isArray(value) && transition?.type === 'spring') {
93
+ const springRefusal = (0, transition_1.springKeyframeCountRefusal)(value.length);
94
+ if (springRefusal !== null)
95
+ return springRefusal;
96
+ }
97
+ return keyframeTimingRefusal(componentId, key, value, transition);
98
+ }
99
+ /** `initial` is a synchronous scalar first-paint supplier, never a keyframe command. */
100
+ function initialTargetRefusal(componentId, key, value) {
101
+ if (!Array.isArray(value))
102
+ return null;
103
+ return new validate_1.InvalidTargetError(componentId, key, value, `initial.${key} must be a scalar first-paint value; keyframe arrays belong to animation-time suppliers (REQ-API-033)`);
104
+ }
105
+ // `keyframesGenerator` is exported and can therefore be reached without a component/driver supplier.
106
+ // Keep its raw timing configuration on the SAME schema/cross-field authority as those suppliers: a
107
+ // malformed direct call must fail typed before it builds segments. Shape-valid count mismatches are
108
+ // deliberate pin behavior: offsets fall back to even and missing list easings become linear.
109
+ function keyframeConfigRefusal(keyframes, config) {
110
+ const timing = keyframeTimingRefusal(undefined, 'keyframes', keyframes, config);
111
+ if (timing !== null)
112
+ return timing;
113
+ if (config?.ease === undefined)
114
+ return null;
115
+ const easeSegments = isEasingList(config.ease) ? config.ease : [config.ease];
116
+ for (const segment of easeSegments) {
117
+ if (!isValidSingleEasing(segment)) {
118
+ return invalidTiming(undefined, 'ease', config.ease, 'ease must be a named easing or one 4-point cubic-bezier (REQ-API-033 law d)');
119
+ }
120
+ // Derived executability (i73cik): finite cubic control points can still sample NaN.
121
+ const easer = (0, timing_1.resolveEasing)(segment);
122
+ for (const progress of [0, 0.25, 0.5, 0.75, 1]) {
123
+ if (!Number.isFinite(easer(progress))) {
124
+ return invalidTiming(undefined, 'ease', config.ease, `the derived segment ease emits a non-finite sample at progress ${progress} — this ` +
125
+ 'config is not executable (G-INV-8)');
126
+ }
127
+ }
128
+ }
129
+ return null;
130
+ }
@@ -0,0 +1,18 @@
1
+ import { InvalidTargetError, InvalidTransitionError } from "../component/validate.cjs";
2
+ type TimingTransition = Readonly<{
3
+ type?: unknown;
4
+ times?: unknown;
5
+ ease?: unknown;
6
+ easings?: unknown;
7
+ easing?: unknown;
8
+ }>;
9
+ export declare function keyframeTimingRefusal(componentId: string | undefined, key: string, value: unknown, transition: TimingTransition | undefined): InvalidTransitionError | null;
10
+ /** The complete target/effective-transition keyframe law shared by every supplier. */
11
+ export declare function keyframeTransitionRefusal(componentId: string | undefined, key: string, value: unknown, transition: TimingTransition | undefined): Error | null;
12
+ /** `initial` is a synchronous scalar first-paint supplier, never a keyframe command. */
13
+ export declare function initialTargetRefusal(componentId: string | undefined, key: string, value: unknown): InvalidTargetError | null;
14
+ export declare function keyframeConfigRefusal(keyframes: readonly unknown[], config: Readonly<{
15
+ times?: unknown;
16
+ ease?: unknown;
17
+ }> | undefined): InvalidTransitionError | null;
18
+ export {};
@@ -0,0 +1,18 @@
1
+ import { InvalidTargetError, InvalidTransitionError } from "../component/validate.js";
2
+ type TimingTransition = Readonly<{
3
+ type?: unknown;
4
+ times?: unknown;
5
+ ease?: unknown;
6
+ easings?: unknown;
7
+ easing?: unknown;
8
+ }>;
9
+ export declare function keyframeTimingRefusal(componentId: string | undefined, key: string, value: unknown, transition: TimingTransition | undefined): InvalidTransitionError | null;
10
+ /** The complete target/effective-transition keyframe law shared by every supplier. */
11
+ export declare function keyframeTransitionRefusal(componentId: string | undefined, key: string, value: unknown, transition: TimingTransition | undefined): Error | null;
12
+ /** `initial` is a synchronous scalar first-paint supplier, never a keyframe command. */
13
+ export declare function initialTargetRefusal(componentId: string | undefined, key: string, value: unknown): InvalidTargetError | null;
14
+ export declare function keyframeConfigRefusal(keyframes: readonly unknown[], config: Readonly<{
15
+ times?: unknown;
16
+ ease?: unknown;
17
+ }> | undefined): InvalidTransitionError | null;
18
+ export {};