@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,36 @@
|
|
|
1
|
+
'worklet';
|
|
2
|
+
// @frame-path — executes inside the UI-runtime frame step; allocation-gated (REQ-DRIVER-021).
|
|
3
|
+
// SPEC-LAYOUT §2 — the plain geometric data the projection engine operates on. Host-agnostic: a `Rect` is a
|
|
4
|
+
// bounding box in its projection frame, fed in by the host measurement adapter as plain numbers; `core` never
|
|
5
|
+
// imports a Fabric measure API (REQ-CORE-003). Projection sessions may operate in any caller-chosen coherent
|
|
6
|
+
// frame; layout identity is stricter and accepts only branded common/window-space rects so cross-container
|
|
7
|
+
// FLIP cannot accidentally compare parent-relative coordinates (REQ-LAYOUT-020). A `Transform` is the
|
|
8
|
+
// transform-ONLY correction the substrate executes — translate + scale, no rotation/skew at M1 (the FLIP
|
|
9
|
+
// correction never mutates layout-authoritative style, REQ-LAYOUT-001).
|
|
10
|
+
const WINDOW_SPACE_RECT = Symbol('WindowSpaceRect');
|
|
11
|
+
function requireFiniteWindowSpaceField(rect, field) {
|
|
12
|
+
'worklet';
|
|
13
|
+
const value = rect[field];
|
|
14
|
+
if (!Number.isFinite(value)) {
|
|
15
|
+
throw new Error(`layout identity: window-space rect field '${field}' is ${JSON.stringify(value)}; ` +
|
|
16
|
+
'a finite number is required before entering the projection registry.');
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Brand a host-validated common-space measurement for the layoutId registry. Host bindings own the
|
|
22
|
+
* measurement primitive; core owns the unrepresentable-if-raw registry contract.
|
|
23
|
+
*/
|
|
24
|
+
// alloc-ok: js-thread — measure-commit conversion on the JS side (onLayout path), never per frame.
|
|
25
|
+
export function toWindowSpaceRect(rect) {
|
|
26
|
+
'worklet';
|
|
27
|
+
const x = requireFiniteWindowSpaceField(rect, 'x');
|
|
28
|
+
const y = requireFiniteWindowSpaceField(rect, 'y');
|
|
29
|
+
const width = requireFiniteWindowSpaceField(rect, 'width');
|
|
30
|
+
const height = requireFiniteWindowSpaceField(rect, 'height');
|
|
31
|
+
if (width <= 0 || height <= 0) {
|
|
32
|
+
throw new Error(`layout identity: window-space rect dimensions must be positive before projection ` +
|
|
33
|
+
`(width=${width}, height=${height}).`);
|
|
34
|
+
}
|
|
35
|
+
return { x, y, width, height };
|
|
36
|
+
}
|
|
@@ -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,201 @@
|
|
|
1
|
+
'worklet';
|
|
2
|
+
// @frame-path — executes inside the UI-runtime frame step; allocation-gated (REQ-DRIVER-021).
|
|
3
|
+
// Duration+ease timing (tween) generator (SPEC-SPRING REQ-TIMING-001/002). value = mix(origin, target,
|
|
4
|
+
// ease(clamp(0,1,t/duration))); velocity is a 5ms finite difference of that curve; done at t ≥ duration;
|
|
5
|
+
// incoming velocity is ignored. Host-agnostic (REQ-CORE-003): the cubic-bezier evaluator is an exact
|
|
6
|
+
// port of motion-utils' Gaëtan Renaudeau BezierEasing (binary subdivision), NOT imported. Numerics are
|
|
7
|
+
// pinned to motion@12.42.2 and proven by golden parity in timing.test.ts.
|
|
8
|
+
//
|
|
9
|
+
// Worklet safety (hytd3r / Remote Function crash class): NEVER store named-easing evaluators as
|
|
10
|
+
// module-init function values. reverseEasing/mirrorEasing wrappers and cubicBezier returns built at
|
|
11
|
+
// load time lacked __workletHash and crashed when UI-runtime call sites invoked them. All named and
|
|
12
|
+
// cubic evaluation is pure math over number tables; resolveEasing returns thin closures that capture
|
|
13
|
+
// only serializable primitives (name string or control points).
|
|
14
|
+
// Runtime-readable named set (validate / keyframeTiming / shippedSurface share this authority).
|
|
15
|
+
export const NAMED_EASING_KEYS = [
|
|
16
|
+
'linear',
|
|
17
|
+
'easeIn',
|
|
18
|
+
'easeOut',
|
|
19
|
+
'easeInOut',
|
|
20
|
+
'circIn',
|
|
21
|
+
'circOut',
|
|
22
|
+
'circInOut',
|
|
23
|
+
'backIn',
|
|
24
|
+
'backOut',
|
|
25
|
+
'backInOut',
|
|
26
|
+
'anticipate',
|
|
27
|
+
];
|
|
28
|
+
const DEFAULT_DURATION_MS = 300; // REQ-TIMING-002 (0.3s)
|
|
29
|
+
const DEFAULT_EASE = [0.25, 0.1, 0.35, 1]; // REQ-TIMING-002
|
|
30
|
+
const VELOCITY_SAMPLE_MS = 5;
|
|
31
|
+
// Named cubic-bezier control points (motion-utils/easing/ease + back). Data only — no function values.
|
|
32
|
+
const EASE_IN = [0.42, 0, 1, 1];
|
|
33
|
+
const EASE_OUT = [0, 0, 0.58, 1];
|
|
34
|
+
const EASE_IN_OUT = [0.42, 0, 0.58, 1];
|
|
35
|
+
// motion-utils/easing/back.ts: backOut = cubicBezier(0.33, 1.53, 0.69, 0.99)
|
|
36
|
+
const BACK_OUT = [0.33, 1.53, 0.69, 0.99];
|
|
37
|
+
const clamp01 = (v) => Math.max(0, Math.min(1, v));
|
|
38
|
+
const mix = (from, to, p) => from + (to - from) * p;
|
|
39
|
+
const SUBDIVISION_PRECISION = 1e-7;
|
|
40
|
+
const SUBDIVISION_MAX_ITERATIONS = 12;
|
|
41
|
+
// x(t)/y(t) of the bezier given a single control-point pair (exact port of motion-utils calcBezier).
|
|
42
|
+
const calcBezier = (t, a1, a2) => ((1 - 3 * a2 + 3 * a1) * t + (3 * a2 - 6 * a1)) * t * t + 3 * a1 * t;
|
|
43
|
+
/** Pure cubic-bezier sample — no nested function allocation at module init (hytd3r). */
|
|
44
|
+
function evaluateCubic(x1, y1, x2, y2, t) {
|
|
45
|
+
if (t === 0 || t === 1)
|
|
46
|
+
return t;
|
|
47
|
+
// Linear gradient is the identity; also avoids a degenerate subdivision.
|
|
48
|
+
if (x1 === y1 && x2 === y2)
|
|
49
|
+
return t;
|
|
50
|
+
let lower = 0;
|
|
51
|
+
let upper = 1;
|
|
52
|
+
let currentT = 0;
|
|
53
|
+
let currentX = 0;
|
|
54
|
+
let i = 0;
|
|
55
|
+
do {
|
|
56
|
+
currentT = lower + (upper - lower) / 2;
|
|
57
|
+
currentX = calcBezier(currentT, x1, x2) - t;
|
|
58
|
+
if (currentX > 0)
|
|
59
|
+
upper = currentT;
|
|
60
|
+
else
|
|
61
|
+
lower = currentT;
|
|
62
|
+
} while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS);
|
|
63
|
+
return calcBezier(currentT, y1, y2);
|
|
64
|
+
}
|
|
65
|
+
// motion-utils/easing/circ.ts — pure forms of reverse/mirror so no function-valued table.
|
|
66
|
+
function evaluateCircIn(p) {
|
|
67
|
+
return 1 - Math.sin(Math.acos(clamp01(p)));
|
|
68
|
+
}
|
|
69
|
+
function evaluateCircOut(p) {
|
|
70
|
+
// reverse(circIn): 1 - circIn(1 - p)
|
|
71
|
+
return 1 - evaluateCircIn(1 - p);
|
|
72
|
+
}
|
|
73
|
+
function evaluateCircInOut(p) {
|
|
74
|
+
// mirror(circIn)
|
|
75
|
+
return p <= 0.5 ? evaluateCircIn(2 * p) / 2 : (2 - evaluateCircIn(2 * (1 - p))) / 2;
|
|
76
|
+
}
|
|
77
|
+
function evaluateBackOut(p) {
|
|
78
|
+
return evaluateCubic(BACK_OUT[0], BACK_OUT[1], BACK_OUT[2], BACK_OUT[3], p);
|
|
79
|
+
}
|
|
80
|
+
function evaluateBackIn(p) {
|
|
81
|
+
// reverse(backOut)
|
|
82
|
+
return 1 - evaluateBackOut(1 - p);
|
|
83
|
+
}
|
|
84
|
+
function evaluateBackInOut(p) {
|
|
85
|
+
// mirror(backIn)
|
|
86
|
+
return p <= 0.5 ? evaluateBackIn(2 * p) / 2 : (2 - evaluateBackIn(2 * (1 - p))) / 2;
|
|
87
|
+
}
|
|
88
|
+
// motion-utils/easing/anticipate.ts
|
|
89
|
+
function evaluateAnticipate(p) {
|
|
90
|
+
if (p >= 1)
|
|
91
|
+
return 1;
|
|
92
|
+
const doubled = p * 2;
|
|
93
|
+
return doubled < 1 ? 0.5 * evaluateBackIn(doubled) : 0.5 * (2 - Math.pow(2, -10 * (doubled - 1)));
|
|
94
|
+
}
|
|
95
|
+
const NAMED_EASING_SET = new Set(NAMED_EASING_KEYS);
|
|
96
|
+
function isNamedEasing(value) {
|
|
97
|
+
return NAMED_EASING_SET.has(value);
|
|
98
|
+
}
|
|
99
|
+
/** Pure named-easing sample — captures no function values (hytd3r Remote Function law). */
|
|
100
|
+
function evaluateNamedEasing(name, p) {
|
|
101
|
+
switch (name) {
|
|
102
|
+
case 'linear':
|
|
103
|
+
return p;
|
|
104
|
+
case 'easeIn':
|
|
105
|
+
return evaluateCubic(EASE_IN[0], EASE_IN[1], EASE_IN[2], EASE_IN[3], p);
|
|
106
|
+
case 'easeOut':
|
|
107
|
+
return evaluateCubic(EASE_OUT[0], EASE_OUT[1], EASE_OUT[2], EASE_OUT[3], p);
|
|
108
|
+
case 'easeInOut':
|
|
109
|
+
return evaluateCubic(EASE_IN_OUT[0], EASE_IN_OUT[1], EASE_IN_OUT[2], EASE_IN_OUT[3], p);
|
|
110
|
+
case 'circIn':
|
|
111
|
+
return evaluateCircIn(p);
|
|
112
|
+
case 'circOut':
|
|
113
|
+
return evaluateCircOut(p);
|
|
114
|
+
case 'circInOut':
|
|
115
|
+
return evaluateCircInOut(p);
|
|
116
|
+
case 'backIn':
|
|
117
|
+
return evaluateBackIn(p);
|
|
118
|
+
case 'backOut':
|
|
119
|
+
return evaluateBackOut(p);
|
|
120
|
+
case 'backInOut':
|
|
121
|
+
return evaluateBackInOut(p);
|
|
122
|
+
case 'anticipate':
|
|
123
|
+
return evaluateAnticipate(p);
|
|
124
|
+
default: {
|
|
125
|
+
// Exhaustiveness + runtime cast guard (9a4arp): never sample NaN for an unknown name.
|
|
126
|
+
const _exhaustive = name;
|
|
127
|
+
throw new Error(`unknown named easing '${String(_exhaustive)}'`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// alloc-ok: lifecycle-edge — easing resolution at factory time. Exported for the multi-keyframe
|
|
132
|
+
// generator (keyframes.ts, REQ-API-033), which resolves ONE easing per segment through this same
|
|
133
|
+
// exact BezierEasing / Motion-named port — never a second easing vocabulary.
|
|
134
|
+
// Closures capture only primitives (name string or four numbers) so worklet serialization never
|
|
135
|
+
// embeds a JS-thread Remote Function (hytd3r).
|
|
136
|
+
// Fail-closed (9a4arp): unknown named strings throw before any sample can emit NaN. The public
|
|
137
|
+
// validateTransition boundary also refuses them; this is the generator-seam backstop for direct
|
|
138
|
+
// timingGenerator / resolveEasing callers.
|
|
139
|
+
export function resolveEasing(ease) {
|
|
140
|
+
if (typeof ease === 'string') {
|
|
141
|
+
if (!isNamedEasing(ease)) {
|
|
142
|
+
throw new Error(`unknown named easing '${ease}' — expected one of ${NAMED_EASING_KEYS.join(', ')} ` +
|
|
143
|
+
'(REQ-TIMING-001 / REQ-API-033)');
|
|
144
|
+
}
|
|
145
|
+
const name = ease;
|
|
146
|
+
// Named returned closures (frame-path manifest anonymous-sampler repair): the qualified
|
|
147
|
+
// symbols are resolveEasing.namedEvaluator / resolveEasing.cubicEvaluator.
|
|
148
|
+
return function namedEvaluator(p) {
|
|
149
|
+
return evaluateNamedEasing(name, p);
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const x1 = ease[0];
|
|
153
|
+
const y1 = ease[1];
|
|
154
|
+
const x2 = ease[2];
|
|
155
|
+
const y2 = ease[3];
|
|
156
|
+
return function cubicEvaluator(p) {
|
|
157
|
+
return evaluateCubic(x1, y1, x2, y2, p);
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
// Disambiguate a PER-SEGMENT easing list (`['easeIn', 'easeOut']`, `[[…],[…]]`) from a SINGLE
|
|
161
|
+
// cubic-bezier easing (`[0.25, 0.1, 0.35, 1]`) — both are arrays. A per-segment list's elements are
|
|
162
|
+
// themselves easings (a named string or a nested bezier array); a single CubicBezier's elements are
|
|
163
|
+
// plain numbers. Mirrors motion-dom's `isEasingArray`. Without this, a single-bezier `ease` on a
|
|
164
|
+
// keyframe array would be mis-read element-by-element as per-segment (REQ-API-033).
|
|
165
|
+
export function isEasingList(ease) {
|
|
166
|
+
return Array.isArray(ease) && typeof ease[0] !== 'number';
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Build a tween `GeneratorFactory` (REQ-API-004). `seed.from` is the origin; the seed's incoming
|
|
170
|
+
* velocity is intentionally ignored (REQ-TIMING-001). The tween finishes deterministically at
|
|
171
|
+
* `t ≥ duration`.
|
|
172
|
+
*/
|
|
173
|
+
// alloc-ok: lifecycle-edge — the factory resolves duration/ease once per animate command;
|
|
174
|
+
// per-frame work is sample() below, which mutates the reused record and allocates nothing.
|
|
175
|
+
export function timingGenerator(target, config) {
|
|
176
|
+
const duration = config?.duration ?? DEFAULT_DURATION_MS;
|
|
177
|
+
const ease = resolveEasing(config?.ease ?? DEFAULT_EASE);
|
|
178
|
+
// alloc-ok: lifecycle-edge — generator construction at command time; the one reused sample
|
|
179
|
+
// record is built here, never per frame.
|
|
180
|
+
return (seed) => {
|
|
181
|
+
const origin = seed.from;
|
|
182
|
+
const value = (t) => mix(origin, target, ease(clamp01(t / duration)));
|
|
183
|
+
// One sample record per generator, mutated and returned every call — motion's own generators
|
|
184
|
+
// return their reused `state` the same way. Per-frame sampling must not allocate
|
|
185
|
+
// (REQ-DRIVER-015); callers read the scalars immediately and never retain the record.
|
|
186
|
+
const out = { value: origin, velocity: 0, done: false };
|
|
187
|
+
return {
|
|
188
|
+
sample(elapsedMs) {
|
|
189
|
+
const current = value(elapsedMs);
|
|
190
|
+
const prevT = Math.max(elapsedMs - VELOCITY_SAMPLE_MS, 0);
|
|
191
|
+
const dt = elapsedMs - prevT;
|
|
192
|
+
const velocity = dt ? ((current - value(prevT)) / dt) * 1000 : 0; // px/s (REQ-TIMING-001)
|
|
193
|
+
const done = elapsedMs >= duration;
|
|
194
|
+
out.value = done ? target : current;
|
|
195
|
+
out.velocity = velocity;
|
|
196
|
+
out.done = done;
|
|
197
|
+
return out;
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// @frame-path — executes inside the UI-runtime frame step; allocation-gated (REQ-DRIVER-021).
|
|
3
|
+
// UI-runtime layout entry — the source of truth for the `./worklet-layout` subpath export.
|
|
4
|
+
// Re-exports exactly the layout-identity crossing chain a UI worklet may execute (registry/session +
|
|
5
|
+
// the spring/projection callees they reach) plus the types callers need to hold them. Building this
|
|
6
|
+
// from src (zshy emits dist/worklet-layout.{js,d.ts}; `tsgo -b` emits the same on a bare checkout)
|
|
7
|
+
// keeps the subpath resolvable without a prior package build — the review-found false green where
|
|
8
|
+
// only a previously-built dist made `nub run check` pass.
|
|
9
|
+
//
|
|
10
|
+
// This file stays pragma-free (core src never carries 'worklet' — the isolation gate's law); the
|
|
11
|
+
// worklet tagging is post-build only: scripts/tag-worklet-dist.mjs redirects the emitted barrel at
|
|
12
|
+
// FILE-level-tagged copies under dist/worklet-layout/. JS-thread callers keep importing the package
|
|
13
|
+
// root, whose dist stays function-tagged for the dual-use modules.
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.createLayoutIdentityRegistry = exports.createProjectionSession = exports.createProjectionTree = exports.isIdentity = exports.applyTransformToRect = exports.projectAtProgress = exports.invert = exports.IDENTITY_TRANSFORM = exports.MAX_VELOCITY_DELTA_MS = exports.DT_CLAMP_MS = exports.EPSILON_VEL = exports.EPSILON_SCALE = exports.EPSILON_POS = exports.toWindowSpaceRect = exports.retargetSpring = exports.resolveSpringGenerator = exports.resolveSpring = void 0;
|
|
16
|
+
var transition_1 = require("./transition.cjs");
|
|
17
|
+
Object.defineProperty(exports, "resolveSpring", { enumerable: true, get: function () { return transition_1.resolveSpring; } });
|
|
18
|
+
Object.defineProperty(exports, "resolveSpringGenerator", { enumerable: true, get: function () { return transition_1.resolveSpringGenerator; } });
|
|
19
|
+
Object.defineProperty(exports, "retargetSpring", { enumerable: true, get: function () { return transition_1.retargetSpring; } });
|
|
20
|
+
var types_1 = require("./layout/types.cjs");
|
|
21
|
+
Object.defineProperty(exports, "toWindowSpaceRect", { enumerable: true, get: function () { return types_1.toWindowSpaceRect; } });
|
|
22
|
+
// accountForLayoutScroll (REQ-LAYOUT-022) is package-root only this rung: the layoutScroll
|
|
23
|
+
// measure correction runs on the JS measure-commit path, not inside the UI frame step.
|
|
24
|
+
var constants_1 = require("./layout/constants.cjs");
|
|
25
|
+
Object.defineProperty(exports, "EPSILON_POS", { enumerable: true, get: function () { return constants_1.EPSILON_POS; } });
|
|
26
|
+
Object.defineProperty(exports, "EPSILON_SCALE", { enumerable: true, get: function () { return constants_1.EPSILON_SCALE; } });
|
|
27
|
+
Object.defineProperty(exports, "EPSILON_VEL", { enumerable: true, get: function () { return constants_1.EPSILON_VEL; } });
|
|
28
|
+
// REQ-CLOCK-003: the UI-runtime graph adapter clamps per-frame elapsed with CORE's number, not a
|
|
29
|
+
// fork — a background/long-frame gap must not jump a layout flight forward unbounded.
|
|
30
|
+
var constants_2 = require("./config/constants.cjs");
|
|
31
|
+
Object.defineProperty(exports, "DT_CLAMP_MS", { enumerable: true, get: function () { return constants_2.DT_CLAMP_MS; } });
|
|
32
|
+
Object.defineProperty(exports, "MAX_VELOCITY_DELTA_MS", { enumerable: true, get: function () { return constants_2.MAX_VELOCITY_DELTA_MS; } });
|
|
33
|
+
var projection_1 = require("./layout/projection.cjs");
|
|
34
|
+
Object.defineProperty(exports, "IDENTITY_TRANSFORM", { enumerable: true, get: function () { return projection_1.IDENTITY_TRANSFORM; } });
|
|
35
|
+
Object.defineProperty(exports, "invert", { enumerable: true, get: function () { return projection_1.invert; } });
|
|
36
|
+
Object.defineProperty(exports, "projectAtProgress", { enumerable: true, get: function () { return projection_1.projectAtProgress; } });
|
|
37
|
+
Object.defineProperty(exports, "applyTransformToRect", { enumerable: true, get: function () { return projection_1.applyTransformToRect; } });
|
|
38
|
+
Object.defineProperty(exports, "isIdentity", { enumerable: true, get: function () { return projection_1.isIdentity; } });
|
|
39
|
+
// M3.5 component projection tree. This stays on the worklet-only layout surface: native owns
|
|
40
|
+
// UI-runtime records and calls the pure tree during its existing frame callback; the public core
|
|
41
|
+
// root exports the same host-agnostic types for deterministic unit coverage.
|
|
42
|
+
var tree_1 = require("./layout/tree.cjs");
|
|
43
|
+
Object.defineProperty(exports, "createProjectionTree", { enumerable: true, get: function () { return tree_1.createProjectionTree; } });
|
|
44
|
+
var session_1 = require("./layout/session.cjs");
|
|
45
|
+
Object.defineProperty(exports, "createProjectionSession", { enumerable: true, get: function () { return session_1.createProjectionSession; } });
|
|
46
|
+
var identity_1 = require("./layout/identity.cjs");
|
|
47
|
+
Object.defineProperty(exports, "createLayoutIdentityRegistry", { enumerable: true, get: function () { return identity_1.createLayoutIdentityRegistry; } });
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type { ActivityHandle, Animation, Generator, GeneratorFactory, MotionGraph, MotionValue, MotionValueEvent, ValueChangeListener, } from "./types.cjs";
|
|
2
|
+
export { resolveSpring, resolveSpringGenerator, retargetSpring } from "./transition.cjs";
|
|
3
|
+
export type { SpringConfig, ResolvedSpring } from "./transition.cjs";
|
|
4
|
+
export type { TimingConfig, Easing, NamedEasing, CubicBezier } from "./timing.cjs";
|
|
5
|
+
export { toWindowSpaceRect } from "./layout/types.cjs";
|
|
6
|
+
export type { Rect, Transform, LayoutIdToken, WindowSpaceRect } from "./layout/types.cjs";
|
|
7
|
+
export { EPSILON_POS, EPSILON_SCALE, EPSILON_VEL } from "./layout/constants.cjs";
|
|
8
|
+
export { DT_CLAMP_MS, MAX_VELOCITY_DELTA_MS } from "./config/constants.cjs";
|
|
9
|
+
export { IDENTITY_TRANSFORM, invert, projectAtProgress, applyTransformToRect, isIdentity, } from "./layout/projection.cjs";
|
|
10
|
+
export { createProjectionTree } from "./layout/tree.cjs";
|
|
11
|
+
export type { ProjectionNodeOutput, ProjectionStaleNodeCode, ProjectionStaleNodeReport, ProjectionTree, ProjectionTreeNode, ProjectionTreeOptions, } from "./layout/tree.cjs";
|
|
12
|
+
export { createProjectionSession } from "./layout/session.cjs";
|
|
13
|
+
export type { MutableRect, ProjectionSession, ProjectionSessionOptions, ProjectionSessionState, RectVelocity, } from "./layout/session.cjs";
|
|
14
|
+
export { createLayoutIdentityRegistry } from "./layout/identity.cjs";
|
|
15
|
+
export type { IdentityEnterResult, IdentityHolderHandle, IdentityHolderWrite, IdentityRead, LayoutIdentityFlightOptions, LayoutIdentityRegistry, LayoutIdentityRegistryOptions, LayoutIdentityRole, } from "./layout/identity.cjs";
|