@unrulysystems/native-motion 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 +16 -0
- package/LICENSE +21 -0
- package/README.md +55 -0
- package/android/build.gradle +24 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/unrulysystems/nativemotion/GestureExclusionModule.kt +55 -0
- package/android/src/main/java/com/unrulysystems/nativemotion/NativeMotionPackage.kt +27 -0
- package/package.json +57 -0
- package/react-native.config.js +14 -0
- package/src/native/driver/instantWindow.ts +31 -0
- package/src/native/driver/strictModeReplay.ts +20 -0
- package/src/native/driver/uiLayoutEngine.ts +845 -0
- package/src/native/driver/uiLayoutGraph.ts +338 -0
- package/src/native/driver/uiValueChannel.ts +4640 -0
- package/src/native/driver/workletDriver.ts +3860 -0
- package/src/native/motion/AnimatePresence.tsx +1605 -0
- package/src/native/motion/LayoutGroup.tsx +163 -0
- package/src/native/motion/MotionConfig.tsx +400 -0
- package/src/native/motion/MotionRoot.tsx +250 -0
- package/src/native/motion/MotionView.tsx +6761 -0
- package/src/native/motion/addScaleCorrector.ts +97 -0
- package/src/native/motion/colorEndpointUiFeed.ts +32 -0
- package/src/native/motion/colorProjection.ts +39 -0
- package/src/native/motion/composeTransform.ts +271 -0
- package/src/native/motion/constraintMeasure.ts +157 -0
- package/src/native/motion/deferredPendingSnapshots.ts +25 -0
- package/src/native/motion/discreteProjection.ts +36 -0
- package/src/native/motion/dragAncestorPanContext.ts +28 -0
- package/src/native/motion/dragControls.ts +152 -0
- package/src/native/motion/dragGestureWiring.ts +1168 -0
- package/src/native/motion/dragHandoffBinding.ts +179 -0
- package/src/native/motion/dragHubStream.ts +756 -0
- package/src/native/motion/dragPropagationLock.ts +57 -0
- package/src/native/motion/driverValueChannel.ts +3288 -0
- package/src/native/motion/externalDragDriver.ts +214 -0
- package/src/native/motion/frameData.ts +31 -0
- package/src/native/motion/gestureBinding.ts +107 -0
- package/src/native/motion/gestureStateGate.ts +563 -0
- package/src/native/motion/gestureStateResolver.ts +286 -0
- package/src/native/motion/identityValueChannelControllerAdapter.ts +2043 -0
- package/src/native/motion/identityValueChannelControllerReconciliation.ts +176 -0
- package/src/native/motion/identityValueChannelLaneMarker.ts +14 -0
- package/src/native/motion/imperativeAnimate.ts +816 -0
- package/src/native/motion/keyframeTiming.ts +7 -0
- package/src/native/motion/layoutIdBinding.ts +261 -0
- package/src/native/motion/layoutIdFlightConfig.ts +51 -0
- package/src/native/motion/layoutProjection.ts +89 -0
- package/src/native/motion/layoutScroll.ts +204 -0
- package/src/native/motion/layoutTransition.ts +1302 -0
- package/src/native/motion/lengthLayoutContext.tsx +100 -0
- package/src/native/motion/lengthLayoutHost.ts +44 -0
- package/src/native/motion/mappedKeys.ts +184 -0
- package/src/native/motion/motionViewController.ts +3180 -0
- package/src/native/motion/nativeHostMarker.ts +22 -0
- package/src/native/motion/panSessionWiring.ts +127 -0
- package/src/native/motion/pathTransition.ts +101 -0
- package/src/native/motion/popLayout.ts +176 -0
- package/src/native/motion/presenceBinding.ts +382 -0
- package/src/native/motion/scaleCorrectorRegistry.ts +123 -0
- package/src/native/motion/scrollValues.ts +260 -0
- package/src/native/motion/serializablePayload.ts +151 -0
- package/src/native/motion/severity.ts +16 -0
- package/src/native/motion/shippedSurface.ts +843 -0
- package/src/native/motion/staticLengthGate.ts +86 -0
- package/src/native/motion/styleBaseGate.ts +72 -0
- package/src/native/motion/styleValueBinding.ts +657 -0
- package/src/native/motion/systemGestureExclusion.ts +61 -0
- package/src/native/motion/tapGestureWiring.ts +215 -0
- package/src/native/motion/transformOrder.ts +38 -0
- package/src/native/motion/transformStringBinding.ts +200 -0
- package/src/native/motion/transformTemplateGate.ts +45 -0
- package/src/native/motion/transitionGate.ts +435 -0
- package/src/native/motion/useAnimate.ts +52 -0
- package/src/native/motion/useCycle.ts +44 -0
- package/src/native/motion/useInstantTransition.ts +68 -0
- package/src/native/motion/useReducedMotion.ts +58 -0
- package/src/native/motion/useScroll.ts +162 -0
- package/src/native/motion/useViewportScroll.ts +29 -0
- package/src/native/motion/valueChannel.ts +7121 -0
- package/src/native/motion/valueHooks.ts +901 -0
- package/src/native/motion/variantChildRegistry.ts +67 -0
- package/src/native/motion/variantContext.tsx +172 -0
- package/src/native/motion/variantProps.ts +598 -0
- package/src/native.ts +159 -0
- package/src/verification/harnessMetrics.ts +78 -0
- package/src/verification/probe/LayoutIdentityWorkletProbe.tsx +251 -0
- package/src/verification/probe/WorkletParityProbe.tsx +162 -0
- package/src/verification/probe/layoutIdentityProbeEngine.ts +429 -0
- package/src/verification/screens/ArcPathChecksScreen.tsx +273 -0
- package/src/verification/screens/BooleanAnimateChecksScreen.tsx +358 -0
- package/src/verification/screens/ChoreographyGalleryScreen.tsx +1010 -0
- package/src/verification/screens/ColorBindingChecksScreen.tsx +568 -0
- package/src/verification/screens/CompletionChecksScreen.tsx +294 -0
- package/src/verification/screens/ConformanceScreen.tsx +573 -0
- package/src/verification/screens/ContentionProbeScreen.tsx +224 -0
- package/src/verification/screens/DriverSmokeScreen.tsx +99 -0
- package/src/verification/screens/DurationOnlyTweenChecksScreen.tsx +294 -0
- package/src/verification/screens/DynamicDragConfigChecksScreen.tsx +237 -0
- package/src/verification/screens/FrameDataChecksScreen.tsx +252 -0
- package/src/verification/screens/GestureChecksScreen.tsx +685 -0
- package/src/verification/screens/InstantTransitionChecksScreen.tsx +839 -0
- package/src/verification/screens/LayoutAnimationStartChecksScreen.tsx +648 -0
- package/src/verification/screens/LayoutChecksScreen.tsx +822 -0
- package/src/verification/screens/LayoutCommitSpikeScreen.tsx +142 -0
- package/src/verification/screens/MotionViewChecksScreen.tsx +930 -0
- package/src/verification/screens/PresenceChecksScreen.tsx +614 -0
- package/src/verification/screens/ReducedMotionChecksScreen.tsx +614 -0
- package/src/verification/screens/RestThresholdChecksScreen.tsx +634 -0
- package/src/verification/screens/ScaleCorrectorChecksScreen.tsx +425 -0
- package/src/verification/screens/SharedLayoutContinuityChecksScreen.tsx +338 -0
- package/src/verification/screens/SharedLayoutCrossfadeChecksScreen.tsx +425 -0
- package/src/verification/screens/TransitionDefaultSelectionChecksScreen.tsx +514 -0
- package/src/verification/screens/ViewportScrollAliasChecksScreen.tsx +303 -0
- package/src/verification/screens/conformanceBanner.ts +21 -0
- package/src/verification/screens/proofConsoleTap.ts +18 -0
- package/src/verification.ts +47 -0
- package/src/web.ts +98 -0
|
@@ -0,0 +1,843 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isLengthTarget,
|
|
3
|
+
isPositionalLengthAxis,
|
|
4
|
+
POSITIONAL_LENGTH_AXES,
|
|
5
|
+
ORCHESTRATION_OPTION_KEYS,
|
|
6
|
+
TRANSITION_OPTION_KEYS,
|
|
7
|
+
UNIVERSAL_SUBSET,
|
|
8
|
+
} from '@unrulysystems/native-motion-core'
|
|
9
|
+
// The executable value matrix is verdict-shaped — it rides the internal-driver seam (review
|
|
10
|
+
// ab7c5370fc5a), never the public root.
|
|
11
|
+
import { executableLengthValueRefusal } from '@unrulysystems/native-motion-core/internal-driver'
|
|
12
|
+
|
|
13
|
+
function deepFreeze<T>(value: T): T {
|
|
14
|
+
if (typeof value !== 'object' || value === null || Object.isFrozen(value)) return value
|
|
15
|
+
for (const key of Object.getOwnPropertyNames(value)) {
|
|
16
|
+
deepFreeze((value as Record<string, unknown>)[key])
|
|
17
|
+
}
|
|
18
|
+
return Object.freeze(value)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// The exported components and the parity classifier read this one data-only declaration. Keep it
|
|
22
|
+
// RN-free: node-side tooling must be able to import the public-surface fact without loading the
|
|
23
|
+
// native entry. Each vocabulary follows the props the component itself consumes or validates; host
|
|
24
|
+
// props forwarded by View are intentionally not represented as Motion capabilities here.
|
|
25
|
+
const VIEW_MOTION_PROP_NAMES = [
|
|
26
|
+
'initial',
|
|
27
|
+
'animate',
|
|
28
|
+
'exit',
|
|
29
|
+
'transition',
|
|
30
|
+
'style',
|
|
31
|
+
'layout',
|
|
32
|
+
'layoutId',
|
|
33
|
+
// R11 M2 (REQ-LAYOUT-022 / REQ-API-038): scroll-offset measurement root on the host.
|
|
34
|
+
'layoutScroll',
|
|
35
|
+
'onAnimationComplete',
|
|
36
|
+
'onLayoutAnimationComplete',
|
|
37
|
+
// U7e (REQ-LAYOUT-001 lineage): the layout-flight start callback.
|
|
38
|
+
'onLayoutAnimationStart',
|
|
39
|
+
'drag',
|
|
40
|
+
'dragConstraints',
|
|
41
|
+
'dragElastic',
|
|
42
|
+
'dragSnapPoints',
|
|
43
|
+
'dragPropagation',
|
|
44
|
+
// R15 D3 (REQ-API-043): imperative hub + element-originated start gate.
|
|
45
|
+
'dragControls',
|
|
46
|
+
'dragListener',
|
|
47
|
+
// R15 D4 (REQ-API-044): direction lock.
|
|
48
|
+
'dragDirectionLock',
|
|
49
|
+
'onDirectionLock',
|
|
50
|
+
// R15 D5 (REQ-API-045 / REQ-API-046): release physics + external drag values.
|
|
51
|
+
'dragMomentum',
|
|
52
|
+
'dragTransition',
|
|
53
|
+
'dragSnapToOrigin',
|
|
54
|
+
'_dragX',
|
|
55
|
+
'_dragY',
|
|
56
|
+
'onPanSessionStart',
|
|
57
|
+
'onPanStart',
|
|
58
|
+
'onPan',
|
|
59
|
+
'onPanEnd',
|
|
60
|
+
'onMeasureDragConstraints',
|
|
61
|
+
'onDragStart',
|
|
62
|
+
'onDrag',
|
|
63
|
+
'onDragEnd',
|
|
64
|
+
// The tap family (R5, REQ-API-030).
|
|
65
|
+
'onTap',
|
|
66
|
+
'onTapStart',
|
|
67
|
+
'onTapCancel',
|
|
68
|
+
// U7d (REQ-API-030 global form): the press observes the MotionRoot hub touch stream.
|
|
69
|
+
'globalTapTarget',
|
|
70
|
+
// The gesture-state pair (R6, REQ-API-031).
|
|
71
|
+
'whileTap',
|
|
72
|
+
'whileDrag',
|
|
73
|
+
// The variants surface (R7, REQ-API-032): the dictionary + the pinned visual-state switch;
|
|
74
|
+
// initial/animate/exit/whileTap/whileDrag additionally accept the label form under it.
|
|
75
|
+
'variants',
|
|
76
|
+
'inherit',
|
|
77
|
+
// T17 (specs/T17-TRANSFORM-TEMPLATE-BUILD-PACKET.md): the pin's per-frame transform template —
|
|
78
|
+
// a branded-worklet function on native (mount gate), parsed through the bounded core grammar.
|
|
79
|
+
'transformTemplate',
|
|
80
|
+
// T23 C (pin VisualElement.ts:574-586): the per-frame latest-values callback — one UI→JS
|
|
81
|
+
// marshal per frame while any committed value changes, primitive-only payload.
|
|
82
|
+
'onUpdate',
|
|
83
|
+
// T24 B1 (pin resolve-variants.ts:32-70): the dynamic-variants custom payload — the
|
|
84
|
+
// non-exit default for RESOLVER invocation; any value is legal (the pin types it `any`),
|
|
85
|
+
// and it never crosses to the UI runtime (JS-altitude resolution only).
|
|
86
|
+
'custom',
|
|
87
|
+
] as const
|
|
88
|
+
|
|
89
|
+
// MotionView destructures these in its component boundary and rejects other Motion capabilities
|
|
90
|
+
// through gateDeferredViewProps. The container surfaces are deliberately separate: their rest-prop
|
|
91
|
+
// gates reject any prop outside these exact lists, so they must never inherit View's vocabulary.
|
|
92
|
+
const ANIMATE_PRESENCE_PROP_NAMES = [
|
|
93
|
+
'mode',
|
|
94
|
+
'initial',
|
|
95
|
+
'onExitComplete',
|
|
96
|
+
'onRest',
|
|
97
|
+
// Family-6: nested AnimatePresence joins parent exit (REQ-PRESENCE-010 / pin propagate).
|
|
98
|
+
'propagate',
|
|
99
|
+
// T24 B1: presence custom is consumed by exit variant resolution.
|
|
100
|
+
'custom',
|
|
101
|
+
// P2a (REQ-PRESENCE-023): portable pin presence options.
|
|
102
|
+
'anchorX',
|
|
103
|
+
'anchorY',
|
|
104
|
+
'presenceAffectsLayout',
|
|
105
|
+
'children',
|
|
106
|
+
] as const
|
|
107
|
+
|
|
108
|
+
const MOTION_ROOT_PROP_NAMES = ['children'] as const
|
|
109
|
+
|
|
110
|
+
// REQ-API-035: MotionConfig is a provider, not a host element. nonce is accepted on the web
|
|
111
|
+
// entry and fails loud on native (WEB-ONLY-BY-LAW); it still appears here so the surface
|
|
112
|
+
// records the contracted prop name the classifier and both entries share.
|
|
113
|
+
const MOTION_CONFIG_PROP_NAMES = [
|
|
114
|
+
'children',
|
|
115
|
+
'transition',
|
|
116
|
+
'reducedMotion',
|
|
117
|
+
'nonce',
|
|
118
|
+
'isStatic',
|
|
119
|
+
'skipAnimations',
|
|
120
|
+
// R15 D3 (REQ-API-043): page-point correction under transformed ancestors.
|
|
121
|
+
'transformPagePoint',
|
|
122
|
+
] as const
|
|
123
|
+
|
|
124
|
+
// REQ-API-036: LayoutGroup is a provider, not a host element. id/inherit mirror the pin.
|
|
125
|
+
const LAYOUT_GROUP_PROP_NAMES = ['children', 'id', 'inherit'] as const
|
|
126
|
+
|
|
127
|
+
// These literal domains are the node-safe reflection of the public engine contracts. The parity
|
|
128
|
+
// classifier consumes this data rather than treating a prop name as proof that every value Motion
|
|
129
|
+
// accepts is native-ready. Authorities: Transition/Easing in packages/core/src/component/types.ts
|
|
130
|
+
// and packages/core/src/timing.ts; AnimatePresenceProps and gateDeferredPresenceProps in the
|
|
131
|
+
// sibling AnimatePresence.tsx; target leaves in core's validateTarget/normalizeCapability path.
|
|
132
|
+
// Property commands marshal only the values emitted by the component converters and driver's
|
|
133
|
+
// `type` discriminator. `delay` ships here since T18-b (REQ-API-050): the property lane executes
|
|
134
|
+
// it as the REQ-TIMING-004 elapsed-time rebase around the fold, so it is no longer a silent
|
|
135
|
+
// no-op on this lane.
|
|
136
|
+
const PROPERTY_TRANSITION_VALUE_DOMAINS = {
|
|
137
|
+
// U7a: `false` is the authored instant lane (the pin's makeAnimationInstant) — the property
|
|
138
|
+
// lane commits the final keyframe on the driver's next update tick, delay honored. The LAYOUT
|
|
139
|
+
// domain below keeps the spring/tween-only copy: those lanes refuse the instant lane loud
|
|
140
|
+
// (transitionGate), so their domain must not admit it.
|
|
141
|
+
type: { kind: 'enum', values: ['spring', 'tween', false] },
|
|
142
|
+
duration: { kind: 'number' },
|
|
143
|
+
delay: { kind: 'number' },
|
|
144
|
+
// R8 M2-B (REQ-API-033 law d): the property lane crosses toKeyframesConfig, so for a KEYFRAME-ARRAY
|
|
145
|
+
// target `ease` may be a single easing OR a PER-SEGMENT list of easings (one per segment). The
|
|
146
|
+
// `easing-list` domain admits both; the keyframe-count cross-field length law is enforced at the
|
|
147
|
+
// supplying boundary (keyframeTiming.ts), which no value domain can express.
|
|
148
|
+
// Family-2: Motion-named curve set (circ/back/anticipate) ships. Catalog `easings` is an alias of
|
|
149
|
+
// `ease` (same domain); converters normalize to `ease` (toKeyframesConfig / toTimingConfig).
|
|
150
|
+
ease: {
|
|
151
|
+
kind: 'easing-list',
|
|
152
|
+
values: [
|
|
153
|
+
'linear',
|
|
154
|
+
'easeIn',
|
|
155
|
+
'easeOut',
|
|
156
|
+
'easeInOut',
|
|
157
|
+
'circIn',
|
|
158
|
+
'circOut',
|
|
159
|
+
'circInOut',
|
|
160
|
+
'backIn',
|
|
161
|
+
'backOut',
|
|
162
|
+
'backInOut',
|
|
163
|
+
'anticipate',
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
easings: {
|
|
167
|
+
kind: 'easing-list',
|
|
168
|
+
values: [
|
|
169
|
+
'linear',
|
|
170
|
+
'easeIn',
|
|
171
|
+
'easeOut',
|
|
172
|
+
'easeInOut',
|
|
173
|
+
'circIn',
|
|
174
|
+
'circOut',
|
|
175
|
+
'circInOut',
|
|
176
|
+
'backIn',
|
|
177
|
+
'backOut',
|
|
178
|
+
'backInOut',
|
|
179
|
+
'anticipate',
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
easing: {
|
|
183
|
+
kind: 'easing-list',
|
|
184
|
+
values: [
|
|
185
|
+
'linear',
|
|
186
|
+
'easeIn',
|
|
187
|
+
'easeOut',
|
|
188
|
+
'easeInOut',
|
|
189
|
+
'circIn',
|
|
190
|
+
'circOut',
|
|
191
|
+
'circInOut',
|
|
192
|
+
'backIn',
|
|
193
|
+
'backOut',
|
|
194
|
+
'backInOut',
|
|
195
|
+
'anticipate',
|
|
196
|
+
],
|
|
197
|
+
},
|
|
198
|
+
// R8 M2-B (REQ-API-033 law d): custom keyframe OFFSETS (0–1), one per keyframe — a keyframes-generator
|
|
199
|
+
// param. The layout builder never interpolates a keyframe array, so `times` is PROPERTY-lane only and
|
|
200
|
+
// is deliberately absent from the layout transition domain below (no option accepted but inert).
|
|
201
|
+
times: { kind: 'number-array' },
|
|
202
|
+
stiffness: { kind: 'number' },
|
|
203
|
+
damping: { kind: 'number' },
|
|
204
|
+
mass: { kind: 'number' },
|
|
205
|
+
bounce: { kind: 'number' },
|
|
206
|
+
visualDuration: { kind: 'number' },
|
|
207
|
+
// T21 (REQ-API-053): authored spring velocity — the physics-family seed, executed by core
|
|
208
|
+
// `resolveSpringGenerator` (an authored number replaces the live seed velocity on every start,
|
|
209
|
+
// the pin's animateMotionValue options spread). Executed by the LAYOUT lane too (r2 review
|
|
210
|
+
// major 2): the flight's sampled spring reads it from the same core generator config
|
|
211
|
+
// (`toSpringConfig` carries it), so it appears in the layout domain below. Only layoutId
|
|
212
|
+
// flights refuse it, with the rest of the spring-physics family (`transitionGate`).
|
|
213
|
+
velocity: { kind: 'number' },
|
|
214
|
+
// U7c (REQ-SPRING-014): the authored settle thresholds. PROPERTY lane: executed — toSpringConfig
|
|
215
|
+
// forwards them untransformed into the solver's settle condition (a spring; a tween accepts and
|
|
216
|
+
// IGNORES them, exactly like the pin — its tween/keyframes generators never read them).
|
|
217
|
+
// LAYOUT lane: executed — the flight's sampled spring builds from the same toSpringConfig (the
|
|
218
|
+
// velocity precedent). LayoutId: executed under an explicit type:'spring' flight, refused in an
|
|
219
|
+
// untyped physics bag (transitionGate). EXIT lane: refused loud (below) — the normalized 0→1
|
|
220
|
+
// progress factory would misapply value-units thresholds in progress units (the velocity
|
|
221
|
+
// recorded-divergence class).
|
|
222
|
+
restDelta: { kind: 'number' },
|
|
223
|
+
restSpeed: { kind: 'number' },
|
|
224
|
+
// T18-a (REQ-API-049): the repeat family. PROPERTY-lane only — a layout/layoutId flight animates
|
|
225
|
+
// one snapshot delta and never repeats it (packet L4), so these are deliberately absent from the
|
|
226
|
+
// layout transition domain below and `transitionGate` refuses them on those lanes.
|
|
227
|
+
repeat: { kind: 'number' },
|
|
228
|
+
repeatType: { kind: 'enum', values: ['loop', 'reverse', 'mirror'] },
|
|
229
|
+
repeatDelay: { kind: 'number' },
|
|
230
|
+
// U8 (REQ-API-059): `arc()` factory result. Identifiers (useMemo binding) and calls are valid.
|
|
231
|
+
path: { kind: 'path' },
|
|
232
|
+
} as const
|
|
233
|
+
|
|
234
|
+
// The property lane's executable option set (r5 major d6c3850af147): the gesture-state gate
|
|
235
|
+
// refuses any option outside it — an option the lane never reads (`velocity`, pre-T21) must fail
|
|
236
|
+
// loud, never ride along as a silent no-op (G-INV-8). RUNTIME-immutable (r6 major
|
|
237
|
+
// c61e8a3b52df): a bare Set's ReadonlySet typing is compile-time-only (add/delete remain
|
|
238
|
+
// callable and Object.freeze cannot seal Set internals), so this authority is the module's
|
|
239
|
+
// frozen-membership shape — no mutation surface exists to legalize a refused option.
|
|
240
|
+
/**
|
|
241
|
+
* Property-lane options the PRESENCE EXIT lane cannot execute (T18-a fix-up, review MAJOR).
|
|
242
|
+
*
|
|
243
|
+
* The exit lane drives a progress factory, not a sampled generator fold, so these options
|
|
244
|
+
* have no meaning there — an exit transition carrying one would ride along doing nothing,
|
|
245
|
+
* which is the exact silently-inert class G-INV-8 exists to refuse. It is enumerated
|
|
246
|
+
* EXPLICITLY rather than derived, because derivation is the defect: the exit lane's
|
|
247
|
+
* vocabulary gate read the property lane's record, so shipping the repeat family on the
|
|
248
|
+
* property lane silently widened a different lane's fail-closed gate. `delay` joined at
|
|
249
|
+
* T18-b for the same reason (REQ-API-050): the property lane executes the rebase, the exit
|
|
250
|
+
* progress factory never reads it. `velocity` joined at T21 (REQ-API-053) likewise: it is a
|
|
251
|
+
* per-value physics seed in value units/s, and the exit lane drives one NORMALIZED 0→1
|
|
252
|
+
* progress factory — a units-bearing velocity has no meaning on that curve, so it is refused
|
|
253
|
+
* loud (in the pin, exit runs as ordinary per-value animations where authored velocity works;
|
|
254
|
+
* refusing here is the same recorded-divergence class as `repeat`). U7c (REQ-SPRING-014):
|
|
255
|
+
* `restDelta`/`restSpeed` join for the same reason — value-units / units-per-second thresholds
|
|
256
|
+
* are meaningless on the normalized curve, and worse, the factory's progress springs build
|
|
257
|
+
* through `toSpringConfig`, so an accepted threshold would apply in PROGRESS units and could
|
|
258
|
+
* settle the 0→1 curve mid-flight (a wrong unmount, not just an inert ride-along).
|
|
259
|
+
* `shippedSurface.exitLane.test.ts` ("exit-lane vocabulary is a total, explicit
|
|
260
|
+
* partition of the property lane") keeps this
|
|
261
|
+
* partition exhaustive, so a future property option cannot slip through unclassified.
|
|
262
|
+
*
|
|
263
|
+
* This is a recorded DIVERGENCE from the pin, not a match: the pinned Motion runs `exit` as an
|
|
264
|
+
* ordinary animation (`ExitAnimationFeature` awaits `setActive("exit", …)`), so `repeat` works
|
|
265
|
+
* there and an endless exit repeat means the element never unmounts. Executing it here is a
|
|
266
|
+
* scoped successor rung; refusing loud is the honest interim.
|
|
267
|
+
*/
|
|
268
|
+
export const EXIT_LANE_INEXECUTABLE_OPTIONS = Object.freeze([
|
|
269
|
+
'repeat',
|
|
270
|
+
'repeatType',
|
|
271
|
+
'repeatDelay',
|
|
272
|
+
'delay',
|
|
273
|
+
'velocity',
|
|
274
|
+
// U7c (REQ-SPRING-014): value-units settle thresholds — refused on the normalized 0→1 exit
|
|
275
|
+
// progress factory (see the header above).
|
|
276
|
+
'restDelta',
|
|
277
|
+
'restSpeed',
|
|
278
|
+
// U8 (REQ-API-059): the parametric path channel is driven by the element's own transition
|
|
279
|
+
// through the driver (progress 0→1000); the exit lane's normalized 0→1 progress factory has
|
|
280
|
+
// no path channel, so `path` would ride along inert — refused loud like the repeat family.
|
|
281
|
+
'path',
|
|
282
|
+
] as const)
|
|
283
|
+
|
|
284
|
+
const EXIT_LANE_INEXECUTABLE_SET: ReadonlySet<string> = new Set(EXIT_LANE_INEXECUTABLE_OPTIONS)
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* The options the exit lane's progress factory DOES execute — enumerated, not derived. Together
|
|
288
|
+
* with `EXIT_LANE_INEXECUTABLE_OPTIONS` this partitions the property lane's option record exactly;
|
|
289
|
+
* `shippedSurface.exitLane.test.ts` asserts that totality, so adding a property-lane option without deciding
|
|
290
|
+
* what the exit lane does with it fails the gate instead of silently defaulting to "executable".
|
|
291
|
+
* That default is what shipped the T18-a regression.
|
|
292
|
+
*/
|
|
293
|
+
export const EXIT_LANE_EXECUTABLE_OPTIONS = Object.freeze([
|
|
294
|
+
'type',
|
|
295
|
+
'duration',
|
|
296
|
+
'ease',
|
|
297
|
+
'easings',
|
|
298
|
+
'easing',
|
|
299
|
+
'times',
|
|
300
|
+
'stiffness',
|
|
301
|
+
'damping',
|
|
302
|
+
'mass',
|
|
303
|
+
'bounce',
|
|
304
|
+
'visualDuration',
|
|
305
|
+
] as const)
|
|
306
|
+
|
|
307
|
+
const EXIT_LANE_EXECUTABLE_SET: ReadonlySet<string> = new Set(EXIT_LANE_EXECUTABLE_OPTIONS)
|
|
308
|
+
|
|
309
|
+
export function exitLaneInexecutableOption(option: string): boolean {
|
|
310
|
+
return EXIT_LANE_INEXECUTABLE_SET.has(option)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** True when the exit lane can actually run this option. Unknown options are NOT executable. */
|
|
314
|
+
export function exitLaneExecutableOption(option: string): boolean {
|
|
315
|
+
return EXIT_LANE_EXECUTABLE_SET.has(option)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** The property-lane option names, for the totality floor. */
|
|
319
|
+
export const PROPERTY_TRANSITION_OPTION_NAMES: readonly string[] = Object.freeze(
|
|
320
|
+
Object.keys(PROPERTY_TRANSITION_VALUE_DOMAINS),
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
const PROPERTY_TRANSITION_OPTION_SET = new Set(Object.keys(PROPERTY_TRANSITION_VALUE_DOMAINS))
|
|
324
|
+
export const NATIVE_PROPERTY_TRANSITION_OPTIONS = Object.freeze({
|
|
325
|
+
options: Object.freeze(Object.keys(PROPERTY_TRANSITION_VALUE_DOMAINS)) as readonly string[],
|
|
326
|
+
has(value: string): boolean {
|
|
327
|
+
// Closured Set membership (r6 minor 9b752a446d91): object-property lookup answers true
|
|
328
|
+
// for prototype members (toString, constructor) — a membership authority must not.
|
|
329
|
+
return PROPERTY_TRANSITION_OPTION_SET.has(value)
|
|
330
|
+
},
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
// Reanimated's layout transition builder consumes the authored delay itself, in addition to the
|
|
334
|
+
// converter-owned options above. Since the T21 r1 fix-up the layout flight EXECUTES authored
|
|
335
|
+
// `velocity` (review r2 major 2): the flight's sampled spring channels build from the core
|
|
336
|
+
// generator config that `toSpringConfig` carries it into (REQ-SPRING-007's authored-override
|
|
337
|
+
// law — the duration family zeroes it like any seed), so `velocity` belongs to this domain;
|
|
338
|
+
// only layoutId flights refuse it, with the rest of the spring-physics family (transitionGate).
|
|
339
|
+
// The layout builder interpolates a single from→to (never a keyframe array), so it takes a
|
|
340
|
+
// SINGLE `ease` only and does NOT consume the keyframe-timing params `times` (R8 M2-B) — both
|
|
341
|
+
// are property-lane keyframe-array params, absent here (no option accepted but inert). This is
|
|
342
|
+
// why the layout domain is NOT the full transition vocabulary: it is the scalar options +
|
|
343
|
+
// `delay`, minus the keyframe-only `times`.
|
|
344
|
+
const LAYOUT_TRANSITION_VALUE_DOMAINS = {
|
|
345
|
+
// NOT the property domain's `type`: U7a's authored instant lane (`type: false`) is
|
|
346
|
+
// property-only — the layout/layoutId lanes refuse it loud (transitionGate), so this domain
|
|
347
|
+
// keeps the closed spring/tween vocabulary.
|
|
348
|
+
type: { kind: 'enum', values: ['spring', 'tween'] },
|
|
349
|
+
duration: PROPERTY_TRANSITION_VALUE_DOMAINS.duration,
|
|
350
|
+
ease: {
|
|
351
|
+
kind: 'easing',
|
|
352
|
+
values: [
|
|
353
|
+
'linear',
|
|
354
|
+
'easeIn',
|
|
355
|
+
'easeOut',
|
|
356
|
+
'easeInOut',
|
|
357
|
+
'circIn',
|
|
358
|
+
'circOut',
|
|
359
|
+
'circInOut',
|
|
360
|
+
'backIn',
|
|
361
|
+
'backOut',
|
|
362
|
+
'backInOut',
|
|
363
|
+
'anticipate',
|
|
364
|
+
],
|
|
365
|
+
},
|
|
366
|
+
// Family-2: scalar layout alias of ease (per-segment lists remain property-lane only —
|
|
367
|
+
// easing-list domain is not used here, so a list shape is domain-invalid on layout).
|
|
368
|
+
easings: {
|
|
369
|
+
kind: 'easing',
|
|
370
|
+
values: [
|
|
371
|
+
'linear',
|
|
372
|
+
'easeIn',
|
|
373
|
+
'easeOut',
|
|
374
|
+
'easeInOut',
|
|
375
|
+
'circIn',
|
|
376
|
+
'circOut',
|
|
377
|
+
'circInOut',
|
|
378
|
+
'backIn',
|
|
379
|
+
'backOut',
|
|
380
|
+
'backInOut',
|
|
381
|
+
'anticipate',
|
|
382
|
+
],
|
|
383
|
+
},
|
|
384
|
+
easing: {
|
|
385
|
+
kind: 'easing',
|
|
386
|
+
values: [
|
|
387
|
+
'linear',
|
|
388
|
+
'easeIn',
|
|
389
|
+
'easeOut',
|
|
390
|
+
'easeInOut',
|
|
391
|
+
'circIn',
|
|
392
|
+
'circOut',
|
|
393
|
+
'circInOut',
|
|
394
|
+
'backIn',
|
|
395
|
+
'backOut',
|
|
396
|
+
'backInOut',
|
|
397
|
+
'anticipate',
|
|
398
|
+
],
|
|
399
|
+
},
|
|
400
|
+
stiffness: PROPERTY_TRANSITION_VALUE_DOMAINS.stiffness,
|
|
401
|
+
damping: PROPERTY_TRANSITION_VALUE_DOMAINS.damping,
|
|
402
|
+
mass: PROPERTY_TRANSITION_VALUE_DOMAINS.mass,
|
|
403
|
+
bounce: PROPERTY_TRANSITION_VALUE_DOMAINS.bounce,
|
|
404
|
+
visualDuration: PROPERTY_TRANSITION_VALUE_DOMAINS.visualDuration,
|
|
405
|
+
velocity: PROPERTY_TRANSITION_VALUE_DOMAINS.velocity,
|
|
406
|
+
// U7c (REQ-SPRING-014): the layout flight's sampled spring builds through toSpringConfig, which
|
|
407
|
+
// carries the authored settle thresholds — executed here exactly like velocity.
|
|
408
|
+
restDelta: PROPERTY_TRANSITION_VALUE_DOMAINS.restDelta,
|
|
409
|
+
restSpeed: PROPERTY_TRANSITION_VALUE_DOMAINS.restSpeed,
|
|
410
|
+
delay: PROPERTY_TRANSITION_VALUE_DOMAINS.delay,
|
|
411
|
+
} as const
|
|
412
|
+
|
|
413
|
+
export type NativeMountTargetValueKind = 'number' | 'color' | 'discrete'
|
|
414
|
+
|
|
415
|
+
// This is the data-only mirror of MotionView's actual mount split: colors are projected through
|
|
416
|
+
// the color lane, discrete keywords (T23 B) through the keyword-step lane, and every other target
|
|
417
|
+
// key reaches the numeric worklet driver. The controller calls this same function before
|
|
418
|
+
// registration, so catalog validation cannot be more permissive than native mounting (notably,
|
|
419
|
+
// core-valid px/deg/rad strings still fail the numeric lane).
|
|
420
|
+
export function nativeMountTargetValueKind(key: string): NativeMountTargetValueKind {
|
|
421
|
+
const valueType = UNIVERSAL_SUBSET.get(key)?.valueType
|
|
422
|
+
if (valueType === 'rgba') return 'color'
|
|
423
|
+
if (valueType === 'discrete') return 'discrete'
|
|
424
|
+
return 'number'
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// The positional keys whose length STRINGS the native numeric lane can measure-resolve
|
|
428
|
+
// (REQ-VALUETYPE-013): %/auto/pin-simple-calc/px execute only here (resolveLengthToPx is
|
|
429
|
+
// axis-scoped). The ONE authority is core's executable value matrix (review 91d41675d511) —
|
|
430
|
+
// the variants gate, the gesture-state gate, and the style-base gate all read it, so no
|
|
431
|
+
// boundary admits a string the lane cannot drive.
|
|
432
|
+
export const NATIVE_MEASURE_LENGTH_KEYS = Object.freeze({
|
|
433
|
+
keys: POSITIONAL_LENGTH_AXES as readonly string[],
|
|
434
|
+
has(key: string): boolean {
|
|
435
|
+
return isPositionalLengthAxis(key)
|
|
436
|
+
},
|
|
437
|
+
})
|
|
438
|
+
export function isNativeMeasureLengthKey(key: string): boolean {
|
|
439
|
+
return isPositionalLengthAxis(key)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// The strict DRIVABILITY predicate over core's executable matrix (review 91d41675d511): the
|
|
443
|
+
// numeric lane drives numbers, or length STRINGS the matrix executes — scalar or every keyframe
|
|
444
|
+
// element (null-first is the legal from-current marker). The walker alone returns null for
|
|
445
|
+
// non-length strings (the value-type lane owns those refusals); the gates need the strict
|
|
446
|
+
// either/or, so non-length strings read as undrivable here.
|
|
447
|
+
export function isNativeExecutableLengthValue(key: string, value: unknown): boolean {
|
|
448
|
+
if (typeof value === 'number') return true
|
|
449
|
+
if (Array.isArray(value)) {
|
|
450
|
+
return value.every(
|
|
451
|
+
(element, index) =>
|
|
452
|
+
(element === null && index === 0) ||
|
|
453
|
+
typeof element === 'number' ||
|
|
454
|
+
(typeof element === 'string' &&
|
|
455
|
+
isLengthTarget(element) &&
|
|
456
|
+
executableLengthValueRefusal(key, element) === null),
|
|
457
|
+
)
|
|
458
|
+
}
|
|
459
|
+
return (
|
|
460
|
+
typeof value === 'string' &&
|
|
461
|
+
isLengthTarget(value) &&
|
|
462
|
+
executableLengthValueRefusal(key, value) === null
|
|
463
|
+
)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const COMPONENT_VALUE_DOMAINS = {
|
|
467
|
+
View: {
|
|
468
|
+
initial: { kind: 'target-or-false' },
|
|
469
|
+
animate: { kind: 'target-or-boolean' },
|
|
470
|
+
exit: { kind: 'target' },
|
|
471
|
+
transition: { kind: 'transition' },
|
|
472
|
+
// These domains reflect the native mount boundaries rather than merely documenting prop names:
|
|
473
|
+
// layout/layoutId are gated before registration, drag is resolved through core, and style's
|
|
474
|
+
// transform leaves share core's target normalizer with the animated target lane.
|
|
475
|
+
style: { kind: 'style' },
|
|
476
|
+
layout: { kind: 'layout' },
|
|
477
|
+
layoutId: { kind: 'layout-id' },
|
|
478
|
+
layoutScroll: { kind: 'boolean' },
|
|
479
|
+
onAnimationComplete: { kind: 'callback' },
|
|
480
|
+
onLayoutAnimationComplete: { kind: 'callback' },
|
|
481
|
+
onLayoutAnimationStart: { kind: 'callback' },
|
|
482
|
+
drag: { kind: 'drag-config' },
|
|
483
|
+
dragConstraints: { kind: 'drag-config' },
|
|
484
|
+
dragElastic: { kind: 'drag-config' },
|
|
485
|
+
dragSnapPoints: { kind: 'drag-config' },
|
|
486
|
+
dragPropagation: { kind: 'drag-config' },
|
|
487
|
+
dragControls: { kind: 'drag-config' },
|
|
488
|
+
dragListener: { kind: 'drag-config' },
|
|
489
|
+
// R15 D4 (REQ-API-044)
|
|
490
|
+
dragDirectionLock: { kind: 'drag-config' },
|
|
491
|
+
onDirectionLock: { kind: 'callback' },
|
|
492
|
+
dragMomentum: { kind: 'drag-config' },
|
|
493
|
+
dragTransition: { kind: 'drag-config' },
|
|
494
|
+
dragSnapToOrigin: { kind: 'drag-config' },
|
|
495
|
+
_dragX: { kind: 'drag-config' },
|
|
496
|
+
_dragY: { kind: 'drag-config' },
|
|
497
|
+
onPanSessionStart: { kind: 'callback' },
|
|
498
|
+
onPanStart: { kind: 'callback' },
|
|
499
|
+
onPan: { kind: 'callback' },
|
|
500
|
+
onPanEnd: { kind: 'callback' },
|
|
501
|
+
onMeasureDragConstraints: { kind: 'callback' },
|
|
502
|
+
onDragStart: { kind: 'callback' },
|
|
503
|
+
onDrag: { kind: 'callback' },
|
|
504
|
+
onDragEnd: { kind: 'callback' },
|
|
505
|
+
onTap: { kind: 'callback' },
|
|
506
|
+
onTapStart: { kind: 'callback' },
|
|
507
|
+
onTapCancel: { kind: 'callback' },
|
|
508
|
+
// U7d (REQ-API-030 global form): the root-observing press scope flag.
|
|
509
|
+
globalTapTarget: { kind: 'boolean' },
|
|
510
|
+
whileTap: { kind: 'target' },
|
|
511
|
+
whileDrag: { kind: 'target' },
|
|
512
|
+
// R7 (REQ-API-032): the dictionary validates per entry through the SAME target/transition
|
|
513
|
+
// boundaries the animate lane rides; label-form VALUES on the target props re-derive in
|
|
514
|
+
// the parity ledger pass (the R7 M4 milestone owns the classifier's label disposition).
|
|
515
|
+
variants: { kind: 'variants-dictionary' },
|
|
516
|
+
inherit: { kind: 'boolean' },
|
|
517
|
+
},
|
|
518
|
+
// REQ-API-034 law (b): Text executes the exact same Motion vocabulary through the shared host
|
|
519
|
+
// seam. Its RN-only text props are ordinary forwarded host props, not Motion capabilities.
|
|
520
|
+
Text: {
|
|
521
|
+
initial: { kind: 'target-or-false' },
|
|
522
|
+
animate: { kind: 'target-or-boolean' },
|
|
523
|
+
exit: { kind: 'target' },
|
|
524
|
+
transition: { kind: 'transition' },
|
|
525
|
+
style: { kind: 'style' },
|
|
526
|
+
layout: { kind: 'layout' },
|
|
527
|
+
layoutId: { kind: 'layout-id' },
|
|
528
|
+
layoutScroll: { kind: 'boolean' },
|
|
529
|
+
onAnimationComplete: { kind: 'callback' },
|
|
530
|
+
onLayoutAnimationComplete: { kind: 'callback' },
|
|
531
|
+
onLayoutAnimationStart: { kind: 'callback' },
|
|
532
|
+
drag: { kind: 'drag-config' },
|
|
533
|
+
dragConstraints: { kind: 'drag-config' },
|
|
534
|
+
dragElastic: { kind: 'drag-config' },
|
|
535
|
+
dragSnapPoints: { kind: 'drag-config' },
|
|
536
|
+
dragPropagation: { kind: 'drag-config' },
|
|
537
|
+
dragControls: { kind: 'drag-config' },
|
|
538
|
+
dragListener: { kind: 'drag-config' },
|
|
539
|
+
dragDirectionLock: { kind: 'drag-config' },
|
|
540
|
+
onDirectionLock: { kind: 'callback' },
|
|
541
|
+
dragMomentum: { kind: 'drag-config' },
|
|
542
|
+
dragTransition: { kind: 'drag-config' },
|
|
543
|
+
dragSnapToOrigin: { kind: 'drag-config' },
|
|
544
|
+
_dragX: { kind: 'drag-config' },
|
|
545
|
+
_dragY: { kind: 'drag-config' },
|
|
546
|
+
onPanSessionStart: { kind: 'callback' },
|
|
547
|
+
onPanStart: { kind: 'callback' },
|
|
548
|
+
onPan: { kind: 'callback' },
|
|
549
|
+
onPanEnd: { kind: 'callback' },
|
|
550
|
+
onMeasureDragConstraints: { kind: 'callback' },
|
|
551
|
+
onDragStart: { kind: 'callback' },
|
|
552
|
+
onDrag: { kind: 'callback' },
|
|
553
|
+
onDragEnd: { kind: 'callback' },
|
|
554
|
+
onTap: { kind: 'callback' },
|
|
555
|
+
onTapStart: { kind: 'callback' },
|
|
556
|
+
onTapCancel: { kind: 'callback' },
|
|
557
|
+
// U7d (REQ-API-030 global form): the root-observing press scope flag.
|
|
558
|
+
globalTapTarget: { kind: 'boolean' },
|
|
559
|
+
whileTap: { kind: 'target' },
|
|
560
|
+
whileDrag: { kind: 'target' },
|
|
561
|
+
variants: { kind: 'variants-dictionary' },
|
|
562
|
+
inherit: { kind: 'boolean' },
|
|
563
|
+
},
|
|
564
|
+
// REQ-API-034 law (c): Image is the same Motion vocabulary on a childless host. Element-
|
|
565
|
+
// specific `source` is a host prop, not a Motion capability row.
|
|
566
|
+
Image: {
|
|
567
|
+
initial: { kind: 'target-or-false' },
|
|
568
|
+
animate: { kind: 'target-or-boolean' },
|
|
569
|
+
exit: { kind: 'target' },
|
|
570
|
+
transition: { kind: 'transition' },
|
|
571
|
+
style: { kind: 'style' },
|
|
572
|
+
layout: { kind: 'layout' },
|
|
573
|
+
layoutId: { kind: 'layout-id' },
|
|
574
|
+
layoutScroll: { kind: 'boolean' },
|
|
575
|
+
onAnimationComplete: { kind: 'callback' },
|
|
576
|
+
onLayoutAnimationComplete: { kind: 'callback' },
|
|
577
|
+
onLayoutAnimationStart: { kind: 'callback' },
|
|
578
|
+
drag: { kind: 'drag-config' },
|
|
579
|
+
dragConstraints: { kind: 'drag-config' },
|
|
580
|
+
dragElastic: { kind: 'drag-config' },
|
|
581
|
+
dragSnapPoints: { kind: 'drag-config' },
|
|
582
|
+
dragPropagation: { kind: 'drag-config' },
|
|
583
|
+
dragControls: { kind: 'drag-config' },
|
|
584
|
+
dragListener: { kind: 'drag-config' },
|
|
585
|
+
dragDirectionLock: { kind: 'drag-config' },
|
|
586
|
+
onDirectionLock: { kind: 'callback' },
|
|
587
|
+
dragMomentum: { kind: 'drag-config' },
|
|
588
|
+
dragTransition: { kind: 'drag-config' },
|
|
589
|
+
dragSnapToOrigin: { kind: 'drag-config' },
|
|
590
|
+
_dragX: { kind: 'drag-config' },
|
|
591
|
+
_dragY: { kind: 'drag-config' },
|
|
592
|
+
onPanSessionStart: { kind: 'callback' },
|
|
593
|
+
onPanStart: { kind: 'callback' },
|
|
594
|
+
onPan: { kind: 'callback' },
|
|
595
|
+
onPanEnd: { kind: 'callback' },
|
|
596
|
+
onMeasureDragConstraints: { kind: 'callback' },
|
|
597
|
+
onDragStart: { kind: 'callback' },
|
|
598
|
+
onDrag: { kind: 'callback' },
|
|
599
|
+
onDragEnd: { kind: 'callback' },
|
|
600
|
+
onTap: { kind: 'callback' },
|
|
601
|
+
onTapStart: { kind: 'callback' },
|
|
602
|
+
onTapCancel: { kind: 'callback' },
|
|
603
|
+
// U7d (REQ-API-030 global form): the root-observing press scope flag.
|
|
604
|
+
globalTapTarget: { kind: 'boolean' },
|
|
605
|
+
whileTap: { kind: 'target' },
|
|
606
|
+
whileDrag: { kind: 'target' },
|
|
607
|
+
variants: { kind: 'variants-dictionary' },
|
|
608
|
+
inherit: { kind: 'boolean' },
|
|
609
|
+
},
|
|
610
|
+
AnimatePresence: {
|
|
611
|
+
// T24 P: native presence composes the retained child's last parent-relative rect; web forwards
|
|
612
|
+
// the same mode to motion/react's PopChild.
|
|
613
|
+
mode: { kind: 'enum', values: ['sync', 'wait', 'popLayout'] },
|
|
614
|
+
initial: { kind: 'boolean' },
|
|
615
|
+
onExitComplete: { kind: 'callback' },
|
|
616
|
+
// R13 V4: pin-catalog alias of onExitComplete (same callback lifecycle).
|
|
617
|
+
onRest: { kind: 'callback' },
|
|
618
|
+
// Family-6: nested AP joins parent exit (REQ-PRESENCE-010).
|
|
619
|
+
propagate: { kind: 'boolean' },
|
|
620
|
+
},
|
|
621
|
+
// REQ-API-035: provider vocabulary (not a Motion host element).
|
|
622
|
+
MotionConfig: {
|
|
623
|
+
transition: { kind: 'transition' },
|
|
624
|
+
reducedMotion: { kind: 'enum', values: ['always', 'never', 'user'] },
|
|
625
|
+
nonce: { kind: 'string' },
|
|
626
|
+
isStatic: { kind: 'boolean' },
|
|
627
|
+
skipAnimations: { kind: 'boolean' },
|
|
628
|
+
// R15 D3 (REQ-API-043): function domain — accepted by name; runtime gate owns the shape.
|
|
629
|
+
transformPagePoint: { kind: 'callback' },
|
|
630
|
+
},
|
|
631
|
+
// REQ-API-036: LayoutGroup provider vocabulary. inherit is true | false | "id" — the
|
|
632
|
+
// classifier has no mixed boolean/string enum domain; the prop is accepted by name and the
|
|
633
|
+
// runtime gate owns the closed vocabulary (same posture as provider props without a domain).
|
|
634
|
+
LayoutGroup: {
|
|
635
|
+
id: { kind: 'string' },
|
|
636
|
+
},
|
|
637
|
+
} as const
|
|
638
|
+
|
|
639
|
+
const NATIVE_TYPE_EXPORTS = [
|
|
640
|
+
'MotionTargetProps',
|
|
641
|
+
'Target',
|
|
642
|
+
'Transition',
|
|
643
|
+
'TransitionType',
|
|
644
|
+
'DragInfo',
|
|
645
|
+
// R5 (REQ-API-030): the tap payload types ship on both entries.
|
|
646
|
+
'TapInfo',
|
|
647
|
+
'TapCallback',
|
|
648
|
+
// R15 D3 (REQ-API-043): imperative hub types (seal review r10 major — classifier authority).
|
|
649
|
+
'DragControlOptions',
|
|
650
|
+
'DragControlsHandle',
|
|
651
|
+
'DragControls',
|
|
652
|
+
'ImageProps',
|
|
653
|
+
'TextProps',
|
|
654
|
+
'ViewProps',
|
|
655
|
+
'MotionRootProps',
|
|
656
|
+
'MotionConfigProps',
|
|
657
|
+
'MotionConfigTransition',
|
|
658
|
+
'ReducedMotionConfig',
|
|
659
|
+
'LayoutGroupProps',
|
|
660
|
+
'LayoutGroupInherit',
|
|
661
|
+
'MotionValue',
|
|
662
|
+
// R11 M1 (REQ-API-037)
|
|
663
|
+
'ScrollMotionValues',
|
|
664
|
+
'UseScrollOptions',
|
|
665
|
+
'ArcOptions',
|
|
666
|
+
] as const
|
|
667
|
+
|
|
668
|
+
// MotionView forwards ordinary RN host props, but its gate rejects this finite oracle vocabulary
|
|
669
|
+
// plus these semantic prefixes. Keeping the predicate with the shipped surface lets node tooling
|
|
670
|
+
// distinguish a forwarded host prop (for example onClick in a web sandbox) from a known, rejected
|
|
671
|
+
// Motion capability without recreating a broad classifier-side name heuristic.
|
|
672
|
+
const VIEW_DEFERRED_MOTION_PROP_NAMES = [
|
|
673
|
+
'animate',
|
|
674
|
+
'exit',
|
|
675
|
+
'variants',
|
|
676
|
+
'initial',
|
|
677
|
+
'style',
|
|
678
|
+
'values',
|
|
679
|
+
'transition',
|
|
680
|
+
'transformTemplate',
|
|
681
|
+
'custom',
|
|
682
|
+
'inherit',
|
|
683
|
+
'onBeforeLayoutMeasure',
|
|
684
|
+
'onAnimationStart',
|
|
685
|
+
'onAnimationComplete',
|
|
686
|
+
'onUpdate',
|
|
687
|
+
'onDragStart',
|
|
688
|
+
'onDrag',
|
|
689
|
+
'onDragEnd',
|
|
690
|
+
'onMeasureDragConstraints',
|
|
691
|
+
'onDirectionLock',
|
|
692
|
+
'onDragTransitionEnd',
|
|
693
|
+
'_dragX',
|
|
694
|
+
'_dragY',
|
|
695
|
+
'onHoverStart',
|
|
696
|
+
'onHoverEnd',
|
|
697
|
+
'onViewportEnter',
|
|
698
|
+
'onViewportLeave',
|
|
699
|
+
// U7d: globalTapTarget is SHIPPED (View surface) but stays in this vocabulary list — the
|
|
700
|
+
// predicate is pinned-vocabulary, not shipped-ness (the oracle canary; animate/transition
|
|
701
|
+
// precedents). Shipped members are destructured before the deferred gate sees them.
|
|
702
|
+
'globalTapTarget',
|
|
703
|
+
'propagate',
|
|
704
|
+
'ignoreStrict',
|
|
705
|
+
'viewport',
|
|
706
|
+
] as const
|
|
707
|
+
|
|
708
|
+
const viewDeferredMotionPropNames = new Set<string>(VIEW_DEFERRED_MOTION_PROP_NAMES)
|
|
709
|
+
|
|
710
|
+
export function isNativeViewMotionProp(key: string): boolean {
|
|
711
|
+
// MOTION VOCABULARY, not shipped-ness: shipped members (drag/onTap/whileTap/whileDrag/…)
|
|
712
|
+
// are destructured out of `rest` by the component and never reach the deferred gate — this
|
|
713
|
+
// predicate must keep matching the pinned isValidMotionProp shape (the oracle canary).
|
|
714
|
+
return (
|
|
715
|
+
key.startsWith('while') ||
|
|
716
|
+
(key.startsWith('drag') && key !== 'draggable') ||
|
|
717
|
+
key.startsWith('layout') ||
|
|
718
|
+
key.startsWith('onTap') ||
|
|
719
|
+
key.startsWith('onPan') ||
|
|
720
|
+
(key.startsWith('onLayout') && key !== 'onLayout') ||
|
|
721
|
+
viewDeferredMotionPropNames.has(key)
|
|
722
|
+
)
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function immutableMembership(values: readonly string[]) {
|
|
726
|
+
const members = new Set(values)
|
|
727
|
+
return Object.freeze({
|
|
728
|
+
size: members.size,
|
|
729
|
+
has(value: string): boolean {
|
|
730
|
+
return members.has(value)
|
|
731
|
+
},
|
|
732
|
+
})
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function immutableComponentProps(propsByComponent: Readonly<Record<string, readonly string[]>>) {
|
|
736
|
+
const memberships = new Map(
|
|
737
|
+
Object.entries(propsByComponent).map(([component, props]) => [
|
|
738
|
+
component,
|
|
739
|
+
immutableMembership(props),
|
|
740
|
+
]),
|
|
741
|
+
)
|
|
742
|
+
return Object.freeze({
|
|
743
|
+
get(component: string): ReturnType<typeof immutableMembership> | undefined {
|
|
744
|
+
return memberships.get(component)
|
|
745
|
+
},
|
|
746
|
+
})
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function immutableValueDomains(
|
|
750
|
+
domainsByComponent: Readonly<Record<string, Readonly<Record<string, unknown>>>>,
|
|
751
|
+
) {
|
|
752
|
+
const domains = new Map(
|
|
753
|
+
Object.entries(domainsByComponent).map(([component, props]) => [
|
|
754
|
+
component,
|
|
755
|
+
new Map(Object.entries(props).map(([prop, domain]) => [prop, deepFreeze(domain)])),
|
|
756
|
+
]),
|
|
757
|
+
)
|
|
758
|
+
return Object.freeze({
|
|
759
|
+
get(component: string, prop: string): unknown {
|
|
760
|
+
return domains.get(component)?.get(prop)
|
|
761
|
+
},
|
|
762
|
+
})
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
export const NATIVE_SHIPPED_SURFACE = deepFreeze({
|
|
766
|
+
// The catalog's `motion`/`m` factories normalize to the native public `View` export. Keep this
|
|
767
|
+
// mapping with the engine surface so the parity classifier never maintains a private whitelist.
|
|
768
|
+
motionFactoryImports: immutableMembership(['motion', 'm']),
|
|
769
|
+
viewMotionPropNames: VIEW_MOTION_PROP_NAMES,
|
|
770
|
+
viewMotionProps: immutableMembership(VIEW_MOTION_PROP_NAMES),
|
|
771
|
+
typeExports: immutableMembership(NATIVE_TYPE_EXPORTS),
|
|
772
|
+
componentProps: immutableComponentProps({
|
|
773
|
+
View: VIEW_MOTION_PROP_NAMES,
|
|
774
|
+
Text: VIEW_MOTION_PROP_NAMES,
|
|
775
|
+
Image: VIEW_MOTION_PROP_NAMES,
|
|
776
|
+
AnimatePresence: ANIMATE_PRESENCE_PROP_NAMES,
|
|
777
|
+
MotionRoot: MOTION_ROOT_PROP_NAMES,
|
|
778
|
+
MotionConfig: MOTION_CONFIG_PROP_NAMES,
|
|
779
|
+
LayoutGroup: LAYOUT_GROUP_PROP_NAMES,
|
|
780
|
+
}),
|
|
781
|
+
transitionOptions: immutableMembership(TRANSITION_OPTION_KEYS),
|
|
782
|
+
// T21 (REQ-API-053): the tree-lane family — shipped at the transition ROOT only, consumed
|
|
783
|
+
// by the variant tree's start scheduling (never a driver/layout option; map entries and
|
|
784
|
+
// layout bags refuse). Single-sourced from core so the classifier tracks the engine.
|
|
785
|
+
orchestrationTransitionOptions: immutableMembership(ORCHESTRATION_OPTION_KEYS),
|
|
786
|
+
propertyTransitionValueDomains: PROPERTY_TRANSITION_VALUE_DOMAINS,
|
|
787
|
+
layoutTransitionValueDomains: LAYOUT_TRANSITION_VALUE_DOMAINS,
|
|
788
|
+
componentValueDomains: immutableValueDomains(COMPONENT_VALUE_DOMAINS),
|
|
789
|
+
components: immutableMembership([
|
|
790
|
+
'View',
|
|
791
|
+
'Text',
|
|
792
|
+
'Image',
|
|
793
|
+
'AnimatePresence',
|
|
794
|
+
'MotionRoot',
|
|
795
|
+
'MotionConfig',
|
|
796
|
+
'LayoutGroup',
|
|
797
|
+
]),
|
|
798
|
+
// R4 shipped the public value channel (REQ-API-025..027); R11 M1 adds useScroll (REQ-API-037);
|
|
799
|
+
// R12 M1 adds useAnimate + free-function animate (REQ-API-039); R13 M1 adds useCycle
|
|
800
|
+
// (REQ-API-040); T21 ships stagger from both entries (REQ-API-053); T23 A ships mix from
|
|
801
|
+
// both entries. U5 ships useMotionTemplate (REQ-API-025). U7a adds useReducedMotion (the
|
|
802
|
+
// pin's mount-time snapshot). U7b adds useInstantTransition + disableInstantTransitions (the
|
|
803
|
+
// global instant window). U7i adds useViewportScroll (deprecated zero-arg alias of useScroll).
|
|
804
|
+
// Deferred: useAnimation, useElementScroll (the other deprecated alias).
|
|
805
|
+
hooks: immutableMembership([
|
|
806
|
+
'useIsPresent',
|
|
807
|
+
'usePresence',
|
|
808
|
+
'useMotionValue',
|
|
809
|
+
'useMotionValueEvent',
|
|
810
|
+
'useMotionTemplate',
|
|
811
|
+
'useScroll',
|
|
812
|
+
// U7i (REQ-API-037(e)): deprecated zero-arg alias of useScroll().
|
|
813
|
+
'useViewportScroll',
|
|
814
|
+
'useSpring',
|
|
815
|
+
'useTransform',
|
|
816
|
+
'useVelocity',
|
|
817
|
+
'useAnimate',
|
|
818
|
+
'useCycle',
|
|
819
|
+
'useReducedMotion',
|
|
820
|
+
// U7b: the global instant window — the opener hook plus the pin's sibling flag-clear (one
|
|
821
|
+
// pin surface, use-instant-transition.ts:9-45).
|
|
822
|
+
'useInstantTransition',
|
|
823
|
+
'disableInstantTransitions',
|
|
824
|
+
// R15 D3 (REQ-API-043): imperative drag hub (hook + constructible class).
|
|
825
|
+
'useDragControls',
|
|
826
|
+
'DragControls',
|
|
827
|
+
// Free-function export — membership here is the parity classifier's runtime-import authority
|
|
828
|
+
// (signals.ts treats non-use* names as import.*; hooks.has gates both).
|
|
829
|
+
'animate',
|
|
830
|
+
// T24 W: public page-domain modulo helper, shared with the native entry.
|
|
831
|
+
'wrap',
|
|
832
|
+
// T21 (REQ-API-053): the public stagger() cascade factory, shipped by both entries.
|
|
833
|
+
'stagger',
|
|
834
|
+
// T23 A: the public mix() utility — one core implementation on both entries.
|
|
835
|
+
'mix',
|
|
836
|
+
// U7g (REQ-LAYOUT-024): user scale-corrector registration (web is the raw pin).
|
|
837
|
+
'addScaleCorrector',
|
|
838
|
+
// U7j (REQ-API-058): read-only JS-frame clock. The `frame` scheduler stays residual.
|
|
839
|
+
'frameData',
|
|
840
|
+
// U8 (REQ-API-059): parametric arc path factory.
|
|
841
|
+
'arc',
|
|
842
|
+
]),
|
|
843
|
+
})
|