@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,100 @@
1
+ // REQ-VALUETYPE-013 — host layout context for measure-resolved length targets.
2
+ // Parent geometry is opt-in via MotionLengthLayoutProvider (gallery stages, sheet hosts).
3
+ // Viewport defaults from Dimensions; auto sizes come from the element's own onLayout.
4
+
5
+ import { createContext, useContext, useMemo, type ReactNode } from 'react'
6
+ import type { LengthLayoutContext } from '@unrulysystems/native-motion-core'
7
+
8
+ const LengthLayoutReactContext = createContext<LengthLayoutContext | null>(null)
9
+
10
+ export function useLengthLayoutParentContext(): LengthLayoutContext | null {
11
+ return useContext(LengthLayoutReactContext)
12
+ }
13
+
14
+ export interface MotionLengthLayoutProviderProps {
15
+ readonly value: LengthLayoutContext
16
+ readonly children: ReactNode
17
+ }
18
+
19
+ /** Provide parent/viewport geometry for descendant measure-resolved %/calc targets. */
20
+ export function MotionLengthLayoutProvider({
21
+ value,
22
+ children,
23
+ }: MotionLengthLayoutProviderProps): ReactNode {
24
+ const parent = useContext(LengthLayoutReactContext)
25
+ const merged = useMemo(
26
+ (): LengthLayoutContext => ({
27
+ ...parent,
28
+ ...value,
29
+ }),
30
+ [parent, value],
31
+ )
32
+ return (
33
+ <LengthLayoutReactContext.Provider value={merged}>{children}</LengthLayoutReactContext.Provider>
34
+ )
35
+ }
36
+
37
+ /**
38
+ * The strict lazy-RN-load classifier (review c513f5cf4a15): only a positively-identified
39
+ * missing *requested* 'react-native' module is an absence. A SyntaxError or a transitive
40
+ * MODULE_NOT_FOUND is a broken dependency/module initialization and must stay loud — never be
41
+ * laundered into an absent optional facility.
42
+ */
43
+ function isMissingModuleAbsence(error: unknown): boolean {
44
+ const code =
45
+ error !== null && typeof error === 'object' && 'code' in error
46
+ ? String((error as { code?: unknown }).code)
47
+ : ''
48
+ const message = error instanceof Error ? error.message : String(error)
49
+ return code === 'MODULE_NOT_FOUND' && /Cannot find module ['"]react-native['"]/i.test(message)
50
+ }
51
+
52
+ /** Lazy require of react-native for the optional host facilities; absence is classified above. */
53
+ function requireReactNativeOptional<T>(): { Dimensions?: T | undefined } | undefined {
54
+ let Dimensions: T | undefined
55
+ // The destructure lives INSIDE the try: a getter-backed host module faulting on export ACCESS
56
+ // is classified by the same law as a module-evaluation fault.
57
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- lazy host touch
58
+ try {
59
+ ;({ Dimensions } = require('react-native') as { Dimensions?: T })
60
+ } catch (error) {
61
+ if (isMissingModuleAbsence(error)) return undefined
62
+ throw error
63
+ }
64
+ if (Dimensions === undefined) return undefined
65
+ return { Dimensions }
66
+ }
67
+
68
+ /**
69
+ * Viewport size for vw/vh measure-resolve. Lazy require of react-native so vitest RN mocks
70
+ * that omit Dimensions do not crash module evaluation. An absent optional facility → `{}`.
71
+ * A successful require whose Dimensions.get throws propagates (host dependency fault).
72
+ */
73
+ export function defaultViewportContext(): LengthLayoutContext {
74
+ const reactNative = requireReactNativeOptional<{
75
+ get: (key: string) => { width: number; height: number }
76
+ }>()
77
+ const Dimensions = reactNative?.Dimensions
78
+ if (Dimensions === undefined || typeof Dimensions.get !== 'function') return {}
79
+ const { width, height } = Dimensions.get('window')
80
+ if (!Number.isFinite(width) || !Number.isFinite(height)) return {}
81
+ return { viewportWidth: width, viewportHeight: height }
82
+ }
83
+
84
+ /**
85
+ * Subscribe to viewport geometry changes (rotation / window resize). Resting vw/vh values
86
+ * re-measure against the fresh viewport on this edge (review ae0a42832d85). An absent optional
87
+ * facility (no Dimensions, or no change event on the host) is a no-op unsubscribe — but the
88
+ * SAME strict classifier as defaultViewportContext (review 8e777bfcf811): a parse/init or
89
+ * transitive load fault propagates, never masquerades as absence.
90
+ */
91
+ export function subscribeViewportContextChange(onChange: () => void): () => void {
92
+ const reactNative = requireReactNativeOptional<{
93
+ addEventListener?: (
94
+ event: string,
95
+ listener: (dims: { window: unknown; screen: unknown }) => void,
96
+ ) => { remove: () => void }
97
+ }>()
98
+ const subscription = reactNative?.Dimensions?.addEventListener?.('change', onChange)
99
+ return () => subscription?.remove()
100
+ }
@@ -0,0 +1,44 @@
1
+ // REQ-VALUETYPE-013 — host onLayout → length-context auto measures + deferred flush.
2
+
3
+ import type { LengthLayoutContext } from '@unrulysystems/native-motion-core'
4
+
5
+ export interface HostLayoutSize {
6
+ readonly width: number
7
+ readonly height: number
8
+ }
9
+
10
+ export interface HostLayoutGate {
11
+ readonly severity: 'development' | 'production'
12
+ readonly report: (error: Error) => void
13
+ }
14
+
15
+ /**
16
+ * Seed autoWidth/autoHeight from the host's content layout and flush deferred measure-resolved
17
+ * animate targets. Returns whether a deferred flush commanded. No-op when layout is absent.
18
+ */
19
+ export function seedLengthLayoutFromHostLayout(
20
+ layout: HostLayoutSize | undefined,
21
+ box: { current: LengthLayoutContext },
22
+ flushDeferredLengthMeasures: () => boolean,
23
+ gate?: HostLayoutGate,
24
+ ): boolean {
25
+ if (layout === undefined) return false
26
+ if (!Number.isFinite(layout.width) || !Number.isFinite(layout.height)) {
27
+ const error = new Error(
28
+ `Motion host onLayout returned a non-finite size (${layout.width}, ${layout.height}); ` +
29
+ 'layout measurements must be finite before measure-resolved lengths can flush (REQ-VALUETYPE-013).',
30
+ )
31
+ // The optional gate keeps the standalone host adapter backward-compatible for non-runtime
32
+ // callers; MotionView always supplies it, so malformed native layout never becomes a silent
33
+ // pending state in the public runtime.
34
+ if (gate?.severity === 'development') throw error
35
+ gate?.report(error)
36
+ return false
37
+ }
38
+ box.current = {
39
+ ...box.current,
40
+ autoWidth: layout.width,
41
+ autoHeight: layout.height,
42
+ }
43
+ return flushDeferredLengthMeasures()
44
+ }
@@ -0,0 +1,184 @@
1
+ // The driver-visible target keys are data so node-side parity tooling can classify the native
2
+ // surface without importing a React Native entry point. MotionView remains their runtime owner.
3
+ // Do not export a Set: Object.freeze(new Set()) still permits Set.prototype.add(), which would make
4
+ // classification depend on import order. The mutable collection stays module-private.
5
+ // R4 (REQ-DRIVER-027, the zero-delta law): this set equals the registry's full shipped value-key
6
+ // set — every transform, opacity, color, layout-prop, and discrete key. The list stays literal — the
7
+ // registry cross-check test (mappedKeys.test.ts) is what binds it, and node-side parity tooling
8
+ // must not drag core's registry module into every consumer of this data export.
9
+ // The three mapped classes as LITERAL const arrays (r15 d32a7c1e94b6): the public
10
+ // MotionViewStyle type derives its bindable-key unions from the numeric-style and color
11
+ // classes (plus the U5-W3 TRANSFORM_STRING_KEY_NAME literal below for `transform`), so the
12
+ // binding surface the TYPES admit is the surface the runtime accepts by
13
+ // construction.
14
+ export const TRANSFORM_KEY_NAMES = [
15
+ // Transform class (composeTransform.ts owns the canonical composition order; the order data
16
+ // itself lives in transformOrder.ts, RN-import-free).
17
+ 'x',
18
+ 'y',
19
+ 'scale',
20
+ 'scaleX',
21
+ 'scaleY',
22
+ 'rotate',
23
+ 'rotateX',
24
+ 'rotateY',
25
+ 'skewX',
26
+ 'skewY',
27
+ 'perspective',
28
+ ] as const
29
+
30
+ export const NUMERIC_STYLE_KEY_NAMES = [
31
+ // Opacity.
32
+ 'opacity',
33
+ // Layout-prop class: numeric style commits on the generic lane.
34
+ 'width',
35
+ 'height',
36
+ 'top',
37
+ 'right',
38
+ 'bottom',
39
+ 'left',
40
+ 'borderRadius',
41
+ 'borderTopLeftRadius',
42
+ 'borderTopRightRadius',
43
+ 'borderBottomLeftRadius',
44
+ 'borderBottomRightRadius',
45
+ 'borderWidth',
46
+ 'borderTopWidth',
47
+ 'borderRightWidth',
48
+ 'borderBottomWidth',
49
+ 'borderLeftWidth',
50
+ 'margin',
51
+ 'marginTop',
52
+ 'marginRight',
53
+ 'marginBottom',
54
+ 'marginLeft',
55
+ 'padding',
56
+ 'paddingTop',
57
+ 'paddingRight',
58
+ 'paddingBottom',
59
+ 'paddingLeft',
60
+ ] as const
61
+
62
+ export const COLOR_KEY_NAMES = [
63
+ // Color class: the [0,100] progress lane with projected rgba endpoints.
64
+ 'backgroundColor',
65
+ 'color',
66
+ 'borderColor',
67
+ ] as const
68
+
69
+ export const DISCRETE_KEY_NAMES = [
70
+ // Discrete class (T23 B): the color-lane pattern with a projected keyword (a step or a
71
+ // constant, per REQ-VALUETYPE-015's two paths). `display`
72
+ // commits the RN style field; `visibility` commits as the ratified composed override
73
+ // (charter Decision 15: hidden = effective opacity 0 + pointerEvents 'none').
74
+ 'display',
75
+ 'visibility',
76
+ ] as const
77
+
78
+ /** The numeric style keys a public MotionValue<number> may BIND to (mapped, non-rgba). */
79
+ export type BindableStyleKeyName = (typeof NUMERIC_STYLE_KEY_NAMES)[number]
80
+
81
+ /**
82
+ * The rgba-class keys a public MotionValue<string> may BIND to (U5, REQ-API-026). Derived from
83
+ * the literal class for the same reason `BindableStyleKeyName` is: a hand-written union is a
84
+ * second authority that drifts silently.
85
+ */
86
+ export type BindableColorKeyName = (typeof COLOR_KEY_NAMES)[number]
87
+
88
+ // U5 W3 (REQ-API-026, packet contract 2): the `transform` style key binds a
89
+ // MotionValue<string> parsed per set() into the driver-mapped transform components. `transform`
90
+ // itself is NOT a driver-mapped value key — the parsed components are — so it stays OUT of the
91
+ // mappedKeys set below; the literal const is the single authority the public MotionViewStyle
92
+ // union and the runtime split gate both derive from.
93
+ export const TRANSFORM_STRING_KEY_NAME = 'transform' as const
94
+
95
+ /** The style key a public MotionValue<string> of transform functions may BIND to (U5 W3). */
96
+ export type BindableTransformStringKeyName = typeof TRANSFORM_STRING_KEY_NAME
97
+
98
+ /**
99
+ * The Motion transform shorthands as a type — the public `MotionViewStyle` both SUBTRACTS this set
100
+ * from its RN-derived members and re-declares it. It derives from the array above for the same
101
+ * reason `BindableStyleKeyName` does: a hand-written union is a second authority that drifts
102
+ * silently, and this one had already been transcribed name-for-name (fix-up review).
103
+ */
104
+ export type TransformShorthandName = (typeof TRANSFORM_KEY_NAMES)[number]
105
+
106
+ const mappedKeys = new Set<string>([
107
+ ...TRANSFORM_KEY_NAMES,
108
+ ...NUMERIC_STYLE_KEY_NAMES,
109
+ ...COLOR_KEY_NAMES,
110
+ ...DISCRETE_KEY_NAMES,
111
+ ])
112
+
113
+ // The full name list as immutable data (a frozen array, not a Set — Set.prototype.add survives
114
+ // Object.freeze): the REQ-DRIVER-027 zero-delta test needs BOTH directions enumerable, so a
115
+ // mapped typo or an extra unknown key fails set equality instead of hiding behind a
116
+ // membership-only probe (review r2 major e5226f7bf313).
117
+ export const MAPPED_KEY_NAMES: readonly string[] = Object.freeze([...mappedKeys])
118
+
119
+ export const MAPPED_KEYS = Object.freeze({
120
+ size: mappedKeys.size,
121
+ has(key: string): boolean {
122
+ return mappedKeys.has(key)
123
+ },
124
+ })
125
+
126
+ // The SHIPPED Motion prop surface as literal data (R7 M4): the classifier derives
127
+ // "shipped => no blocker" from THIS export — the value-channel precedent (derived from
128
+ // the shipped surface, never assumed). The runtime owner is shippedSurface.ts's
129
+ // VIEW_MOTION_PROP_NAMES; the cross-check test (mappedKeys.test.ts) binds the literal to
130
+ // it, keeping node-side parity tooling RN-import-free.
131
+ export const SHIPPED_MOTION_PROP_KEYS = [
132
+ 'initial',
133
+ 'animate',
134
+ 'exit',
135
+ 'transition',
136
+ 'style',
137
+ 'layout',
138
+ 'layoutId',
139
+ // R11 M2 (REQ-LAYOUT-022 / REQ-API-038): scroll-offset measurement root.
140
+ 'layoutScroll',
141
+ 'onAnimationComplete',
142
+ 'onLayoutAnimationComplete',
143
+ // U7e (REQ-LAYOUT-001 lineage): the layout-flight start callback.
144
+ 'onLayoutAnimationStart',
145
+ 'drag',
146
+ 'dragConstraints',
147
+ 'dragElastic',
148
+ 'dragSnapPoints',
149
+ 'dragPropagation',
150
+ 'dragControls',
151
+ 'dragListener',
152
+ // R15 D4 (REQ-API-044): direction lock.
153
+ 'dragDirectionLock',
154
+ 'onDirectionLock',
155
+ // R15 D5 (REQ-API-045 / REQ-API-046): release physics + external drag values.
156
+ 'dragMomentum',
157
+ 'dragTransition',
158
+ 'dragSnapToOrigin',
159
+ '_dragX',
160
+ '_dragY',
161
+ 'onPanSessionStart',
162
+ 'onPanStart',
163
+ 'onPan',
164
+ 'onPanEnd',
165
+ 'onMeasureDragConstraints',
166
+ 'onDragStart',
167
+ 'onDrag',
168
+ 'onDragEnd',
169
+ 'onTap',
170
+ 'onTapStart',
171
+ 'onTapCancel',
172
+ // U7d (REQ-API-030 global form): the root-observing press scope flag.
173
+ 'globalTapTarget',
174
+ 'whileTap',
175
+ 'whileDrag',
176
+ 'variants',
177
+ 'inherit',
178
+ // T17 (specs/T17-TRANSFORM-TEMPLATE-BUILD-PACKET.md): branded-worklet transform template.
179
+ 'transformTemplate',
180
+ // T23 C (specs/T23-MIX-DISCRETE-BUILD-PACKET.md): per-frame latest-values callback.
181
+ 'onUpdate',
182
+ // T24 B1: the dynamic-variants custom payload.
183
+ 'custom',
184
+ ] as const