@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,124 @@
|
|
|
1
|
+
// The shared target-count-dependent keyframe timing law (REQ-API-033 law d). Component
|
|
2
|
+
// suppliers and raw Driver.command preparation both use this one predicate, so a transition
|
|
3
|
+
// cannot pass one boundary and become inert or fall back at another.
|
|
4
|
+
import { InvalidTargetError, InvalidTransitionError } from "../component/validate.js";
|
|
5
|
+
import { NAMED_EASING_KEYS, resolveEasing } from "../timing.js";
|
|
6
|
+
import { springKeyframeCountRefusal } from "../transition.js";
|
|
7
|
+
function isPresentArrayIndex(value, index) {
|
|
8
|
+
return Object.hasOwn(value, index);
|
|
9
|
+
}
|
|
10
|
+
function invalidTiming(componentId, key, value, reason) {
|
|
11
|
+
return new InvalidTransitionError(componentId, key, value, reason);
|
|
12
|
+
}
|
|
13
|
+
const NAMED_EASING_SET = new Set(NAMED_EASING_KEYS);
|
|
14
|
+
function isValidSingleEasing(value) {
|
|
15
|
+
if (typeof value === 'string')
|
|
16
|
+
return NAMED_EASING_SET.has(value);
|
|
17
|
+
if (!Array.isArray(value) || value.length !== 4)
|
|
18
|
+
return false;
|
|
19
|
+
for (let index = 0; index < 4; index++) {
|
|
20
|
+
if (!isPresentArrayIndex(value, index))
|
|
21
|
+
return false;
|
|
22
|
+
const point = value[index];
|
|
23
|
+
if (typeof point !== 'number' || !Number.isFinite(point))
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
function isEasingList(value) {
|
|
29
|
+
return Array.isArray(value) && typeof value[0] !== 'number';
|
|
30
|
+
}
|
|
31
|
+
export function keyframeTimingRefusal(componentId, key, value, transition) {
|
|
32
|
+
if (transition === undefined)
|
|
33
|
+
return null;
|
|
34
|
+
const hasTimes = transition.times !== undefined;
|
|
35
|
+
// Family-2 / T20: easings and easing are exclusive aliases of ease (validated upstream).
|
|
36
|
+
const easeField = transition.ease !== undefined
|
|
37
|
+
? transition.ease
|
|
38
|
+
: transition.easings !== undefined
|
|
39
|
+
? transition.easings
|
|
40
|
+
: transition.easing;
|
|
41
|
+
const easeKey = transition.ease !== undefined ? 'ease' : transition.easings !== undefined ? 'easings' : 'easing';
|
|
42
|
+
const easeIsList = easeField !== undefined && isEasingList(easeField);
|
|
43
|
+
if (!hasTimes && !easeIsList)
|
|
44
|
+
return null;
|
|
45
|
+
if (hasTimes) {
|
|
46
|
+
const times = transition.times;
|
|
47
|
+
if (!Array.isArray(times)) {
|
|
48
|
+
return invalidTiming(componentId, 'times', times, 'times must be an array of finite offsets in [0, 1] (REQ-API-033 law d)');
|
|
49
|
+
}
|
|
50
|
+
for (let index = 0; index < times.length; index++) {
|
|
51
|
+
if (!isPresentArrayIndex(times, index)) {
|
|
52
|
+
return invalidTiming(componentId, 'times', times, `times is missing index ${index}; sparse timing arrays are invalid (REQ-API-033 law d)`);
|
|
53
|
+
}
|
|
54
|
+
const offset = times[index];
|
|
55
|
+
if (typeof offset !== 'number' || !Number.isFinite(offset) || offset < 0 || offset > 1) {
|
|
56
|
+
return invalidTiming(componentId, 'times', times, 'times must contain only finite offsets in [0, 1] (REQ-API-033 law d)');
|
|
57
|
+
}
|
|
58
|
+
if (index > 0 && offset < times[index - 1]) {
|
|
59
|
+
return invalidTiming(componentId, 'times', times, 'times must be nondecreasing so every keyframe segment has a non-negative duration (REQ-API-033 law d)');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (easeIsList) {
|
|
64
|
+
const easeList = easeField;
|
|
65
|
+
for (let index = 0; index < easeList.length; index++) {
|
|
66
|
+
if (!isPresentArrayIndex(easeList, index)) {
|
|
67
|
+
return invalidTiming(componentId, easeKey, easeList, `${easeKey} is missing index ${index}; sparse timing arrays are invalid (REQ-API-033 law d)`);
|
|
68
|
+
}
|
|
69
|
+
if (!isValidSingleEasing(easeList[index])) {
|
|
70
|
+
return invalidTiming(componentId, easeKey, easeList, `each per-segment ${easeKey} entry must be a named easing or one 4-point cubic-bezier (REQ-API-033 law d)`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (!Array.isArray(value)) {
|
|
75
|
+
return invalidTiming(componentId, hasTimes ? 'times' : easeKey, hasTimes ? transition.times : easeField, `${hasTimes ? 'times' : 'a per-segment ease list'} applies only to a KEYFRAME-ARRAY target — ` +
|
|
76
|
+
`'${key}' is a single value, not a keyframe array (REQ-API-033 law d)`);
|
|
77
|
+
}
|
|
78
|
+
if (transition.type === 'spring') {
|
|
79
|
+
return invalidTiming(componentId, hasTimes ? 'times' : easeKey, hasTimes ? transition.times : easeField, `an explicit spring ignores ${hasTimes ? 'times' : 'a per-segment ease list'} for '${key}'; ` +
|
|
80
|
+
'keyframe timing is not executable on an explicit spring (REQ-API-033 law d)');
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
/** The complete target/effective-transition keyframe law shared by every supplier. */
|
|
85
|
+
export function keyframeTransitionRefusal(componentId, key, value, transition) {
|
|
86
|
+
if (Array.isArray(value) && transition?.type === 'spring') {
|
|
87
|
+
const springRefusal = springKeyframeCountRefusal(value.length);
|
|
88
|
+
if (springRefusal !== null)
|
|
89
|
+
return springRefusal;
|
|
90
|
+
}
|
|
91
|
+
return keyframeTimingRefusal(componentId, key, value, transition);
|
|
92
|
+
}
|
|
93
|
+
/** `initial` is a synchronous scalar first-paint supplier, never a keyframe command. */
|
|
94
|
+
export function initialTargetRefusal(componentId, key, value) {
|
|
95
|
+
if (!Array.isArray(value))
|
|
96
|
+
return null;
|
|
97
|
+
return new InvalidTargetError(componentId, key, value, `initial.${key} must be a scalar first-paint value; keyframe arrays belong to animation-time suppliers (REQ-API-033)`);
|
|
98
|
+
}
|
|
99
|
+
// `keyframesGenerator` is exported and can therefore be reached without a component/driver supplier.
|
|
100
|
+
// Keep its raw timing configuration on the SAME schema/cross-field authority as those suppliers: a
|
|
101
|
+
// malformed direct call must fail typed before it builds segments. Shape-valid count mismatches are
|
|
102
|
+
// deliberate pin behavior: offsets fall back to even and missing list easings become linear.
|
|
103
|
+
export function keyframeConfigRefusal(keyframes, config) {
|
|
104
|
+
const timing = keyframeTimingRefusal(undefined, 'keyframes', keyframes, config);
|
|
105
|
+
if (timing !== null)
|
|
106
|
+
return timing;
|
|
107
|
+
if (config?.ease === undefined)
|
|
108
|
+
return null;
|
|
109
|
+
const easeSegments = isEasingList(config.ease) ? config.ease : [config.ease];
|
|
110
|
+
for (const segment of easeSegments) {
|
|
111
|
+
if (!isValidSingleEasing(segment)) {
|
|
112
|
+
return invalidTiming(undefined, 'ease', config.ease, 'ease must be a named easing or one 4-point cubic-bezier (REQ-API-033 law d)');
|
|
113
|
+
}
|
|
114
|
+
// Derived executability (i73cik): finite cubic control points can still sample NaN.
|
|
115
|
+
const easer = resolveEasing(segment);
|
|
116
|
+
for (const progress of [0, 0.25, 0.5, 0.75, 1]) {
|
|
117
|
+
if (!Number.isFinite(easer(progress))) {
|
|
118
|
+
return invalidTiming(undefined, 'ease', config.ease, `the derived segment ease emits a non-finite sample at progress ${progress} — this ` +
|
|
119
|
+
'config is not executable (G-INV-8)');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// The target-count-dependent part of the pinned keyframes generator, isolated as a pure dual-runtime
|
|
3
|
+
// seam. Core's reference generator and native's UI-runtime generator both call these functions, so a
|
|
4
|
+
// shape-valid authored list cannot take different fallback paths on JS and UI.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.keyframeOffsetAt = keyframeOffsetAt;
|
|
7
|
+
exports.keyframeSegmentEase = keyframeSegmentEase;
|
|
8
|
+
/** Motion uses authored offsets only when there is exactly one per keyframe; otherwise offsets are even. */
|
|
9
|
+
function keyframeOffsetAt(times, index, keyframeCount) {
|
|
10
|
+
if (times !== undefined && times.length === keyframeCount) {
|
|
11
|
+
const authored = times[index];
|
|
12
|
+
if (authored !== undefined)
|
|
13
|
+
return authored;
|
|
14
|
+
}
|
|
15
|
+
return index / (keyframeCount - 1);
|
|
16
|
+
}
|
|
17
|
+
/** Motion indexes an easing list by segment, using linear for a missing entry and ignoring extras. */
|
|
18
|
+
function keyframeSegmentEase(ease, segmentIndex) {
|
|
19
|
+
if (ease === undefined)
|
|
20
|
+
return 'easeInOut';
|
|
21
|
+
if (Array.isArray(ease) && typeof ease[0] !== 'number')
|
|
22
|
+
return ease[segmentIndex] ?? 'linear';
|
|
23
|
+
return ease;
|
|
24
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Easing } from "../timing.cjs";
|
|
2
|
+
/** Motion uses authored offsets only when there is exactly one per keyframe; otherwise offsets are even. */
|
|
3
|
+
export declare function keyframeOffsetAt(times: readonly number[] | undefined, index: number, keyframeCount: number): number;
|
|
4
|
+
/** Motion indexes an easing list by segment, using linear for a missing entry and ignoring extras. */
|
|
5
|
+
export declare function keyframeSegmentEase(ease: Easing | readonly Easing[] | undefined, segmentIndex: number): Easing;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Easing } from "../timing.js";
|
|
2
|
+
/** Motion uses authored offsets only when there is exactly one per keyframe; otherwise offsets are even. */
|
|
3
|
+
export declare function keyframeOffsetAt(times: readonly number[] | undefined, index: number, keyframeCount: number): number;
|
|
4
|
+
/** Motion indexes an easing list by segment, using linear for a missing entry and ignoring extras. */
|
|
5
|
+
export declare function keyframeSegmentEase(ease: Easing | readonly Easing[] | undefined, segmentIndex: number): Easing;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// The target-count-dependent part of the pinned keyframes generator, isolated as a pure dual-runtime
|
|
2
|
+
// seam. Core's reference generator and native's UI-runtime generator both call these functions, so a
|
|
3
|
+
// shape-valid authored list cannot take different fallback paths on JS and UI.
|
|
4
|
+
/** Motion uses authored offsets only when there is exactly one per keyframe; otherwise offsets are even. */
|
|
5
|
+
export function keyframeOffsetAt(times, index, keyframeCount) {
|
|
6
|
+
'worklet';
|
|
7
|
+
if (times !== undefined && times.length === keyframeCount) {
|
|
8
|
+
const authored = times[index];
|
|
9
|
+
if (authored !== undefined)
|
|
10
|
+
return authored;
|
|
11
|
+
}
|
|
12
|
+
return index / (keyframeCount - 1);
|
|
13
|
+
}
|
|
14
|
+
/** Motion indexes an easing list by segment, using linear for a missing entry and ignoring extras. */
|
|
15
|
+
export function keyframeSegmentEase(ease, segmentIndex) {
|
|
16
|
+
'worklet';
|
|
17
|
+
if (ease === undefined)
|
|
18
|
+
return 'easeInOut';
|
|
19
|
+
if (Array.isArray(ease) && typeof ease[0] !== 'number')
|
|
20
|
+
return ease[segmentIndex] ?? 'linear';
|
|
21
|
+
return ease;
|
|
22
|
+
}
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// REQ-DRIVER-030 — the one raw DriverCommand → immutable PreparedDriverCommand boundary. Concrete
|
|
3
|
+
// backends receive only the prepared protocol, so caller accessors, mutation, optional velocity, and
|
|
4
|
+
// per-property transition fallback semantics are settled exactly once before backend dispatch.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.captureTransition = captureTransition;
|
|
7
|
+
exports.prepareDriverCommand = prepareDriverCommand;
|
|
8
|
+
exports.adaptPreparedDriver = adaptPreparedDriver;
|
|
9
|
+
const keyframeTiming_1 = require("./keyframeTiming.cjs");
|
|
10
|
+
const boundedArray_1 = require("../component/boundedArray.cjs");
|
|
11
|
+
const validate_1 = require("../component/validate.cjs");
|
|
12
|
+
const types_1 = require("../component/types.cjs");
|
|
13
|
+
// `PropTransition` additionally carries controller-resolved DefaultTransition springs. `restSpeed`
|
|
14
|
+
// is internal-only (never an authored public Transition option) but must survive preparation so both
|
|
15
|
+
// drivers retain the pinned default's settle threshold.
|
|
16
|
+
function captureTarget(to) {
|
|
17
|
+
if (typeof to === 'number')
|
|
18
|
+
return to;
|
|
19
|
+
// Read the caller's cardinality exactly once, then copy that many indexed values. Array iteration
|
|
20
|
+
// may re-read a stateful proxy's `length` between elements; this boundary must capture one target
|
|
21
|
+
// truth before validation and dispatch.
|
|
22
|
+
const capture = (0, boundedArray_1.captureBoundedArray)(to);
|
|
23
|
+
if (capture.kind !== 'captured') {
|
|
24
|
+
throw new validate_1.InvalidTargetError(undefined, 'to', (0, boundedArray_1.capturedArrayDescription)(capture), 'driver keyframe targets must have a safe array length (REQ-API-033)');
|
|
25
|
+
}
|
|
26
|
+
return capture.values;
|
|
27
|
+
}
|
|
28
|
+
// Capture the one nested level the legal timing schema admits: an outer `ease` list may contain a
|
|
29
|
+
// 4-number cubic-bezier tuple. Generic recursive cloning was unsafe — a malformed cyclic/deeper array
|
|
30
|
+
// overflowed before it reached the typed validator. Sparse slots deliberately stay sparse; validation
|
|
31
|
+
// names the missing index rather than letting Array.prototype.every skip it. A deeper array remains an
|
|
32
|
+
// opaque malformed member and is rejected by the bounded timing predicate before any backend runs it.
|
|
33
|
+
function captureTimingArray(value, nested = false) {
|
|
34
|
+
const capture = nested ? (0, boundedArray_1.captureBoundedCubicBezier)(value) : (0, boundedArray_1.captureBoundedArray)(value);
|
|
35
|
+
if (capture.kind !== 'captured')
|
|
36
|
+
return (0, boundedArray_1.capturedArrayDescription)(capture);
|
|
37
|
+
const length = capture.values.length;
|
|
38
|
+
const captured = [];
|
|
39
|
+
captured.length = length;
|
|
40
|
+
for (let index = 0; index < length; index++) {
|
|
41
|
+
if (!Object.hasOwn(capture.values, index))
|
|
42
|
+
continue;
|
|
43
|
+
const element = capture.values[index];
|
|
44
|
+
captured[index] =
|
|
45
|
+
!nested && Array.isArray(element) ? captureTimingArray(element, true) : element;
|
|
46
|
+
}
|
|
47
|
+
return Object.freeze(captured);
|
|
48
|
+
}
|
|
49
|
+
const TRANSITION_MAP_KEYS = new Set(['default', 'layout', ...types_1.TARGET_PROPERTY_KEYS]);
|
|
50
|
+
function transitionMapRefusal(transition) {
|
|
51
|
+
if (typeof transition !== 'object' || transition === null || Array.isArray(transition))
|
|
52
|
+
return null;
|
|
53
|
+
for (const key of Reflect.ownKeys(transition)) {
|
|
54
|
+
if (typeof key === 'string' && TRANSITION_MAP_KEYS.has(key)) {
|
|
55
|
+
return new validate_1.InvalidTransitionError(undefined, key, transition[key], 'a raw DriverCommand must carry a flat transition selected by the component boundary; ' +
|
|
56
|
+
'transition maps are not executable at the backend protocol (REQ-DRIVER-033)');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function isTransitionMapKey(key) {
|
|
62
|
+
return TRANSITION_MAP_KEYS.has(key);
|
|
63
|
+
}
|
|
64
|
+
function targetTransitionsShapeRefusal(value, targets) {
|
|
65
|
+
if (value === undefined)
|
|
66
|
+
return null;
|
|
67
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
68
|
+
return new validate_1.InvalidTransitionError(undefined, 'targetTransitions', value, 'retarget targetTransitions must be a plain object keyed by retarget targets (REQ-DRIVER-030)');
|
|
69
|
+
}
|
|
70
|
+
const prototype = Object.getPrototypeOf(value);
|
|
71
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
72
|
+
return new validate_1.InvalidTransitionError(undefined, 'targetTransitions', value, 'retarget targetTransitions must be a plain object keyed by retarget targets (REQ-DRIVER-030)');
|
|
73
|
+
}
|
|
74
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
75
|
+
if (typeof key !== 'string') {
|
|
76
|
+
return new validate_1.InvalidTransitionError(undefined, String(key), undefined, 'retarget targetTransitions may only contain string retarget target keys (REQ-DRIVER-030)');
|
|
77
|
+
}
|
|
78
|
+
if (!Object.hasOwn(targets, key)) {
|
|
79
|
+
return new validate_1.InvalidTransitionError(undefined, key, undefined, 'retarget targetTransitions may only override a key present in targets (REQ-DRIVER-030)');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
// Capture exactly one legal nested transition-bag level. A nested map entry is retained as an opaque
|
|
85
|
+
// value so the validator can refuse recursive maps; recursively cloning malformed input here would
|
|
86
|
+
// turn a fail-closed schema error into an unbounded/cyclic capture walk.
|
|
87
|
+
function captureNestedTransitionBag(value) {
|
|
88
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
89
|
+
return value;
|
|
90
|
+
const proto = Object.getPrototypeOf(value);
|
|
91
|
+
if (proto !== Object.prototype && proto !== null)
|
|
92
|
+
return value;
|
|
93
|
+
const source = value;
|
|
94
|
+
const captured = Object.create(null);
|
|
95
|
+
for (const key of Reflect.ownKeys(source)) {
|
|
96
|
+
const member = source[key];
|
|
97
|
+
const capturedMember = typeof key === 'string' &&
|
|
98
|
+
(key === 'times' || key === 'ease' || key === 'easings' || key === 'easing') &&
|
|
99
|
+
Array.isArray(member)
|
|
100
|
+
? captureTimingArray(member)
|
|
101
|
+
: member;
|
|
102
|
+
Object.defineProperty(captured, key, {
|
|
103
|
+
value: capturedMember,
|
|
104
|
+
enumerable: Object.getOwnPropertyDescriptor(source, key)?.enumerable ?? true,
|
|
105
|
+
configurable: true,
|
|
106
|
+
writable: true,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return Object.freeze(captured);
|
|
110
|
+
}
|
|
111
|
+
// Capture a transition independently of its caller. Every own field (including non-enumerable fields and
|
|
112
|
+
// symbols) is read once and preserved; capture is deliberately NOT the schema/severity authority. Its
|
|
113
|
+
// callers validate this immutable truth, so an unknown authored option is refused by the established
|
|
114
|
+
// typed boundary instead of escaping before production can report it. Arrays are DEEP-cloned and frozen because
|
|
115
|
+
// generator/contending state may retain them after command() returns (including a per-segment ease list's
|
|
116
|
+
// nested cubic-bezier arrays, R8 M2-B). A null-prototype accumulator preserves an own `__proto__` field
|
|
117
|
+
// instead of invoking the legacy setter.
|
|
118
|
+
function captureTransition(transition) {
|
|
119
|
+
if (typeof transition !== 'object' || transition === null || Array.isArray(transition))
|
|
120
|
+
return transition;
|
|
121
|
+
const captured = Object.create(null);
|
|
122
|
+
for (const key of Reflect.ownKeys(transition)) {
|
|
123
|
+
const value = transition[key];
|
|
124
|
+
const capturedValue = typeof key === 'string' &&
|
|
125
|
+
(key === 'times' || key === 'ease' || key === 'easings' || key === 'easing') &&
|
|
126
|
+
Array.isArray(value)
|
|
127
|
+
? captureTimingArray(value)
|
|
128
|
+
: typeof key === 'string' && isTransitionMapKey(key)
|
|
129
|
+
? captureNestedTransitionBag(value)
|
|
130
|
+
: value;
|
|
131
|
+
Object.defineProperty(captured, key, {
|
|
132
|
+
value: capturedValue,
|
|
133
|
+
enumerable: Object.getOwnPropertyDescriptor(transition, key)?.enumerable ?? true,
|
|
134
|
+
configurable: true,
|
|
135
|
+
writable: true,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return Object.freeze(captured);
|
|
139
|
+
}
|
|
140
|
+
// A command-level transition is ALWAYS authored. Only a per-property transition can be the controller's
|
|
141
|
+
// count-aware DefaultTransition, because defaults differ by property/keyframe count. Keep those two raw
|
|
142
|
+
// slots distinct at this boundary instead of inferring provenance from a field such as `restSpeed`.
|
|
143
|
+
function internalPropTransitionRefusal(transition) {
|
|
144
|
+
if (typeof transition !== 'object' || transition === null || Array.isArray(transition)) {
|
|
145
|
+
return (0, validate_1.validateTransitionRefusal)(transition);
|
|
146
|
+
}
|
|
147
|
+
const value = transition;
|
|
148
|
+
const type = value['type'];
|
|
149
|
+
const hasRestSpeed = Object.hasOwn(value, 'restSpeed');
|
|
150
|
+
// R16 (REQ-GESTURE-023): the internal inertia transition — the gesture free-drag release's
|
|
151
|
+
// two-phase generator descriptor. Command-layer only; validated field-by-field here, then
|
|
152
|
+
// passed through unchanged (both drivers construct the SAME generator physics, no skew).
|
|
153
|
+
if (type === 'inertia') {
|
|
154
|
+
const allowed = new Set([
|
|
155
|
+
'type',
|
|
156
|
+
'power',
|
|
157
|
+
'timeConstant',
|
|
158
|
+
'bounceStiffness',
|
|
159
|
+
'bounceDamping',
|
|
160
|
+
'restDelta',
|
|
161
|
+
'restSpeed',
|
|
162
|
+
'min',
|
|
163
|
+
'max',
|
|
164
|
+
]);
|
|
165
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
166
|
+
if (!allowed.has(key)) {
|
|
167
|
+
return new validate_1.InvalidTransitionError(undefined, typeof key === 'string' ? key : String(key), value[key], 'not a recognized internal inertia transition option (REQ-DRIVER-030, R16)');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const key of [
|
|
171
|
+
'power',
|
|
172
|
+
'timeConstant',
|
|
173
|
+
'bounceStiffness',
|
|
174
|
+
'bounceDamping',
|
|
175
|
+
'restDelta',
|
|
176
|
+
'restSpeed',
|
|
177
|
+
]) {
|
|
178
|
+
const field = value[key];
|
|
179
|
+
if (!Object.hasOwn(value, key) ||
|
|
180
|
+
typeof field !== 'number' ||
|
|
181
|
+
!Number.isFinite(field) ||
|
|
182
|
+
field <= 0) {
|
|
183
|
+
return new validate_1.InvalidTransitionError(undefined, key, field, `an internal inertia transition requires an own finite ${key} greater than zero (REQ-DRIVER-030, R16)`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Bounds follow the drag-constraints sentinel law: open sides are ±Infinity of the correct
|
|
187
|
+
// polarity; wrong-polarity infinities and min > max are refused (G-INV-8).
|
|
188
|
+
const min = value['min'];
|
|
189
|
+
const max = value['max'];
|
|
190
|
+
if (min !== undefined && (typeof min !== 'number' || Number.isNaN(min) || min === Infinity)) {
|
|
191
|
+
return new validate_1.InvalidTransitionError(undefined, 'min', min, 'an internal inertia transition min must be a number or -Infinity (REQ-DRIVER-030, R16)');
|
|
192
|
+
}
|
|
193
|
+
if (max !== undefined && (typeof max !== 'number' || Number.isNaN(max) || max === -Infinity)) {
|
|
194
|
+
return new validate_1.InvalidTransitionError(undefined, 'max', max, 'an internal inertia transition max must be a number or +Infinity (REQ-DRIVER-030, R16)');
|
|
195
|
+
}
|
|
196
|
+
if (min !== undefined && max !== undefined && min > max) {
|
|
197
|
+
return new validate_1.InvalidTransitionError(undefined, 'min', min, 'an internal inertia transition requires min <= max (REQ-DRIVER-030, R16)');
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
if (type !== 'keyframes' && !hasRestSpeed)
|
|
202
|
+
return (0, validate_1.validateTransitionRefusal)(transition);
|
|
203
|
+
if (type !== 'keyframes') {
|
|
204
|
+
// U7c (REQ-SPRING-014): restSpeed/restDelta are AUTHORED vocabulary now, so restSpeed
|
|
205
|
+
// presence can no longer discriminate the controller's internal spring default from an
|
|
206
|
+
// authored spring — the shapes genuinely overlap, and Motion fidelity gives the authored
|
|
207
|
+
// reading precedence (a legal authored per-prop spring must never refuse as a "malformed
|
|
208
|
+
// internal default"). Validate as authored first; only when that fails does the STRICT
|
|
209
|
+
// internal shape law below get its say, so a malformed controller default still fails loud
|
|
210
|
+
// instead of riding along.
|
|
211
|
+
const authoredRefusal = (0, validate_1.validateTransitionRefusal)(transition);
|
|
212
|
+
if (authoredRefusal === null)
|
|
213
|
+
return null;
|
|
214
|
+
const internalRefusal = internalSpringDefaultRefusal(value);
|
|
215
|
+
return internalRefusal ?? authoredRefusal;
|
|
216
|
+
}
|
|
217
|
+
const allowed = new Set([
|
|
218
|
+
'type',
|
|
219
|
+
'duration',
|
|
220
|
+
'ease',
|
|
221
|
+
'delay',
|
|
222
|
+
'repeat',
|
|
223
|
+
'repeatType',
|
|
224
|
+
'repeatDelay',
|
|
225
|
+
]);
|
|
226
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
227
|
+
if (!allowed.has(key)) {
|
|
228
|
+
return new validate_1.InvalidTransitionError(undefined, typeof key === 'string' ? key : String(key), value[key], 'not a recognized internal driver transition option (REQ-DRIVER-030)');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const duration = value['duration'];
|
|
232
|
+
if (typeof duration !== 'number' || !Number.isFinite(duration) || duration <= 0) {
|
|
233
|
+
return new validate_1.InvalidTransitionError(undefined, 'duration', duration, 'an internal keyframes default requires a finite duration greater than zero (REQ-DRIVER-030)');
|
|
234
|
+
}
|
|
235
|
+
if (value['ease'] !== undefined && !isInternalKeyframesEase(value['ease'])) {
|
|
236
|
+
return new validate_1.InvalidTransitionError(undefined, 'ease', value['ease'], 'an internal keyframes default permits only one finite four-number cubic-bezier ease tuple (REQ-DRIVER-030)');
|
|
237
|
+
}
|
|
238
|
+
const authored = Object.create(null);
|
|
239
|
+
authored['type'] = 'tween';
|
|
240
|
+
authored['duration'] = duration;
|
|
241
|
+
if (value['ease'] !== undefined)
|
|
242
|
+
authored['ease'] = value['ease'];
|
|
243
|
+
for (const key of ['delay', 'repeat', 'repeatType', 'repeatDelay']) {
|
|
244
|
+
if (value[key] !== undefined)
|
|
245
|
+
authored[key] = value[key];
|
|
246
|
+
}
|
|
247
|
+
return (0, validate_1.validateTransitionRefusal)(authored);
|
|
248
|
+
}
|
|
249
|
+
// The STRICT internal spring-default shape (r16): the controller's count-aware
|
|
250
|
+
// `getDefaultTransition` output carries exactly this vocabulary, every physics field present and
|
|
251
|
+
// positive. Reached only when the authored reading already failed (U7c — see above), so this is
|
|
252
|
+
// the fail-loud backstop for malformed controller output, never a gate on authored input.
|
|
253
|
+
function internalSpringDefaultRefusal(value) {
|
|
254
|
+
const allowed = new Set([
|
|
255
|
+
'type',
|
|
256
|
+
'stiffness',
|
|
257
|
+
'damping',
|
|
258
|
+
'restSpeed',
|
|
259
|
+
'delay',
|
|
260
|
+
'repeat',
|
|
261
|
+
'repeatType',
|
|
262
|
+
'repeatDelay',
|
|
263
|
+
]);
|
|
264
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
265
|
+
if (!allowed.has(key)) {
|
|
266
|
+
return new validate_1.InvalidTransitionError(undefined, typeof key === 'string' ? key : String(key), value[key], 'not a recognized internal driver transition option (REQ-DRIVER-030)');
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (value['type'] !== 'spring') {
|
|
270
|
+
return new validate_1.InvalidTransitionError(undefined, 'restSpeed', value['restSpeed'], 'restSpeed is internal-only and valid only on an internal spring transition (REQ-DRIVER-030)');
|
|
271
|
+
}
|
|
272
|
+
for (const key of ['stiffness', 'damping', 'restSpeed']) {
|
|
273
|
+
const field = value[key];
|
|
274
|
+
if (!Object.hasOwn(value, key) ||
|
|
275
|
+
typeof field !== 'number' ||
|
|
276
|
+
!Number.isFinite(field) ||
|
|
277
|
+
field <= 0) {
|
|
278
|
+
return new validate_1.InvalidTransitionError(undefined, key, field, `an internal spring default requires an own finite ${key} greater than zero (REQ-DRIVER-030)`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const authored = {
|
|
282
|
+
type: 'spring',
|
|
283
|
+
stiffness: value['stiffness'],
|
|
284
|
+
damping: value['damping'],
|
|
285
|
+
};
|
|
286
|
+
for (const key of ['delay', 'repeat', 'repeatType', 'repeatDelay']) {
|
|
287
|
+
if (value[key] !== undefined)
|
|
288
|
+
authored[key] = value[key];
|
|
289
|
+
}
|
|
290
|
+
return (0, validate_1.validateTransitionRefusal)(authored);
|
|
291
|
+
}
|
|
292
|
+
function isInternalKeyframesEase(value) {
|
|
293
|
+
if (!Array.isArray(value) || value.length !== 4)
|
|
294
|
+
return false;
|
|
295
|
+
for (let index = 0; index < 4; index++) {
|
|
296
|
+
if (!Object.hasOwn(value, index) ||
|
|
297
|
+
typeof value[index] !== 'number' ||
|
|
298
|
+
!Number.isFinite(value[index])) {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
function validatePreparedPropTransition(transition) {
|
|
305
|
+
// The raw/prepared seam admits controller defaults in addition to the closed authored Transition
|
|
306
|
+
// vocabulary ONLY in a PropTarget slot. Supplying and command boundaries call the authored validator.
|
|
307
|
+
const refusal = internalPropTransitionRefusal(transition);
|
|
308
|
+
if (refusal !== null)
|
|
309
|
+
throw refusal;
|
|
310
|
+
return transition;
|
|
311
|
+
}
|
|
312
|
+
function captureAndValidatePropTransition(transition) {
|
|
313
|
+
const shapeRefusal = (0, validate_1.transitionShapeRefusal)(transition);
|
|
314
|
+
if (shapeRefusal !== null)
|
|
315
|
+
throw shapeRefusal;
|
|
316
|
+
const captured = captureTransition(transition);
|
|
317
|
+
const mapRefusal = transitionMapRefusal(captured);
|
|
318
|
+
if (mapRefusal !== null)
|
|
319
|
+
throw mapRefusal;
|
|
320
|
+
return validatePreparedPropTransition(captured);
|
|
321
|
+
}
|
|
322
|
+
function captureAndValidateCommandTransition(transition) {
|
|
323
|
+
// Component slots admit `undefined` as an omitted transition, but the raw Driver command protocol
|
|
324
|
+
// has no defaulting authority: every start/retarget must carry its authored fallback before a target
|
|
325
|
+
// can be prepared or either backend can schedule. Keep requiredness here, ahead of capture, because
|
|
326
|
+
// generic validation intentionally treats undefined as legal absence for component consumers.
|
|
327
|
+
if (transition === undefined) {
|
|
328
|
+
throw new validate_1.InvalidTransitionError(undefined, 'transition', transition, 'a DriverCommand start or retarget requires an authored transition (REQ-DRIVER-030)');
|
|
329
|
+
}
|
|
330
|
+
const shapeRefusal = (0, validate_1.transitionShapeRefusal)(transition);
|
|
331
|
+
if (shapeRefusal !== null)
|
|
332
|
+
throw shapeRefusal;
|
|
333
|
+
const captured = captureTransition(transition);
|
|
334
|
+
const refusal = (0, validate_1.validateTransitionRefusal)(captured);
|
|
335
|
+
if (refusal !== null)
|
|
336
|
+
throw refusal;
|
|
337
|
+
const mapRefusal = transitionMapRefusal(captured);
|
|
338
|
+
if (mapRefusal !== null)
|
|
339
|
+
throw mapRefusal;
|
|
340
|
+
return captured;
|
|
341
|
+
}
|
|
342
|
+
function prepareStart(command) {
|
|
343
|
+
// Command transition first preserves the established defensive capture convention when one hostile
|
|
344
|
+
// object occupies both command and per-prop slots. The order is not public semantics; both backends
|
|
345
|
+
// observe only the resulting snapshot.
|
|
346
|
+
const commandTransition = captureAndValidateCommandTransition(command.transition);
|
|
347
|
+
const rawTargets = command.targets;
|
|
348
|
+
const releasesGesture = command.releasesGesture === true;
|
|
349
|
+
const targets = Object.create(null);
|
|
350
|
+
for (const key of Object.keys(rawTargets)) {
|
|
351
|
+
const rawTarget = rawTargets[key];
|
|
352
|
+
// One canonical read per field. Do not destructure/re-read inside either backend.
|
|
353
|
+
const to = rawTarget.to;
|
|
354
|
+
const from = rawTarget.from;
|
|
355
|
+
const velocity = rawTarget.velocity;
|
|
356
|
+
const transition = rawTarget.transition;
|
|
357
|
+
const preparedTransition = transition === undefined ? commandTransition : captureAndValidatePropTransition(transition);
|
|
358
|
+
const capturedTo = captureTarget(to);
|
|
359
|
+
const timingRefusal = (0, keyframeTiming_1.keyframeTimingRefusal)(undefined, key, capturedTo, preparedTransition);
|
|
360
|
+
if (timingRefusal !== null)
|
|
361
|
+
throw timingRefusal;
|
|
362
|
+
targets[key] = Object.freeze({
|
|
363
|
+
to: capturedTo,
|
|
364
|
+
from,
|
|
365
|
+
velocity: velocity ?? 0,
|
|
366
|
+
transition: preparedTransition,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
const path = command.path === undefined
|
|
370
|
+
? undefined
|
|
371
|
+
: Object.freeze({
|
|
372
|
+
fromX: command.path.fromX,
|
|
373
|
+
fromY: command.path.fromY,
|
|
374
|
+
toX: command.path.toX,
|
|
375
|
+
toY: command.path.toY,
|
|
376
|
+
peak: command.path.peak,
|
|
377
|
+
signedStrength: command.path.signedStrength,
|
|
378
|
+
rotationScale: command.path.rotationScale,
|
|
379
|
+
transition: captureAndValidatePropTransition(command.path.transition),
|
|
380
|
+
});
|
|
381
|
+
return Object.freeze({
|
|
382
|
+
kind: 'start',
|
|
383
|
+
targets: Object.freeze(targets),
|
|
384
|
+
releasesGesture,
|
|
385
|
+
...(path === undefined ? {} : { path }),
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
function prepareRetarget(command) {
|
|
389
|
+
const transition = captureAndValidateCommandTransition(command.transition);
|
|
390
|
+
const rawTargets = command.targets;
|
|
391
|
+
const rawTargetTransitions = command.targetTransitions;
|
|
392
|
+
const targetTransitionsRefusal = targetTransitionsShapeRefusal(rawTargetTransitions, rawTargets);
|
|
393
|
+
if (targetTransitionsRefusal !== null)
|
|
394
|
+
throw targetTransitionsRefusal;
|
|
395
|
+
const targets = Object.create(null);
|
|
396
|
+
const targetTransitions = Object.create(null);
|
|
397
|
+
for (const key of Object.keys(rawTargets)) {
|
|
398
|
+
const target = rawTargets[key];
|
|
399
|
+
const hasRawTargetTransition = rawTargetTransitions !== undefined && Object.hasOwn(rawTargetTransitions, key);
|
|
400
|
+
const rawTargetTransition = hasRawTargetTransition ? rawTargetTransitions[key] : undefined;
|
|
401
|
+
if (hasRawTargetTransition && rawTargetTransition === undefined) {
|
|
402
|
+
throw new validate_1.InvalidTransitionError(undefined, key, undefined, 'retarget targetTransitions entries must contain a transition (REQ-DRIVER-030)');
|
|
403
|
+
}
|
|
404
|
+
let targetTransition;
|
|
405
|
+
if (!hasRawTargetTransition) {
|
|
406
|
+
targetTransition = transition;
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
// The undefined case was rejected immediately above; this assertion records that
|
|
410
|
+
// runtime proof for the narrowed prepared boundary.
|
|
411
|
+
targetTransition = captureAndValidatePropTransition(rawTargetTransition);
|
|
412
|
+
}
|
|
413
|
+
const timingRefusal = (0, keyframeTiming_1.keyframeTimingRefusal)(undefined, key, target, targetTransition);
|
|
414
|
+
if (timingRefusal !== null)
|
|
415
|
+
throw timingRefusal;
|
|
416
|
+
targets[key] = target;
|
|
417
|
+
if (hasRawTargetTransition) {
|
|
418
|
+
targetTransitions[key] = targetTransition;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return Object.freeze({
|
|
422
|
+
kind: 'retarget',
|
|
423
|
+
targets: Object.freeze(targets),
|
|
424
|
+
transition,
|
|
425
|
+
...(Object.keys(targetTransitions).length === 0
|
|
426
|
+
? {}
|
|
427
|
+
: { targetTransitions: Object.freeze(targetTransitions) }),
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
const PREPARED_STOP = Object.freeze({ kind: 'stop' });
|
|
431
|
+
function prepareDriverCommand(command) {
|
|
432
|
+
switch (command.kind) {
|
|
433
|
+
case 'start':
|
|
434
|
+
return prepareStart(command);
|
|
435
|
+
case 'retarget':
|
|
436
|
+
return prepareRetarget(command);
|
|
437
|
+
case 'stop':
|
|
438
|
+
return PREPARED_STOP;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
// Preserve the small public Driver surface while making the raw→prepared transition structural: a
|
|
442
|
+
// concrete backend implements PreparedDriver and therefore cannot accidentally inspect PropTarget.
|
|
443
|
+
function adaptPreparedDriver(backend, assertCommandable) {
|
|
444
|
+
return {
|
|
445
|
+
register: (initial) => backend.register(initial),
|
|
446
|
+
command: (handle, command) => {
|
|
447
|
+
// Preserve each backend's lifecycle/handle failure precedence: an invalid destination fails
|
|
448
|
+
// before any hostile caller getter can run, just as it did before the shared adapter existed.
|
|
449
|
+
assertCommandable(handle);
|
|
450
|
+
backend.command(handle, prepareDriverCommand(command));
|
|
451
|
+
},
|
|
452
|
+
step: (elapsedMs) => backend.step(elapsedMs),
|
|
453
|
+
committed: (handle) => backend.committed(handle),
|
|
454
|
+
setActive: (handle, active) => backend.setActive(handle, active),
|
|
455
|
+
isActive: (handle) => backend.isActive(handle),
|
|
456
|
+
write: (handle, values, velocities) => backend.write(handle, values, velocities),
|
|
457
|
+
liveFor: (handle, key) => backend.liveFor(handle, key),
|
|
458
|
+
};
|
|
459
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Driver, DriverCommand, PreparedDriver, PreparedDriverCommand, PropTransition } from "./types.cjs";
|
|
2
|
+
export declare function captureTransition<T extends PropTransition>(transition: T): T;
|
|
3
|
+
export declare function prepareDriverCommand(command: DriverCommand): PreparedDriverCommand;
|
|
4
|
+
export declare function adaptPreparedDriver(backend: PreparedDriver, assertCommandable: (handle: Parameters<Driver['command']>[0]) => void): Driver;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Driver, DriverCommand, PreparedDriver, PreparedDriverCommand, PropTransition } from "./types.js";
|
|
2
|
+
export declare function captureTransition<T extends PropTransition>(transition: T): T;
|
|
3
|
+
export declare function prepareDriverCommand(command: DriverCommand): PreparedDriverCommand;
|
|
4
|
+
export declare function adaptPreparedDriver(backend: PreparedDriver, assertCommandable: (handle: Parameters<Driver['command']>[0]) => void): Driver;
|