@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,24 @@
|
|
|
1
|
+
// SPEC-LAYOUT REQ-LAYOUT-022 — pure scroll-offset accounting for layout measurement.
|
|
2
|
+
// Pin removeElementScroll (motion@12.42.2 create-projection-node): a viewport/window-space
|
|
3
|
+
// layout box is recovered into content space by ADDING each layoutScroll ancestor's scroll
|
|
4
|
+
// offset (HTMLProjectionNode measureScroll = instance.scrollLeft/scrollTop container scroll;
|
|
5
|
+
// DocumentProjectionNode is the always-on window root). Host-agnostic pure math: the binding
|
|
6
|
+
// supplies offsets when available; closed RN hosts that are not scroll containers contribute
|
|
7
|
+
// {0,0} this rung (stack+math only — REQ-LAYOUT-022(c)). Worklet-safe: plain number math only.
|
|
8
|
+
/**
|
|
9
|
+
* Convert a viewport/window-space rect into content-space by adding every layoutScroll
|
|
10
|
+
* ancestor's offset (pin removeElementScroll). Zero offsets leave the rect unchanged.
|
|
11
|
+
* Offsets apply in path order (root → leaf); cumulative add is commutative for pure
|
|
12
|
+
* translation so order is documented, not load-bearing for scalar sums.
|
|
13
|
+
*/
|
|
14
|
+
export function accountForLayoutScroll(rect, offsets) {
|
|
15
|
+
'worklet';
|
|
16
|
+
let x = rect.x;
|
|
17
|
+
let y = rect.y;
|
|
18
|
+
for (let i = 0; i < offsets.length; i++) {
|
|
19
|
+
const offset = offsets[i];
|
|
20
|
+
x += offset.x;
|
|
21
|
+
y += offset.y;
|
|
22
|
+
}
|
|
23
|
+
return { x, y, width: rect.width, height: rect.height };
|
|
24
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// @frame-path — executes inside the UI-runtime frame step; allocation-gated (REQ-DRIVER-021).
|
|
3
|
+
// SPEC-LAYOUT §2/§4a — the projection SESSION (REQ-LAYOUT-002/011/018/019). Drives an element's visual rect
|
|
4
|
+
// to its host-computed layout through the reused SPRING, exposing the transform-only correction the substrate
|
|
5
|
+
// executes. A mid-flight host layout change retargets from the current visual position AND velocity (C0/C1);
|
|
6
|
+
// a null read fails closed to identity + unsettled; settle is measurement-availability. Host-agnostic — no
|
|
7
|
+
// driver/React/Fabric import; drives the injected MotionGraph + built SPRING generators, no forked math.
|
|
8
|
+
//
|
|
9
|
+
// Modeling decision (deviates from the build packet's single-progress sketch): the visual rect is animated
|
|
10
|
+
// PER COMPONENT — four motion values (x, y, width, height) each springing toward the after-rect. A single
|
|
11
|
+
// scalar progress cannot preserve velocity continuity across ALL axes on a retarget (the progress→visual-
|
|
12
|
+
// velocity ratio differs per component), whereas per-component springs get C0/C1 for free from the graph's
|
|
13
|
+
// seed-from-live (REQ-API-003) — exactly as GESTURE's owned retarget does. transform() is then the FLIP
|
|
14
|
+
// invert of the live visual rect against the after rect, which is identity at settle (REQ-LAYOUT-002).
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.createProjectionSession = createProjectionSession;
|
|
17
|
+
const transition_1 = require("../transition.cjs");
|
|
18
|
+
const timing_1 = require("../timing.cjs");
|
|
19
|
+
const projection_1 = require("./projection.cjs");
|
|
20
|
+
// Near-critical projection spring (physics ⇒ carries the seeded velocity, REQ-SPRING-007) — minimal overshoot
|
|
21
|
+
// for a layout settle. Config constant, not an inline literal; refinable — the exact feel binds cross-engine
|
|
22
|
+
// parity at M2, not the M1 continuity/identity/settle invariants (§7 vetoable).
|
|
23
|
+
const LAYOUT_PROJECTION_SPRING = { stiffness: 550, damping: 45 };
|
|
24
|
+
// alloc-ok: construction — one session per layout-identity pairing; the per-frame surface is
|
|
25
|
+
// sync()/settledAgainst()/writeVisualInto(), which allocate nothing.
|
|
26
|
+
function createProjectionSession(options) {
|
|
27
|
+
const { graph } = options;
|
|
28
|
+
const spring = options.spring ?? LAYOUT_PROJECTION_SPRING;
|
|
29
|
+
const timing = options.timing;
|
|
30
|
+
// The visual rect, tracked as four independently-animated motion values (see the modeling note above).
|
|
31
|
+
const vx = graph.motionValue(0);
|
|
32
|
+
const vy = graph.motionValue(0);
|
|
33
|
+
const vw = graph.motionValue(0);
|
|
34
|
+
const vh = graph.motionValue(0);
|
|
35
|
+
let state = 'idle';
|
|
36
|
+
let measured = false;
|
|
37
|
+
let currentAfter = null;
|
|
38
|
+
let anims = [];
|
|
39
|
+
// Held while unmeasured (a null read): there is no in-flight animation to keep the graph unsettled, yet the
|
|
40
|
+
// element is not settled — it is waiting for a valid measurement (REQ-LAYOUT-019 / G-INV-7 analog).
|
|
41
|
+
let measureHandle;
|
|
42
|
+
function acquireMeasureHandle() {
|
|
43
|
+
if (measureHandle === undefined)
|
|
44
|
+
measureHandle = graph.registerActivity();
|
|
45
|
+
}
|
|
46
|
+
function releaseMeasureHandle() {
|
|
47
|
+
measureHandle?.release();
|
|
48
|
+
measureHandle = undefined;
|
|
49
|
+
}
|
|
50
|
+
function stopAnims() {
|
|
51
|
+
// Indexed + length=0: sync() reaches this on the settle frame, so a for..of iterator and a
|
|
52
|
+
// fresh array are frame-executed garbage (round-7 major 3; REQ-DRIVER-021).
|
|
53
|
+
for (let i = 0; i < anims.length; i++)
|
|
54
|
+
anims[i].stop();
|
|
55
|
+
anims.length = 0;
|
|
56
|
+
}
|
|
57
|
+
// Drive each visual channel toward the after-rect. Spring path: graph seeds {from, velocity} for
|
|
58
|
+
// C0/C1 retarget (REQ-CORE-002 / REQ-API-003). Timing path (family-3): duration/ease tween — seed
|
|
59
|
+
// velocity is ignored by timingGenerator (REQ-TIMING-001); retarget reseeds from live value.
|
|
60
|
+
// Do NOT stop the prior animation first — stop() clears animated velocity and breaks spring C1.
|
|
61
|
+
// alloc-ok: lifecycle-edge — flight start/retarget only (begin/retarget), never per frame.
|
|
62
|
+
function driveTo(after) {
|
|
63
|
+
if (timing !== undefined) {
|
|
64
|
+
anims = [
|
|
65
|
+
graph.animate(vx, (0, timing_1.timingGenerator)(after.x, timing)),
|
|
66
|
+
graph.animate(vy, (0, timing_1.timingGenerator)(after.y, timing)),
|
|
67
|
+
graph.animate(vw, (0, timing_1.timingGenerator)(after.width, timing)),
|
|
68
|
+
graph.animate(vh, (0, timing_1.timingGenerator)(after.height, timing)),
|
|
69
|
+
];
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
anims = [
|
|
73
|
+
graph.animate(vx, (0, transition_1.resolveSpringGenerator)(after.x, spring)),
|
|
74
|
+
graph.animate(vy, (0, transition_1.resolveSpringGenerator)(after.y, spring)),
|
|
75
|
+
graph.animate(vw, (0, transition_1.resolveSpringGenerator)(after.width, spring)),
|
|
76
|
+
graph.animate(vh, (0, transition_1.resolveSpringGenerator)(after.height, spring)),
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
// alloc-ok: js-thread — record-building read for the JS-side API (transform/visualRect); the
|
|
80
|
+
// frame path uses writeVisualInto/settledAgainst.
|
|
81
|
+
function visual() {
|
|
82
|
+
return { x: vx.get(), y: vy.get(), width: vw.get(), height: vh.get() };
|
|
83
|
+
}
|
|
84
|
+
// Reconcile the internal state to the graph: once the projection springs settle (graph settled) while
|
|
85
|
+
// measured, the session returns to idle. Every state-dependent entry point reconciles first — the getter
|
|
86
|
+
// AND the mutators — so post-settle reads and a subsequent retarget agree on the state (the GESTURE review
|
|
87
|
+
// lesson: a getter-only reconcile leaves the mutators acting on a stale field).
|
|
88
|
+
function sync() {
|
|
89
|
+
if (state === 'projecting' && measured && graph.isSettled()) {
|
|
90
|
+
stopAnims();
|
|
91
|
+
state = 'idle';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function failClosed(after) {
|
|
95
|
+
// A null read: unmeasured ⇒ identity + unsettled, retried next epoch. Never a garbage delta (REQ-LAYOUT-011).
|
|
96
|
+
stopAnims();
|
|
97
|
+
measured = false;
|
|
98
|
+
currentAfter = after;
|
|
99
|
+
acquireMeasureHandle();
|
|
100
|
+
state = 'projecting';
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
begin(token, before, after) {
|
|
104
|
+
void token; // identity binding is the measure tracker's concern (M3); the session takes resolved rects
|
|
105
|
+
sync();
|
|
106
|
+
if (before === null || after === null) {
|
|
107
|
+
failClosed(after);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// Fresh projection: jump the visual rect to `before` (velocity 0) and spring to `after`.
|
|
111
|
+
releaseMeasureHandle();
|
|
112
|
+
vx.jump(before.x);
|
|
113
|
+
vy.jump(before.y);
|
|
114
|
+
vw.jump(before.width);
|
|
115
|
+
vh.jump(before.height);
|
|
116
|
+
measured = true;
|
|
117
|
+
currentAfter = after;
|
|
118
|
+
driveTo(after);
|
|
119
|
+
state = 'projecting';
|
|
120
|
+
},
|
|
121
|
+
retarget(after) {
|
|
122
|
+
sync();
|
|
123
|
+
if (state !== 'projecting' || !measured) {
|
|
124
|
+
// Nothing in flight to retarget. Fail loud, consistent with the total state machine (never a silent
|
|
125
|
+
// no-op) — the caller must begin() a measured projection first.
|
|
126
|
+
throw new Error(`layout: retarget() with no active measured projection (state='${state}', measured=${measured}) — begin() first`);
|
|
127
|
+
}
|
|
128
|
+
if (after === null) {
|
|
129
|
+
failClosed(after);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// Continue from the LIVE visual position (+ velocity on spring path) toward the new target —
|
|
133
|
+
// no jump; the graph re-seeds each channel from live state ⇒ C0/C1 on spring (REQ-LAYOUT-018).
|
|
134
|
+
currentAfter = after;
|
|
135
|
+
driveTo(after);
|
|
136
|
+
},
|
|
137
|
+
settleAt(after) {
|
|
138
|
+
// Pin type:false — no multi-frame flight. Jump every channel to the post-layout rect and
|
|
139
|
+
// drop spring work so settledAgainst/isSettled read true on the same edge (REQ-API-035).
|
|
140
|
+
releaseMeasureHandle();
|
|
141
|
+
stopAnims();
|
|
142
|
+
vx.jump(after.x);
|
|
143
|
+
vy.jump(after.y);
|
|
144
|
+
vw.jump(after.width);
|
|
145
|
+
vh.jump(after.height);
|
|
146
|
+
measured = true;
|
|
147
|
+
currentAfter = after;
|
|
148
|
+
state = 'idle';
|
|
149
|
+
},
|
|
150
|
+
transform() {
|
|
151
|
+
sync();
|
|
152
|
+
if (!measured || currentAfter === null)
|
|
153
|
+
return projection_1.IDENTITY_TRANSFORM;
|
|
154
|
+
return (0, projection_1.invert)(visual(), currentAfter);
|
|
155
|
+
},
|
|
156
|
+
visualRect() {
|
|
157
|
+
sync();
|
|
158
|
+
return measured ? visual() : null;
|
|
159
|
+
},
|
|
160
|
+
// alloc-ok: js-thread — record-building read for the JS-side API; the frame path uses
|
|
161
|
+
// settledAgainst's in-place comparison.
|
|
162
|
+
velocity() {
|
|
163
|
+
return {
|
|
164
|
+
x: vx.getVelocity(),
|
|
165
|
+
y: vy.getVelocity(),
|
|
166
|
+
width: vw.getVelocity(),
|
|
167
|
+
height: vh.getVelocity(),
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
settledAgainst(target, epsPos, epsVel) {
|
|
171
|
+
sync();
|
|
172
|
+
if (!measured)
|
|
173
|
+
return false;
|
|
174
|
+
return (Math.abs(vx.get() - target.x) < epsPos &&
|
|
175
|
+
Math.abs(vy.get() - target.y) < epsPos &&
|
|
176
|
+
Math.abs(vw.get() - target.width) < epsPos &&
|
|
177
|
+
Math.abs(vh.get() - target.height) < epsPos &&
|
|
178
|
+
Math.abs(vx.getVelocity()) < epsVel &&
|
|
179
|
+
Math.abs(vy.getVelocity()) < epsVel &&
|
|
180
|
+
Math.abs(vw.getVelocity()) < epsVel &&
|
|
181
|
+
Math.abs(vh.getVelocity()) < epsVel);
|
|
182
|
+
},
|
|
183
|
+
writeVisualInto(out) {
|
|
184
|
+
sync();
|
|
185
|
+
if (!measured)
|
|
186
|
+
return false;
|
|
187
|
+
out.x = vx.get();
|
|
188
|
+
out.y = vy.get();
|
|
189
|
+
out.width = vw.get();
|
|
190
|
+
out.height = vh.get();
|
|
191
|
+
return true;
|
|
192
|
+
},
|
|
193
|
+
measured: () => measured,
|
|
194
|
+
isSettled: () => graph.isSettled(),
|
|
195
|
+
state: () => {
|
|
196
|
+
sync();
|
|
197
|
+
return state;
|
|
198
|
+
},
|
|
199
|
+
dispose() {
|
|
200
|
+
stopAnims();
|
|
201
|
+
releaseMeasureHandle();
|
|
202
|
+
measured = false;
|
|
203
|
+
currentAfter = null;
|
|
204
|
+
state = 'idle';
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type SpringConfig } from "../transition.cjs";
|
|
2
|
+
import { type TimingConfig } from "../timing.cjs";
|
|
3
|
+
import type { MotionGraph } from "../types.cjs";
|
|
4
|
+
import type { LayoutIdToken, Rect, Transform } from "./types.cjs";
|
|
5
|
+
export type ProjectionSessionState = 'idle' | 'projecting';
|
|
6
|
+
/** The live velocity of the visual rect, per component (px/s) — for retarget C1 assertions + diagnostics. */
|
|
7
|
+
export interface RectVelocity {
|
|
8
|
+
readonly x: number;
|
|
9
|
+
readonly y: number;
|
|
10
|
+
readonly width: number;
|
|
11
|
+
readonly height: number;
|
|
12
|
+
}
|
|
13
|
+
/** A caller-owned rect the hot path writes into — the allocation-free counterpart of `Rect`. */
|
|
14
|
+
export interface MutableRect {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
}
|
|
20
|
+
export interface ProjectionSessionOptions {
|
|
21
|
+
readonly graph: MotionGraph;
|
|
22
|
+
readonly spring?: SpringConfig;
|
|
23
|
+
/**
|
|
24
|
+
* Optional duration/ease tween flight (family-3 layoutId transition slice).
|
|
25
|
+
* When set, each visual channel uses `timingGenerator` instead of the layout spring —
|
|
26
|
+
* catalog residuals author `transition={{ duration: N }}` on layoutId hosts.
|
|
27
|
+
* Mutually exclusive with relying on `spring` for the same session: timing wins when present.
|
|
28
|
+
*/
|
|
29
|
+
readonly timing?: TimingConfig;
|
|
30
|
+
}
|
|
31
|
+
export interface ProjectionSession {
|
|
32
|
+
/** Start a fresh projection from `before` to `after`. A null before/after fails closed (unmeasured). */
|
|
33
|
+
begin(token: LayoutIdToken, before: Rect | null, after: Rect | null): void;
|
|
34
|
+
/** A host layout change mid-projection: continue from the live visual position + velocity toward `after`. */
|
|
35
|
+
retarget(after: Rect | null): void;
|
|
36
|
+
/**
|
|
37
|
+
* Instant settle at `after` (pin `type: false` under shouldReduceMotion / skipAnimations):
|
|
38
|
+
* jump every visual channel to the target, stop springs, idle. Used by layout-identity
|
|
39
|
+
* force-instant edges so the flight never multi-frames under MotionConfig policy (REQ-API-035).
|
|
40
|
+
*/
|
|
41
|
+
settleAt(after: Rect): void;
|
|
42
|
+
/** The transform-only correction to apply this frame; identity while unmeasured or at settle. */
|
|
43
|
+
transform(): Transform;
|
|
44
|
+
/** The current visual rect, or null while unmeasured. */
|
|
45
|
+
visualRect(): Rect | null;
|
|
46
|
+
/** The live per-component velocity of the visual rect. */
|
|
47
|
+
velocity(): RectVelocity;
|
|
48
|
+
/**
|
|
49
|
+
* Allocation-free settle test — the hot-path counterpart of comparing visualRect()+velocity()
|
|
50
|
+
* against `target` (REQ-DRIVER-015): measured AND every visual channel within `epsPos` of the
|
|
51
|
+
* target AND every channel velocity within `epsVel` of rest. False while unmeasured
|
|
52
|
+
* (fail-closed, REQ-LAYOUT-019).
|
|
53
|
+
*/
|
|
54
|
+
settledAgainst(target: Rect, epsPos: number, epsVel: number): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Allocation-free visual-rect read: writes the live rect into the caller-owned `out` and
|
|
57
|
+
* returns true, or returns false while unmeasured (out untouched). The hot-path counterpart of
|
|
58
|
+
* visualRect() (REQ-DRIVER-015).
|
|
59
|
+
*/
|
|
60
|
+
writeVisualInto(out: MutableRect): boolean;
|
|
61
|
+
/** A non-null read has landed for the current projection. */
|
|
62
|
+
measured(): boolean;
|
|
63
|
+
isSettled(): boolean;
|
|
64
|
+
state(): ProjectionSessionState;
|
|
65
|
+
/**
|
|
66
|
+
* Close the session out: stop the projection springs, release the waiting-for-measurement
|
|
67
|
+
* activity, return to idle. For an owner ABANDONING the session (a reverted or degraded
|
|
68
|
+
* identity pairing, an entry GC) — an unmeasured session parked in fail-closed wait would
|
|
69
|
+
* otherwise hold the graph's settle ledger open forever (L2 review cycle 3).
|
|
70
|
+
*/
|
|
71
|
+
dispose(): void;
|
|
72
|
+
}
|
|
73
|
+
export declare function createProjectionSession(options: ProjectionSessionOptions): ProjectionSession;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type SpringConfig } from "../transition.js";
|
|
2
|
+
import { type TimingConfig } from "../timing.js";
|
|
3
|
+
import type { MotionGraph } from "../types.js";
|
|
4
|
+
import type { LayoutIdToken, Rect, Transform } from "./types.js";
|
|
5
|
+
export type ProjectionSessionState = 'idle' | 'projecting';
|
|
6
|
+
/** The live velocity of the visual rect, per component (px/s) — for retarget C1 assertions + diagnostics. */
|
|
7
|
+
export interface RectVelocity {
|
|
8
|
+
readonly x: number;
|
|
9
|
+
readonly y: number;
|
|
10
|
+
readonly width: number;
|
|
11
|
+
readonly height: number;
|
|
12
|
+
}
|
|
13
|
+
/** A caller-owned rect the hot path writes into — the allocation-free counterpart of `Rect`. */
|
|
14
|
+
export interface MutableRect {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
}
|
|
20
|
+
export interface ProjectionSessionOptions {
|
|
21
|
+
readonly graph: MotionGraph;
|
|
22
|
+
readonly spring?: SpringConfig;
|
|
23
|
+
/**
|
|
24
|
+
* Optional duration/ease tween flight (family-3 layoutId transition slice).
|
|
25
|
+
* When set, each visual channel uses `timingGenerator` instead of the layout spring —
|
|
26
|
+
* catalog residuals author `transition={{ duration: N }}` on layoutId hosts.
|
|
27
|
+
* Mutually exclusive with relying on `spring` for the same session: timing wins when present.
|
|
28
|
+
*/
|
|
29
|
+
readonly timing?: TimingConfig;
|
|
30
|
+
}
|
|
31
|
+
export interface ProjectionSession {
|
|
32
|
+
/** Start a fresh projection from `before` to `after`. A null before/after fails closed (unmeasured). */
|
|
33
|
+
begin(token: LayoutIdToken, before: Rect | null, after: Rect | null): void;
|
|
34
|
+
/** A host layout change mid-projection: continue from the live visual position + velocity toward `after`. */
|
|
35
|
+
retarget(after: Rect | null): void;
|
|
36
|
+
/**
|
|
37
|
+
* Instant settle at `after` (pin `type: false` under shouldReduceMotion / skipAnimations):
|
|
38
|
+
* jump every visual channel to the target, stop springs, idle. Used by layout-identity
|
|
39
|
+
* force-instant edges so the flight never multi-frames under MotionConfig policy (REQ-API-035).
|
|
40
|
+
*/
|
|
41
|
+
settleAt(after: Rect): void;
|
|
42
|
+
/** The transform-only correction to apply this frame; identity while unmeasured or at settle. */
|
|
43
|
+
transform(): Transform;
|
|
44
|
+
/** The current visual rect, or null while unmeasured. */
|
|
45
|
+
visualRect(): Rect | null;
|
|
46
|
+
/** The live per-component velocity of the visual rect. */
|
|
47
|
+
velocity(): RectVelocity;
|
|
48
|
+
/**
|
|
49
|
+
* Allocation-free settle test — the hot-path counterpart of comparing visualRect()+velocity()
|
|
50
|
+
* against `target` (REQ-DRIVER-015): measured AND every visual channel within `epsPos` of the
|
|
51
|
+
* target AND every channel velocity within `epsVel` of rest. False while unmeasured
|
|
52
|
+
* (fail-closed, REQ-LAYOUT-019).
|
|
53
|
+
*/
|
|
54
|
+
settledAgainst(target: Rect, epsPos: number, epsVel: number): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Allocation-free visual-rect read: writes the live rect into the caller-owned `out` and
|
|
57
|
+
* returns true, or returns false while unmeasured (out untouched). The hot-path counterpart of
|
|
58
|
+
* visualRect() (REQ-DRIVER-015).
|
|
59
|
+
*/
|
|
60
|
+
writeVisualInto(out: MutableRect): boolean;
|
|
61
|
+
/** A non-null read has landed for the current projection. */
|
|
62
|
+
measured(): boolean;
|
|
63
|
+
isSettled(): boolean;
|
|
64
|
+
state(): ProjectionSessionState;
|
|
65
|
+
/**
|
|
66
|
+
* Close the session out: stop the projection springs, release the waiting-for-measurement
|
|
67
|
+
* activity, return to idle. For an owner ABANDONING the session (a reverted or degraded
|
|
68
|
+
* identity pairing, an entry GC) — an unmeasured session parked in fail-closed wait would
|
|
69
|
+
* otherwise hold the graph's settle ledger open forever (L2 review cycle 3).
|
|
70
|
+
*/
|
|
71
|
+
dispose(): void;
|
|
72
|
+
}
|
|
73
|
+
export declare function createProjectionSession(options: ProjectionSessionOptions): ProjectionSession;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// @frame-path — executes inside the UI-runtime frame step; allocation-gated (REQ-DRIVER-021).
|
|
2
|
+
// SPEC-LAYOUT §2/§4a — the projection SESSION (REQ-LAYOUT-002/011/018/019). Drives an element's visual rect
|
|
3
|
+
// to its host-computed layout through the reused SPRING, exposing the transform-only correction the substrate
|
|
4
|
+
// executes. A mid-flight host layout change retargets from the current visual position AND velocity (C0/C1);
|
|
5
|
+
// a null read fails closed to identity + unsettled; settle is measurement-availability. Host-agnostic — no
|
|
6
|
+
// driver/React/Fabric import; drives the injected MotionGraph + built SPRING generators, no forked math.
|
|
7
|
+
//
|
|
8
|
+
// Modeling decision (deviates from the build packet's single-progress sketch): the visual rect is animated
|
|
9
|
+
// PER COMPONENT — four motion values (x, y, width, height) each springing toward the after-rect. A single
|
|
10
|
+
// scalar progress cannot preserve velocity continuity across ALL axes on a retarget (the progress→visual-
|
|
11
|
+
// velocity ratio differs per component), whereas per-component springs get C0/C1 for free from the graph's
|
|
12
|
+
// seed-from-live (REQ-API-003) — exactly as GESTURE's owned retarget does. transform() is then the FLIP
|
|
13
|
+
// invert of the live visual rect against the after rect, which is identity at settle (REQ-LAYOUT-002).
|
|
14
|
+
import { resolveSpringGenerator } from "../transition.js";
|
|
15
|
+
import { timingGenerator } from "../timing.js";
|
|
16
|
+
import { IDENTITY_TRANSFORM, invert } from "./projection.js";
|
|
17
|
+
// Near-critical projection spring (physics ⇒ carries the seeded velocity, REQ-SPRING-007) — minimal overshoot
|
|
18
|
+
// for a layout settle. Config constant, not an inline literal; refinable — the exact feel binds cross-engine
|
|
19
|
+
// parity at M2, not the M1 continuity/identity/settle invariants (§7 vetoable).
|
|
20
|
+
const LAYOUT_PROJECTION_SPRING = { stiffness: 550, damping: 45 };
|
|
21
|
+
// alloc-ok: construction — one session per layout-identity pairing; the per-frame surface is
|
|
22
|
+
// sync()/settledAgainst()/writeVisualInto(), which allocate nothing.
|
|
23
|
+
export function createProjectionSession(options) {
|
|
24
|
+
'worklet';
|
|
25
|
+
const { graph } = options;
|
|
26
|
+
const spring = options.spring ?? LAYOUT_PROJECTION_SPRING;
|
|
27
|
+
const timing = options.timing;
|
|
28
|
+
// The visual rect, tracked as four independently-animated motion values (see the modeling note above).
|
|
29
|
+
const vx = graph.motionValue(0);
|
|
30
|
+
const vy = graph.motionValue(0);
|
|
31
|
+
const vw = graph.motionValue(0);
|
|
32
|
+
const vh = graph.motionValue(0);
|
|
33
|
+
let state = 'idle';
|
|
34
|
+
let measured = false;
|
|
35
|
+
let currentAfter = null;
|
|
36
|
+
let anims = [];
|
|
37
|
+
// Held while unmeasured (a null read): there is no in-flight animation to keep the graph unsettled, yet the
|
|
38
|
+
// element is not settled — it is waiting for a valid measurement (REQ-LAYOUT-019 / G-INV-7 analog).
|
|
39
|
+
let measureHandle;
|
|
40
|
+
function acquireMeasureHandle() {
|
|
41
|
+
if (measureHandle === undefined)
|
|
42
|
+
measureHandle = graph.registerActivity();
|
|
43
|
+
}
|
|
44
|
+
function releaseMeasureHandle() {
|
|
45
|
+
measureHandle?.release();
|
|
46
|
+
measureHandle = undefined;
|
|
47
|
+
}
|
|
48
|
+
function stopAnims() {
|
|
49
|
+
// Indexed + length=0: sync() reaches this on the settle frame, so a for..of iterator and a
|
|
50
|
+
// fresh array are frame-executed garbage (round-7 major 3; REQ-DRIVER-021).
|
|
51
|
+
for (let i = 0; i < anims.length; i++)
|
|
52
|
+
anims[i].stop();
|
|
53
|
+
anims.length = 0;
|
|
54
|
+
}
|
|
55
|
+
// Drive each visual channel toward the after-rect. Spring path: graph seeds {from, velocity} for
|
|
56
|
+
// C0/C1 retarget (REQ-CORE-002 / REQ-API-003). Timing path (family-3): duration/ease tween — seed
|
|
57
|
+
// velocity is ignored by timingGenerator (REQ-TIMING-001); retarget reseeds from live value.
|
|
58
|
+
// Do NOT stop the prior animation first — stop() clears animated velocity and breaks spring C1.
|
|
59
|
+
// alloc-ok: lifecycle-edge — flight start/retarget only (begin/retarget), never per frame.
|
|
60
|
+
function driveTo(after) {
|
|
61
|
+
if (timing !== undefined) {
|
|
62
|
+
anims = [
|
|
63
|
+
graph.animate(vx, timingGenerator(after.x, timing)),
|
|
64
|
+
graph.animate(vy, timingGenerator(after.y, timing)),
|
|
65
|
+
graph.animate(vw, timingGenerator(after.width, timing)),
|
|
66
|
+
graph.animate(vh, timingGenerator(after.height, timing)),
|
|
67
|
+
];
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
anims = [
|
|
71
|
+
graph.animate(vx, resolveSpringGenerator(after.x, spring)),
|
|
72
|
+
graph.animate(vy, resolveSpringGenerator(after.y, spring)),
|
|
73
|
+
graph.animate(vw, resolveSpringGenerator(after.width, spring)),
|
|
74
|
+
graph.animate(vh, resolveSpringGenerator(after.height, spring)),
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
// alloc-ok: js-thread — record-building read for the JS-side API (transform/visualRect); the
|
|
78
|
+
// frame path uses writeVisualInto/settledAgainst.
|
|
79
|
+
function visual() {
|
|
80
|
+
return { x: vx.get(), y: vy.get(), width: vw.get(), height: vh.get() };
|
|
81
|
+
}
|
|
82
|
+
// Reconcile the internal state to the graph: once the projection springs settle (graph settled) while
|
|
83
|
+
// measured, the session returns to idle. Every state-dependent entry point reconciles first — the getter
|
|
84
|
+
// AND the mutators — so post-settle reads and a subsequent retarget agree on the state (the GESTURE review
|
|
85
|
+
// lesson: a getter-only reconcile leaves the mutators acting on a stale field).
|
|
86
|
+
function sync() {
|
|
87
|
+
if (state === 'projecting' && measured && graph.isSettled()) {
|
|
88
|
+
stopAnims();
|
|
89
|
+
state = 'idle';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function failClosed(after) {
|
|
93
|
+
// A null read: unmeasured ⇒ identity + unsettled, retried next epoch. Never a garbage delta (REQ-LAYOUT-011).
|
|
94
|
+
stopAnims();
|
|
95
|
+
measured = false;
|
|
96
|
+
currentAfter = after;
|
|
97
|
+
acquireMeasureHandle();
|
|
98
|
+
state = 'projecting';
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
begin(token, before, after) {
|
|
102
|
+
void token; // identity binding is the measure tracker's concern (M3); the session takes resolved rects
|
|
103
|
+
sync();
|
|
104
|
+
if (before === null || after === null) {
|
|
105
|
+
failClosed(after);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Fresh projection: jump the visual rect to `before` (velocity 0) and spring to `after`.
|
|
109
|
+
releaseMeasureHandle();
|
|
110
|
+
vx.jump(before.x);
|
|
111
|
+
vy.jump(before.y);
|
|
112
|
+
vw.jump(before.width);
|
|
113
|
+
vh.jump(before.height);
|
|
114
|
+
measured = true;
|
|
115
|
+
currentAfter = after;
|
|
116
|
+
driveTo(after);
|
|
117
|
+
state = 'projecting';
|
|
118
|
+
},
|
|
119
|
+
retarget(after) {
|
|
120
|
+
sync();
|
|
121
|
+
if (state !== 'projecting' || !measured) {
|
|
122
|
+
// Nothing in flight to retarget. Fail loud, consistent with the total state machine (never a silent
|
|
123
|
+
// no-op) — the caller must begin() a measured projection first.
|
|
124
|
+
throw new Error(`layout: retarget() with no active measured projection (state='${state}', measured=${measured}) — begin() first`);
|
|
125
|
+
}
|
|
126
|
+
if (after === null) {
|
|
127
|
+
failClosed(after);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// Continue from the LIVE visual position (+ velocity on spring path) toward the new target —
|
|
131
|
+
// no jump; the graph re-seeds each channel from live state ⇒ C0/C1 on spring (REQ-LAYOUT-018).
|
|
132
|
+
currentAfter = after;
|
|
133
|
+
driveTo(after);
|
|
134
|
+
},
|
|
135
|
+
settleAt(after) {
|
|
136
|
+
// Pin type:false — no multi-frame flight. Jump every channel to the post-layout rect and
|
|
137
|
+
// drop spring work so settledAgainst/isSettled read true on the same edge (REQ-API-035).
|
|
138
|
+
releaseMeasureHandle();
|
|
139
|
+
stopAnims();
|
|
140
|
+
vx.jump(after.x);
|
|
141
|
+
vy.jump(after.y);
|
|
142
|
+
vw.jump(after.width);
|
|
143
|
+
vh.jump(after.height);
|
|
144
|
+
measured = true;
|
|
145
|
+
currentAfter = after;
|
|
146
|
+
state = 'idle';
|
|
147
|
+
},
|
|
148
|
+
transform() {
|
|
149
|
+
sync();
|
|
150
|
+
if (!measured || currentAfter === null)
|
|
151
|
+
return IDENTITY_TRANSFORM;
|
|
152
|
+
return invert(visual(), currentAfter);
|
|
153
|
+
},
|
|
154
|
+
visualRect() {
|
|
155
|
+
sync();
|
|
156
|
+
return measured ? visual() : null;
|
|
157
|
+
},
|
|
158
|
+
// alloc-ok: js-thread — record-building read for the JS-side API; the frame path uses
|
|
159
|
+
// settledAgainst's in-place comparison.
|
|
160
|
+
velocity() {
|
|
161
|
+
return {
|
|
162
|
+
x: vx.getVelocity(),
|
|
163
|
+
y: vy.getVelocity(),
|
|
164
|
+
width: vw.getVelocity(),
|
|
165
|
+
height: vh.getVelocity(),
|
|
166
|
+
};
|
|
167
|
+
},
|
|
168
|
+
settledAgainst(target, epsPos, epsVel) {
|
|
169
|
+
sync();
|
|
170
|
+
if (!measured)
|
|
171
|
+
return false;
|
|
172
|
+
return (Math.abs(vx.get() - target.x) < epsPos &&
|
|
173
|
+
Math.abs(vy.get() - target.y) < epsPos &&
|
|
174
|
+
Math.abs(vw.get() - target.width) < epsPos &&
|
|
175
|
+
Math.abs(vh.get() - target.height) < epsPos &&
|
|
176
|
+
Math.abs(vx.getVelocity()) < epsVel &&
|
|
177
|
+
Math.abs(vy.getVelocity()) < epsVel &&
|
|
178
|
+
Math.abs(vw.getVelocity()) < epsVel &&
|
|
179
|
+
Math.abs(vh.getVelocity()) < epsVel);
|
|
180
|
+
},
|
|
181
|
+
writeVisualInto(out) {
|
|
182
|
+
sync();
|
|
183
|
+
if (!measured)
|
|
184
|
+
return false;
|
|
185
|
+
out.x = vx.get();
|
|
186
|
+
out.y = vy.get();
|
|
187
|
+
out.width = vw.get();
|
|
188
|
+
out.height = vh.get();
|
|
189
|
+
return true;
|
|
190
|
+
},
|
|
191
|
+
measured: () => measured,
|
|
192
|
+
isSettled: () => graph.isSettled(),
|
|
193
|
+
state: () => {
|
|
194
|
+
sync();
|
|
195
|
+
return state;
|
|
196
|
+
},
|
|
197
|
+
dispose() {
|
|
198
|
+
stopAnims();
|
|
199
|
+
releaseMeasureHandle();
|
|
200
|
+
measured = false;
|
|
201
|
+
currentAfter = null;
|
|
202
|
+
state = 'idle';
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|