@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.
Files changed (117) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +55 -0
  4. package/android/build.gradle +24 -0
  5. package/android/src/main/AndroidManifest.xml +1 -0
  6. package/android/src/main/java/com/unrulysystems/nativemotion/GestureExclusionModule.kt +55 -0
  7. package/android/src/main/java/com/unrulysystems/nativemotion/NativeMotionPackage.kt +27 -0
  8. package/package.json +57 -0
  9. package/react-native.config.js +14 -0
  10. package/src/native/driver/instantWindow.ts +31 -0
  11. package/src/native/driver/strictModeReplay.ts +20 -0
  12. package/src/native/driver/uiLayoutEngine.ts +845 -0
  13. package/src/native/driver/uiLayoutGraph.ts +338 -0
  14. package/src/native/driver/uiValueChannel.ts +4640 -0
  15. package/src/native/driver/workletDriver.ts +3860 -0
  16. package/src/native/motion/AnimatePresence.tsx +1605 -0
  17. package/src/native/motion/LayoutGroup.tsx +163 -0
  18. package/src/native/motion/MotionConfig.tsx +400 -0
  19. package/src/native/motion/MotionRoot.tsx +250 -0
  20. package/src/native/motion/MotionView.tsx +6761 -0
  21. package/src/native/motion/addScaleCorrector.ts +97 -0
  22. package/src/native/motion/colorEndpointUiFeed.ts +32 -0
  23. package/src/native/motion/colorProjection.ts +39 -0
  24. package/src/native/motion/composeTransform.ts +271 -0
  25. package/src/native/motion/constraintMeasure.ts +157 -0
  26. package/src/native/motion/deferredPendingSnapshots.ts +25 -0
  27. package/src/native/motion/discreteProjection.ts +36 -0
  28. package/src/native/motion/dragAncestorPanContext.ts +28 -0
  29. package/src/native/motion/dragControls.ts +152 -0
  30. package/src/native/motion/dragGestureWiring.ts +1168 -0
  31. package/src/native/motion/dragHandoffBinding.ts +179 -0
  32. package/src/native/motion/dragHubStream.ts +756 -0
  33. package/src/native/motion/dragPropagationLock.ts +57 -0
  34. package/src/native/motion/driverValueChannel.ts +3288 -0
  35. package/src/native/motion/externalDragDriver.ts +214 -0
  36. package/src/native/motion/frameData.ts +31 -0
  37. package/src/native/motion/gestureBinding.ts +107 -0
  38. package/src/native/motion/gestureStateGate.ts +563 -0
  39. package/src/native/motion/gestureStateResolver.ts +286 -0
  40. package/src/native/motion/identityValueChannelControllerAdapter.ts +2043 -0
  41. package/src/native/motion/identityValueChannelControllerReconciliation.ts +176 -0
  42. package/src/native/motion/identityValueChannelLaneMarker.ts +14 -0
  43. package/src/native/motion/imperativeAnimate.ts +816 -0
  44. package/src/native/motion/keyframeTiming.ts +7 -0
  45. package/src/native/motion/layoutIdBinding.ts +261 -0
  46. package/src/native/motion/layoutIdFlightConfig.ts +51 -0
  47. package/src/native/motion/layoutProjection.ts +89 -0
  48. package/src/native/motion/layoutScroll.ts +204 -0
  49. package/src/native/motion/layoutTransition.ts +1302 -0
  50. package/src/native/motion/lengthLayoutContext.tsx +100 -0
  51. package/src/native/motion/lengthLayoutHost.ts +44 -0
  52. package/src/native/motion/mappedKeys.ts +184 -0
  53. package/src/native/motion/motionViewController.ts +3180 -0
  54. package/src/native/motion/nativeHostMarker.ts +22 -0
  55. package/src/native/motion/panSessionWiring.ts +127 -0
  56. package/src/native/motion/pathTransition.ts +101 -0
  57. package/src/native/motion/popLayout.ts +176 -0
  58. package/src/native/motion/presenceBinding.ts +382 -0
  59. package/src/native/motion/scaleCorrectorRegistry.ts +123 -0
  60. package/src/native/motion/scrollValues.ts +260 -0
  61. package/src/native/motion/serializablePayload.ts +151 -0
  62. package/src/native/motion/severity.ts +16 -0
  63. package/src/native/motion/shippedSurface.ts +843 -0
  64. package/src/native/motion/staticLengthGate.ts +86 -0
  65. package/src/native/motion/styleBaseGate.ts +72 -0
  66. package/src/native/motion/styleValueBinding.ts +657 -0
  67. package/src/native/motion/systemGestureExclusion.ts +61 -0
  68. package/src/native/motion/tapGestureWiring.ts +215 -0
  69. package/src/native/motion/transformOrder.ts +38 -0
  70. package/src/native/motion/transformStringBinding.ts +200 -0
  71. package/src/native/motion/transformTemplateGate.ts +45 -0
  72. package/src/native/motion/transitionGate.ts +435 -0
  73. package/src/native/motion/useAnimate.ts +52 -0
  74. package/src/native/motion/useCycle.ts +44 -0
  75. package/src/native/motion/useInstantTransition.ts +68 -0
  76. package/src/native/motion/useReducedMotion.ts +58 -0
  77. package/src/native/motion/useScroll.ts +162 -0
  78. package/src/native/motion/useViewportScroll.ts +29 -0
  79. package/src/native/motion/valueChannel.ts +7121 -0
  80. package/src/native/motion/valueHooks.ts +901 -0
  81. package/src/native/motion/variantChildRegistry.ts +67 -0
  82. package/src/native/motion/variantContext.tsx +172 -0
  83. package/src/native/motion/variantProps.ts +598 -0
  84. package/src/native.ts +159 -0
  85. package/src/verification/harnessMetrics.ts +78 -0
  86. package/src/verification/probe/LayoutIdentityWorkletProbe.tsx +251 -0
  87. package/src/verification/probe/WorkletParityProbe.tsx +162 -0
  88. package/src/verification/probe/layoutIdentityProbeEngine.ts +429 -0
  89. package/src/verification/screens/ArcPathChecksScreen.tsx +273 -0
  90. package/src/verification/screens/BooleanAnimateChecksScreen.tsx +358 -0
  91. package/src/verification/screens/ChoreographyGalleryScreen.tsx +1010 -0
  92. package/src/verification/screens/ColorBindingChecksScreen.tsx +568 -0
  93. package/src/verification/screens/CompletionChecksScreen.tsx +294 -0
  94. package/src/verification/screens/ConformanceScreen.tsx +573 -0
  95. package/src/verification/screens/ContentionProbeScreen.tsx +224 -0
  96. package/src/verification/screens/DriverSmokeScreen.tsx +99 -0
  97. package/src/verification/screens/DurationOnlyTweenChecksScreen.tsx +294 -0
  98. package/src/verification/screens/DynamicDragConfigChecksScreen.tsx +237 -0
  99. package/src/verification/screens/FrameDataChecksScreen.tsx +252 -0
  100. package/src/verification/screens/GestureChecksScreen.tsx +685 -0
  101. package/src/verification/screens/InstantTransitionChecksScreen.tsx +839 -0
  102. package/src/verification/screens/LayoutAnimationStartChecksScreen.tsx +648 -0
  103. package/src/verification/screens/LayoutChecksScreen.tsx +822 -0
  104. package/src/verification/screens/LayoutCommitSpikeScreen.tsx +142 -0
  105. package/src/verification/screens/MotionViewChecksScreen.tsx +930 -0
  106. package/src/verification/screens/PresenceChecksScreen.tsx +614 -0
  107. package/src/verification/screens/ReducedMotionChecksScreen.tsx +614 -0
  108. package/src/verification/screens/RestThresholdChecksScreen.tsx +634 -0
  109. package/src/verification/screens/ScaleCorrectorChecksScreen.tsx +425 -0
  110. package/src/verification/screens/SharedLayoutContinuityChecksScreen.tsx +338 -0
  111. package/src/verification/screens/SharedLayoutCrossfadeChecksScreen.tsx +425 -0
  112. package/src/verification/screens/TransitionDefaultSelectionChecksScreen.tsx +514 -0
  113. package/src/verification/screens/ViewportScrollAliasChecksScreen.tsx +303 -0
  114. package/src/verification/screens/conformanceBanner.ts +21 -0
  115. package/src/verification/screens/proofConsoleTap.ts +18 -0
  116. package/src/verification.ts +47 -0
  117. package/src/web.ts +98 -0
