@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,657 @@
1
+ "use strict";
2
+ // SPEC-PRESENCE §2/§3 — the presence controller: mount authority, exit-target resolution, retention, settle
3
+ // integration, cancellation continuity, and mode ordering. Diffs the child set each commit; retains a
4
+ // dropped child with an exit until its exit resolves; makes every in-flight exit visible to the graph's
5
+ // settle ledger; cancels an exit on key re-entry with value/velocity continuity; and orders enters vs exits
6
+ // per `mode`. The exit is driven off the injected MotionGraph (`graph.animate` over the child's transition)
7
+ // — the controller forks no animation math and imports no driver/React (SPEC-PRESENCE §1). Host-agnostic
8
+ // (REQ-CORE-003): relative imports only.
9
+ //
10
+ // Modeling decision (deterministic interior): the controller owns ONE scalar EXIT-PROGRESS value per exiting
11
+ // child (0 = fully present, 1 = fully exited), driven by the graph. This is a well-defined completion +
12
+ // continuity signal for retention + settle; the per-property exit VALUES a child renders from the resolved
13
+ // exit target are the child/component's concern (SPEC-PRESENCE §1 non-scope).
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.UnimplementedPresenceModeError = void 0;
16
+ exports.createPresenceController = createPresenceController;
17
+ const component_1 = require("../component/index.cjs");
18
+ const transition_1 = require("../component/transition.cjs");
19
+ const timing_1 = require("../timing.cjs");
20
+ const transition_2 = require("../transition.cjs");
21
+ const subset_1 = require("../subset/index.cjs");
22
+ const color_1 = require("../value-types/color.cjs");
23
+ const machine_1 = require("./machine.cjs");
24
+ // Default exit transition when a child declares none: a deterministic 300ms tween. Config, not a magic
25
+ // literal in-line (Motion's own default is transition-dependent; the first spike pins this — refinable).
26
+ // The ease is SPELLED rather than left to injection. Its value is the pin's NON-TRANSFORM per-key
27
+ // default ease `[0.25, 0.1, 0.35, 1]` (motion-dom@12.42.2 utils/default-transitions.ts:35-48) —
28
+ // NOT the `animateMotionValue` easeOut base, which the pin applies only to an AUTHORED transition.
29
+ // (This constant is a flat tween for EVERY key, so it is only an approximation of the pin's
30
+ // genuinely per-key default, which is a 500/25 spring for transform keys — see the
31
+ // transition-dependent caveat above. The ease spelled here is correct for the non-transform case
32
+ // this retention clock actually serves.) Leaving it unspelled made the curve depend on which
33
+ // config builder happened to read it, and routing the exit lane through `toTimingLaneConfig`
34
+ // (transition-default-selection F1) duly injected easeOut into the fallback.
35
+ const DEFAULT_EXIT_TRANSITION = {
36
+ type: 'tween',
37
+ duration: 0.3,
38
+ ease: [0.25, 0.1, 0.35, 1],
39
+ };
40
+ // A transition with at least one DEFINED option (review major 62): an EMPTY `{}` (and one whose every
41
+ // value is explicitly `undefined`) is NOT a transition — the property driver treats it as absent and
42
+ // rides the count-aware default, so presence must too, else a `transition:{}` exit runs a default spring
43
+ // while the property runs the pinned 800ms keyframes default (retention outlives the actual exit).
44
+ function hasDefinedExitTransition(transition) {
45
+ if (transition === undefined)
46
+ return false;
47
+ return Reflect.ownKeys(transition).some((key) => typeof key === 'string' && transition[key] !== undefined);
48
+ }
49
+ function exitTransitionForProperty(transition, key) {
50
+ return transition === undefined ? undefined : (0, transition_1.resolveTransitionForKey)(transition, key);
51
+ }
52
+ // The keyframes generator's default duration when the transition carries none (`keyframes({duration=300})`
53
+ // in pinned Motion). A tween's own default is the same; the presence progress is a 2-keyframe scalar.
54
+ const DEFAULT_KEYFRAMES_MS = 300;
55
+ // A COLOR keyframe array drives a [0,100] numeric PROGRESS spline (REQ-SPRING-013 / REQ-API-033): the RGBA
56
+ // sequence is projected THROUGH that progress, so a color exit settles over the progress range, never the
57
+ // color strings. Single-sourced with the driver's progress convention.
58
+ const COLOR_PROGRESS_END = 100;
59
+ // The exit-progress GeneratorFactory (review majors 57/62/65/4f7ac2b/fc0a71): the scalar 0→1 progress
60
+ // completes exactly when the child's LONGEST exit property truly settles — driven off the property driver's
61
+ // OWN generators, never a bounded settle probe (a pathological/underdamped spring runs past any frame cap,
62
+ // and a probe that returns its cap as "settled" unmounts the child early — fails OPEN). A single property
63
+ // rides its own 0→1 factory; multiple compose into a generator that is `done` only when EVERY sub-generator
64
+ // is, tracking the least-advanced value so re-entry continuity still reads a real position.
65
+ function exitProgressFactory(exit, childTransition, liveValues, resolvedExits) {
66
+ const subFactories = Object.entries(exit ?? {})
67
+ // transitionEnd is a settle-JUMP sub-target, not an animated property (T23 B3): it
68
+ // contributes no exit duration and never enters the progress composition.
69
+ .filter(([key]) => key !== 'transitionEnd')
70
+ .map(([key, value]) => propertyProgressFactory(key, value, childTransition, liveValues, resolvedExits));
71
+ if (subFactories.length === 0) {
72
+ // An empty/absent exit selects the transition's default/root consumer before falling back to
73
+ // the base default. A map must never cross into progressFactory unresolved.
74
+ const selectedTransition = childTransition === undefined ? undefined : (0, transition_1.resolveTransitionForKey)(childTransition, '');
75
+ return progressFactory(hasDefinedExitTransition(selectedTransition) ? selectedTransition : DEFAULT_EXIT_TRANSITION, 1);
76
+ }
77
+ if (subFactories.length === 1)
78
+ return subFactories[0];
79
+ return (seed) => {
80
+ const generators = subFactories.map((factory) => factory(seed));
81
+ const sample = { value: 0, velocity: 0, done: false }; // owned + reused (REQ-DRIVER-015)
82
+ return {
83
+ sample(elapsedMs) {
84
+ let minValue = Number.POSITIVE_INFINITY;
85
+ let velocityAtMin = 0;
86
+ let allDone = true;
87
+ for (const generator of generators) {
88
+ const next = generator.sample(elapsedMs);
89
+ if (!next.done)
90
+ allDone = false;
91
+ if (next.value < minValue) {
92
+ minValue = next.value;
93
+ velocityAtMin = next.velocity;
94
+ }
95
+ }
96
+ sample.value = minValue;
97
+ sample.velocity = velocityAtMin;
98
+ sample.done = allDone;
99
+ return sample;
100
+ },
101
+ };
102
+ };
103
+ }
104
+ // One exit property's 0→1 progress GeneratorFactory. An ARRAY routes through the property driver's OWN
105
+ // generator selection: an explicit / 2-keyframe-transform-default SPRING runs keyframes[0]→keyframes[last]
106
+ // with the ABSOLUTE `restSpeed`, NORMALIZED to 0→1 so the progress is `done` WHEN the property is (over the
107
+ // REAL distance, past any frame cap); everything else is the keyframes generator's config duration as a
108
+ // bounded 0→1 tween. A null-first array receives its actual drop-time committed origin through the
109
+ // host-supplied snapshot (REQ-PRESENCE-020); presence never reads a driver or substitutes a declaration.
110
+ function propertyProgressFactory(key, value, childTransition, liveValues, resolvedExits) {
111
+ const propertyTransition = exitTransitionForProperty(childTransition, key);
112
+ const defined = hasDefinedExitTransition(propertyTransition);
113
+ if (Array.isArray(value)) {
114
+ const t = defined
115
+ ? propertyTransition
116
+ : (0, transition_2.getDefaultTransition)(key, value);
117
+ if (t.type === 'spring') {
118
+ const [from, to] = arraySpringEndpoints(key, value, liveValues?.[key], resolvedExits?.[key]);
119
+ const config = (0, component_1.toSpringConfig)(t);
120
+ const restSpeed = 'restSpeed' in t && t.restSpeed !== undefined ? t.restSpeed : undefined;
121
+ return normalizedFactory((0, transition_2.resolveSpringGenerator)(to, restSpeed !== undefined ? { ...config, restSpeed } : config), from, to);
122
+ }
123
+ const ms = (0, component_1.toKeyframesConfig)(t).duration ?? DEFAULT_KEYFRAMES_MS;
124
+ return (0, timing_1.timingGenerator)(1, { duration: ms });
125
+ }
126
+ // A SCALAR measure-length exit under an explicit spring (review 7ae34b29f081): a 0→1 unit
127
+ // progress spring is terminal-frame divergent from the property lane's measured px flight —
128
+ // measure the SAME host-resolved px endpoints (drop-time committed from, resolved to). An
129
+ // authored restDelta/restSpeed never reaches here — the exit lane refuses them loud (U7c /
130
+ // REQ-SPRING-014, the velocity precedent: value-units thresholds have no meaning on a
131
+ // normalized progress factory). A scalar ANGLE
132
+ // exit (review 4f95f4f8076a) resolves in degrees — from the committed snapshot to the parsed
133
+ // target, exact without a host.
134
+ if (typeof value === 'string' && !(0, color_1.isColor)(value) && defined) {
135
+ const t = propertyTransition;
136
+ if (t.type === 'spring') {
137
+ if (subset_1.UNIVERSAL_SUBSET.get(key)?.valueType === 'angle') {
138
+ const committedAngle = liveValues?.[key];
139
+ if (typeof committedAngle !== 'number') {
140
+ throw new Error(`presence scalar angle exit '${key}' requires a numeric drop-time live committed value ` +
141
+ `(received ${typeof committedAngle}; REQ-PRESENCE-020).`);
142
+ }
143
+ const to = angleExitEndpoint(key, value, undefined);
144
+ return normalizedFactory((0, transition_2.resolveSpringGenerator)(to, (0, component_1.toSpringConfig)(t)), committedAngle, to);
145
+ }
146
+ const endpoints = resolvedExits?.[key];
147
+ if (endpoints === undefined ||
148
+ endpoints.length !== 2 ||
149
+ !Number.isFinite(endpoints[0]) ||
150
+ !Number.isFinite(endpoints[1])) {
151
+ throw resolvedExitsRefusal(key);
152
+ }
153
+ return normalizedFactory((0, transition_2.resolveSpringGenerator)(endpoints[1], (0, component_1.toSpringConfig)(t)), endpoints[0], endpoints[1]);
154
+ }
155
+ }
156
+ // Scalar targets use the same count-aware per-property fallback as a two-keyframe property
157
+ // command. An empty selected bag is still a selected consumer, but it carries no executable
158
+ // option, so the property lane's default owns the retention clock (review fj96pq major 1).
159
+ const scalarTransition = defined
160
+ ? propertyTransition
161
+ : typeof value === 'number'
162
+ ? (0, transition_2.getDefaultTransition)(key, [0, value])
163
+ : DEFAULT_EXIT_TRANSITION;
164
+ if (typeof value === 'number' && scalarTransition.type === 'spring') {
165
+ const committed = liveValues?.[key];
166
+ if (typeof committed === 'number') {
167
+ return normalizedFactory((0, transition_2.resolveSpringGenerator)(value, (0, component_1.toSpringConfig)(scalarTransition)), committed, value);
168
+ }
169
+ }
170
+ return progressFactory(scalarTransition, 1);
171
+ }
172
+ // The (from,to) distance a 2-keyframe array spring covers, so the normalized progress settles WHEN the
173
+ // property does (review major 7c54a3b). A COLOR array (its string elements parse as colors) drives a
174
+ // [0,100] progress spline — measured over the progress range, never the RGBA strings (which have no
175
+ // numeric distance). An ANGLE array (review 4f95f4f8076a, classified by the registry value type) is
176
+ // NOT a length: angles resolve in their own unit, so same-unit DEGREE magnitudes are exact without a
177
+ // host layout context (the subset normalizer converts rad → degrees). A measure-resolved LENGTH array
178
+ // (reviews 02e2f855bfd5 + 2f3a8e6c1d90) is NOT a color array, and presence is context-free
179
+ // (SPEC-PRESENCE §1) — so it measures the SAME resolved px endpoints the property lane flies, supplied
180
+ // by the host at the removal boundary (the absolute restSpeed/restDelta thresholds make unit-magnitude
181
+ // estimates terminal-frame-divergent). A NUMERIC array runs keyframes[0]→keyframes[last]; every
182
+ // null-FIRST element (from-current, R8-F2) anchors at the HOST'S immutable removal-boundary snapshot
183
+ // (REQ-PRESENCE-020), never a declared endpoint.
184
+ function arraySpringEndpoints(key, value, committed, resolved) {
185
+ assertNullFirstLiveOriginKind(key, value, committed);
186
+ if (value.some((element) => typeof element === 'string' && (0, color_1.isColor)(element))) {
187
+ return [0, COLOR_PROGRESS_END];
188
+ }
189
+ if (subset_1.UNIVERSAL_SUBSET.get(key)?.valueType === 'angle') {
190
+ return [
191
+ angleExitEndpoint(key, value[0], committed),
192
+ angleExitEndpoint(key, value[value.length - 1], committed),
193
+ ];
194
+ }
195
+ if (value.some((element) => typeof element === 'string')) {
196
+ // The sync preflight proves the host's resolved endpoints before any mutation; this backstop
197
+ // keeps the same law on the factory path.
198
+ if (resolved === undefined)
199
+ throw resolvedExitsRefusal(key);
200
+ // Bounded ≥2 elements with null only at index 0 are proven by the preflight shape check.
201
+ const from = resolved[0] === null ? committed : resolved[0];
202
+ const to = resolved[resolved.length - 1];
203
+ if (typeof from !== 'number' || typeof to !== 'number')
204
+ throw resolvedExitsRefusal(key);
205
+ return [from, to];
206
+ }
207
+ const first = value[0];
208
+ const last = value[value.length - 1];
209
+ let from;
210
+ if (typeof first === 'number') {
211
+ from = first;
212
+ }
213
+ else {
214
+ if (typeof committed !== 'number') {
215
+ throw new Error(`presence null-first numeric exit '${key}' requires a numeric drop-time live committed value ` +
216
+ `(received ${typeof committed}; REQ-PRESENCE-020).`);
217
+ }
218
+ from = committed;
219
+ }
220
+ const to = typeof last === 'number' ? last : from;
221
+ return [from, to];
222
+ }
223
+ // One ANGLE exit endpoint in degrees (review 4f95f4f8076a): a number is itself (already degrees —
224
+ // the engine-native angle encoding); a null from-current reads the committed degrees snapshot; a
225
+ // deg/rad string normalizes through the ONE subset normalizer (rad → degrees). Angles need no host
226
+ // layout context — same-unit degree magnitudes are exact, unlike measure-resolved lengths.
227
+ function angleExitEndpoint(key, element, committed) {
228
+ if (typeof element === 'number')
229
+ return element;
230
+ if (element === null) {
231
+ if (typeof committed !== 'number') {
232
+ throw new Error(`presence null-first angle exit '${key}' requires a numeric drop-time live committed value ` +
233
+ `(received ${typeof committed}; REQ-PRESENCE-020).`);
234
+ }
235
+ return committed;
236
+ }
237
+ const entry = subset_1.UNIVERSAL_SUBSET.get(key);
238
+ if (entry === undefined) {
239
+ throw new Error(`presence angle exit '${key}' is not a universal-subset property (the boundary validation ` +
240
+ 'should have refused it first).');
241
+ }
242
+ const normalized = (0, subset_1.normalizeCapability)(entry, element);
243
+ if (typeof normalized !== 'number') {
244
+ throw new Error(`presence angle exit '${key}' normalized to a non-numeric value (the registry classifies ` +
245
+ 'it as angle; the boundary validation should have refused a non-angle string first).');
246
+ }
247
+ return normalized;
248
+ }
249
+ // The host-contract refusal for a measure-length exit missing its resolved px endpoints (review
250
+ // 2f3a8e6c1d90) — one message shared by the sync preflight and the factory backstop.
251
+ function resolvedExitsRefusal(property, childKey) {
252
+ const location = childKey === undefined ? '' : ` on '${childKey}'`;
253
+ return new Error(`presence length exit '${property}'${location} requires host-resolved px endpoints ` +
254
+ '(REQ-PRESENCE-020); the host must resolve measure-length exit arrays against the live ' +
255
+ 'layout context at the removal boundary.');
256
+ }
257
+ // Validate one host-resolved endpoint array against its authored exit array (review 2f3a8e6c1d90):
258
+ // same keyframe count, `null` only at index 0 (the from-current marker presence anchors at the
259
+ // committed snapshot), finite numbers elsewhere.
260
+ function assertResolvedExitEndpoints(property, authored, resolved, childKey) {
261
+ if (resolved === undefined ||
262
+ resolved.length !== authored.length ||
263
+ resolved.length < 2 ||
264
+ resolved.some((element, index) => (element === null && index !== 0) || (element !== null && !Number.isFinite(element)))) {
265
+ throw resolvedExitsRefusal(property, childKey);
266
+ }
267
+ }
268
+ // Validate the host snapshot as one atomic removal precondition. PresenceLiveValues is structurally
269
+ // scalar (number|string), so existence alone cannot prove that the host value can seed this sequence.
270
+ // The authored keyframes are already homogeneously validated at the supplying boundary; their scalar
271
+ // member therefore defines the one legal live-origin kind.
272
+ function assertNullFirstLiveOriginKind(property, value, committed, childKey) {
273
+ if (value[0] !== null)
274
+ return;
275
+ const location = childKey === undefined ? '' : ` on '${childKey}'`;
276
+ if (committed === undefined) {
277
+ throw new Error(`presence null-first exit '${property}'${location} has no drop-time live committed value ` +
278
+ '(REQ-PRESENCE-020); refusing to substitute a declared endpoint or zero.');
279
+ }
280
+ const expectedKind = value.some((element) => typeof element === 'string' && (0, color_1.isColor)(element))
281
+ ? 'string'
282
+ : 'number';
283
+ if (typeof committed !== expectedKind) {
284
+ const expected = expectedKind === 'number' ? 'numeric' : 'string';
285
+ throw new Error(`presence null-first ${expected} exit '${property}'${location} requires a ${expected} ` +
286
+ `drop-time live committed value (received ${typeof committed}; REQ-PRESENCE-020).`);
287
+ }
288
+ }
289
+ // Wrap a property generator (running `propFrom`→`propTo`) so its value/velocity read as a 0→1 PROGRESS, and
290
+ // its `done` — the property's real settle — becomes the progress's. The seed carries the progress position
291
+ // (0 fresh exit; the live value+velocity on cancel), mapped back into property space for continuity. A
292
+ // zero-distance array is already at target — a 0ms tween settles it immediately.
293
+ function normalizedFactory(propertyFactory, propFrom, propTo) {
294
+ const distance = propTo - propFrom;
295
+ if (distance === 0)
296
+ return (0, timing_1.timingGenerator)(1, { duration: 0 });
297
+ return (seed) => {
298
+ const generator = propertyFactory({
299
+ from: propFrom + seed.from * distance,
300
+ velocity: seed.velocity * distance,
301
+ });
302
+ const sample = { value: 0, velocity: 0, done: false }; // owned + reused
303
+ return {
304
+ sample(elapsedMs) {
305
+ const next = generator.sample(elapsedMs);
306
+ sample.value = (next.value - propFrom) / distance;
307
+ sample.velocity = next.velocity / distance;
308
+ sample.done = next.done;
309
+ return sample;
310
+ },
311
+ };
312
+ };
313
+ }
314
+ // Model a Target as a plain resolved-value record for the pure resolve/merge functions (mirrors COMPONENT).
315
+ // Spreading `undefined` in an object literal is a safe no-op, so no `?? {}` fallback is needed.
316
+ // transitionEnd is a settle-JUMP sub-target, never an animated value (T23 B3) — it must not enter
317
+ // the presence value model.
318
+ function asValues(target) {
319
+ const { transitionEnd: _transitionEnd, ...values } = { ...target };
320
+ return values;
321
+ }
322
+ // A progress factory driving the scalar exit-progress toward `target` (1 = exit, 0 = re-enter/cancel), over
323
+ // the child's transition. Reuses the built SPRING/TIMING generators via the COMPONENT converters — no fork.
324
+ // The graph seeds `from`/`velocity` from the live value on (re)animate, so a cancel is continuous.
325
+ function progressFactory(transition, target) {
326
+ const t = transition ?? DEFAULT_EXIT_TRANSITION;
327
+ // transition-default-selection F1: the exit tween reads the ONE timing-lane config — the pin's
328
+ // `animateMotionValue` `ease: "easeOut"` base applies to presence exits too
329
+ // (motion-dom@12.42.2 motion-value.ts:45-49), so an exit tween with no authored ease runs
330
+ // [0, 0, 0.58, 1], never the generator default.
331
+ if (t.type === 'tween')
332
+ return (0, timing_1.timingGenerator)(target, (0, transition_1.toTimingLaneConfig)(t));
333
+ if (t.type === 'keyframes') {
334
+ // A count-aware keyframes default normalizes to a tween (a 2-keyframe scalar progress has one segment).
335
+ return (0, timing_1.timingGenerator)(target, {
336
+ duration: t.duration * 1000,
337
+ ...(t.ease !== undefined ? { ease: t.ease } : {}),
338
+ });
339
+ }
340
+ // A spring DefaultTransition carries `restSpeed` the public Transition cannot — thread it into the
341
+ // settle threshold (review major 65) so the progress quiesces WHEN the property spring does.
342
+ const config = (0, component_1.toSpringConfig)(t);
343
+ const restSpeed = 'restSpeed' in t && t.restSpeed !== undefined ? t.restSpeed : undefined;
344
+ return (0, transition_2.resolveSpringGenerator)(target, restSpeed !== undefined ? { ...config, restSpeed } : config);
345
+ }
346
+ // Re-entry is a scalar graph animation, but its transition is still authored per property. Resolve each
347
+ // previously exiting property before constructing a generator; a map cannot cross this flat consumer. When
348
+ // several properties are exiting, the progress remains until every reverse generator settles, mirroring the
349
+ // exit-side composition above (the greatest remaining progress is authoritative on the way to 0).
350
+ function reentryProgressFactory(childTransition, propertyKeys) {
351
+ if (propertyKeys.length === 0) {
352
+ const selected = childTransition === undefined ? undefined : (0, transition_1.resolveTransitionForKey)(childTransition, '');
353
+ return progressFactory(hasDefinedExitTransition(selected) ? selected : undefined, 0);
354
+ }
355
+ const factories = propertyKeys.map((key) => {
356
+ const selected = exitTransitionForProperty(childTransition, key);
357
+ return progressFactory(hasDefinedExitTransition(selected) ? selected : undefined, 0);
358
+ });
359
+ if (factories.length === 1)
360
+ return factories[0];
361
+ return (seed) => {
362
+ const generators = factories.map((factory) => factory(seed));
363
+ const sample = { value: seed.from, velocity: seed.velocity, done: false };
364
+ return {
365
+ sample(elapsedMs) {
366
+ let maxValue = Number.NEGATIVE_INFINITY;
367
+ let velocityAtMax = 0;
368
+ let allDone = true;
369
+ for (const generator of generators) {
370
+ const next = generator.sample(elapsedMs);
371
+ if (!next.done)
372
+ allDone = false;
373
+ if (next.value > maxValue) {
374
+ maxValue = next.value;
375
+ velocityAtMax = next.velocity;
376
+ }
377
+ }
378
+ sample.value = maxValue;
379
+ sample.velocity = velocityAtMax;
380
+ sample.done = allDone;
381
+ return sample;
382
+ },
383
+ };
384
+ };
385
+ }
386
+ // Legacy typed error retained for public-surface compatibility. The former popLayout construction
387
+ // refusal was removed in T24 P; the native presence layer now owns its layout composition.
388
+ class UnimplementedPresenceModeError extends Error {
389
+ mode;
390
+ constructor(mode) {
391
+ super(`presence mode '${mode}' is not yet implemented (it depends on the layout subsystem); it fails loud rather than degrading.`);
392
+ this.name = 'UnimplementedPresenceModeError';
393
+ this.mode = mode;
394
+ }
395
+ }
396
+ exports.UnimplementedPresenceModeError = UnimplementedPresenceModeError;
397
+ function createPresenceController(options) {
398
+ const { graph } = options;
399
+ const mode = options.mode ?? 'sync';
400
+ const initial = options.initial ?? true;
401
+ const onExitComplete = options.onExitComplete;
402
+ const ledger = (0, machine_1.createPresenceLedger)();
403
+ const tracked = new Map();
404
+ // 'wait' mode: entering children buffered until every exit resolves (REQ-PRESENCE-015).
405
+ const pendingEnters = new Map();
406
+ let firstCommitDone = false;
407
+ function exitingKeys() {
408
+ return ledger.trackedKeys().filter((k) => ledger.stateOf(k) === 'exiting');
409
+ }
410
+ // Commit the deferred ('wait' mode) entering children — called AFTER onExitComplete so enters land strictly
411
+ // after the exit callback (§3 Callback exactness / REQ-PRESENCE-015).
412
+ function flushPendingEnters() {
413
+ for (const [key, child] of pendingEnters) {
414
+ ledger.track(key);
415
+ tracked.set(key, { child });
416
+ }
417
+ pendingEnters.clear();
418
+ }
419
+ // The exiting set just emptied: fire onExitComplete exactly once, then commit any deferred enters.
420
+ function onExitingEmptied() {
421
+ onExitComplete?.();
422
+ flushPendingEnters();
423
+ }
424
+ // Resolve any exiting key whose exit animation has completed: exiting → removed, drop its record. Pull
425
+ // model — every query reconciles against the graph's advanced state. An OPEN manual authority (drop-time
426
+ // `deferred` or a mid-exit adoption) blocks resolution outright: a deferred exit has no animation and an
427
+ // adopted one must not resolve on animation completion alone — safeToRemove owns that leg
428
+ // (REQ-PRESENCE-001/014).
429
+ function reconcile() {
430
+ const before = exitingKeys();
431
+ if (before.length === 0)
432
+ return;
433
+ let anyResolved = false;
434
+ for (const key of before) {
435
+ const rec = tracked.get(key);
436
+ if (rec?.activity !== undefined)
437
+ continue;
438
+ if (rec?.animation?.done === true) {
439
+ ledger.send(key, 'resolve');
440
+ tracked.delete(key);
441
+ anyResolved = true;
442
+ }
443
+ }
444
+ if (anyResolved && exitingKeys().length === 0)
445
+ onExitingEmptied();
446
+ }
447
+ function beginExit(key, child, liveValues, resolvedExits) {
448
+ // Resolve the authored exit target ONCE at drop, held immutable for the exit's duration (§3).
449
+ // Live values seed only from-current origins/retention; they do not manufacture authored hold keys.
450
+ const resolvedExit = (0, component_1.resolveTarget)(asValues(child.animate), asValues(child.exit));
451
+ const progress = graph.motionValue(0);
452
+ // The progress completes when the child's LONGEST exit property truly settles — driven off the property
453
+ // driver's own generators (major 57: an omitted >2-keyframe exit rides its 800ms keyframes default, not
454
+ // the 300ms scalar default; fc0a71: a pathological spring runs to its real settle, never a probe cap;
455
+ // 7c54a3b: a null-FIRST numeric exit seeds its from-current distance from the committed `base` value;
456
+ // 2f3a8e6c1d90: a measure-length exit measures the host's RESOLVED px endpoints).
457
+ const animation = graph.animate(progress, exitProgressFactory(child.exit, child.transition, liveValues, resolvedExits));
458
+ tracked.set(key, { child, progress, animation, resolvedExit });
459
+ ledger.send(key, 'drop-with-exit');
460
+ }
461
+ function enterPresent(child) {
462
+ // First-commit enter suppression under initial={false} (REQ-PRESENCE-016). Presence owns the suppression
463
+ // DECISION; the enter transition itself is the component's concern (§1 non-scope), so no animation here.
464
+ const enterSuppressed = !firstCommitDone && initial === false;
465
+ ledger.track(child.key);
466
+ tracked.set(child.key, { child, enterSuppressed });
467
+ }
468
+ return {
469
+ syncChildren(children, liveValuesByKey, resolvedExitsByKey) {
470
+ const incoming = new Set(children.map((c) => c.key));
471
+ // Validate every outgoing null-first origin and length-exit endpoint supply before mutating
472
+ // any lifecycle record. A missing host snapshot/endpoints refuses the whole commit rather
473
+ // than partially dropping siblings (REQ-PRESENCE-020, review 2f3a8e6c1d90).
474
+ for (const key of ledger.trackedKeys()) {
475
+ if (incoming.has(key) || ledger.stateOf(key) !== 'present')
476
+ continue;
477
+ const child = tracked.get(key)?.child;
478
+ // Family-6 major 370652a15b84: deferred + authored exit still begins the exit animation
479
+ // (consumer ∧ animation conjunction) — validate endpoints the same as the non-deferred path.
480
+ // Deferred without an exit target stays no-animation and needs no endpoint supply.
481
+ if (child?.exit === undefined)
482
+ continue;
483
+ for (const [property, value] of Object.entries(child.exit)) {
484
+ if (!Array.isArray(value)) {
485
+ // A SCALAR measure-length exit under an explicit spring measures the host's resolved
486
+ // px endpoints (review 7ae34b29f081); a scalar ANGLE exit measures its committed
487
+ // degrees origin + parsed target instead (review 4f95f4f8076a). Bounded-duration
488
+ // lanes need neither.
489
+ if (typeof value === 'string' &&
490
+ !(0, color_1.isColor)(value) &&
491
+ exitTransitionForProperty(child.transition, property)?.type === 'spring') {
492
+ if (subset_1.UNIVERSAL_SUBSET.get(property)?.valueType === 'angle') {
493
+ const committedAngle = liveValuesByKey?.get(key)?.[property];
494
+ if (typeof committedAngle !== 'number') {
495
+ throw new Error(`presence scalar angle exit '${property}' on '${key}' requires a numeric ` +
496
+ `drop-time live committed value (received ${typeof committedAngle}; ` +
497
+ 'REQ-PRESENCE-020).');
498
+ }
499
+ continue;
500
+ }
501
+ const resolved = resolvedExitsByKey?.get(key)?.[property];
502
+ if (resolved === undefined ||
503
+ resolved.length !== 2 ||
504
+ !Number.isFinite(resolved[0]) ||
505
+ !Number.isFinite(resolved[1])) {
506
+ throw resolvedExitsRefusal(property, key);
507
+ }
508
+ }
509
+ continue;
510
+ }
511
+ if (value[0] === null) {
512
+ assertNullFirstLiveOriginKind(property, value, liveValuesByKey?.get(key)?.[property], key);
513
+ }
514
+ if (value.some((element) => typeof element === 'string' && !(0, color_1.isColor)(element)) &&
515
+ subset_1.UNIVERSAL_SUBSET.get(property)?.valueType !== 'angle') {
516
+ assertResolvedExitEndpoints(property, value, resolvedExitsByKey?.get(key)?.[property], key);
517
+ }
518
+ }
519
+ }
520
+ // 1) Drops first — so `mode:'wait'` enter-buffering below sees the exits started this commit.
521
+ for (const key of ledger.trackedKeys()) {
522
+ if (incoming.has(key))
523
+ continue;
524
+ if (ledger.stateOf(key) !== 'present')
525
+ continue; // already exiting/held → do NOT re-resolve (§3)
526
+ const child = tracked.get(key)?.child;
527
+ if (child?.deferred === true) {
528
+ // Manual deferral (REQ-PRESENCE-014): retained until safeToRemove. Hold an open activity
529
+ // handle so the graph stays unsettled while the manual exit is pending (REQ-PRESENCE-013).
530
+ // Family-6 major 370652a15b84: when an authored exit is also present, start the exit
531
+ // animation too — safeToRemove ∧ animation-complete is the retention conjunction
532
+ // (same as mid-exit adoption). Deferred with no exit stays no-animation.
533
+ if (child.exit !== undefined) {
534
+ beginExit(key, child, liveValuesByKey?.get(key), resolvedExitsByKey?.get(key));
535
+ const rec = tracked.get(key);
536
+ if (rec !== undefined && rec.activity === undefined) {
537
+ rec.activity = graph.registerActivity();
538
+ }
539
+ }
540
+ else {
541
+ tracked.set(key, { child, activity: graph.registerActivity() });
542
+ ledger.send(key, 'drop-with-exit');
543
+ }
544
+ }
545
+ else if (child?.exit !== undefined) {
546
+ beginExit(key, child, liveValuesByKey?.get(key), resolvedExitsByKey?.get(key));
547
+ }
548
+ else {
549
+ ledger.send(key, 'drop-immediate'); // no exit + no deferral → removed this commit (REQ-PRESENCE-011)
550
+ tracked.delete(key);
551
+ }
552
+ }
553
+ // 2) Enters / cancels.
554
+ for (const child of children) {
555
+ const state = ledger.stateOf(child.key);
556
+ if (state === 'exiting') {
557
+ // Re-entry cancels the exit (REQ-PRESENCE-012): retarget the live progress back toward present (0).
558
+ // graph.animate reseeds from the live value+velocity (REQ-CORE-002/REQ-API-003) — continuity, no
559
+ // jump, motion-value identity preserved. A deferred exit has no progress; just release + return.
560
+ const prev = tracked.get(child.key);
561
+ if (prev?.progress !== undefined) {
562
+ // Install the re-entry animation BEFORE releasing an adopted manual hold (r7 finding
563
+ // 4e1c3a8f72bd): when the scalar exit already completed under the hold, releasing
564
+ // first crosses the graph into settled and resolves pending whenSettled() — a false
565
+ // completion edge between the release and the re-animate (REQ-PRESENCE-012/-013).
566
+ const enterAnim = graph.animate(prev.progress, reentryProgressFactory(child.transition, [
567
+ ...new Set([...Object.keys(child.animate ?? {}), ...Object.keys(child.exit ?? {})]),
568
+ ]));
569
+ prev.activity?.release();
570
+ tracked.set(child.key, { child, progress: prev.progress, animation: enterAnim });
571
+ }
572
+ else {
573
+ prev?.activity?.release();
574
+ tracked.set(child.key, { child });
575
+ }
576
+ ledger.send(child.key, 'cancel');
577
+ }
578
+ else if (state === 'present') {
579
+ const rec = tracked.get(child.key);
580
+ if (rec !== undefined)
581
+ rec.child = child; // refresh last-seen props
582
+ }
583
+ else if (pendingEnters.has(child.key)) {
584
+ pendingEnters.set(child.key, child); // already deferred — refresh
585
+ }
586
+ else if (mode === 'wait' && exitingKeys().length > 0) {
587
+ pendingEnters.set(child.key, child); // defer until exits resolve (REQ-PRESENCE-015)
588
+ }
589
+ else {
590
+ enterPresent(child);
591
+ }
592
+ }
593
+ firstCommitDone = true;
594
+ reconcile();
595
+ },
596
+ mountedKeys() {
597
+ reconcile();
598
+ return ledger.trackedKeys(); // ledger holds only present + retained-exiting keys (pending enters excluded)
599
+ },
600
+ stateOf(key) {
601
+ reconcile();
602
+ return ledger.stateOf(key);
603
+ },
604
+ isPresent(key) {
605
+ reconcile();
606
+ return ledger.stateOf(key) === 'present';
607
+ },
608
+ safeToRemove(key) {
609
+ // Resolve a manual (deferred) exit; idempotent — a no-op once the key has resolved or re-entered.
610
+ reconcile();
611
+ if (ledger.stateOf(key) !== 'exiting')
612
+ return;
613
+ const rec = tracked.get(key);
614
+ if (rec !== undefined) {
615
+ const heldAuthority = rec.activity !== undefined;
616
+ rec.activity?.release(); // release the manual authority handle
617
+ delete rec.activity;
618
+ // A mid-exit adoption composes with the exit animation as a CONJUNCTION (REQ-PRESENCE-001):
619
+ // with the manual authority now released, a still-running animation keeps the key retained
620
+ // and reconcile resolves it at completion. Without adopted authority this call stays the
621
+ // external override it has always been (immediate resolve), and a no-animation deferral has
622
+ // nothing else to await.
623
+ if (heldAuthority && rec.animation !== undefined && rec.animation.done !== true)
624
+ return;
625
+ }
626
+ ledger.send(key, 'resolve');
627
+ tracked.delete(key);
628
+ if (exitingKeys().length === 0)
629
+ onExitingEmptied();
630
+ },
631
+ deferExit(key) {
632
+ // Mid-exit manual-authority adoption (see types.ts): the consumer registered only after the
633
+ // drop commit, so the `deferred` flag could not have carried its authority into this record.
634
+ reconcile();
635
+ if (ledger.stateOf(key) !== 'exiting')
636
+ return;
637
+ const rec = tracked.get(key);
638
+ // Idempotent: a record already holding authority (drop-time deferral or a prior adoption)
639
+ // must not stack a second unreleasable handle.
640
+ if (rec === undefined || rec.activity !== undefined)
641
+ return;
642
+ rec.activity = graph.registerActivity();
643
+ },
644
+ resolvedExitTarget(key) {
645
+ return tracked.get(key)?.resolvedExit;
646
+ },
647
+ enterSuppressed(key) {
648
+ return tracked.get(key)?.enterSuppressed ?? false;
649
+ },
650
+ exitProgress(key) {
651
+ return tracked.get(key)?.progress?.get();
652
+ },
653
+ exitVelocity(key) {
654
+ return tracked.get(key)?.progress?.getVelocity();
655
+ },
656
+ };
657
+ }
@@ -0,0 +1,6 @@
1
+ import type { PresenceController, PresenceControllerOptions, PresenceMode } from "./types.cjs";
2
+ export declare class UnimplementedPresenceModeError extends Error {
3
+ readonly mode: PresenceMode;
4
+ constructor(mode: PresenceMode);
5
+ }
6
+ export declare function createPresenceController(options: PresenceControllerOptions): PresenceController;