@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
package/dist/spring.cjs
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// @frame-path — executes inside the UI-runtime frame step; allocation-gated (REQ-DRIVER-021).
|
|
3
|
+
// Analytic scalar spring solver (SPEC-SPRING REQ-SPRING-001/002/008/010/011). A pure closed-form
|
|
4
|
+
// evaluator — value(t)/velocity(t) computed directly per damping regime, never stepped by Euler/RK.
|
|
5
|
+
// Host-agnostic (REQ-CORE-003): imports nothing from motion/react. Numerics are pinned to
|
|
6
|
+
// motion@12.42.2 (see specs/SPEC-SPRING.md) and proven by golden parity in spring.test.ts.
|
|
7
|
+
//
|
|
8
|
+
// Unit/sign convention (REQ-SPRING-008): time `t` is ms; the undamped angular frequency ω0 is per-ms
|
|
9
|
+
// (rad/ms); internal velocity is px/ms. The public boundary is px/s. The solver seeds internal
|
|
10
|
+
// v0 = −(seed.velocity_px_per_s / 1000) so the position's initial derivative x'(0) equals the true
|
|
11
|
+
// incoming public velocity; returned velocity is internal px/ms × 1000. A sign flip here inverts the
|
|
12
|
+
// first-bounce direction (a conformance failure), so it is asserted by the sign test.
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.assertTwoKeyframes = assertTwoKeyframes;
|
|
15
|
+
exports.springGenerator = springGenerator;
|
|
16
|
+
// REQ-SPRING-013 / REQ-API-005: the scalar spring/inertia generator is two-keyframe ([from, to]). More
|
|
17
|
+
// than two keyframes is a dev-time error — fail loudly, never silently truncate.
|
|
18
|
+
function assertTwoKeyframes(keyframes) {
|
|
19
|
+
if (keyframes.length > 2) {
|
|
20
|
+
throw new Error(`Native Motion spring is scalar two-keyframe ([from, to]); got ${keyframes.length} keyframes.`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Rest thresholds (REQ-SPRING-003), pinned from motion source (springDefaults). The scale switches to
|
|
24
|
+
// the granular values when the move is tiny (REQ-SPRING-011). Owned here — SPEC-SPRING tightens the
|
|
25
|
+
// core seed constants.
|
|
26
|
+
const REST_SPEED = { default: 2, granular: 0.01 };
|
|
27
|
+
const REST_DELTA = { default: 0.5, granular: 0.005 };
|
|
28
|
+
const GRANULAR_SCALE = 5;
|
|
29
|
+
/**
|
|
30
|
+
* Build a spring `GeneratorFactory` from a target and resolved physics. The seed supplies the origin
|
|
31
|
+
* (`from`) and the incoming public velocity (`velocity`, px/s); transition-config resolution
|
|
32
|
+
* (findSpring / visualDuration / defaults) is a separate concern (SPEC-SPRING Milestone 2).
|
|
33
|
+
*/
|
|
34
|
+
// alloc-ok: lifecycle-edge — the factory resolves physics once per animate command; per-frame work
|
|
35
|
+
// is sample() below, which mutates the reused record and allocates nothing.
|
|
36
|
+
function springGenerator(target, physics, settle) {
|
|
37
|
+
const { stiffness, damping, mass } = physics;
|
|
38
|
+
const omega0 = Math.sqrt(stiffness / mass) / 1000; // undamped angular freq, rad/ms
|
|
39
|
+
const z = damping / (2 * Math.sqrt(stiffness * mass)); // damping ratio; regime by strict <1/===1/>1
|
|
40
|
+
const settleDuration = settle?.settleDuration ?? null;
|
|
41
|
+
// alloc-ok: lifecycle-edge — generator construction at command/interruption time; the closed
|
|
42
|
+
// forms and the one reused sample record are built here, never per frame.
|
|
43
|
+
return (seed) => {
|
|
44
|
+
const origin = seed.from;
|
|
45
|
+
const delta = target - origin; // initialDelta
|
|
46
|
+
const v0 = -(seed.velocity / 1000); // internal initial velocity, px/ms (REQ-SPRING-008)
|
|
47
|
+
// Rest thresholds by move scale (REQ-SPRING-011); per-context overrides win over the scale default.
|
|
48
|
+
const granular = Math.abs(delta) < GRANULAR_SCALE;
|
|
49
|
+
// Pinned zero-as-absent (r15 c6e31fd509a8): Motion 12.42.2 reads these thresholds falsy —
|
|
50
|
+
// an explicit 0 falls back to the scale defaults, never an unbounded active spring.
|
|
51
|
+
const restSpeed = settle?.restSpeed || (granular ? REST_SPEED.granular : REST_SPEED.default);
|
|
52
|
+
const restDelta = settle?.restDelta || (granular ? REST_DELTA.granular : REST_DELTA.default);
|
|
53
|
+
// Regime-specific closed forms. value(t) → px; velocityPerMs(t) → px/ms (= x'(t)).
|
|
54
|
+
// Each is a NAMED function (frame-path manifest anonymous-sampler repair): the qualified
|
|
55
|
+
// symbols are springGenerator.factory.<regime>Value/<regime>Velocity.
|
|
56
|
+
let value;
|
|
57
|
+
let velocityPerMs;
|
|
58
|
+
if (z < 1) {
|
|
59
|
+
// Underdamped (REQ-SPRING-002).
|
|
60
|
+
const wd = omega0 * Math.sqrt(1 - z * z); // damped angular freq
|
|
61
|
+
const a = (v0 + z * omega0 * delta) / wd;
|
|
62
|
+
const sinCoeff = z * omega0 * a + delta * wd;
|
|
63
|
+
const cosCoeff = z * omega0 * delta - a * wd;
|
|
64
|
+
function underdampedValue(t) {
|
|
65
|
+
const env = Math.exp(-z * omega0 * t);
|
|
66
|
+
return target - env * (a * Math.sin(wd * t) + delta * Math.cos(wd * t));
|
|
67
|
+
}
|
|
68
|
+
function underdampedVelocity(t) {
|
|
69
|
+
const env = Math.exp(-z * omega0 * t);
|
|
70
|
+
return env * (sinCoeff * Math.sin(wd * t) + cosCoeff * Math.cos(wd * t));
|
|
71
|
+
}
|
|
72
|
+
value = underdampedValue;
|
|
73
|
+
velocityPerMs = underdampedVelocity;
|
|
74
|
+
}
|
|
75
|
+
else if (z === 1) {
|
|
76
|
+
// Critically damped (REQ-SPRING-002).
|
|
77
|
+
const c = v0 + omega0 * delta;
|
|
78
|
+
function criticalValue(t) {
|
|
79
|
+
return target - Math.exp(-omega0 * t) * (delta + c * t);
|
|
80
|
+
}
|
|
81
|
+
function criticalVelocity(t) {
|
|
82
|
+
return Math.exp(-omega0 * t) * (omega0 * c * t - v0);
|
|
83
|
+
}
|
|
84
|
+
value = criticalValue;
|
|
85
|
+
velocityPerMs = criticalVelocity;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// Overdamped — pure-exponential form (REQ-SPRING-010). Both λ < 0 → no overflow, no sinh/cosh
|
|
89
|
+
// cap; the capped form (Motion #1207) is never used. λslow uses −ω0/(z+√(z²−1)) to avoid
|
|
90
|
+
// catastrophic cancellation at large z.
|
|
91
|
+
const root = Math.sqrt(z * z - 1);
|
|
92
|
+
const wd = omega0 * root;
|
|
93
|
+
const lambdaSlow = -omega0 / (z + root);
|
|
94
|
+
const lambdaFast = -omega0 * (z + root);
|
|
95
|
+
const cSlow = (v0 + z * omega0 * delta + wd * delta) / (2 * wd);
|
|
96
|
+
const cFast = delta - cSlow;
|
|
97
|
+
function overdampedValue(t) {
|
|
98
|
+
return target - (cSlow * Math.exp(lambdaSlow * t) + cFast * Math.exp(lambdaFast * t));
|
|
99
|
+
}
|
|
100
|
+
function overdampedVelocity(t) {
|
|
101
|
+
return -(cSlow * lambdaSlow * Math.exp(lambdaSlow * t) +
|
|
102
|
+
cFast * lambdaFast * Math.exp(lambdaFast * t));
|
|
103
|
+
}
|
|
104
|
+
value = overdampedValue;
|
|
105
|
+
velocityPerMs = overdampedVelocity;
|
|
106
|
+
}
|
|
107
|
+
// One sample record per generator, mutated and returned every call — motion's own generators
|
|
108
|
+
// return their reused `state` the same way. Per-frame sampling must not allocate
|
|
109
|
+
// (REQ-DRIVER-015); callers read the scalars immediately and never retain the record.
|
|
110
|
+
const out = { value: seed.from, velocity: seed.velocity, done: false };
|
|
111
|
+
return {
|
|
112
|
+
sample(elapsedMs) {
|
|
113
|
+
const current = value(elapsedMs);
|
|
114
|
+
const velocity = velocityPerMs(elapsedMs) * 1000; // → px/s (REQ-SPRING-008)
|
|
115
|
+
// Settle (REQ-SPRING-011): findSpring (duration) springs finish deterministically at
|
|
116
|
+
// t ≥ settleDuration; physics + visualDuration springs rest by speed AND delta. On done, snap to
|
|
117
|
+
// target (matches motion's next()), so an interruption re-seeds from an exact endpoint.
|
|
118
|
+
const done = settleDuration !== null
|
|
119
|
+
? elapsedMs >= settleDuration
|
|
120
|
+
: Math.abs(velocity) <= restSpeed && Math.abs(target - current) <= restDelta;
|
|
121
|
+
out.value = done ? target : current;
|
|
122
|
+
out.velocity = velocity;
|
|
123
|
+
out.done = done;
|
|
124
|
+
return out;
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { GeneratorFactory } from "./types.cjs";
|
|
2
|
+
export interface SpringPhysics {
|
|
3
|
+
stiffness: number;
|
|
4
|
+
damping: number;
|
|
5
|
+
mass: number;
|
|
6
|
+
}
|
|
7
|
+
export interface SpringSettleOptions {
|
|
8
|
+
restSpeed?: number;
|
|
9
|
+
restDelta?: number;
|
|
10
|
+
settleDuration?: number | null;
|
|
11
|
+
}
|
|
12
|
+
export declare function assertTwoKeyframes(keyframes: readonly unknown[]): void;
|
|
13
|
+
/**
|
|
14
|
+
* Build a spring `GeneratorFactory` from a target and resolved physics. The seed supplies the origin
|
|
15
|
+
* (`from`) and the incoming public velocity (`velocity`, px/s); transition-config resolution
|
|
16
|
+
* (findSpring / visualDuration / defaults) is a separate concern (SPEC-SPRING Milestone 2).
|
|
17
|
+
*/
|
|
18
|
+
export declare function springGenerator(target: number, physics: SpringPhysics, settle?: SpringSettleOptions): GeneratorFactory;
|
package/dist/spring.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { GeneratorFactory } from "./types.js";
|
|
2
|
+
export interface SpringPhysics {
|
|
3
|
+
stiffness: number;
|
|
4
|
+
damping: number;
|
|
5
|
+
mass: number;
|
|
6
|
+
}
|
|
7
|
+
export interface SpringSettleOptions {
|
|
8
|
+
restSpeed?: number;
|
|
9
|
+
restDelta?: number;
|
|
10
|
+
settleDuration?: number | null;
|
|
11
|
+
}
|
|
12
|
+
export declare function assertTwoKeyframes(keyframes: readonly unknown[]): void;
|
|
13
|
+
/**
|
|
14
|
+
* Build a spring `GeneratorFactory` from a target and resolved physics. The seed supplies the origin
|
|
15
|
+
* (`from`) and the incoming public velocity (`velocity`, px/s); transition-config resolution
|
|
16
|
+
* (findSpring / visualDuration / defaults) is a separate concern (SPEC-SPRING Milestone 2).
|
|
17
|
+
*/
|
|
18
|
+
export declare function springGenerator(target: number, physics: SpringPhysics, settle?: SpringSettleOptions): GeneratorFactory;
|
package/dist/spring.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
'worklet';
|
|
2
|
+
// @frame-path — executes inside the UI-runtime frame step; allocation-gated (REQ-DRIVER-021).
|
|
3
|
+
// Analytic scalar spring solver (SPEC-SPRING REQ-SPRING-001/002/008/010/011). A pure closed-form
|
|
4
|
+
// evaluator — value(t)/velocity(t) computed directly per damping regime, never stepped by Euler/RK.
|
|
5
|
+
// Host-agnostic (REQ-CORE-003): imports nothing from motion/react. Numerics are pinned to
|
|
6
|
+
// motion@12.42.2 (see specs/SPEC-SPRING.md) and proven by golden parity in spring.test.ts.
|
|
7
|
+
//
|
|
8
|
+
// Unit/sign convention (REQ-SPRING-008): time `t` is ms; the undamped angular frequency ω0 is per-ms
|
|
9
|
+
// (rad/ms); internal velocity is px/ms. The public boundary is px/s. The solver seeds internal
|
|
10
|
+
// v0 = −(seed.velocity_px_per_s / 1000) so the position's initial derivative x'(0) equals the true
|
|
11
|
+
// incoming public velocity; returned velocity is internal px/ms × 1000. A sign flip here inverts the
|
|
12
|
+
// first-bounce direction (a conformance failure), so it is asserted by the sign test.
|
|
13
|
+
// REQ-SPRING-013 / REQ-API-005: the scalar spring/inertia generator is two-keyframe ([from, to]). More
|
|
14
|
+
// than two keyframes is a dev-time error — fail loudly, never silently truncate.
|
|
15
|
+
export function assertTwoKeyframes(keyframes) {
|
|
16
|
+
if (keyframes.length > 2) {
|
|
17
|
+
throw new Error(`Native Motion spring is scalar two-keyframe ([from, to]); got ${keyframes.length} keyframes.`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// Rest thresholds (REQ-SPRING-003), pinned from motion source (springDefaults). The scale switches to
|
|
21
|
+
// the granular values when the move is tiny (REQ-SPRING-011). Owned here — SPEC-SPRING tightens the
|
|
22
|
+
// core seed constants.
|
|
23
|
+
const REST_SPEED = { default: 2, granular: 0.01 };
|
|
24
|
+
const REST_DELTA = { default: 0.5, granular: 0.005 };
|
|
25
|
+
const GRANULAR_SCALE = 5;
|
|
26
|
+
/**
|
|
27
|
+
* Build a spring `GeneratorFactory` from a target and resolved physics. The seed supplies the origin
|
|
28
|
+
* (`from`) and the incoming public velocity (`velocity`, px/s); transition-config resolution
|
|
29
|
+
* (findSpring / visualDuration / defaults) is a separate concern (SPEC-SPRING Milestone 2).
|
|
30
|
+
*/
|
|
31
|
+
// alloc-ok: lifecycle-edge — the factory resolves physics once per animate command; per-frame work
|
|
32
|
+
// is sample() below, which mutates the reused record and allocates nothing.
|
|
33
|
+
export function springGenerator(target, physics, settle) {
|
|
34
|
+
const { stiffness, damping, mass } = physics;
|
|
35
|
+
const omega0 = Math.sqrt(stiffness / mass) / 1000; // undamped angular freq, rad/ms
|
|
36
|
+
const z = damping / (2 * Math.sqrt(stiffness * mass)); // damping ratio; regime by strict <1/===1/>1
|
|
37
|
+
const settleDuration = settle?.settleDuration ?? null;
|
|
38
|
+
// alloc-ok: lifecycle-edge — generator construction at command/interruption time; the closed
|
|
39
|
+
// forms and the one reused sample record are built here, never per frame.
|
|
40
|
+
return (seed) => {
|
|
41
|
+
const origin = seed.from;
|
|
42
|
+
const delta = target - origin; // initialDelta
|
|
43
|
+
const v0 = -(seed.velocity / 1000); // internal initial velocity, px/ms (REQ-SPRING-008)
|
|
44
|
+
// Rest thresholds by move scale (REQ-SPRING-011); per-context overrides win over the scale default.
|
|
45
|
+
const granular = Math.abs(delta) < GRANULAR_SCALE;
|
|
46
|
+
// Pinned zero-as-absent (r15 c6e31fd509a8): Motion 12.42.2 reads these thresholds falsy —
|
|
47
|
+
// an explicit 0 falls back to the scale defaults, never an unbounded active spring.
|
|
48
|
+
const restSpeed = settle?.restSpeed || (granular ? REST_SPEED.granular : REST_SPEED.default);
|
|
49
|
+
const restDelta = settle?.restDelta || (granular ? REST_DELTA.granular : REST_DELTA.default);
|
|
50
|
+
// Regime-specific closed forms. value(t) → px; velocityPerMs(t) → px/ms (= x'(t)).
|
|
51
|
+
// Each is a NAMED function (frame-path manifest anonymous-sampler repair): the qualified
|
|
52
|
+
// symbols are springGenerator.factory.<regime>Value/<regime>Velocity.
|
|
53
|
+
let value;
|
|
54
|
+
let velocityPerMs;
|
|
55
|
+
if (z < 1) {
|
|
56
|
+
// Underdamped (REQ-SPRING-002).
|
|
57
|
+
const wd = omega0 * Math.sqrt(1 - z * z); // damped angular freq
|
|
58
|
+
const a = (v0 + z * omega0 * delta) / wd;
|
|
59
|
+
const sinCoeff = z * omega0 * a + delta * wd;
|
|
60
|
+
const cosCoeff = z * omega0 * delta - a * wd;
|
|
61
|
+
function underdampedValue(t) {
|
|
62
|
+
const env = Math.exp(-z * omega0 * t);
|
|
63
|
+
return target - env * (a * Math.sin(wd * t) + delta * Math.cos(wd * t));
|
|
64
|
+
}
|
|
65
|
+
function underdampedVelocity(t) {
|
|
66
|
+
const env = Math.exp(-z * omega0 * t);
|
|
67
|
+
return env * (sinCoeff * Math.sin(wd * t) + cosCoeff * Math.cos(wd * t));
|
|
68
|
+
}
|
|
69
|
+
value = underdampedValue;
|
|
70
|
+
velocityPerMs = underdampedVelocity;
|
|
71
|
+
}
|
|
72
|
+
else if (z === 1) {
|
|
73
|
+
// Critically damped (REQ-SPRING-002).
|
|
74
|
+
const c = v0 + omega0 * delta;
|
|
75
|
+
function criticalValue(t) {
|
|
76
|
+
return target - Math.exp(-omega0 * t) * (delta + c * t);
|
|
77
|
+
}
|
|
78
|
+
function criticalVelocity(t) {
|
|
79
|
+
return Math.exp(-omega0 * t) * (omega0 * c * t - v0);
|
|
80
|
+
}
|
|
81
|
+
value = criticalValue;
|
|
82
|
+
velocityPerMs = criticalVelocity;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// Overdamped — pure-exponential form (REQ-SPRING-010). Both λ < 0 → no overflow, no sinh/cosh
|
|
86
|
+
// cap; the capped form (Motion #1207) is never used. λslow uses −ω0/(z+√(z²−1)) to avoid
|
|
87
|
+
// catastrophic cancellation at large z.
|
|
88
|
+
const root = Math.sqrt(z * z - 1);
|
|
89
|
+
const wd = omega0 * root;
|
|
90
|
+
const lambdaSlow = -omega0 / (z + root);
|
|
91
|
+
const lambdaFast = -omega0 * (z + root);
|
|
92
|
+
const cSlow = (v0 + z * omega0 * delta + wd * delta) / (2 * wd);
|
|
93
|
+
const cFast = delta - cSlow;
|
|
94
|
+
function overdampedValue(t) {
|
|
95
|
+
return target - (cSlow * Math.exp(lambdaSlow * t) + cFast * Math.exp(lambdaFast * t));
|
|
96
|
+
}
|
|
97
|
+
function overdampedVelocity(t) {
|
|
98
|
+
return -(cSlow * lambdaSlow * Math.exp(lambdaSlow * t) +
|
|
99
|
+
cFast * lambdaFast * Math.exp(lambdaFast * t));
|
|
100
|
+
}
|
|
101
|
+
value = overdampedValue;
|
|
102
|
+
velocityPerMs = overdampedVelocity;
|
|
103
|
+
}
|
|
104
|
+
// One sample record per generator, mutated and returned every call — motion's own generators
|
|
105
|
+
// return their reused `state` the same way. Per-frame sampling must not allocate
|
|
106
|
+
// (REQ-DRIVER-015); callers read the scalars immediately and never retain the record.
|
|
107
|
+
const out = { value: seed.from, velocity: seed.velocity, done: false };
|
|
108
|
+
return {
|
|
109
|
+
sample(elapsedMs) {
|
|
110
|
+
const current = value(elapsedMs);
|
|
111
|
+
const velocity = velocityPerMs(elapsedMs) * 1000; // → px/s (REQ-SPRING-008)
|
|
112
|
+
// Settle (REQ-SPRING-011): findSpring (duration) springs finish deterministically at
|
|
113
|
+
// t ≥ settleDuration; physics + visualDuration springs rest by speed AND delta. On done, snap to
|
|
114
|
+
// target (matches motion's next()), so an interruption re-seeds from an exact endpoint.
|
|
115
|
+
const done = settleDuration !== null
|
|
116
|
+
? elapsedMs >= settleDuration
|
|
117
|
+
: Math.abs(velocity) <= restSpeed && Math.abs(target - current) <= restDelta;
|
|
118
|
+
out.value = done ? target : current;
|
|
119
|
+
out.velocity = velocity;
|
|
120
|
+
out.done = done;
|
|
121
|
+
return out;
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Shared subscription registry for motion values: value-change listeners (`subscribe`) and named
|
|
3
|
+
// lifecycle events (`on`). Neither path triggers a host render. Extracted so `MotionValueImpl` and
|
|
4
|
+
// `DerivedValue` share one implementation instead of duplicating it (REQ-VALUE-001/002).
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Subscriptions = void 0;
|
|
7
|
+
exports.brandDerivedDependentListener = brandDerivedDependentListener;
|
|
8
|
+
// Derived-dependent branding (REQ-VALUE-015): the MotionValue driver port classifies whether a
|
|
9
|
+
// transported write reached a derived dependent without holding a graph handle. Derived values
|
|
10
|
+
// brand their input subscription so observer presence is queryable without invoking listeners.
|
|
11
|
+
const derivedDependentListeners = new WeakSet();
|
|
12
|
+
/** @internal Brand a change listener as a derived value's input subscription. */
|
|
13
|
+
function brandDerivedDependentListener(listener) {
|
|
14
|
+
derivedDependentListeners.add(listener);
|
|
15
|
+
return listener;
|
|
16
|
+
}
|
|
17
|
+
class Subscriptions {
|
|
18
|
+
#change = new Set();
|
|
19
|
+
// `on('change')` listeners take the latest value (the pinned payload, r15 a62d87e5f419);
|
|
20
|
+
// lifecycle listeners take none — one storage, lifecycle emission simply passes nothing.
|
|
21
|
+
#events = new Map();
|
|
22
|
+
subscribe(listener) {
|
|
23
|
+
this.#change.add(listener);
|
|
24
|
+
return () => {
|
|
25
|
+
this.#change.delete(listener);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
on(event, listener) {
|
|
29
|
+
let listeners = this.#events.get(event);
|
|
30
|
+
if (!listeners) {
|
|
31
|
+
listeners = new Set();
|
|
32
|
+
this.#events.set(event, listeners);
|
|
33
|
+
}
|
|
34
|
+
listeners.add(listener);
|
|
35
|
+
return () => {
|
|
36
|
+
listeners.delete(listener);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Notify change subscribers and any `on('change')` listeners with the current value. */
|
|
40
|
+
emitChange(value) {
|
|
41
|
+
for (const listener of this.#change)
|
|
42
|
+
listener(value);
|
|
43
|
+
const listeners = this.#events.get('change');
|
|
44
|
+
if (listeners)
|
|
45
|
+
for (const listener of listeners)
|
|
46
|
+
listener(value);
|
|
47
|
+
}
|
|
48
|
+
/** Fire a named lifecycle event (e.g. `animationStart`/`animationComplete`). */
|
|
49
|
+
emit(event) {
|
|
50
|
+
const listeners = this.#events.get(event);
|
|
51
|
+
// Lifecycle listeners are payload-free by contract (the overloads above); the stored shape
|
|
52
|
+
// carries T only for the 'change' set.
|
|
53
|
+
if (listeners)
|
|
54
|
+
for (const listener of listeners)
|
|
55
|
+
listener();
|
|
56
|
+
}
|
|
57
|
+
/** @internal Driver-port seam: whether any change observer (`subscribe`/`on('change')`) exists. */
|
|
58
|
+
hasChangeObservers() {
|
|
59
|
+
return this.#change.size > 0 || (this.#events.get('change')?.size ?? 0) > 0;
|
|
60
|
+
}
|
|
61
|
+
/** @internal Driver-port seam: whether a branded derived dependent is among the change listeners. */
|
|
62
|
+
hasDerivedDependentObserver() {
|
|
63
|
+
for (const listener of this.#change) {
|
|
64
|
+
if (derivedDependentListeners.has(listener))
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
clear() {
|
|
70
|
+
this.#change.clear();
|
|
71
|
+
this.#events.clear();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.Subscriptions = Subscriptions;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { MotionValueEvent, ValueChangeListener } from "./types.cjs";
|
|
2
|
+
/** @internal Brand a change listener as a derived value's input subscription. */
|
|
3
|
+
export declare function brandDerivedDependentListener<L extends (...args: never[]) => void>(listener: L): L;
|
|
4
|
+
export declare class Subscriptions<T> {
|
|
5
|
+
#private;
|
|
6
|
+
subscribe(listener: ValueChangeListener<T>): () => void;
|
|
7
|
+
on(event: 'change', listener: (latest: T) => void): () => void;
|
|
8
|
+
on(event: Exclude<MotionValueEvent, 'change'>, listener: () => void): () => void;
|
|
9
|
+
on(event: MotionValueEvent, listener: (latest: T) => void): () => void;
|
|
10
|
+
/** Notify change subscribers and any `on('change')` listeners with the current value. */
|
|
11
|
+
emitChange(value: T): void;
|
|
12
|
+
/** Fire a named lifecycle event (e.g. `animationStart`/`animationComplete`). */
|
|
13
|
+
emit(event: Exclude<MotionValueEvent, 'change'>): void;
|
|
14
|
+
/** @internal Driver-port seam: whether any change observer (`subscribe`/`on('change')`) exists. */
|
|
15
|
+
hasChangeObservers(): boolean;
|
|
16
|
+
/** @internal Driver-port seam: whether a branded derived dependent is among the change listeners. */
|
|
17
|
+
hasDerivedDependentObserver(): boolean;
|
|
18
|
+
clear(): void;
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { MotionValueEvent, ValueChangeListener } from "./types.js";
|
|
2
|
+
/** @internal Brand a change listener as a derived value's input subscription. */
|
|
3
|
+
export declare function brandDerivedDependentListener<L extends (...args: never[]) => void>(listener: L): L;
|
|
4
|
+
export declare class Subscriptions<T> {
|
|
5
|
+
#private;
|
|
6
|
+
subscribe(listener: ValueChangeListener<T>): () => void;
|
|
7
|
+
on(event: 'change', listener: (latest: T) => void): () => void;
|
|
8
|
+
on(event: Exclude<MotionValueEvent, 'change'>, listener: () => void): () => void;
|
|
9
|
+
on(event: MotionValueEvent, listener: (latest: T) => void): () => void;
|
|
10
|
+
/** Notify change subscribers and any `on('change')` listeners with the current value. */
|
|
11
|
+
emitChange(value: T): void;
|
|
12
|
+
/** Fire a named lifecycle event (e.g. `animationStart`/`animationComplete`). */
|
|
13
|
+
emit(event: Exclude<MotionValueEvent, 'change'>): void;
|
|
14
|
+
/** @internal Driver-port seam: whether any change observer (`subscribe`/`on('change')`) exists. */
|
|
15
|
+
hasChangeObservers(): boolean;
|
|
16
|
+
/** @internal Driver-port seam: whether a branded derived dependent is among the change listeners. */
|
|
17
|
+
hasDerivedDependentObserver(): boolean;
|
|
18
|
+
clear(): void;
|
|
19
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Shared subscription registry for motion values: value-change listeners (`subscribe`) and named
|
|
2
|
+
// lifecycle events (`on`). Neither path triggers a host render. Extracted so `MotionValueImpl` and
|
|
3
|
+
// `DerivedValue` share one implementation instead of duplicating it (REQ-VALUE-001/002).
|
|
4
|
+
// Derived-dependent branding (REQ-VALUE-015): the MotionValue driver port classifies whether a
|
|
5
|
+
// transported write reached a derived dependent without holding a graph handle. Derived values
|
|
6
|
+
// brand their input subscription so observer presence is queryable without invoking listeners.
|
|
7
|
+
const derivedDependentListeners = new WeakSet();
|
|
8
|
+
/** @internal Brand a change listener as a derived value's input subscription. */
|
|
9
|
+
export function brandDerivedDependentListener(listener) {
|
|
10
|
+
derivedDependentListeners.add(listener);
|
|
11
|
+
return listener;
|
|
12
|
+
}
|
|
13
|
+
export class Subscriptions {
|
|
14
|
+
#change = new Set();
|
|
15
|
+
// `on('change')` listeners take the latest value (the pinned payload, r15 a62d87e5f419);
|
|
16
|
+
// lifecycle listeners take none — one storage, lifecycle emission simply passes nothing.
|
|
17
|
+
#events = new Map();
|
|
18
|
+
subscribe(listener) {
|
|
19
|
+
this.#change.add(listener);
|
|
20
|
+
return () => {
|
|
21
|
+
this.#change.delete(listener);
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
on(event, listener) {
|
|
25
|
+
let listeners = this.#events.get(event);
|
|
26
|
+
if (!listeners) {
|
|
27
|
+
listeners = new Set();
|
|
28
|
+
this.#events.set(event, listeners);
|
|
29
|
+
}
|
|
30
|
+
listeners.add(listener);
|
|
31
|
+
return () => {
|
|
32
|
+
listeners.delete(listener);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Notify change subscribers and any `on('change')` listeners with the current value. */
|
|
36
|
+
emitChange(value) {
|
|
37
|
+
for (const listener of this.#change)
|
|
38
|
+
listener(value);
|
|
39
|
+
const listeners = this.#events.get('change');
|
|
40
|
+
if (listeners)
|
|
41
|
+
for (const listener of listeners)
|
|
42
|
+
listener(value);
|
|
43
|
+
}
|
|
44
|
+
/** Fire a named lifecycle event (e.g. `animationStart`/`animationComplete`). */
|
|
45
|
+
emit(event) {
|
|
46
|
+
const listeners = this.#events.get(event);
|
|
47
|
+
// Lifecycle listeners are payload-free by contract (the overloads above); the stored shape
|
|
48
|
+
// carries T only for the 'change' set.
|
|
49
|
+
if (listeners)
|
|
50
|
+
for (const listener of listeners)
|
|
51
|
+
listener();
|
|
52
|
+
}
|
|
53
|
+
/** @internal Driver-port seam: whether any change observer (`subscribe`/`on('change')`) exists. */
|
|
54
|
+
hasChangeObservers() {
|
|
55
|
+
return this.#change.size > 0 || (this.#events.get('change')?.size ?? 0) > 0;
|
|
56
|
+
}
|
|
57
|
+
/** @internal Driver-port seam: whether a branded derived dependent is among the change listeners. */
|
|
58
|
+
hasDerivedDependentObserver() {
|
|
59
|
+
for (const listener of this.#change) {
|
|
60
|
+
if (derivedDependentListeners.has(listener))
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
clear() {
|
|
66
|
+
this.#change.clear();
|
|
67
|
+
this.#events.clear();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Public surface of the SPEC-UNIVERSAL-SUBSET subsystem: the frozen universal-subset registry and its
|
|
3
|
+
// entry schema types. Host-agnostic (REQ-SUBSET-010) — the native runtime, the web shim, and the
|
|
4
|
+
// conformance runner all read this one object. Re-exported additively from the core index. The loud-fail
|
|
5
|
+
// resolver (Milestone 2) and the normalizer (Milestone 3) join this surface as they land.
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.normalizeCapability = exports.epsilonFor = exports.resolveTargetCapabilities = exports.resolveCapability = exports.hostCapabilities = exports.UnsupportedCapabilityError = exports.UNIVERSAL_SUBSET = void 0;
|
|
8
|
+
var registry_1 = require("./registry.cjs");
|
|
9
|
+
Object.defineProperty(exports, "UNIVERSAL_SUBSET", { enumerable: true, get: function () { return registry_1.UNIVERSAL_SUBSET; } });
|
|
10
|
+
// Loud-fail capability resolver (REQ-SUBSET-014): the fail-closed subset-membership gate + typed error.
|
|
11
|
+
// The verdict-shaped resolution/normalization forms are CORE-INTERNAL (M3 r8 major
|
|
12
|
+
// 4a5b686f1f5f): the ratified SUBSET public seam enumerates the throwing APIs only —
|
|
13
|
+
// internal consumers import the verdicts from the modules directly.
|
|
14
|
+
var resolve_1 = require("./resolve.cjs");
|
|
15
|
+
Object.defineProperty(exports, "UnsupportedCapabilityError", { enumerable: true, get: function () { return resolve_1.UnsupportedCapabilityError; } });
|
|
16
|
+
Object.defineProperty(exports, "hostCapabilities", { enumerable: true, get: function () { return resolve_1.hostCapabilities; } });
|
|
17
|
+
Object.defineProperty(exports, "resolveCapability", { enumerable: true, get: function () { return resolve_1.resolveCapability; } });
|
|
18
|
+
Object.defineProperty(exports, "resolveTargetCapabilities", { enumerable: true, get: function () { return resolve_1.resolveTargetCapabilities; } });
|
|
19
|
+
// Per-value-type normalizer + epsilon floors (REQ-SUBSET-016): reduces engine-native encodings to one
|
|
20
|
+
// comparable value, delegating to VALUE-TYPES (one parser, one set of floors).
|
|
21
|
+
var normalize_1 = require("./normalize.cjs");
|
|
22
|
+
Object.defineProperty(exports, "epsilonFor", { enumerable: true, get: function () { return normalize_1.epsilonFor; } });
|
|
23
|
+
Object.defineProperty(exports, "normalizeCapability", { enumerable: true, get: function () { return normalize_1.normalizeCapability; } });
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { UNIVERSAL_SUBSET } from "./registry.cjs";
|
|
2
|
+
export type { CapabilityClass, CapabilityEntry, ReferenceDisposition, ShippingStatus, Universality, ValueType, WebEnginePath, } from "./registry.cjs";
|
|
3
|
+
export { UnsupportedCapabilityError, hostCapabilities, resolveCapability, resolveTargetCapabilities, } from "./resolve.cjs";
|
|
4
|
+
export type { HostCapabilities, UnsupportedReason } from "./resolve.cjs";
|
|
5
|
+
export { epsilonFor, normalizeCapability } from "./normalize.cjs";
|
|
6
|
+
export type { NormalizedValue } from "./normalize.cjs";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { UNIVERSAL_SUBSET } from "./registry.js";
|
|
2
|
+
export type { CapabilityClass, CapabilityEntry, ReferenceDisposition, ShippingStatus, Universality, ValueType, WebEnginePath, } from "./registry.js";
|
|
3
|
+
export { UnsupportedCapabilityError, hostCapabilities, resolveCapability, resolveTargetCapabilities, } from "./resolve.js";
|
|
4
|
+
export type { HostCapabilities, UnsupportedReason } from "./resolve.js";
|
|
5
|
+
export { epsilonFor, normalizeCapability } from "./normalize.js";
|
|
6
|
+
export type { NormalizedValue } from "./normalize.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Public surface of the SPEC-UNIVERSAL-SUBSET subsystem: the frozen universal-subset registry and its
|
|
2
|
+
// entry schema types. Host-agnostic (REQ-SUBSET-010) — the native runtime, the web shim, and the
|
|
3
|
+
// conformance runner all read this one object. Re-exported additively from the core index. The loud-fail
|
|
4
|
+
// resolver (Milestone 2) and the normalizer (Milestone 3) join this surface as they land.
|
|
5
|
+
export { UNIVERSAL_SUBSET } from "./registry.js";
|
|
6
|
+
// Loud-fail capability resolver (REQ-SUBSET-014): the fail-closed subset-membership gate + typed error.
|
|
7
|
+
// The verdict-shaped resolution/normalization forms are CORE-INTERNAL (M3 r8 major
|
|
8
|
+
// 4a5b686f1f5f): the ratified SUBSET public seam enumerates the throwing APIs only —
|
|
9
|
+
// internal consumers import the verdicts from the modules directly.
|
|
10
|
+
export { UnsupportedCapabilityError, hostCapabilities, resolveCapability, resolveTargetCapabilities, } from "./resolve.js";
|
|
11
|
+
// Per-value-type normalizer + epsilon floors (REQ-SUBSET-016): reduces engine-native encodings to one
|
|
12
|
+
// comparable value, delegating to VALUE-TYPES (one parser, one set of floors).
|
|
13
|
+
export { epsilonFor, normalizeCapability } from "./normalize.js";
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// SPEC-UNIVERSAL-SUBSET §3 — the per-value-type normalizer + epsilon floors (REQ-SUBSET-016). Reduces an
|
|
3
|
+
// engine-native encoding (RN unitless dp number, web `px`/`deg`/`rad` string, any color syntax) to ONE
|
|
4
|
+
// comparable value per value type so the cross-engine comparator compares like for like. It DELEGATES to
|
|
5
|
+
// VALUE-TYPES for every parse and for the epsilon floors — there is exactly one parser and one set of
|
|
6
|
+
// floors in the codebase (REQ-SUBSET-016 "adopted verbatim"); this file re-declares neither.
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.normalizeCapabilityVerdict = normalizeCapabilityVerdict;
|
|
9
|
+
exports.normalizeCapability = normalizeCapability;
|
|
10
|
+
exports.epsilonFor = epsilonFor;
|
|
11
|
+
const value_types_1 = require("../value-types/index.cjs");
|
|
12
|
+
const color_1 = require("../value-types/color.cjs");
|
|
13
|
+
const numeric_1 = require("../value-types/numeric.cjs");
|
|
14
|
+
const DEG_PER_RAD = 180 / Math.PI;
|
|
15
|
+
// The units each numeric value type may carry and still reduce to ONE comparable value. `''` is the
|
|
16
|
+
// engine-native unitless encoding (RN dp for length, a bare scalar for unitless, degrees for angle).
|
|
17
|
+
// length is px + dp ONLY — vw/vh/% are viewport-/parent-relative and NOT px-comparable without a layout
|
|
18
|
+
// context (they are excluded from the guaranteed subset, REQ-SUBSET-011/§8); an angle unit on a length
|
|
19
|
+
// (or vice versa) is a category error. Anything outside the set fails loud rather than being silently
|
|
20
|
+
// reduced to a bare number that would make incompatible encodings compare equal (REQ-SUBSET-016).
|
|
21
|
+
const COMPATIBLE_UNITS = {
|
|
22
|
+
length: new Set(['', 'px']),
|
|
23
|
+
unitless: new Set(['']),
|
|
24
|
+
angle: new Set(['', 'deg', 'rad']),
|
|
25
|
+
};
|
|
26
|
+
const refuseNormalize = (message) => ({
|
|
27
|
+
value: null,
|
|
28
|
+
refusal: new Error(message),
|
|
29
|
+
});
|
|
30
|
+
function scalarVerdict(kind, engineValue) {
|
|
31
|
+
if (typeof engineValue === 'number')
|
|
32
|
+
return { value: engineValue, refusal: null };
|
|
33
|
+
const parsed = (0, numeric_1.parseNumericVerdict)(engineValue);
|
|
34
|
+
if (parsed.refusal !== null)
|
|
35
|
+
return refuseNormalize(parsed.refusal);
|
|
36
|
+
const { value, unit } = parsed.parsed;
|
|
37
|
+
if (!COMPATIBLE_UNITS[kind].has(unit)) {
|
|
38
|
+
const allowed = [...COMPATIBLE_UNITS[kind]].map((u) => u || '<unitless>').join(', ');
|
|
39
|
+
return refuseNormalize(`Incompatible unit '${unit}' for a ${kind} value ${JSON.stringify(engineValue)}: it cannot be ` +
|
|
40
|
+
`normalized as ${kind} (compatible units: ${allowed}).`);
|
|
41
|
+
}
|
|
42
|
+
return { value: kind === 'angle' && unit === 'rad' ? value * DEG_PER_RAD : value, refusal: null };
|
|
43
|
+
}
|
|
44
|
+
function normalizeCapabilityVerdict(entry, engineValue) {
|
|
45
|
+
switch (entry.valueType) {
|
|
46
|
+
case 'length':
|
|
47
|
+
case 'unitless':
|
|
48
|
+
case 'angle':
|
|
49
|
+
return scalarVerdict(entry.valueType, engineValue);
|
|
50
|
+
case 'rgba': {
|
|
51
|
+
const parsed = (0, color_1.parseColorVerdict)(String(engineValue));
|
|
52
|
+
return parsed.refusal !== null
|
|
53
|
+
? refuseNormalize(parsed.refusal)
|
|
54
|
+
: { value: parsed.parsed, refusal: null };
|
|
55
|
+
}
|
|
56
|
+
case 'complex':
|
|
57
|
+
case 'gesture':
|
|
58
|
+
case 'discrete':
|
|
59
|
+
// Web-only compositor effects (complex), gestures, and discrete keywords (T23 B — compared
|
|
60
|
+
// by exact keyword equality, never a normalized scalar) have no cross-engine numeric value to
|
|
61
|
+
// compare — asking for one is a caller error, not a silent zero. Fail loud (REQ-SUBSET-014 ethos).
|
|
62
|
+
return refuseNormalize(`Capability '${entry.keys[0]}' (valueType '${entry.valueType}') has no cross-engine ` +
|
|
63
|
+
`normalized value — it is not a comparable animated scalar/color.`);
|
|
64
|
+
}
|
|
65
|
+
return refuseNormalize(`unreachable valueType`);
|
|
66
|
+
}
|
|
67
|
+
function normalizeCapability(entry, engineValue) {
|
|
68
|
+
const verdict = normalizeCapabilityVerdict(entry, engineValue);
|
|
69
|
+
if (verdict.refusal !== null)
|
|
70
|
+
throw verdict.refusal;
|
|
71
|
+
return verdict.value;
|
|
72
|
+
}
|
|
73
|
+
// The comparison epsilon FLOOR for a value type, imported verbatim from VALUE-TYPES (REQ-SUBSET-016).
|
|
74
|
+
// complex/gesture have no comparison tolerance — requesting one is a loud failure, not a default.
|
|
75
|
+
function epsilonFor(valueType) {
|
|
76
|
+
switch (valueType) {
|
|
77
|
+
case 'length':
|
|
78
|
+
return value_types_1.EPS_LENGTH;
|
|
79
|
+
case 'unitless':
|
|
80
|
+
return value_types_1.EPS_UNITLESS;
|
|
81
|
+
case 'angle':
|
|
82
|
+
return value_types_1.EPS_ANGLE;
|
|
83
|
+
case 'rgba':
|
|
84
|
+
return value_types_1.EPS_COLOR_CHANNEL;
|
|
85
|
+
case 'complex':
|
|
86
|
+
case 'gesture':
|
|
87
|
+
case 'discrete':
|
|
88
|
+
throw new Error(`valueType '${valueType}' has no comparison epsilon — it is not a cross-engine numeric value.`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RGBA } from "../value-types/color.cjs";
|
|
2
|
+
import type { CapabilityEntry, ValueType } from "./registry.cjs";
|
|
3
|
+
export type NormalizedValue = number | RGBA;
|
|
4
|
+
export interface NormalizeVerdict {
|
|
5
|
+
readonly value: NormalizedValue | null;
|
|
6
|
+
readonly refusal: Error | null;
|
|
7
|
+
}
|
|
8
|
+
export declare function normalizeCapabilityVerdict(entry: CapabilityEntry, engineValue: string | number): NormalizeVerdict;
|
|
9
|
+
export declare function normalizeCapability(entry: CapabilityEntry, engineValue: string | number): NormalizedValue;
|
|
10
|
+
export declare function epsilonFor(valueType: ValueType): number;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RGBA } from "../value-types/color.js";
|
|
2
|
+
import type { CapabilityEntry, ValueType } from "./registry.js";
|
|
3
|
+
export type NormalizedValue = number | RGBA;
|
|
4
|
+
export interface NormalizeVerdict {
|
|
5
|
+
readonly value: NormalizedValue | null;
|
|
6
|
+
readonly refusal: Error | null;
|
|
7
|
+
}
|
|
8
|
+
export declare function normalizeCapabilityVerdict(entry: CapabilityEntry, engineValue: string | number): NormalizeVerdict;
|
|
9
|
+
export declare function normalizeCapability(entry: CapabilityEntry, engineValue: string | number): NormalizedValue;
|
|
10
|
+
export declare function epsilonFor(valueType: ValueType): number;
|