@@ -0,0 +1,179 @@
1
+ // REQ-GESTURE-022 at the binding altitude (FLAG 5a, specs/SPEC-GESTURE.md) —
2
+ // the per-axis HANDOFF binding: platform Pan events → validated translations/velocities →
3
+ // handoff-session calls. The session owns EVERY semantic (dead-zone, clamp, elastic, release
4
+ // target selection — REQ-GESTURE-011/-015/-016/-017); this layer owns exactly the M2.4 binding's
5
+ // two concerns: (a) fail-loud event-shape validation (never coerce a missing field to 0 — the
6
+ // M2.2/M2.3 boundary lesson) and (b) the A3 axis parametrization: 'x' reads
7
+ // translationX/velocityX, 'y' reads translationY/velocityY — never a cross-axis coercion. No
8
+ // clock: the handoff session is a time-free event fold; the driver owns integration time.
9
+ //
10
+ // RNGH end/finalize routing (review cycle 1 of the M2.4 binding, carried verbatim):
11
+ // gesture-handler fires `onEnd(event, false)` for gestures that FAIL or are CANCELLED — before
12
+ // `onFinalize(event, false)`. Only a SUCCESSFUL end is a release; an unsuccessful end must leave
13
+ // the session active so the finalize path can cancel to the pre-grab value (REQ-GESTURE-018
14
+ // totality). routeEnd/routeFinalize ARE the component's wiring, exported so tests drive the
15
+ // exact routing.
16
+
17
+ import {
18
+ createGestureHandoffSession,
19
+ describeValue,
20
+ type DragAxis,
21
+ type DragConstraints,
22
+ type GestureHandoffSession,
23
+ type ReleaseHandoff,
24
+ type ResolvedDragAxisConfig,
25
+ } from '@unrulysystems/native-motion-core'
26
+
27
+ // The fields this binding reads off a gesture-handler Pan event, both axes. Kept loose (unknown)
28
+ // because the validation IS the contract — a malformed event fails loud at this boundary.
29
+ export interface DragPanEventShape {
30
+ readonly translationX?: unknown
31
+ readonly translationY?: unknown
32
+ readonly velocityX?: unknown
33
+ readonly velocityY?: unknown
34
+ }
35
+
36
+ export interface DragHandoffBinding {
37
+ // `seed.value` is the driver's live value at the grab instant (REQ-DRIVER-025 → REQ-GESTURE-012).
38
+ // `constraints` are the GRAB-TIME measured bounds for the element-ref channel
39
+ // (REQ-GESTURE-028): applied to this grab only, replacing the constructed config.
40
+ onStart(
41
+ event: DragPanEventShape,
42
+ seed: { readonly value: number },
43
+ constraints?: DragConstraints,
44
+ ): void
45
+ onUpdate(event: DragPanEventShape): number
46
+ // `releaseOrigin` is the driver's LIVE value at the release instant (REQ-DRIVER-025), threaded from
47
+ // the wiring (which owns the driver): the handoff ORIGIN, so a contending declarative drift on the
48
+ // axis (REQ-DRIVER-026) selects the release/snap target, not the finger-tracked session value.
49
+ onEnd(event: DragPanEventShape, releaseOrigin: number): ReleaseHandoff
50
+ // The release edge split into two phases so the wiring can reconcile the DRIVER between them
51
+ // (r11 major 32). `beginRelease` validates both release fields (fail-loud, no half-apply) and
52
+ // COMMITS the final drag sample to the session (G-INV-8: the end translation is never dropped — the
53
+ // M1 session commits it too), returning the finger's final value (so the wiring can catch the driver
54
+ // up iff the finger moved) and the platform velocity. `finishRelease` then finalizes from the
55
+ // driver's live origin (contention-aware). `onEnd` is `beginRelease`+`finishRelease` with no driver
56
+ // reconcile (the test/routing path passes the origin directly).
57
+ beginRelease(event: DragPanEventShape): {
58
+ readonly fingerValue: number
59
+ readonly velocity: number
60
+ // Whether the finger MOVED since the last update, by RAW translation (r12 major 10): true even
61
+ // when a hard clamp pins the constrained value, so the caller catches the driver up to the bound.
62
+ readonly moved: boolean
63
+ }
64
+ finishRelease(velocity: number, releaseOrigin: number): ReleaseHandoff
65
+ // RNGH routing: a successful end releases; an unsuccessful one returns null and leaves the
66
+ // session active for routeFinalize to cancel.
67
+ routeEnd(event: DragPanEventShape, success: boolean, releaseOrigin: number): ReleaseHandoff | null
68
+ // `releaseOrigin` is the driver's LIVE value at the finalize instant (REQ-DRIVER-025): the cancel
69
+ // return spring starts from it, not the finger, so a contending drift does not rewind (r11 major 31).
70
+ routeFinalize(success: boolean, releaseOrigin: number): ReleaseHandoff | null
71
+ readonly session: GestureHandoffSession
72
+ }
73
+
74
+ // Fail-loud field extraction (the M2.4 binding's rule verbatim): a missing or non-numeric
75
+ // recognizer field is a caller/platform bug — naming the field beats silently coercing to 0.
76
+ function requireNumber(value: unknown, field: string): number {
77
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
78
+ // describeValue (core, single-sourced) is a SAFE formatter (review r4 minor 844ee5e): a BigInt
79
+ // recognizer field produces this typed error naming the field, never a raw JSON.stringify crash.
80
+ throw new Error(
81
+ `drag binding: Pan event field '${field}' is ${describeValue(value)} — a finite number ` +
82
+ 'is required and is never coerced (specs/SPEC-GESTURE.md).',
83
+ )
84
+ }
85
+ return value
86
+ }
87
+
88
+ export function createDragHandoffBinding(
89
+ axis: DragAxis,
90
+ config: ResolvedDragAxisConfig,
91
+ momentum?: boolean,
92
+ ): DragHandoffBinding {
93
+ const session = createGestureHandoffSession({
94
+ ...(config.constraints === undefined ? {} : { constraints: config.constraints }),
95
+ ...(config.snap === undefined ? {} : { snap: config.snap }),
96
+ // REQ-GESTURE-027 (R16): momentum gating moved INTO the session so the projection velocity
97
+ // itself is zeroed (settle-at-release), not merely the integrator seed.
98
+ ...(momentum === undefined ? {} : { momentum }),
99
+ })
100
+ // Axis field selection (packet A3): exactly the declared axis's fields, never a fallback pair.
101
+ const translationField = axis === 'x' ? 'translationX' : 'translationY'
102
+ const velocityField = axis === 'x' ? 'velocityX' : 'velocityY'
103
+ // The last RAW recognizer translation applied (grab or drag). The release edge decides "did the
104
+ // finger move" from THIS — the raw translation, not the constrained value: a finger dragging PAST a
105
+ // hard bound moves while its clamped value stays pinned at the bound (r12 major 10). Null between
106
+ // gestures; each grab reseeds it.
107
+ let lastTranslation: number | null = null
108
+ // Last finite platform velocity applied on an update (or grab). Some recognizers report near-zero
109
+ // velocity on end after a fling sample already carried momentum on the prior update — seed release
110
+ // from the larger of |end velocity| and |last update velocity| so free-drag projection and boundary
111
+ // spring-back are not silently dropped (M2 r2: fling freeze at end pose with ends:1).
112
+ let lastUpdateVelocity = 0
113
+
114
+ function translation(event: DragPanEventShape): number {
115
+ return requireNumber(event[translationField], translationField)
116
+ }
117
+
118
+ function releaseVelocity(endVelocity: number): number {
119
+ // Prefer the end sample when it carries magnitude; otherwise the last update's velocity.
120
+ if (Math.abs(endVelocity) >= Math.abs(lastUpdateVelocity)) return endVelocity
121
+ return lastUpdateVelocity
122
+ }
123
+
124
+ const binding: DragHandoffBinding = {
125
+ onStart(event, seed, constraints) {
126
+ const t = translation(event)
127
+ session.begin(t, seed, constraints === undefined ? undefined : { constraints })
128
+ lastTranslation = t
129
+ lastUpdateVelocity = 0
130
+ },
131
+ onUpdate(event) {
132
+ // Validate BEFORE the session call so a malformed event never half-applies, then return the
133
+ // constrained value for the caller's driver write (REQ-DRIVER-024).
134
+ const t = translation(event)
135
+ const v = requireNumber(event[velocityField], velocityField)
136
+ session.active(t)
137
+ lastTranslation = t
138
+ lastUpdateVelocity = v
139
+ return session.value()
140
+ },
141
+ onEnd(event, releaseOrigin) {
142
+ // The release edge with NO driver reconcile (the test/routing path supplies the origin directly):
143
+ // commit the final sample, then finalize from the passed origin.
144
+ const { velocity } = binding.beginRelease(event)
145
+ return binding.finishRelease(velocity, releaseOrigin)
146
+ },
147
+ beginRelease(event) {
148
+ // Both fields validate BEFORE any session mutation: a malformed release leaves the session active
149
+ // at its last good value, never half-released (major 6 law). Then COMMIT the final drag sample
150
+ // (session.active) so session.value() / the onDragEnd info reflect the finger's final position —
151
+ // the M1 session commits the end sample too; dropping it violates G-INV-8 (r11 major 32).
152
+ const t = translation(event)
153
+ const endV = requireNumber(event[velocityField], velocityField)
154
+ const v = releaseVelocity(endV)
155
+ // "Moved" is decided on the RAW translation, not the constrained value (r12 major 10): a finger
156
+ // dragging past a hard bound changes translation while its clamped value stays pinned. A held
157
+ // finger reports the same translation ⇒ not moved ⇒ the caller preserves a contending drift.
158
+ const moved = lastTranslation === null || t !== lastTranslation
159
+ session.active(t)
160
+ lastTranslation = t
161
+ return { fingerValue: session.value(), velocity: v, moved }
162
+ },
163
+ finishRelease(velocity, releaseOrigin) {
164
+ // Finalize from the driver's live origin (contention-aware, REQ-GESTURE-022): the finger's final
165
+ // position if it moved, or the contending drift if it held.
166
+ return session.end(velocity, releaseOrigin)
167
+ },
168
+ routeEnd(event, success, releaseOrigin) {
169
+ if (!success) return null
170
+ return binding.onEnd(event, releaseOrigin)
171
+ },
172
+ routeFinalize(success, releaseOrigin) {
173
+ if (!success && session.state() === 'active') return session.cancel(releaseOrigin)
174
+ return null
175
+ },
176
+ session,
177
+ }
178
+ return binding
179
+ }