@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,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Public surface of the SPEC-PRESENCE subsystem: the host-agnostic exit-lifecycle controller `core` defines
|
|
3
|
+
// and the native/web presence surfaces bind to. Host-agnostic (SPEC-PRESENCE §1) — imports no react, no
|
|
4
|
+
// driver, no host package. The lifecycle types + total state machine land first (Milestone 1); the
|
|
5
|
+
// controller, settle integration, and cancellation join this surface as they land.
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.createPresenceController = exports.UnimplementedPresenceModeError = exports.transition = exports.createPresenceLedger = void 0;
|
|
8
|
+
// The total exit-lifecycle state machine (SPEC-PRESENCE §3): the pure transition + the key→state ledger the
|
|
9
|
+
// controller owns.
|
|
10
|
+
var machine_1 = require("./machine.cjs");
|
|
11
|
+
Object.defineProperty(exports, "createPresenceLedger", { enumerable: true, get: function () { return machine_1.createPresenceLedger; } });
|
|
12
|
+
Object.defineProperty(exports, "transition", { enumerable: true, get: function () { return machine_1.transition; } });
|
|
13
|
+
// The presence controller (REQ-PRESENCE-001/011): mount authority + exit-target resolution + retention,
|
|
14
|
+
// driven off the MotionGraph settle ledger. The React usePresence/useIsPresent hooks bind over it (M2 host).
|
|
15
|
+
// The legacy typed error remains exported for public-surface compatibility; popLayout itself now
|
|
16
|
+
// uses core's ordinary lifecycle ordering and is composed by the host presence layers.
|
|
17
|
+
var controller_1 = require("./controller.cjs");
|
|
18
|
+
Object.defineProperty(exports, "UnimplementedPresenceModeError", { enumerable: true, get: function () { return controller_1.UnimplementedPresenceModeError; } });
|
|
19
|
+
Object.defineProperty(exports, "createPresenceController", { enumerable: true, get: function () { return controller_1.createPresenceController; } });
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { PresenceChild, PresenceController, PresenceControllerOptions, PresenceMode, PresenceState, } from "./types.cjs";
|
|
2
|
+
export { createPresenceLedger, transition } from "./machine.cjs";
|
|
3
|
+
export type { PresenceEvent, PresenceLedger } from "./machine.cjs";
|
|
4
|
+
export { UnimplementedPresenceModeError, createPresenceController } from "./controller.cjs";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { PresenceChild, PresenceController, PresenceControllerOptions, PresenceMode, PresenceState, } from "./types.js";
|
|
2
|
+
export { createPresenceLedger, transition } from "./machine.js";
|
|
3
|
+
export type { PresenceEvent, PresenceLedger } from "./machine.js";
|
|
4
|
+
export { UnimplementedPresenceModeError, createPresenceController } from "./controller.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Public surface of the SPEC-PRESENCE subsystem: the host-agnostic exit-lifecycle controller `core` defines
|
|
2
|
+
// and the native/web presence surfaces bind to. Host-agnostic (SPEC-PRESENCE §1) — imports no react, no
|
|
3
|
+
// driver, no host package. The lifecycle types + total state machine land first (Milestone 1); the
|
|
4
|
+
// controller, settle integration, and cancellation join this surface as they land.
|
|
5
|
+
// The total exit-lifecycle state machine (SPEC-PRESENCE §3): the pure transition + the key→state ledger the
|
|
6
|
+
// controller owns.
|
|
7
|
+
export { createPresenceLedger, transition } from "./machine.js";
|
|
8
|
+
// The presence controller (REQ-PRESENCE-001/011): mount authority + exit-target resolution + retention,
|
|
9
|
+
// driven off the MotionGraph settle ledger. The React usePresence/useIsPresent hooks bind over it (M2 host).
|
|
10
|
+
// The legacy typed error remains exported for public-surface compatibility; popLayout itself now
|
|
11
|
+
// uses core's ordinary lifecycle ordering and is composed by the host presence layers.
|
|
12
|
+
export { UnimplementedPresenceModeError, createPresenceController } from "./controller.js";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// SPEC-PRESENCE §3 — the total exit-lifecycle state machine. A presence-tracked key is always in exactly one
|
|
3
|
+
// of `present | exiting | removed`, and only four edges move it. Every other edge throws (fail-loud): an
|
|
4
|
+
// illegal transition is a controller bug, never a silent no-op or a state teleport. Host-agnostic
|
|
5
|
+
// (REQ-CORE-003): relative imports only, no clock/host reads.
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.transition = transition;
|
|
8
|
+
exports.createPresenceLedger = createPresenceLedger;
|
|
9
|
+
const EDGES = {
|
|
10
|
+
'drop-with-exit': { from: 'present', to: 'exiting' },
|
|
11
|
+
'drop-immediate': { from: 'present', to: 'removed' },
|
|
12
|
+
resolve: { from: 'exiting', to: 'removed' },
|
|
13
|
+
cancel: { from: 'exiting', to: 'present' },
|
|
14
|
+
};
|
|
15
|
+
// Pure total transition: map `(current, event)` to the next state, or throw on an illegal edge. Deterministic.
|
|
16
|
+
function transition(current, event) {
|
|
17
|
+
const edge = EDGES[event];
|
|
18
|
+
if (current !== edge.from) {
|
|
19
|
+
throw new Error(`illegal presence transition '${event}' from '${current}': only legal from '${edge.from}'.`);
|
|
20
|
+
}
|
|
21
|
+
return edge.to;
|
|
22
|
+
}
|
|
23
|
+
function createPresenceLedger() {
|
|
24
|
+
// Holds only non-terminal states; absence of a key IS the terminal `removed` state.
|
|
25
|
+
const states = new Map();
|
|
26
|
+
return {
|
|
27
|
+
track(key) {
|
|
28
|
+
if (states.has(key))
|
|
29
|
+
throw new Error(`presence key '${key}' is already tracked`);
|
|
30
|
+
states.set(key, 'present');
|
|
31
|
+
},
|
|
32
|
+
stateOf(key) {
|
|
33
|
+
return states.get(key) ?? 'removed';
|
|
34
|
+
},
|
|
35
|
+
send(key, event) {
|
|
36
|
+
const current = states.get(key) ?? 'removed';
|
|
37
|
+
const next = transition(current, event);
|
|
38
|
+
if (next === 'removed') {
|
|
39
|
+
states.delete(key);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
states.set(key, next);
|
|
43
|
+
}
|
|
44
|
+
return next;
|
|
45
|
+
},
|
|
46
|
+
trackedKeys() {
|
|
47
|
+
return [...states.keys()];
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PresenceState } from "./types.cjs";
|
|
2
|
+
export type PresenceEvent = 'drop-with-exit' | 'drop-immediate' | 'resolve' | 'cancel';
|
|
3
|
+
export declare function transition(current: PresenceState, event: PresenceEvent): PresenceState;
|
|
4
|
+
export interface PresenceLedger {
|
|
5
|
+
track(key: string): void;
|
|
6
|
+
stateOf(key: string): PresenceState;
|
|
7
|
+
send(key: string, event: PresenceEvent): PresenceState;
|
|
8
|
+
trackedKeys(): readonly string[];
|
|
9
|
+
}
|
|
10
|
+
export declare function createPresenceLedger(): PresenceLedger;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PresenceState } from "./types.js";
|
|
2
|
+
export type PresenceEvent = 'drop-with-exit' | 'drop-immediate' | 'resolve' | 'cancel';
|
|
3
|
+
export declare function transition(current: PresenceState, event: PresenceEvent): PresenceState;
|
|
4
|
+
export interface PresenceLedger {
|
|
5
|
+
track(key: string): void;
|
|
6
|
+
stateOf(key: string): PresenceState;
|
|
7
|
+
send(key: string, event: PresenceEvent): PresenceState;
|
|
8
|
+
trackedKeys(): readonly string[];
|
|
9
|
+
}
|
|
10
|
+
export declare function createPresenceLedger(): PresenceLedger;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// SPEC-PRESENCE §3 — the total exit-lifecycle state machine. A presence-tracked key is always in exactly one
|
|
2
|
+
// of `present | exiting | removed`, and only four edges move it. Every other edge throws (fail-loud): an
|
|
3
|
+
// illegal transition is a controller bug, never a silent no-op or a state teleport. Host-agnostic
|
|
4
|
+
// (REQ-CORE-003): relative imports only, no clock/host reads.
|
|
5
|
+
const EDGES = {
|
|
6
|
+
'drop-with-exit': { from: 'present', to: 'exiting' },
|
|
7
|
+
'drop-immediate': { from: 'present', to: 'removed' },
|
|
8
|
+
resolve: { from: 'exiting', to: 'removed' },
|
|
9
|
+
cancel: { from: 'exiting', to: 'present' },
|
|
10
|
+
};
|
|
11
|
+
// Pure total transition: map `(current, event)` to the next state, or throw on an illegal edge. Deterministic.
|
|
12
|
+
export function transition(current, event) {
|
|
13
|
+
const edge = EDGES[event];
|
|
14
|
+
if (current !== edge.from) {
|
|
15
|
+
throw new Error(`illegal presence transition '${event}' from '${current}': only legal from '${edge.from}'.`);
|
|
16
|
+
}
|
|
17
|
+
return edge.to;
|
|
18
|
+
}
|
|
19
|
+
export function createPresenceLedger() {
|
|
20
|
+
// Holds only non-terminal states; absence of a key IS the terminal `removed` state.
|
|
21
|
+
const states = new Map();
|
|
22
|
+
return {
|
|
23
|
+
track(key) {
|
|
24
|
+
if (states.has(key))
|
|
25
|
+
throw new Error(`presence key '${key}' is already tracked`);
|
|
26
|
+
states.set(key, 'present');
|
|
27
|
+
},
|
|
28
|
+
stateOf(key) {
|
|
29
|
+
return states.get(key) ?? 'removed';
|
|
30
|
+
},
|
|
31
|
+
send(key, event) {
|
|
32
|
+
const current = states.get(key) ?? 'removed';
|
|
33
|
+
const next = transition(current, event);
|
|
34
|
+
if (next === 'removed') {
|
|
35
|
+
states.delete(key);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
states.set(key, next);
|
|
39
|
+
}
|
|
40
|
+
return next;
|
|
41
|
+
},
|
|
42
|
+
trackedKeys() {
|
|
43
|
+
return [...states.keys()];
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// SPEC-PRESENCE §2 — the presence/exit lifecycle type contract. Host-agnostic (REQ-CORE-003): the controller
|
|
3
|
+
// lives in `core`, drives exits over the injected MotionGraph + clock, and never imports a driver, React, or
|
|
4
|
+
// the DOM (SPEC-PRESENCE §1). The React `usePresence`/`useIsPresent` hooks are Milestone-2 host bindings over
|
|
5
|
+
// the `isPresent`/`safeToRemove` semantics declared here.
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ResolvedTargetValue, ResolvedValue, Target, Transition } from "../component/index.cjs";
|
|
2
|
+
import type { MotionGraph } from "../types.cjs";
|
|
3
|
+
export type PresenceState = 'present' | 'exiting' | 'removed';
|
|
4
|
+
export type PresenceMode = 'sync' | 'wait' | 'popLayout';
|
|
5
|
+
export type PresenceLiveValues = Readonly<Record<string, ResolvedValue>>;
|
|
6
|
+
export type PresenceResolvedExits = Readonly<Record<string, readonly (number | null)[]>>;
|
|
7
|
+
export interface PresenceChild {
|
|
8
|
+
readonly key: string;
|
|
9
|
+
readonly exit?: Target;
|
|
10
|
+
readonly animate?: Target;
|
|
11
|
+
readonly transition?: Transition;
|
|
12
|
+
readonly deferred?: boolean;
|
|
13
|
+
readonly mountedKeys?: readonly string[];
|
|
14
|
+
}
|
|
15
|
+
export interface PresenceControllerOptions {
|
|
16
|
+
readonly graph: MotionGraph;
|
|
17
|
+
readonly mode?: PresenceMode;
|
|
18
|
+
readonly initial?: boolean;
|
|
19
|
+
readonly onExitComplete?: () => void;
|
|
20
|
+
}
|
|
21
|
+
export interface PresenceController {
|
|
22
|
+
syncChildren(children: readonly PresenceChild[], liveValuesByKey?: ReadonlyMap<string, PresenceLiveValues>, resolvedExitsByKey?: ReadonlyMap<string, PresenceResolvedExits>): void;
|
|
23
|
+
mountedKeys(): readonly string[];
|
|
24
|
+
stateOf(key: string): PresenceState;
|
|
25
|
+
isPresent(key: string): boolean;
|
|
26
|
+
safeToRemove(key: string): void;
|
|
27
|
+
deferExit(key: string): void;
|
|
28
|
+
resolvedExitTarget(key: string): Readonly<Record<string, ResolvedTargetValue>> | undefined;
|
|
29
|
+
enterSuppressed(key: string): boolean;
|
|
30
|
+
exitProgress(key: string): number | undefined;
|
|
31
|
+
exitVelocity(key: string): number | undefined;
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ResolvedTargetValue, ResolvedValue, Target, Transition } from "../component/index.js";
|
|
2
|
+
import type { MotionGraph } from "../types.js";
|
|
3
|
+
export type PresenceState = 'present' | 'exiting' | 'removed';
|
|
4
|
+
export type PresenceMode = 'sync' | 'wait' | 'popLayout';
|
|
5
|
+
export type PresenceLiveValues = Readonly<Record<string, ResolvedValue>>;
|
|
6
|
+
export type PresenceResolvedExits = Readonly<Record<string, readonly (number | null)[]>>;
|
|
7
|
+
export interface PresenceChild {
|
|
8
|
+
readonly key: string;
|
|
9
|
+
readonly exit?: Target;
|
|
10
|
+
readonly animate?: Target;
|
|
11
|
+
readonly transition?: Transition;
|
|
12
|
+
readonly deferred?: boolean;
|
|
13
|
+
readonly mountedKeys?: readonly string[];
|
|
14
|
+
}
|
|
15
|
+
export interface PresenceControllerOptions {
|
|
16
|
+
readonly graph: MotionGraph;
|
|
17
|
+
readonly mode?: PresenceMode;
|
|
18
|
+
readonly initial?: boolean;
|
|
19
|
+
readonly onExitComplete?: () => void;
|
|
20
|
+
}
|
|
21
|
+
export interface PresenceController {
|
|
22
|
+
syncChildren(children: readonly PresenceChild[], liveValuesByKey?: ReadonlyMap<string, PresenceLiveValues>, resolvedExitsByKey?: ReadonlyMap<string, PresenceResolvedExits>): void;
|
|
23
|
+
mountedKeys(): readonly string[];
|
|
24
|
+
stateOf(key: string): PresenceState;
|
|
25
|
+
isPresent(key: string): boolean;
|
|
26
|
+
safeToRemove(key: string): void;
|
|
27
|
+
deferExit(key: string): void;
|
|
28
|
+
resolvedExitTarget(key: string): Readonly<Record<string, ResolvedTargetValue>> | undefined;
|
|
29
|
+
enterSuppressed(key: string): boolean;
|
|
30
|
+
exitProgress(key: string): number | undefined;
|
|
31
|
+
exitVelocity(key: string): number | undefined;
|
|
32
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// SPEC-PRESENCE §2 — the presence/exit lifecycle type contract. Host-agnostic (REQ-CORE-003): the controller
|
|
2
|
+
// lives in `core`, drives exits over the injected MotionGraph + clock, and never imports a driver, React, or
|
|
3
|
+
// the DOM (SPEC-PRESENCE §1). The React `usePresence`/`useIsPresent` hooks are Milestone-2 host bindings over
|
|
4
|
+
// the `isPresent`/`safeToRemove` semantics declared here.
|
|
5
|
+
export {};
|
package/dist/repeat.cjs
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// @frame-path — executes inside the UI-runtime frame step; allocation-gated (REQ-DRIVER-021).
|
|
3
|
+
// T18-a / REQ-TIMING-003 — the repeat elapsed-time FOLD. `repeat`/`repeatType`/`repeatDelay` are not
|
|
4
|
+
// generator features in the pinned Motion: they are a pure `t → t'` map wrapped AROUND a generator
|
|
5
|
+
// (motion-dom@12.42.2 `JSAnimation.initAnimation` + `tick`). This module is that map, so every
|
|
6
|
+
// generator core owns — spring, tween, keyframe array — repeats identically and none of them learns
|
|
7
|
+
// about repetition. Host-agnostic (REQ-CORE-003): relative imports only. Numerics are pinned by
|
|
8
|
+
// per-sample goldens in repeat.test.ts.
|
|
9
|
+
//
|
|
10
|
+
// The fold divides by the RESOLVED iteration length (play + repeatDelay), so it is TOTAL only
|
|
11
|
+
// when that is finite and positive: a zero-length play with no repeatDelay produces NaN at the
|
|
12
|
+
// pin and is refused upstream (`repeatIterationRefusal`), as is an unbounded one. Zero-length
|
|
13
|
+
// plays separated by a repeatDelay are the pin's executable shape (transition-default-selection
|
|
14
|
+
// F5) and fold fine.
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.REPEAT_REFUSAL_KEY = exports.REPEAT_REFUSAL_NAME = exports.MAX_ITERATION_DURATION_MS = exports.ITERATION_SCAN_STEP_MS = void 0;
|
|
17
|
+
exports.calcIterationDurationMs = calcIterationDurationMs;
|
|
18
|
+
exports.repeatIterationRefusal = repeatIterationRefusal;
|
|
19
|
+
exports.iterationMeasurementLane = iterationMeasurementLane;
|
|
20
|
+
exports.isRepeatFoldRefusalIdentity = isRepeatFoldRefusalIdentity;
|
|
21
|
+
exports.isRepeatFoldRefusal = isRepeatFoldRefusal;
|
|
22
|
+
exports.repeatRefusalMessage = repeatRefusalMessage;
|
|
23
|
+
exports.repeatFoldGeometry = repeatFoldGeometry;
|
|
24
|
+
exports.repeatGenerator = repeatGenerator;
|
|
25
|
+
exports.buildRepeatedGenerator = buildRepeatedGenerator;
|
|
26
|
+
// The pin's `calcGeneratorDuration` scan constants (motion-dom keyframes/calc-duration.ts). The
|
|
27
|
+
// 50ms grid is why a physics spring's iteration length is COARSE — it is the pin's number, not an
|
|
28
|
+
// analytic settle time, and reproducing it exactly is what keeps repeated springs in parity.
|
|
29
|
+
exports.ITERATION_SCAN_STEP_MS = 50;
|
|
30
|
+
exports.MAX_ITERATION_DURATION_MS = 20_000;
|
|
31
|
+
/**
|
|
32
|
+
* The iteration length of a generator that does not know its own duration (a physics or
|
|
33
|
+
* visualDuration spring — the pin's `calculatedDuration === null` case). Samples on the pin's 50ms
|
|
34
|
+
* grid until the trajectory reports done, and reports `Infinity` at the cap rather than scanning
|
|
35
|
+
* forever. Safe to run against a live generator: core's generators are pure functions of elapsed
|
|
36
|
+
* time (they latch no state, only reuse their sample record).
|
|
37
|
+
*/
|
|
38
|
+
// alloc-ok: lifecycle-edge — the scan runs once per animate command, never per frame.
|
|
39
|
+
function calcIterationDurationMs(generator) {
|
|
40
|
+
let duration = 0;
|
|
41
|
+
let state = generator.sample(duration);
|
|
42
|
+
while (!state.done && duration < exports.MAX_ITERATION_DURATION_MS) {
|
|
43
|
+
duration += exports.ITERATION_SCAN_STEP_MS;
|
|
44
|
+
state = generator.sample(duration);
|
|
45
|
+
}
|
|
46
|
+
return duration >= exports.MAX_ITERATION_DURATION_MS ? Infinity : duration;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Refuse a fold the pin cannot execute (packet L3). The fold divides by the RESOLVED iteration
|
|
50
|
+
* length (play + repeatDelay), so a zero-length play with NOTHING separating it yields NaN at the
|
|
51
|
+
* pin and is refused here. A zero-length play separated by a `repeatDelay` is a different shape:
|
|
52
|
+
* the pin totals the delay gaps around the zero-length plays (`JSAnimation.initAnimation` —
|
|
53
|
+
* `totalDuration = calculatedDuration * (repeat + 1) + repeatDelay * repeat`), so the fold
|
|
54
|
+
* EXECUTES, holding each play's final keyframe through the gaps (transition-default-selection
|
|
55
|
+
* F5). An unbounded iteration still emits NaN for the rest of the animation and is refused either
|
|
56
|
+
* way. The refusal is CROSS-FIELD — either length is legal without `repeat`, where nothing
|
|
57
|
+
* divides. Returns the loud refusal or null (the `springKeyframeCountRefusal` shape: this module
|
|
58
|
+
* crosses to the UI runtime, where a throw is a bare `std::terminate`).
|
|
59
|
+
*/
|
|
60
|
+
// alloc-ok: lifecycle-edge — a refusal built at command/validation time, never per frame.
|
|
61
|
+
function repeatIterationRefusal(iterationDurationMs, repeat, repeatDelayMs = 0) {
|
|
62
|
+
if (repeat <= 0)
|
|
63
|
+
return null;
|
|
64
|
+
if (iterationDurationMs <= 0 && repeatDelayMs <= 0) {
|
|
65
|
+
return new Error('a repeated transition cannot fold a zero-length iteration — `repeat` divides by the ' +
|
|
66
|
+
"iteration's duration, so a zero-duration play repeats nothing (the pinned Motion " +
|
|
67
|
+
'silently holds the final keyframe instead). Give the transition a duration, or drop ' +
|
|
68
|
+
'`repeat` (REQ-TIMING-003, T18-a L3).');
|
|
69
|
+
}
|
|
70
|
+
if (!Number.isFinite(iterationDurationMs)) {
|
|
71
|
+
return new Error(`a repeated transition cannot fold an iteration that never settles — this trajectory is ` +
|
|
72
|
+
`still moving after ${exports.MAX_ITERATION_DURATION_MS}ms, past the point where the pinned ` +
|
|
73
|
+
'Motion stops measuring it and starts emitting NaN. Bound the transition (duration, or ' +
|
|
74
|
+
'stiffer physics), or drop `repeat` (REQ-TIMING-003, T18-a L3).');
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
function iterationMeasurementLane(targetIsNumeric, isExplicitSpring, isTween) {
|
|
79
|
+
// A keyframe ARRAY target is played by the keyframes generator unless the transition explicitly
|
|
80
|
+
// asked for a spring — so an untyped or tween-typed transition changes lane with the target.
|
|
81
|
+
if (!targetIsNumeric && !isExplicitSpring)
|
|
82
|
+
return 'keyframes';
|
|
83
|
+
if (isTween)
|
|
84
|
+
return 'timing';
|
|
85
|
+
return 'spring';
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The refusal's identity, shared by BOTH backends (T18-a fix-up, review MAJOR).
|
|
89
|
+
*
|
|
90
|
+
* `repeatIterationRefusal` returns a bare `Error` because it crosses to the UI runtime. The
|
|
91
|
+
* reference backend re-wraps it as an `InvalidTransitionError`, which names the offending property
|
|
92
|
+
* instead of surfacing an anonymous driver-lane fault. The worklet backend cannot: it must not
|
|
93
|
+
* construct a core class inside a worklet, and the lane marshals only `{name, message}` to RN
|
|
94
|
+
* anyway, so the class identity could not survive the crossing even if it could be built.
|
|
95
|
+
*
|
|
96
|
+
* What CAN be identical across the crossing is exactly what the crossing carries — the name and the
|
|
97
|
+
* message. Both backends therefore build them here, so a developer sees one refusal whichever
|
|
98
|
+
* engine they are on, and `repeatRefusalParity` pins the two against each other.
|
|
99
|
+
*/
|
|
100
|
+
exports.REPEAT_REFUSAL_NAME = 'InvalidTransitionError';
|
|
101
|
+
/** The offending property a fold refusal names — `InvalidTransitionError.key` on the JS backend. */
|
|
102
|
+
exports.REPEAT_REFUSAL_KEY = 'repeat';
|
|
103
|
+
/**
|
|
104
|
+
* A fold refusal, as a CONSUMER can recognize it after any transport.
|
|
105
|
+
*
|
|
106
|
+
* The severity router used `error instanceof InvalidTransitionError && error.key === 'repeat'`,
|
|
107
|
+
* which is decidable only on the reference backend: the UI runtime cannot construct a core class,
|
|
108
|
+
* and the crossing marshals plain data, so the shipping backend's refusal arrived as a bare `Error`
|
|
109
|
+
* and the router silently stopped firing (T18-a round-9 review BLOCKING 1). Class identity is not
|
|
110
|
+
* available across the crossing, so the recognition contract cannot be built on it — it is
|
|
111
|
+
* STRUCTURAL by necessity, and it lives here, beside the name and the message it completes, so the
|
|
112
|
+
* whole identity is one thing that one floor can hold.
|
|
113
|
+
*
|
|
114
|
+
* `isRepeatFoldRefusalIdentity` is the two-field core, so the UI lane — which holds the refusal as a
|
|
115
|
+
* marshalled `{name, message, key}` record, never an `Error` — decides it with the SAME rule the
|
|
116
|
+
* consumer applies to the rethrown error rather than a second copy that can drift.
|
|
117
|
+
*/
|
|
118
|
+
function isRepeatFoldRefusalIdentity(name, key) {
|
|
119
|
+
return name === exports.REPEAT_REFUSAL_NAME && key === exports.REPEAT_REFUSAL_KEY;
|
|
120
|
+
}
|
|
121
|
+
function isRepeatFoldRefusal(error) {
|
|
122
|
+
if (typeof error !== 'object' || error === null)
|
|
123
|
+
return false;
|
|
124
|
+
const candidate = error;
|
|
125
|
+
return isRepeatFoldRefusalIdentity(candidate.name, candidate.key);
|
|
126
|
+
}
|
|
127
|
+
// Mirrors `InvalidTransitionError`'s message format for an anonymous component with a numeric
|
|
128
|
+
// value. It is duplicated rather than imported because `validate.ts` is JS-side-only (it reaches
|
|
129
|
+
// the subset registry and the value-type parsers) and this module crosses to the UI runtime. The
|
|
130
|
+
// duplication is held honest by the cross-backend floor, not by review.
|
|
131
|
+
// alloc-ok: lifecycle-edge — a refusal built at command time, never per frame.
|
|
132
|
+
function repeatRefusalMessage(repeat, reason) {
|
|
133
|
+
return `<Motion.View>: invalid transition option 'repeat' = ${String(repeat)} — ${reason}`;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The fold's geometry, verbatim from the pin (`JSAnimation.initAnimation:169-170`). The trailing
|
|
137
|
+
* delay of the LAST iteration is subtracted: a repeat delay separates plays, it does not extend
|
|
138
|
+
* the animation past its final one.
|
|
139
|
+
*/
|
|
140
|
+
// alloc-ok: lifecycle-edge — geometry resolved once per animate command.
|
|
141
|
+
function repeatFoldGeometry(config) {
|
|
142
|
+
const resolvedDurationMs = config.iterationDurationMs + config.repeatDelayMs;
|
|
143
|
+
return {
|
|
144
|
+
resolvedDurationMs,
|
|
145
|
+
totalDurationMs: resolvedDurationMs * (config.repeat + 1) - config.repeatDelayMs,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function clamp01(value) {
|
|
149
|
+
return value > 1 ? 1 : value < 0 ? 0 : value;
|
|
150
|
+
}
|
|
151
|
+
// DECLARATION ORDER IS LOAD-BEARING in this module: it ships FILE-tagged, so the workletizer
|
|
152
|
+
// emits every function as a non-hoisted const and a worklet may only reference worklets declared
|
|
153
|
+
// ABOVE it. `buildRepeatedGenerator` calls this, so this comes first. Pinned by
|
|
154
|
+
// check-worklet-forward-refs.test.ts.
|
|
155
|
+
/**
|
|
156
|
+
* Wrap `base` in the repeat fold (REQ-TIMING-003). The returned generator is a `Generator` like any
|
|
157
|
+
* other — the driver steps it without knowing it repeats.
|
|
158
|
+
*
|
|
159
|
+
* Two laws diverge deliberately from a naive port of the pin's `tick`:
|
|
160
|
+
*
|
|
161
|
+
* - A REVERSED leg negates the reported velocity. Our `GeneratorSample` carries the ANALYTIC
|
|
162
|
+
* velocity that the C1 retarget seam and `liveFor` read (REQ-SPRING-009 / REQ-DRIVER-025),
|
|
163
|
+
* whereas the pin's user-visible velocity is a finite-diff of the value — correctly signed by
|
|
164
|
+
* construction even though its generator's internal velocity on a reversed leg is not.
|
|
165
|
+
* Reproducing the pin's internal sign would seed a mid-reverse retarget in the wrong direction.
|
|
166
|
+
* - `done` follows the fold's `totalDuration`, never the base trajectory's own settle. The base
|
|
167
|
+
* settles once per iteration; only the last one ends the animation, and an endless repeat is
|
|
168
|
+
* never done at all (packet L5 — such an element intentionally never quiesces).
|
|
169
|
+
*/
|
|
170
|
+
// alloc-ok: lifecycle-edge — construction resolves geometry and builds the ONE reused sample
|
|
171
|
+
// record; sample() below mutates it and allocates nothing (REQ-DRIVER-021).
|
|
172
|
+
function repeatGenerator(base, config) {
|
|
173
|
+
const { repeat, leg, repeatDelayMs, iterationDurationMs } = config;
|
|
174
|
+
const { resolvedDurationMs, totalDurationMs } = repeatFoldGeometry({
|
|
175
|
+
repeat,
|
|
176
|
+
repeatDelayMs,
|
|
177
|
+
iterationDurationMs,
|
|
178
|
+
});
|
|
179
|
+
const mirrored = leg.type === 'mirror' ? leg.mirrored : null;
|
|
180
|
+
const isReverse = leg.type === 'reverse';
|
|
181
|
+
// The pin's terminal keyframe rule (motion-dom keyframes/get-final.ts), reduced to the only case
|
|
182
|
+
// where it disagrees with where this fold naturally lands:
|
|
183
|
+
//
|
|
184
|
+
// useFirstKeyframe = speed < 0 || (repeat && repeatType !== 'loop' && repeat % 2 === 1)
|
|
185
|
+
//
|
|
186
|
+
// `repeat % 2 === 1` selects exactly the ODD INTEGERS, and `repeatType !== 'loop'` puts BOTH
|
|
187
|
+
// `reverse` and `mirror` in that branch. An integer fold already ENDS on the pin's keyframe by
|
|
188
|
+
// construction — a whole-number progress is corrected to `iterationProgress` 1; a `reverse` leg
|
|
189
|
+
// reflects that to 0 and a `mirror` leg samples the mirrored trajectory at ITS end, which is the
|
|
190
|
+
// base origin, so both odd cases report the pin's FIRST keyframe while a `loop` leg and every
|
|
191
|
+
// EVEN case hold at 1 and report the pin's LAST. So the integer path needs no snap and must not
|
|
192
|
+
// get one. (Measured on the pin, `[0,100]` and `[0,100,30]`, repeats 1-4 × all three types: odd
|
|
193
|
+
// reverse and odd mirror both land on FIRST, everything else on LAST.) A FRACTIONAL repeat is
|
|
194
|
+
// the gap: at forward speed it satisfies no branch of the rule, so the pin commits the LAST keyframe while this
|
|
195
|
+
// fold is mid-play. `speed < 0` has no native producer (drivers accumulate non-negative elapsed
|
|
196
|
+
// only), so that half of the rule is stated and not implemented.
|
|
197
|
+
const snapsAtTerminal = Number.isFinite(repeat) && repeat > 0 && !Number.isInteger(repeat);
|
|
198
|
+
// The pin's snap is NOT part of the fold: `JSAnimation.tick` computes the fold, then applies
|
|
199
|
+
// `getFinalKeyframe` under its `isAnimationFinished` gate
|
|
200
|
+
// (`holdTime === null && (state === 'finished' || (state === 'running' && done))`), and only then
|
|
201
|
+
// calls `finish()` — which never touches the value. A FRESH, non-autoplaying instance never
|
|
202
|
+
// satisfies that gate: it is `paused`, so `holdTime` is non-null and neither disjunct holds, and
|
|
203
|
+
// its `sample()` returns the raw fold at EVERY time. An already-FINISHED instance takes the
|
|
204
|
+
// override at ANY sampled time, mid-play included; a RUNNING one only at or past `totalDuration`,
|
|
205
|
+
// because that disjunct also requires `done`. Measured on `repeat: 1.5 reverse`, whose raw fold is
|
|
206
|
+
// 80 mid-play and 50 at the terminal — paused 80/50, running 80/100, finished 100/100.
|
|
207
|
+
//
|
|
208
|
+
// `gen-repeat-goldens.mjs` builds a FRESH `autoplay: false` instance per sample, so the golden
|
|
209
|
+
// records both layers: `samples` are raw-fold seeks (50 at the terminal) and `terminalValue` comes
|
|
210
|
+
// from playing to the end (100). Keeping `sample` seek-faithful and exposing the commit separately
|
|
211
|
+
// reproduces that split instead of collapsing it; putting the snap inside `sample` broke the
|
|
212
|
+
// pinned `tween-reverse-fractional` samples, which is the golden reporting the wrong layer (H2 F1).
|
|
213
|
+
//
|
|
214
|
+
// The value is the FORWARD base at its end, never the leg the partial play was on: the pin
|
|
215
|
+
// commits `resolvedKeyframes[length - 1]` whatever direction it was travelling, so a `reverse` or
|
|
216
|
+
// `mirror` fold descending toward the origin jumps UP to the target. Core's generators are pure
|
|
217
|
+
// functions of elapsed, so sampling one here is safe and allocation-free at the frame path.
|
|
218
|
+
const finalValue = snapsAtTerminal ? base.sample(resolvedDurationMs).value : undefined;
|
|
219
|
+
// The delay's share of one resolved iteration — the reverse leg's re-bias factor, hoisted so the
|
|
220
|
+
// frame path does no division beyond the progress one.
|
|
221
|
+
const delayFraction = repeatDelayMs / resolvedDurationMs;
|
|
222
|
+
const out = { value: 0, velocity: 0, done: false };
|
|
223
|
+
return {
|
|
224
|
+
finalValue,
|
|
225
|
+
sample(rawElapsedMs) {
|
|
226
|
+
// The pin clamps before folding (`JSAnimation.tick`: `Math.max(timeWithoutDelay, 0)`), and so
|
|
227
|
+
// must this: `floor` of a negative progress is -1, an ODD iteration, so a `reverse` leg would
|
|
228
|
+
// reflect and report the far endpoint at a moment the animation has not begun. Today's
|
|
229
|
+
// drivers only ever accumulate non-negative deltas, but the pin's one producer of negative
|
|
230
|
+
// elapsed is `currentTime - delay` — property-lane `delay`, this packet's successor rung.
|
|
231
|
+
// `Math.max` rather than `rawElapsedMs > 0 ? rawElapsedMs : 0` — they agree on every number
|
|
232
|
+
// and disagree on NaN, which the comparison silently maps to 0 (reporting the trajectory's
|
|
233
|
+
// ORIGIN for a broken clock) while `Math.max` propagates it. A NaN clock is a programmer
|
|
234
|
+
// error, and it must stay loud rather than resolve to a plausible-looking value (review
|
|
235
|
+
// MINOR).
|
|
236
|
+
const elapsedMs = Math.max(rawElapsedMs, 0);
|
|
237
|
+
let elapsed = elapsedMs;
|
|
238
|
+
let frameGenerator = base;
|
|
239
|
+
let reversedLeg = false;
|
|
240
|
+
if (repeat > 0) {
|
|
241
|
+
// Progress across the WHOLE fold in iteration units: 2.5 is halfway through the third play.
|
|
242
|
+
const progress = Math.min(elapsedMs, totalDurationMs) / resolvedDurationMs;
|
|
243
|
+
let currentIteration = Math.floor(progress);
|
|
244
|
+
let iterationProgress = progress % 1;
|
|
245
|
+
// A whole-number progress at or past the first boundary is the END of the PREVIOUS
|
|
246
|
+
// iteration, not the start of the next — otherwise every boundary flickers a frame of the
|
|
247
|
+
// successor's origin.
|
|
248
|
+
if (iterationProgress === 0 && progress >= 1) {
|
|
249
|
+
iterationProgress = 1;
|
|
250
|
+
currentIteration--;
|
|
251
|
+
}
|
|
252
|
+
if (currentIteration > repeat + 1)
|
|
253
|
+
currentIteration = repeat + 1;
|
|
254
|
+
if (currentIteration % 2 !== 0) {
|
|
255
|
+
if (isReverse) {
|
|
256
|
+
iterationProgress = 1 - iterationProgress;
|
|
257
|
+
// Reflecting progress would put the dead time at the START of the reversed play; this
|
|
258
|
+
// subtraction moves it back to the END, so the rule stays uniform across repeat types:
|
|
259
|
+
// every play holds on the value it FINISHED on. A reversed leg therefore begins
|
|
260
|
+
// descending immediately from the iteration's end value and pauses at its start value.
|
|
261
|
+
if (repeatDelayMs > 0)
|
|
262
|
+
iterationProgress -= delayFraction;
|
|
263
|
+
reversedLeg = true;
|
|
264
|
+
}
|
|
265
|
+
else if (mirrored !== null) {
|
|
266
|
+
frameGenerator = mirrored;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
elapsed = clamp01(iterationProgress) * resolvedDurationMs;
|
|
270
|
+
}
|
|
271
|
+
const sample = frameGenerator.sample(elapsed);
|
|
272
|
+
out.value = sample.value;
|
|
273
|
+
out.velocity = reversedLeg ? -sample.velocity : sample.velocity;
|
|
274
|
+
out.done = elapsedMs >= totalDurationMs;
|
|
275
|
+
return out;
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Build the folded generator for a repeated transition — the ONE implementation every driver seam
|
|
281
|
+
* calls (both drivers' start AND retarget seams). Sharing this call does not by itself prevent
|
|
282
|
+
* skew: the fix-up review found both backends sharing it while both omitted it from retarget.
|
|
283
|
+
* Parity is held by the executing floor, `driverParity.differential.test.ts`. Each driver supplies
|
|
284
|
+
* only what it alone knows:
|
|
285
|
+
*
|
|
286
|
+
* - `base` / `leg` — its own already-CONSTRUCTED generators. The mirrored trajectory rides inside
|
|
287
|
+
* the `mirror` leg (origin/target swapped, seed velocity negated, keyframe list reversed), so a
|
|
288
|
+
* mirror fold without one stays unrepresentable.
|
|
289
|
+
* - `knownIterationDurationMs` — the transition's own resolved duration when it has one (a tween,
|
|
290
|
+
* a keyframe array, or a duration-resolved spring, mirroring the pin's `calculatedDuration`), or
|
|
291
|
+
* null for a physics/visualDuration spring, whose length is measured by the scan. A known ZERO
|
|
292
|
+
* is honored exactly: an authored `duration: 0` under a repeatDelay fold has genuinely
|
|
293
|
+
* zero-length plays (transition-default-selection F5) — scanning the base instead would invent
|
|
294
|
+
* a 50ms iteration the pin does not have.
|
|
295
|
+
*
|
|
296
|
+
* Generators are passed as VALUES, never as a factory callback: synchronously invoking a captured
|
|
297
|
+
* function parameter on the UI runtime is the wrong-runtime crash class, and `check:worklet-closures`
|
|
298
|
+
* fails closed on it. Both drivers therefore build their own legs eagerly and hand them over.
|
|
299
|
+
*
|
|
300
|
+
* Returns the folded generator, or the loud refusal for a fold the pin cannot execute (L3). The
|
|
301
|
+
* caller routes that refusal through its severity boundary — this never throws, because it runs on
|
|
302
|
+
* the UI runtime where a throw is a bare `std::terminate`.
|
|
303
|
+
*/
|
|
304
|
+
// alloc-ok: lifecycle-edge — one fold construction per animate command, never per frame.
|
|
305
|
+
function buildRepeatedGenerator(repeat, leg, repeatDelayMs, base, knownIterationDurationMs) {
|
|
306
|
+
const iterationDurationMs = knownIterationDurationMs ?? calcIterationDurationMs(base);
|
|
307
|
+
const refusal = repeatIterationRefusal(iterationDurationMs, repeat, repeatDelayMs);
|
|
308
|
+
if (refusal !== null)
|
|
309
|
+
return refusal;
|
|
310
|
+
return repeatGenerator(base, { repeat, leg, repeatDelayMs, iterationDurationMs });
|
|
311
|
+
}
|