@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.
- package/CHANGELOG.md +9 -0
- package/LICENSE +21 -0
- package/README.md +48 -0
- package/dist/clock.cjs +71 -0
- package/dist/clock.d.cts +23 -0
- package/dist/clock.d.ts +23 -0
- package/dist/clock.js +66 -0
- package/dist/collect-reads.cjs +68 -0
- package/dist/collect-reads.d.cts +23 -0
- package/dist/collect-reads.d.ts +23 -0
- package/dist/collect-reads.js +62 -0
- package/dist/component/boundedArray.cjs +74 -0
- package/dist/component/boundedArray.d.cts +25 -0
- package/dist/component/boundedArray.d.ts +25 -0
- package/dist/component/boundedArray.js +68 -0
- package/dist/component/index.cjs +57 -0
- package/dist/component/index.d.cts +12 -0
- package/dist/component/index.d.ts +12 -0
- package/dist/component/index.js +22 -0
- package/dist/component/orchestration.cjs +129 -0
- package/dist/component/orchestration.d.cts +66 -0
- package/dist/component/orchestration.d.ts +66 -0
- package/dist/component/orchestration.js +124 -0
- package/dist/component/resolve.cjs +104 -0
- package/dist/component/resolve.d.cts +11 -0
- package/dist/component/resolve.d.ts +11 -0
- package/dist/component/resolve.js +97 -0
- package/dist/component/transition.cjs +352 -0
- package/dist/component/transition.d.cts +33 -0
- package/dist/component/transition.d.ts +33 -0
- package/dist/component/transition.js +339 -0
- package/dist/component/types.cjs +114 -0
- package/dist/component/types.d.cts +60 -0
- package/dist/component/types.d.ts +60 -0
- package/dist/component/types.js +111 -0
- package/dist/component/validate.cjs +1015 -0
- package/dist/component/validate.d.cts +37 -0
- package/dist/component/validate.d.ts +37 -0
- package/dist/component/validate.js +1002 -0
- package/dist/component/variants.cjs +333 -0
- package/dist/component/variants.d.cts +106 -0
- package/dist/component/variants.d.ts +106 -0
- package/dist/component/variants.js +321 -0
- package/dist/config/constants.cjs +41 -0
- package/dist/config/constants.d.cts +29 -0
- package/dist/config/constants.d.ts +29 -0
- package/dist/config/constants.js +38 -0
- package/dist/delay.cjs +72 -0
- package/dist/delay.d.cts +26 -0
- package/dist/delay.d.ts +26 -0
- package/dist/delay.js +70 -0
- package/dist/derived.cjs +143 -0
- package/dist/derived.d.cts +41 -0
- package/dist/derived.d.ts +41 -0
- package/dist/derived.js +139 -0
- package/dist/driver/index.cjs +22 -0
- package/dist/driver/index.d.cts +7 -0
- package/dist/driver/index.d.ts +7 -0
- package/dist/driver/index.js +14 -0
- package/dist/driver/keyframeTiming.cjs +130 -0
- package/dist/driver/keyframeTiming.d.cts +18 -0
- package/dist/driver/keyframeTiming.d.ts +18 -0
- package/dist/driver/keyframeTiming.js +124 -0
- package/dist/driver/keyframeTimingConfig.cjs +24 -0
- package/dist/driver/keyframeTimingConfig.d.cts +5 -0
- package/dist/driver/keyframeTimingConfig.d.ts +5 -0
- package/dist/driver/keyframeTimingConfig.js +22 -0
- package/dist/driver/prepare.cjs +459 -0
- package/dist/driver/prepare.d.cts +4 -0
- package/dist/driver/prepare.d.ts +4 -0
- package/dist/driver/prepare.js +454 -0
- package/dist/driver/reference.cjs +762 -0
- package/dist/driver/reference.d.cts +53 -0
- package/dist/driver/reference.d.ts +53 -0
- package/dist/driver/reference.js +757 -0
- package/dist/driver/step.cjs +55 -0
- package/dist/driver/step.d.cts +9 -0
- package/dist/driver/step.d.ts +9 -0
- package/dist/driver/step.js +52 -0
- package/dist/driver/tiers.cjs +40 -0
- package/dist/driver/tiers.d.cts +2 -0
- package/dist/driver/tiers.d.ts +2 -0
- package/dist/driver/tiers.js +37 -0
- package/dist/driver/types.cjs +9 -0
- package/dist/driver/types.d.cts +68 -0
- package/dist/driver/types.d.ts +68 -0
- package/dist/driver/types.js +8 -0
- package/dist/external-animation-ledger.cjs +1250 -0
- package/dist/external-animation-ledger.d.cts +416 -0
- package/dist/external-animation-ledger.d.ts +416 -0
- package/dist/external-animation-ledger.js +1243 -0
- package/dist/gesture/directionLock.cjs +25 -0
- package/dist/gesture/directionLock.d.cts +8 -0
- package/dist/gesture/directionLock.d.ts +8 -0
- package/dist/gesture/directionLock.js +21 -0
- package/dist/gesture/dragConfig.cjs +634 -0
- package/dist/gesture/dragConfig.d.cts +298 -0
- package/dist/gesture/dragConfig.d.ts +298 -0
- package/dist/gesture/dragConfig.js +624 -0
- package/dist/gesture/elastic.cjs +44 -0
- package/dist/gesture/elastic.d.cts +4 -0
- package/dist/gesture/elastic.d.ts +4 -0
- package/dist/gesture/elastic.js +39 -0
- package/dist/gesture/handoffSession.cjs +161 -0
- package/dist/gesture/handoffSession.d.cts +35 -0
- package/dist/gesture/handoffSession.d.ts +35 -0
- package/dist/gesture/handoffSession.js +158 -0
- package/dist/gesture/index.cjs +37 -0
- package/dist/gesture/index.d.cts +12 -0
- package/dist/gesture/index.d.ts +12 -0
- package/dist/gesture/index.js +11 -0
- package/dist/gesture/projection.cjs +95 -0
- package/dist/gesture/projection.d.cts +7 -0
- package/dist/gesture/projection.d.ts +7 -0
- package/dist/gesture/projection.js +87 -0
- package/dist/gesture/session.cjs +162 -0
- package/dist/gesture/session.d.cts +29 -0
- package/dist/gesture/session.d.ts +29 -0
- package/dist/gesture/session.js +158 -0
- package/dist/gesture/types.cjs +5 -0
- package/dist/gesture/types.d.cts +13 -0
- package/dist/gesture/types.d.ts +13 -0
- package/dist/gesture/types.js +4 -0
- package/dist/gesture/viewportConstraints.cjs +38 -0
- package/dist/gesture/viewportConstraints.d.cts +6 -0
- package/dist/gesture/viewportConstraints.d.ts +6 -0
- package/dist/gesture/viewportConstraints.js +34 -0
- package/dist/graph.cjs +226 -0
- package/dist/graph.d.cts +2 -0
- package/dist/graph.d.ts +2 -0
- package/dist/graph.js +223 -0
- package/dist/index.cjs +140 -0
- package/dist/index.d.cts +33 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +70 -0
- package/dist/inertia.cjs +214 -0
- package/dist/inertia.d.cts +53 -0
- package/dist/inertia.d.ts +53 -0
- package/dist/inertia.js +212 -0
- package/dist/instant.cjs +66 -0
- package/dist/instant.d.cts +16 -0
- package/dist/instant.d.ts +16 -0
- package/dist/instant.js +63 -0
- package/dist/internal-driver.cjs +81 -0
- package/dist/internal-driver.d.cts +19 -0
- package/dist/internal-driver.d.ts +19 -0
- package/dist/internal-driver.js +35 -0
- package/dist/keyframes.cjs +191 -0
- package/dist/keyframes.d.cts +13 -0
- package/dist/keyframes.d.ts +13 -0
- package/dist/keyframes.js +188 -0
- package/dist/layout/commitDetector.cjs +67 -0
- package/dist/layout/commitDetector.d.cts +21 -0
- package/dist/layout/commitDetector.d.ts +21 -0
- package/dist/layout/commitDetector.js +64 -0
- package/dist/layout/compose.cjs +67 -0
- package/dist/layout/compose.d.cts +30 -0
- package/dist/layout/compose.d.ts +30 -0
- package/dist/layout/compose.js +65 -0
- package/dist/layout/constants.cjs +13 -0
- package/dist/layout/constants.d.cts +6 -0
- package/dist/layout/constants.d.ts +6 -0
- package/dist/layout/constants.js +10 -0
- package/dist/layout/identity.cjs +700 -0
- package/dist/layout/identity.d.cts +67 -0
- package/dist/layout/identity.d.ts +67 -0
- package/dist/layout/identity.js +698 -0
- package/dist/layout/index.cjs +36 -0
- package/dist/layout/index.d.cts +18 -0
- package/dist/layout/index.d.ts +18 -0
- package/dist/layout/index.js +13 -0
- package/dist/layout/measure.cjs +81 -0
- package/dist/layout/measure.d.cts +43 -0
- package/dist/layout/measure.d.ts +43 -0
- package/dist/layout/measure.js +78 -0
- package/dist/layout/projection.cjs +99 -0
- package/dist/layout/projection.d.cts +23 -0
- package/dist/layout/projection.d.ts +23 -0
- package/dist/layout/projection.js +97 -0
- package/dist/layout/scroll.cjs +27 -0
- package/dist/layout/scroll.d.cts +13 -0
- package/dist/layout/scroll.d.ts +13 -0
- package/dist/layout/scroll.js +24 -0
- package/dist/layout/session.cjs +207 -0
- package/dist/layout/session.d.cts +73 -0
- package/dist/layout/session.d.ts +73 -0
- package/dist/layout/session.js +205 -0
- package/dist/layout/tree.cjs +826 -0
- package/dist/layout/tree.d.cts +70 -0
- package/dist/layout/tree.d.ts +70 -0
- package/dist/layout/tree.js +823 -0
- package/dist/layout/types.cjs +36 -0
- package/dist/layout/types.d.cts +31 -0
- package/dist/layout/types.d.ts +31 -0
- package/dist/layout/types.js +35 -0
- package/dist/motion-arc.cjs +184 -0
- package/dist/motion-arc.d.cts +78 -0
- package/dist/motion-arc.d.ts +78 -0
- package/dist/motion-arc.js +183 -0
- package/dist/motion-mix.cjs +205 -0
- package/dist/motion-mix.d.cts +3 -0
- package/dist/motion-mix.d.ts +3 -0
- package/dist/motion-mix.js +202 -0
- package/dist/motion-value-driver-port.cjs +636 -0
- package/dist/motion-value-driver-port.d.cts +406 -0
- package/dist/motion-value-driver-port.d.ts +406 -0
- package/dist/motion-value-driver-port.js +627 -0
- package/dist/motion-value.cjs +189 -0
- package/dist/motion-value.d.cts +51 -0
- package/dist/motion-value.d.ts +51 -0
- package/dist/motion-value.js +185 -0
- package/dist/presence/controller.cjs +657 -0
- package/dist/presence/controller.d.cts +6 -0
- package/dist/presence/controller.d.ts +6 -0
- package/dist/presence/controller.js +652 -0
- package/dist/presence/index.cjs +19 -0
- package/dist/presence/index.d.cts +4 -0
- package/dist/presence/index.d.ts +4 -0
- package/dist/presence/index.js +12 -0
- package/dist/presence/machine.cjs +50 -0
- package/dist/presence/machine.d.cts +10 -0
- package/dist/presence/machine.d.ts +10 -0
- package/dist/presence/machine.js +46 -0
- package/dist/presence/types.cjs +6 -0
- package/dist/presence/types.d.cts +32 -0
- package/dist/presence/types.d.ts +32 -0
- package/dist/presence/types.js +5 -0
- package/dist/repeat.cjs +311 -0
- package/dist/repeat.d.cts +174 -0
- package/dist/repeat.d.ts +174 -0
- package/dist/repeat.js +300 -0
- package/dist/spring.cjs +128 -0
- package/dist/spring.d.cts +18 -0
- package/dist/spring.d.ts +18 -0
- package/dist/spring.js +125 -0
- package/dist/subscriptions.cjs +74 -0
- package/dist/subscriptions.d.cts +19 -0
- package/dist/subscriptions.d.ts +19 -0
- package/dist/subscriptions.js +69 -0
- package/dist/subset/index.cjs +23 -0
- package/dist/subset/index.d.cts +6 -0
- package/dist/subset/index.d.ts +6 -0
- package/dist/subset/index.js +13 -0
- package/dist/subset/normalize.cjs +90 -0
- package/dist/subset/normalize.d.cts +10 -0
- package/dist/subset/normalize.d.ts +10 -0
- package/dist/subset/normalize.js +85 -0
- package/dist/subset/registry.cjs +259 -0
- package/dist/subset/registry.d.cts +25 -0
- package/dist/subset/registry.d.ts +25 -0
- package/dist/subset/registry.js +256 -0
- package/dist/subset/resolve.cjs +98 -0
- package/dist/subset/resolve.d.cts +24 -0
- package/dist/subset/resolve.d.ts +24 -0
- package/dist/subset/resolve.js +90 -0
- package/dist/timing.cjs +206 -0
- package/dist/timing.d.cts +17 -0
- package/dist/timing.d.ts +17 -0
- package/dist/timing.js +201 -0
- package/dist/transformTemplate.cjs +219 -0
- package/dist/transformTemplate.d.cts +29 -0
- package/dist/transformTemplate.d.ts +29 -0
- package/dist/transformTemplate.js +215 -0
- package/dist/transition.cjs +231 -0
- package/dist/transition.d.cts +89 -0
- package/dist/transition.d.ts +89 -0
- package/dist/transition.js +223 -0
- package/dist/types.cjs +4 -0
- package/dist/types.d.cts +175 -0
- package/dist/types.d.ts +175 -0
- package/dist/types.js +3 -0
- package/dist/value-types/color.cjs +220 -0
- package/dist/value-types/color.d.cts +19 -0
- package/dist/value-types/color.d.ts +19 -0
- package/dist/value-types/color.js +218 -0
- package/dist/value-types/complex.cjs +160 -0
- package/dist/value-types/complex.d.cts +19 -0
- package/dist/value-types/complex.d.ts +19 -0
- package/dist/value-types/complex.js +155 -0
- package/dist/value-types/constants.cjs +15 -0
- package/dist/value-types/constants.d.cts +6 -0
- package/dist/value-types/constants.d.ts +6 -0
- package/dist/value-types/constants.js +12 -0
- package/dist/value-types/discrete.cjs +62 -0
- package/dist/value-types/discrete.d.cts +4 -0
- package/dist/value-types/discrete.d.ts +4 -0
- package/dist/value-types/discrete.js +56 -0
- package/dist/value-types/index.cjs +59 -0
- package/dist/value-types/index.d.cts +14 -0
- package/dist/value-types/index.d.ts +14 -0
- package/dist/value-types/index.js +24 -0
- package/dist/value-types/measure-resolve.cjs +325 -0
- package/dist/value-types/measure-resolve.d.cts +91 -0
- package/dist/value-types/measure-resolve.d.ts +91 -0
- package/dist/value-types/measure-resolve.js +313 -0
- package/dist/value-types/mix.cjs +90 -0
- package/dist/value-types/mix.d.cts +27 -0
- package/dist/value-types/mix.d.ts +27 -0
- package/dist/value-types/mix.js +85 -0
- package/dist/value-types/named-colors.cjs +61 -0
- package/dist/value-types/named-colors.d.cts +2 -0
- package/dist/value-types/named-colors.d.ts +2 -0
- package/dist/value-types/named-colors.js +58 -0
- package/dist/value-types/numeric.cjs +86 -0
- package/dist/value-types/numeric.d.cts +16 -0
- package/dist/value-types/numeric.d.ts +16 -0
- package/dist/value-types/numeric.js +79 -0
- package/dist/worklet-layout/config/constants.js +39 -0
- package/dist/worklet-layout/layout/constants.js +11 -0
- package/dist/worklet-layout/layout/identity.js +699 -0
- package/dist/worklet-layout/layout/projection.js +98 -0
- package/dist/worklet-layout/layout/session.js +206 -0
- package/dist/worklet-layout/layout/tree.js +824 -0
- package/dist/worklet-layout/layout/types.js +36 -0
- package/dist/worklet-layout/spring.js +125 -0
- package/dist/worklet-layout/timing.js +201 -0
- package/dist/worklet-layout/transition.js +223 -0
- package/dist/worklet-layout.cjs +47 -0
- package/dist/worklet-layout.d.cts +15 -0
- package/dist/worklet-layout.d.ts +15 -0
- package/dist/worklet-layout.js +28 -0
- package/dist/wrap.cjs +7 -0
- package/dist/wrap.d.cts +1 -0
- package/dist/wrap.d.ts +1 -0
- package/dist/wrap.js +4 -0
- 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;
|