@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,223 @@
|
|
|
1
|
+
'worklet';
|
|
2
|
+
// @frame-path — executes inside the UI-runtime frame step; allocation-gated (REQ-DRIVER-021).
|
|
3
|
+
// Transition-config resolution (SPEC-SPRING REQ-SPRING-004/005/006/007/012). Turns a user transition
|
|
4
|
+
// config into concrete spring physics + the flags the generator/settle layers need. Host-agnostic
|
|
5
|
+
// (REQ-CORE-003): imports nothing from motion/react. Numerics are pinned to motion@12.42.2 and proven
|
|
6
|
+
// by resolution parity in transition.test.ts.
|
|
7
|
+
//
|
|
8
|
+
// findSpring (REQ-SPRING-004) uses the DETERMINISTIC closed form, not motion's 12-iteration
|
|
9
|
+
// Newton-Raphson: for velocity-free duration springs the closed form is the exact analytic root of
|
|
10
|
+
// motion's envelope, so it reproduces motion's resolution (proven to ~5e-5 relative in the tests) with
|
|
11
|
+
// no iteration.
|
|
12
|
+
import { springGenerator } from "./spring.js";
|
|
13
|
+
// Pinned defaults (REQ-SPRING-003 springDefaults).
|
|
14
|
+
const DEFAULT_PHYSICS = { stiffness: 100, damping: 10, mass: 1 };
|
|
15
|
+
const DEFAULT_DURATION_MS = 800;
|
|
16
|
+
const DEFAULT_BOUNCE = 0.3;
|
|
17
|
+
const SAFE_MIN = 0.001;
|
|
18
|
+
const clamp = (lo, hi, v) => Math.max(lo, Math.min(hi, v));
|
|
19
|
+
// REQ-SPRING-004 critical-root constant: the u solving e^(−u)·(u+1) = SAFE_MIN. Derived from SAFE_MIN
|
|
20
|
+
// by Newton (NOT hard-copied 9.2334…) so it stays consistent if SAFE_MIN changes.
|
|
21
|
+
const CRITICAL_ROOT = (() => {
|
|
22
|
+
let u = 9;
|
|
23
|
+
for (let i = 0; i < 40; i++)
|
|
24
|
+
u -= (Math.exp(-u) * (u + 1) - SAFE_MIN) / (-u * Math.exp(-u));
|
|
25
|
+
return u;
|
|
26
|
+
})();
|
|
27
|
+
// REQ-SPRING-004: duration+bounce → physics via the closed-form undamped frequency (velocity = 0).
|
|
28
|
+
// alloc-ok: lifecycle-edge — duration→physics resolution at command time.
|
|
29
|
+
function findSpring(durationMs, bounce, mass) {
|
|
30
|
+
const z = clamp(0.05, 1, 1 - bounce);
|
|
31
|
+
const tSec = clamp(0.01, 10, durationMs / 1000);
|
|
32
|
+
const omega = z < 1 ? Math.log(z / (SAFE_MIN * Math.sqrt(1 - z * z))) / (z * tSec) : CRITICAL_ROOT / tSec;
|
|
33
|
+
if (Number.isNaN(omega)) {
|
|
34
|
+
return { ...DEFAULT_PHYSICS, mass, durationMs: tSec * 1000 };
|
|
35
|
+
}
|
|
36
|
+
const stiffness = omega * omega * mass;
|
|
37
|
+
return { stiffness, damping: z * 2 * Math.sqrt(mass * stiffness), mass, durationMs: tSec * 1000 };
|
|
38
|
+
}
|
|
39
|
+
// REQ-SPRING-005: visualDuration+bounce → the DISTINCT path (no findSpring). bounce defaults to 0 here
|
|
40
|
+
// (intentional asymmetry with REQ-SPRING-004's 0.3). The 1.2 factor is load-bearing.
|
|
41
|
+
// alloc-ok: lifecycle-edge — visualDuration→physics resolution at command time.
|
|
42
|
+
function visualDurationSpring(visualDuration, bounce, mass) {
|
|
43
|
+
const root = (2 * Math.PI) / (visualDuration * 1.2);
|
|
44
|
+
const stiffness = root * root;
|
|
45
|
+
const damping = 2 * clamp(0.05, 1, 1 - (bounce ?? 0)) * Math.sqrt(stiffness);
|
|
46
|
+
return { stiffness, damping, mass };
|
|
47
|
+
}
|
|
48
|
+
const hasPhysicsKey = (c) => c.stiffness !== undefined || c.damping !== undefined || c.mass !== undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Resolve a spring transition config to concrete physics + carry/settle flags (REQ-SPRING-006
|
|
51
|
+
* precedence). Presence is tested on `!== undefined`.
|
|
52
|
+
*/
|
|
53
|
+
// alloc-ok: lifecycle-edge — config resolution at command time (REQ-SPRING-006 precedence).
|
|
54
|
+
export function resolveSpring(config) {
|
|
55
|
+
const mass = config.mass ?? DEFAULT_PHYSICS.mass;
|
|
56
|
+
// Any physics key ⇒ physics used directly; the entire duration family is ignored; velocity carries.
|
|
57
|
+
if (hasPhysicsKey(config)) {
|
|
58
|
+
return {
|
|
59
|
+
physics: {
|
|
60
|
+
stiffness: config.stiffness ?? DEFAULT_PHYSICS.stiffness,
|
|
61
|
+
damping: config.damping ?? DEFAULT_PHYSICS.damping,
|
|
62
|
+
mass,
|
|
63
|
+
},
|
|
64
|
+
carryVelocity: true,
|
|
65
|
+
isResolvedFromDuration: false,
|
|
66
|
+
calculatedDuration: null,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// Duration family — velocity is ZEROED (REQ-SPRING-007). visualDuration wins over duration, and a
|
|
70
|
+
// visualDuration-only config still takes the vd path (REQ-SPRING-006, post-plan-030); it MUST NOT
|
|
71
|
+
// fall back to default physics.
|
|
72
|
+
if (config.visualDuration !== undefined) {
|
|
73
|
+
return {
|
|
74
|
+
physics: visualDurationSpring(config.visualDuration, config.bounce, mass),
|
|
75
|
+
carryVelocity: false,
|
|
76
|
+
isResolvedFromDuration: false, // vd does NOT set the duration flag (REQ-SPRING-011)
|
|
77
|
+
calculatedDuration: null,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (config.duration !== undefined || config.bounce !== undefined) {
|
|
81
|
+
const fs = findSpring(config.duration ?? DEFAULT_DURATION_MS, config.bounce ?? DEFAULT_BOUNCE, mass);
|
|
82
|
+
return {
|
|
83
|
+
physics: { stiffness: fs.stiffness, damping: fs.damping, mass: fs.mass },
|
|
84
|
+
carryVelocity: false,
|
|
85
|
+
isResolvedFromDuration: true,
|
|
86
|
+
calculatedDuration: fs.durationMs,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// Bare `{type:'spring'}` / empty ⇒ REQ-SPRING-003 defaults (100/10/1); a physics spring (carries).
|
|
90
|
+
return {
|
|
91
|
+
physics: { ...DEFAULT_PHYSICS },
|
|
92
|
+
carryVelocity: true,
|
|
93
|
+
isResolvedFromDuration: false,
|
|
94
|
+
calculatedDuration: null,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Build a `GeneratorFactory` from a target + config, honoring velocity carry/zero (REQ-SPRING-007).
|
|
99
|
+
* Duration/visualDuration springs drop the seed's incoming velocity; physics springs keep it —
|
|
100
|
+
* unless the config carries an AUTHORED `velocity`, which replaces it (REQ-API-053, T21): the pin's
|
|
101
|
+
* animateMotionValue spreads the authored transition over the live-velocity default
|
|
102
|
+
* (motion-dom@12.42.2 motion-value.ts:45-50), so the authored number wins on every start.
|
|
103
|
+
*/
|
|
104
|
+
// alloc-ok: lifecycle-edge — factory construction at command time; sampling stays allocation-free.
|
|
105
|
+
export function resolveSpringGenerator(target, config) {
|
|
106
|
+
const resolved = resolveSpring(config);
|
|
107
|
+
// findSpring (duration) springs settle at t ≥ calculatedDuration; physics/visualDuration settle by
|
|
108
|
+
// threshold (settleDuration null). (REQ-SPRING-011)
|
|
109
|
+
const factory = springGenerator(target, resolved.physics, {
|
|
110
|
+
settleDuration: resolved.calculatedDuration,
|
|
111
|
+
...(config.restSpeed !== undefined ? { restSpeed: config.restSpeed } : {}),
|
|
112
|
+
...(config.restDelta !== undefined ? { restDelta: config.restDelta } : {}),
|
|
113
|
+
});
|
|
114
|
+
if (resolved.carryVelocity) {
|
|
115
|
+
const authoredVelocity = config.velocity;
|
|
116
|
+
if (authoredVelocity === undefined)
|
|
117
|
+
return factory;
|
|
118
|
+
// alloc-ok: lifecycle-edge — the authored-velocity reseed wrapper runs at generator construction.
|
|
119
|
+
return (seed) => factory({ from: seed.from, velocity: authoredVelocity });
|
|
120
|
+
}
|
|
121
|
+
// alloc-ok: lifecycle-edge — the zero-velocity reseed wrapper runs at generator construction.
|
|
122
|
+
return (seed) => factory({ from: seed.from, velocity: 0 });
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Interrupt/retarget (REQ-SPRING-009): build a new generator for `newTarget` from the OUTGOING
|
|
126
|
+
* generator's analytic state at `atMs` — its exact value and analytic velocity (preferred over any
|
|
127
|
+
* finite-diff, which avoids systematic velocity loss at high frame rates). Whether that incoming
|
|
128
|
+
* velocity carries (physics), is replaced by an authored config `velocity` (REQ-API-053), or is
|
|
129
|
+
* zeroed (duration/visualDuration) is decided by
|
|
130
|
+
* `resolveSpringGenerator` (REQ-SPRING-007). The result's `value(0)` equals the outgoing `value(atMs)`
|
|
131
|
+
* exactly — no teleport, no velocity reset. The graph's own interruption seam does the equivalent via
|
|
132
|
+
* `motionValue.getVelocity()`; this is the explicit spring-layer helper for consumers driving
|
|
133
|
+
* generators directly (e.g. the Reanimated driver).
|
|
134
|
+
*/
|
|
135
|
+
// alloc-ok: lifecycle-edge — interruption/retarget construction; samples the outgoing generator
|
|
136
|
+
// once, then builds the successor.
|
|
137
|
+
export function retargetSpring(outgoing, atMs, newTarget, config) {
|
|
138
|
+
const at = outgoing.sample(atMs);
|
|
139
|
+
return resolveSpringGenerator(newTarget, config)({ from: at.value, velocity: at.velocity });
|
|
140
|
+
}
|
|
141
|
+
// REQ-SPRING-012 getDefaultTransition selection table. transformProps mirrors motion's set.
|
|
142
|
+
const TRANSFORM_PROPS = new Set([
|
|
143
|
+
'transformPerspective',
|
|
144
|
+
'x',
|
|
145
|
+
'y',
|
|
146
|
+
'z',
|
|
147
|
+
'translateX',
|
|
148
|
+
'translateY',
|
|
149
|
+
'translateZ',
|
|
150
|
+
'scale',
|
|
151
|
+
'scaleX',
|
|
152
|
+
'scaleY',
|
|
153
|
+
'rotate',
|
|
154
|
+
'rotateX',
|
|
155
|
+
'rotateY',
|
|
156
|
+
'rotateZ',
|
|
157
|
+
'skew',
|
|
158
|
+
'skewX',
|
|
159
|
+
'skewY',
|
|
160
|
+
'pathRotation',
|
|
161
|
+
]);
|
|
162
|
+
// R8-F1: a spring interpolates EXACTLY two keyframes (origin → target); the pinned Motion throws
|
|
163
|
+
// `spring-two-frames` for any other count. The ONE predicate the reference driver's
|
|
164
|
+
// springKeyframeGenerator throws from AND the native controller PRE-VALIDATES with before issuing any
|
|
165
|
+
// driver command (so a rejected multi-command mount/update mutates nothing — atomicity, review major
|
|
166
|
+
// 36). The worklet driver mirrors the SAME message inline on its 'worklet' path. Returns the loud
|
|
167
|
+
// refusal for a non-two count, else null.
|
|
168
|
+
// alloc-ok: lifecycle-edge — a refusal built at command/pre-validation time, never per frame.
|
|
169
|
+
export function springKeyframeCountRefusal(count) {
|
|
170
|
+
if (count === 2)
|
|
171
|
+
return null;
|
|
172
|
+
return new Error(`a spring transition cannot interpolate through ${count} keyframes — a spring animates exactly ` +
|
|
173
|
+
'two (origin → target). Keyframe index 2 (the successor) is unreachable; use a tween for a ' +
|
|
174
|
+
'multi-keyframe array (REQ-API-033, R8-F1).');
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Default transition per property when none is specified (REQ-SPRING-012), keyframe-count-aware to
|
|
178
|
+
* match the pinned `motion-dom` `getDefaultTransition(valueKey, { keyframes })`
|
|
179
|
+
* (default-transitions.ts): MORE than two keyframes → an 800ms keyframes transition (regardless of
|
|
180
|
+
* property); otherwise a transform prop gets an underdamped spring (scale: critically-damped 550,
|
|
181
|
+
* damping 30 for a non-zero target and 2√550 at exactly 0; other transforms: 500/25), and every
|
|
182
|
+
* other property gets the 300ms shallow-browser tween. A scalar animation is the two-keyframe case
|
|
183
|
+
* (`[from, target]`); the last keyframe drives the scale-at-zero branch (Motion reads `keyframes[1]`).
|
|
184
|
+
*/
|
|
185
|
+
// alloc-ok: lifecycle-edge — per-property default selection at command time.
|
|
186
|
+
export function getDefaultTransition(valueKey, keyframes) {
|
|
187
|
+
if (keyframes.length > 2) {
|
|
188
|
+
return { type: 'keyframes', duration: 0.8 };
|
|
189
|
+
}
|
|
190
|
+
if (TRANSFORM_PROPS.has(valueKey)) {
|
|
191
|
+
if (valueKey.startsWith('scale')) {
|
|
192
|
+
const last = keyframes[keyframes.length - 1];
|
|
193
|
+
const target = typeof last === 'number' ? last : 0;
|
|
194
|
+
return {
|
|
195
|
+
type: 'spring',
|
|
196
|
+
stiffness: 550,
|
|
197
|
+
damping: target === 0 ? 2 * Math.sqrt(550) : 30,
|
|
198
|
+
restSpeed: 10,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return { type: 'spring', stiffness: 500, damping: 25, restSpeed: 10 };
|
|
202
|
+
}
|
|
203
|
+
return { type: 'keyframes', ease: [0.25, 0.1, 0.35, 1], duration: 0.3 };
|
|
204
|
+
}
|
|
205
|
+
// Pinned drag-layer physics (startAnimation spread: timeConstant 750, restDelta 1, restSpeed 10;
|
|
206
|
+
// motion-dom inertia `power` default 0.8 — single-sourced with PROJECTION_POWER's pin note).
|
|
207
|
+
export const DRAG_INERTIA_DEFAULTS = {
|
|
208
|
+
power: 0.8,
|
|
209
|
+
timeConstant: 750,
|
|
210
|
+
restDelta: 1,
|
|
211
|
+
restSpeed: 10,
|
|
212
|
+
};
|
|
213
|
+
// The pin's elastic-conditional boundary-spring pairs (VisualElementDragControls: dragElastic
|
|
214
|
+
// truthy → 200/40, falsy → 1e6/1e7 "overdamp the boundary spring"). dragTransition overrides.
|
|
215
|
+
// Shared frozen constants (never a per-call literal — the frame-path allocation law, REQ-DRIVER-021).
|
|
216
|
+
const ELASTIC_BOUNDARY_BOUNCE = Object.freeze({ bounceStiffness: 200, bounceDamping: 40 });
|
|
217
|
+
const RIGID_BOUNDARY_BOUNCE = Object.freeze({
|
|
218
|
+
bounceStiffness: 1000000,
|
|
219
|
+
bounceDamping: 10000000,
|
|
220
|
+
});
|
|
221
|
+
export function dragInertiaBounce(elastic) {
|
|
222
|
+
return elastic !== undefined && elastic > 0 ? ELASTIC_BOUNDARY_BOUNCE : RIGID_BOUNDARY_BOUNCE;
|
|
223
|
+
}
|
package/dist/types.cjs
ADDED
package/dist/types.d.cts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/** Injectable time source. Core reads no ambient time (`Date.now`/`performance.now`) — REQ-CLOCK-001. */
|
|
2
|
+
export interface Clock {
|
|
3
|
+
now(): number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Per-frame step seam (REQ-CLOCK-001). Production drives this from vsync; tests pump frames
|
|
7
|
+
* explicitly. A step receives the elapsed time since the previous frame (already dt-clamped by the
|
|
8
|
+
* graph, REQ-CLOCK-003).
|
|
9
|
+
*/
|
|
10
|
+
export interface Scheduler {
|
|
11
|
+
onFrame(step: (dtMs: number) => void): () => void;
|
|
12
|
+
}
|
|
13
|
+
export type ValueChangeListener<T> = (value: T) => void;
|
|
14
|
+
/**
|
|
15
|
+
* A derived value with a re-attachable input lifecycle (review r4): consumers that bind and
|
|
16
|
+
* unbind across React effect replays detach/re-attach the SAME value; refresh() resyncs
|
|
17
|
+
* synchronously when the compute's captured parameters change. Detaching also CANCELS any
|
|
18
|
+
* already-queued recompute (review r5) — a detached node never applies or notifies.
|
|
19
|
+
*/
|
|
20
|
+
export interface DerivedMotionValue<T> extends MotionValue<T> {
|
|
21
|
+
attachInputs(): void;
|
|
22
|
+
detachInputs(): void;
|
|
23
|
+
refresh(): void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The structural input contract of a derivation (REQ-VALUE-002: "one or more motion values" —
|
|
27
|
+
* ANY value type; REQ-VALUE-013 glitch-freeness holds regardless of T). Every MotionValue<T>
|
|
28
|
+
* satisfies this shape, so string/color values compose into derivations like numeric ones.
|
|
29
|
+
*/
|
|
30
|
+
export interface DerivedSource<V = unknown> {
|
|
31
|
+
get(): V;
|
|
32
|
+
subscribe(listener: () => void): () => void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Per-position value types of a derivation's input tuple (review r6): the compute receives the
|
|
36
|
+
* REAL value type of each input — never-erasure would reject valid string code while admitting
|
|
37
|
+
* mistyped callbacks.
|
|
38
|
+
*/
|
|
39
|
+
export type DerivedSourceValues<Inputs extends readonly DerivedSource[]> = {
|
|
40
|
+
[Index in keyof Inputs]: Inputs[Index] extends DerivedSource<infer Value> ? Value : never;
|
|
41
|
+
};
|
|
42
|
+
export type MotionValueEvent = 'change' | 'animationStart' | 'animationComplete' | 'animationCancel' | 'destroy';
|
|
43
|
+
/**
|
|
44
|
+
* The live animated signal (REQ-VALUE-001/010/011/012). Generic over `T` (default `number`);
|
|
45
|
+
* non-numeric values are opaque and report velocity `0`. `subscribe` never triggers a host render.
|
|
46
|
+
*/
|
|
47
|
+
export interface MotionValue<T = number> {
|
|
48
|
+
get(): T;
|
|
49
|
+
/** Set the value; velocity is computed from clock-timestamped samples. */
|
|
50
|
+
set(value: T): void;
|
|
51
|
+
/**
|
|
52
|
+
* Teleport: set the value, reset velocity to `0`, and END any active animation through
|
|
53
|
+
* `stop()` (a cancellation) — the pinned `jump(v, endAnimation = true)` shape (r15): a dead
|
|
54
|
+
* flight must never overwrite the teleported value on the next frame.
|
|
55
|
+
*/
|
|
56
|
+
jump(value: T): void;
|
|
57
|
+
subscribe(listener: ValueChangeListener<T>): () => void;
|
|
58
|
+
/** Velocity in units/sec; `0` for non-numeric `T`; `0` if the last sample is stale. */
|
|
59
|
+
getVelocity(): number;
|
|
60
|
+
/**
|
|
61
|
+
* `change` delivers the latest value (pinned payload, r15); lifecycle events carry NONE —
|
|
62
|
+
* no broad union overload exists (r16 major 5: it made a payload-taking lifecycle listener
|
|
63
|
+
* representable while runtime supplies undefined). Union-event call sites narrow first.
|
|
64
|
+
*/
|
|
65
|
+
on(event: 'change', listener: (latest: T) => void): () => void;
|
|
66
|
+
on(event: Exclude<MotionValueEvent, 'change'>, listener: () => void): () => void;
|
|
67
|
+
/** Stop any animation currently driving this value, leaving it at its current value. */
|
|
68
|
+
stop(): void;
|
|
69
|
+
/**
|
|
70
|
+
* Whether an animation is currently driving this value (pinned motion-dom member, r12): the
|
|
71
|
+
* style-binding lane refuses an ALREADY-animating value at attach, not just one that starts
|
|
72
|
+
* while bound. Derived values are never animated directly and always report false.
|
|
73
|
+
*/
|
|
74
|
+
isAnimating(): boolean;
|
|
75
|
+
destroy(): void;
|
|
76
|
+
}
|
|
77
|
+
/** Seed handed to a generator factory when an animation (re)starts on a value (REQ-CORE-002). */
|
|
78
|
+
export interface GeneratorSeed {
|
|
79
|
+
from: number;
|
|
80
|
+
velocity: number;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The record is OWNED and REUSED by its generator — sample() mutates and returns the same object
|
|
84
|
+
* every call (motion's next() contract; allocation-free per-frame sampling, REQ-DRIVER-015).
|
|
85
|
+
* Read the scalars immediately; never retain the record across sample() calls.
|
|
86
|
+
*/
|
|
87
|
+
export interface GeneratorSample {
|
|
88
|
+
value: number;
|
|
89
|
+
velocity: number;
|
|
90
|
+
done: boolean;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* An animation source sampled purely by elapsed time (REQ-CORE-001). Core hosts generators through
|
|
94
|
+
* this interface but does not parameterize their numerics — spring/timing math is SPEC-SPRING's.
|
|
95
|
+
*/
|
|
96
|
+
export interface Generator {
|
|
97
|
+
sample(elapsedMs: number): GeneratorSample;
|
|
98
|
+
/**
|
|
99
|
+
* The value to COMMIT when the trajectory finishes, when that differs from what `sample` reports
|
|
100
|
+
* at the terminal instant — the pin's `getFinalKeyframe` position.
|
|
101
|
+
*
|
|
102
|
+
* The pinned Motion applies `getFinalKeyframe` INSIDE `tick`, gated on
|
|
103
|
+
* `holdTime === null && (state === 'finished' || (state === 'running' && done))`, and only then
|
|
104
|
+
* calls `finish()`, which never touches the value (motion-dom `JSAnimation`). A FRESH,
|
|
105
|
+
* non-autoplaying instance is `paused`, so `holdTime` is non-null and neither disjunct holds: its
|
|
106
|
+
* `sample()` returns the raw fold, which is how this repo's goldens are generated. An
|
|
107
|
+
* already-FINISHED instance takes the override at ANY sampled time, mid-play included; a RUNNING
|
|
108
|
+
* one takes it only at or past `totalDuration`, because that disjunct also requires `done`
|
|
109
|
+
* (recomputed in `tick` as `currentTime >= totalDuration` at forward speed). Measured on
|
|
110
|
+
* `repeat: 1.5 reverse`, whose raw fold is 80 mid-play and 50 at the terminal — paused 80/50,
|
|
111
|
+
* running 80/100, finished 100/100.
|
|
112
|
+
*
|
|
113
|
+
* Absent on every generator whose terminal sample already IS its resting value — only a fractional
|
|
114
|
+
* `repeat` fold sets it (REQ-TIMING-003). `stepProp` is the one consumer, so both drivers get the
|
|
115
|
+
* rule from one place.
|
|
116
|
+
*
|
|
117
|
+
* **A generator that WRAPS another and rebuilds the object owes this forward.** It is optional, so
|
|
118
|
+
* a wrapper that omits it silently drops the wrapped trajectory's resting value — exactly what
|
|
119
|
+
* `delayGenerator` did until the H2 round-1 review caught it. Known wrappers that do NOT forward,
|
|
120
|
+
* and why that is currently safe: `presence/controller.ts`'s min/max aggregators and its
|
|
121
|
+
* `normalizedFactory` (which would have to RESCALE a finish value, not forward it). No fold can
|
|
122
|
+
* reach them — `shippedSurface.ts` lists `repeat`/`repeatType`/`repeatDelay`/`delay` in
|
|
123
|
+
* `EXIT_LANE_INEXECUTABLE_OPTIONS`, and the presence controller never builds a repeat fold. Named
|
|
124
|
+
* here so a change that makes them reachable cannot reopen the defect silently (H2 round-2
|
|
125
|
+
* review MINOR 2). A new wrapper needs its own COMPOSED floor: a defect in composition is
|
|
126
|
+
* invisible to every floor that tests the parts.
|
|
127
|
+
*/
|
|
128
|
+
readonly finalValue?: number | undefined;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Constructs a generator from a seed. The seam that carries velocity across interruption: on start
|
|
132
|
+
* the seed is the value's current value+velocity; on retarget the new factory is re-seeded with the
|
|
133
|
+
* current value+velocity so motion continues from the present visual state (REQ-CORE-002).
|
|
134
|
+
*/
|
|
135
|
+
export type GeneratorFactory = (seed: GeneratorSeed) => Generator;
|
|
136
|
+
/** Handle to an in-flight animation. */
|
|
137
|
+
export interface Animation {
|
|
138
|
+
stop(): void;
|
|
139
|
+
readonly done: boolean;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Unsettled-work handle (REQ-SETTLE-001). Gesture and layout (non-core) call `registerActivity`
|
|
143
|
+
* and hold this handle open while their work is in flight; `release` marks that source done.
|
|
144
|
+
*/
|
|
145
|
+
export interface ActivityHandle {
|
|
146
|
+
release(): void;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The motion graph: value factory, derived graph, animation registry, and settle ledger. Owns the
|
|
150
|
+
* injected clock/scheduler and the definition of graph quiescence (REQ-SETTLE-001).
|
|
151
|
+
*/
|
|
152
|
+
export interface MotionGraph {
|
|
153
|
+
motionValue<T = number>(initial: T): MotionValue<T>;
|
|
154
|
+
/**
|
|
155
|
+
* Read-only value computed from any motion values; recomputes off the host path, glitch-free.
|
|
156
|
+
* Two call shapes (review r6 + r7): with NO explicit type arguments the input tuple infers
|
|
157
|
+
* per position, so the compute sees each input's REAL value type and mistyped callbacks are
|
|
158
|
+
* compile errors; with an explicit `derived<T>(...)` output type (the original public shape)
|
|
159
|
+
* values arrive as `unknown` and the caller narrows deliberately — sound in both directions.
|
|
160
|
+
*/
|
|
161
|
+
derived<const Inputs extends readonly DerivedSource[], T>(inputs: Inputs, compute: (...values: DerivedSourceValues<Inputs>) => T): DerivedMotionValue<T>;
|
|
162
|
+
derived<T>(inputs: readonly DerivedSource[], compute: (...values: unknown[]) => T): DerivedMotionValue<T>;
|
|
163
|
+
/** Start (or interrupt-and-restart) an animation driving `value`. */
|
|
164
|
+
animate(value: MotionValue<number>, factory: GeneratorFactory): Animation;
|
|
165
|
+
activeAnimationCount(): number;
|
|
166
|
+
registerActivity(): ActivityHandle;
|
|
167
|
+
/** True iff `activeAnimationCount() === 0` and no activity handle is open. */
|
|
168
|
+
isSettled(): boolean;
|
|
169
|
+
/** Resolves exactly on the frame the graph transitions to settled; re-armable; fail-closed. */
|
|
170
|
+
whenSettled(): Promise<void>;
|
|
171
|
+
}
|
|
172
|
+
export interface MotionGraphOptions {
|
|
173
|
+
clock: Clock;
|
|
174
|
+
scheduler: Scheduler;
|
|
175
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/** Injectable time source. Core reads no ambient time (`Date.now`/`performance.now`) — REQ-CLOCK-001. */
|
|
2
|
+
export interface Clock {
|
|
3
|
+
now(): number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Per-frame step seam (REQ-CLOCK-001). Production drives this from vsync; tests pump frames
|
|
7
|
+
* explicitly. A step receives the elapsed time since the previous frame (already dt-clamped by the
|
|
8
|
+
* graph, REQ-CLOCK-003).
|
|
9
|
+
*/
|
|
10
|
+
export interface Scheduler {
|
|
11
|
+
onFrame(step: (dtMs: number) => void): () => void;
|
|
12
|
+
}
|
|
13
|
+
export type ValueChangeListener<T> = (value: T) => void;
|
|
14
|
+
/**
|
|
15
|
+
* A derived value with a re-attachable input lifecycle (review r4): consumers that bind and
|
|
16
|
+
* unbind across React effect replays detach/re-attach the SAME value; refresh() resyncs
|
|
17
|
+
* synchronously when the compute's captured parameters change. Detaching also CANCELS any
|
|
18
|
+
* already-queued recompute (review r5) — a detached node never applies or notifies.
|
|
19
|
+
*/
|
|
20
|
+
export interface DerivedMotionValue<T> extends MotionValue<T> {
|
|
21
|
+
attachInputs(): void;
|
|
22
|
+
detachInputs(): void;
|
|
23
|
+
refresh(): void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The structural input contract of a derivation (REQ-VALUE-002: "one or more motion values" —
|
|
27
|
+
* ANY value type; REQ-VALUE-013 glitch-freeness holds regardless of T). Every MotionValue<T>
|
|
28
|
+
* satisfies this shape, so string/color values compose into derivations like numeric ones.
|
|
29
|
+
*/
|
|
30
|
+
export interface DerivedSource<V = unknown> {
|
|
31
|
+
get(): V;
|
|
32
|
+
subscribe(listener: () => void): () => void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Per-position value types of a derivation's input tuple (review r6): the compute receives the
|
|
36
|
+
* REAL value type of each input — never-erasure would reject valid string code while admitting
|
|
37
|
+
* mistyped callbacks.
|
|
38
|
+
*/
|
|
39
|
+
export type DerivedSourceValues<Inputs extends readonly DerivedSource[]> = {
|
|
40
|
+
[Index in keyof Inputs]: Inputs[Index] extends DerivedSource<infer Value> ? Value : never;
|
|
41
|
+
};
|
|
42
|
+
export type MotionValueEvent = 'change' | 'animationStart' | 'animationComplete' | 'animationCancel' | 'destroy';
|
|
43
|
+
/**
|
|
44
|
+
* The live animated signal (REQ-VALUE-001/010/011/012). Generic over `T` (default `number`);
|
|
45
|
+
* non-numeric values are opaque and report velocity `0`. `subscribe` never triggers a host render.
|
|
46
|
+
*/
|
|
47
|
+
export interface MotionValue<T = number> {
|
|
48
|
+
get(): T;
|
|
49
|
+
/** Set the value; velocity is computed from clock-timestamped samples. */
|
|
50
|
+
set(value: T): void;
|
|
51
|
+
/**
|
|
52
|
+
* Teleport: set the value, reset velocity to `0`, and END any active animation through
|
|
53
|
+
* `stop()` (a cancellation) — the pinned `jump(v, endAnimation = true)` shape (r15): a dead
|
|
54
|
+
* flight must never overwrite the teleported value on the next frame.
|
|
55
|
+
*/
|
|
56
|
+
jump(value: T): void;
|
|
57
|
+
subscribe(listener: ValueChangeListener<T>): () => void;
|
|
58
|
+
/** Velocity in units/sec; `0` for non-numeric `T`; `0` if the last sample is stale. */
|
|
59
|
+
getVelocity(): number;
|
|
60
|
+
/**
|
|
61
|
+
* `change` delivers the latest value (pinned payload, r15); lifecycle events carry NONE —
|
|
62
|
+
* no broad union overload exists (r16 major 5: it made a payload-taking lifecycle listener
|
|
63
|
+
* representable while runtime supplies undefined). Union-event call sites narrow first.
|
|
64
|
+
*/
|
|
65
|
+
on(event: 'change', listener: (latest: T) => void): () => void;
|
|
66
|
+
on(event: Exclude<MotionValueEvent, 'change'>, listener: () => void): () => void;
|
|
67
|
+
/** Stop any animation currently driving this value, leaving it at its current value. */
|
|
68
|
+
stop(): void;
|
|
69
|
+
/**
|
|
70
|
+
* Whether an animation is currently driving this value (pinned motion-dom member, r12): the
|
|
71
|
+
* style-binding lane refuses an ALREADY-animating value at attach, not just one that starts
|
|
72
|
+
* while bound. Derived values are never animated directly and always report false.
|
|
73
|
+
*/
|
|
74
|
+
isAnimating(): boolean;
|
|
75
|
+
destroy(): void;
|
|
76
|
+
}
|
|
77
|
+
/** Seed handed to a generator factory when an animation (re)starts on a value (REQ-CORE-002). */
|
|
78
|
+
export interface GeneratorSeed {
|
|
79
|
+
from: number;
|
|
80
|
+
velocity: number;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The record is OWNED and REUSED by its generator — sample() mutates and returns the same object
|
|
84
|
+
* every call (motion's next() contract; allocation-free per-frame sampling, REQ-DRIVER-015).
|
|
85
|
+
* Read the scalars immediately; never retain the record across sample() calls.
|
|
86
|
+
*/
|
|
87
|
+
export interface GeneratorSample {
|
|
88
|
+
value: number;
|
|
89
|
+
velocity: number;
|
|
90
|
+
done: boolean;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* An animation source sampled purely by elapsed time (REQ-CORE-001). Core hosts generators through
|
|
94
|
+
* this interface but does not parameterize their numerics — spring/timing math is SPEC-SPRING's.
|
|
95
|
+
*/
|
|
96
|
+
export interface Generator {
|
|
97
|
+
sample(elapsedMs: number): GeneratorSample;
|
|
98
|
+
/**
|
|
99
|
+
* The value to COMMIT when the trajectory finishes, when that differs from what `sample` reports
|
|
100
|
+
* at the terminal instant — the pin's `getFinalKeyframe` position.
|
|
101
|
+
*
|
|
102
|
+
* The pinned Motion applies `getFinalKeyframe` INSIDE `tick`, gated on
|
|
103
|
+
* `holdTime === null && (state === 'finished' || (state === 'running' && done))`, and only then
|
|
104
|
+
* calls `finish()`, which never touches the value (motion-dom `JSAnimation`). A FRESH,
|
|
105
|
+
* non-autoplaying instance is `paused`, so `holdTime` is non-null and neither disjunct holds: its
|
|
106
|
+
* `sample()` returns the raw fold, which is how this repo's goldens are generated. An
|
|
107
|
+
* already-FINISHED instance takes the override at ANY sampled time, mid-play included; a RUNNING
|
|
108
|
+
* one takes it only at or past `totalDuration`, because that disjunct also requires `done`
|
|
109
|
+
* (recomputed in `tick` as `currentTime >= totalDuration` at forward speed). Measured on
|
|
110
|
+
* `repeat: 1.5 reverse`, whose raw fold is 80 mid-play and 50 at the terminal — paused 80/50,
|
|
111
|
+
* running 80/100, finished 100/100.
|
|
112
|
+
*
|
|
113
|
+
* Absent on every generator whose terminal sample already IS its resting value — only a fractional
|
|
114
|
+
* `repeat` fold sets it (REQ-TIMING-003). `stepProp` is the one consumer, so both drivers get the
|
|
115
|
+
* rule from one place.
|
|
116
|
+
*
|
|
117
|
+
* **A generator that WRAPS another and rebuilds the object owes this forward.** It is optional, so
|
|
118
|
+
* a wrapper that omits it silently drops the wrapped trajectory's resting value — exactly what
|
|
119
|
+
* `delayGenerator` did until the H2 round-1 review caught it. Known wrappers that do NOT forward,
|
|
120
|
+
* and why that is currently safe: `presence/controller.ts`'s min/max aggregators and its
|
|
121
|
+
* `normalizedFactory` (which would have to RESCALE a finish value, not forward it). No fold can
|
|
122
|
+
* reach them — `shippedSurface.ts` lists `repeat`/`repeatType`/`repeatDelay`/`delay` in
|
|
123
|
+
* `EXIT_LANE_INEXECUTABLE_OPTIONS`, and the presence controller never builds a repeat fold. Named
|
|
124
|
+
* here so a change that makes them reachable cannot reopen the defect silently (H2 round-2
|
|
125
|
+
* review MINOR 2). A new wrapper needs its own COMPOSED floor: a defect in composition is
|
|
126
|
+
* invisible to every floor that tests the parts.
|
|
127
|
+
*/
|
|
128
|
+
readonly finalValue?: number | undefined;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Constructs a generator from a seed. The seam that carries velocity across interruption: on start
|
|
132
|
+
* the seed is the value's current value+velocity; on retarget the new factory is re-seeded with the
|
|
133
|
+
* current value+velocity so motion continues from the present visual state (REQ-CORE-002).
|
|
134
|
+
*/
|
|
135
|
+
export type GeneratorFactory = (seed: GeneratorSeed) => Generator;
|
|
136
|
+
/** Handle to an in-flight animation. */
|
|
137
|
+
export interface Animation {
|
|
138
|
+
stop(): void;
|
|
139
|
+
readonly done: boolean;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Unsettled-work handle (REQ-SETTLE-001). Gesture and layout (non-core) call `registerActivity`
|
|
143
|
+
* and hold this handle open while their work is in flight; `release` marks that source done.
|
|
144
|
+
*/
|
|
145
|
+
export interface ActivityHandle {
|
|
146
|
+
release(): void;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The motion graph: value factory, derived graph, animation registry, and settle ledger. Owns the
|
|
150
|
+
* injected clock/scheduler and the definition of graph quiescence (REQ-SETTLE-001).
|
|
151
|
+
*/
|
|
152
|
+
export interface MotionGraph {
|
|
153
|
+
motionValue<T = number>(initial: T): MotionValue<T>;
|
|
154
|
+
/**
|
|
155
|
+
* Read-only value computed from any motion values; recomputes off the host path, glitch-free.
|
|
156
|
+
* Two call shapes (review r6 + r7): with NO explicit type arguments the input tuple infers
|
|
157
|
+
* per position, so the compute sees each input's REAL value type and mistyped callbacks are
|
|
158
|
+
* compile errors; with an explicit `derived<T>(...)` output type (the original public shape)
|
|
159
|
+
* values arrive as `unknown` and the caller narrows deliberately — sound in both directions.
|
|
160
|
+
*/
|
|
161
|
+
derived<const Inputs extends readonly DerivedSource[], T>(inputs: Inputs, compute: (...values: DerivedSourceValues<Inputs>) => T): DerivedMotionValue<T>;
|
|
162
|
+
derived<T>(inputs: readonly DerivedSource[], compute: (...values: unknown[]) => T): DerivedMotionValue<T>;
|
|
163
|
+
/** Start (or interrupt-and-restart) an animation driving `value`. */
|
|
164
|
+
animate(value: MotionValue<number>, factory: GeneratorFactory): Animation;
|
|
165
|
+
activeAnimationCount(): number;
|
|
166
|
+
registerActivity(): ActivityHandle;
|
|
167
|
+
/** True iff `activeAnimationCount() === 0` and no activity handle is open. */
|
|
168
|
+
isSettled(): boolean;
|
|
169
|
+
/** Resolves exactly on the frame the graph transitions to settled; re-armable; fail-closed. */
|
|
170
|
+
whenSettled(): Promise<void>;
|
|
171
|
+
}
|
|
172
|
+
export interface MotionGraphOptions {
|
|
173
|
+
clock: Clock;
|
|
174
|
+
scheduler: Scheduler;
|
|
175
|
+
}
|
package/dist/types.js
ADDED