@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,162 @@
1
+ // REQ-API-037 — useScroll on the native entry: scroll-fed motion values into the R4 value
2
+ // channel. Binds a consumer-supplied animated scrollable ref; offsets/progress update without
3
+ // React re-renders. Scroll events are handled on the UI thread (reanimated useEvent worklet)
4
+ // and cross into the value channel via scheduleOnRN — never a JS-thread per-frame poll.
5
+ // Web is a thin pin re-export (native-motion-web).
6
+
7
+ import { useEffect, useLayoutEffect, useRef, useState } from 'react'
8
+ import type { MotionValue } from '@unrulysystems/native-motion-core'
9
+ import { useEvent } from 'react-native-reanimated'
10
+ import { scheduleOnRN } from 'react-native-worklets'
11
+ import { ambientNativeSeverity, consoleReporter } from './severity'
12
+ import {
13
+ applyScrollOffsets,
14
+ gateUseScrollOptions,
15
+ isBindableScrollContainer,
16
+ refuseUnbindableScrollContainer,
17
+ resolveNativeScrollSource,
18
+ settleNativeScrollBinding,
19
+ type AnimatedScrollContainer,
20
+ type ScrollMotionValues,
21
+ type UseScrollOptions,
22
+ } from './scrollValues'
23
+ import { useInternalMotionValue } from './valueHooks'
24
+
25
+ export type { ScrollMotionValues, UseScrollOptions } from './scrollValues'
26
+
27
+ interface NativeScrollEvent {
28
+ readonly contentOffset: { readonly x: number; readonly y: number }
29
+ readonly contentSize: { readonly width: number; readonly height: number }
30
+ readonly layoutMeasurement: { readonly width: number; readonly height: number }
31
+ }
32
+
33
+ const SCROLL_EVENT_NAMES = [
34
+ 'onScroll',
35
+ 'onScrollBeginDrag',
36
+ 'onScrollEndDrag',
37
+ 'onMomentumScrollBegin',
38
+ 'onMomentumScrollEnd',
39
+ ] as const
40
+
41
+ export function useScroll(options: UseScrollOptions = {}): ScrollMotionValues {
42
+ const severity = ambientNativeSeverity()
43
+ const gate = { severity, report: consoleReporter }
44
+ const gated = gateUseScrollOptions(options as Readonly<Record<string, unknown>>, gate)
45
+
46
+ const scrollX = useInternalMotionValue(0)
47
+ const scrollY = useInternalMotionValue(0)
48
+ const scrollXProgress = useInternalMotionValue(0)
49
+ const scrollYProgress = useInternalMotionValue(0)
50
+
51
+ const valuesRef = useRef<ScrollMotionValues | null>(null)
52
+ if (valuesRef.current === null) {
53
+ valuesRef.current = { scrollX, scrollY, scrollXProgress, scrollYProgress }
54
+ }
55
+ const values = valuesRef.current
56
+
57
+ // Deferred settle (microtask) throws outside React's effect try/catch — rethrow on the next
58
+ // render so createRoot/act and Error Boundaries observe the pin use-scroll-ref refuse
59
+ // (major a41c9e7f2b08). Production path reports without throwing; no re-render needed.
60
+ const [settleError, setSettleError] = useState<Error | null>(null)
61
+ if (settleError !== null) throw settleError
62
+
63
+ // Stable hop target: a per-render function is a new JS identity the UI worklet cannot
64
+ // reliably scheduleOnRN. valuesRef is mount-stable.
65
+ const applyOnJS = useRef(
66
+ (xOffset: number, yOffset: number, xMaxOffset: number, yMaxOffset: number) => {
67
+ if (valuesRef.current === null) return
68
+ applyScrollOffsets(valuesRef.current, { xOffset, yOffset, xMaxOffset, yMaxOffset })
69
+ },
70
+ ).current
71
+
72
+ // UI-thread scroll handler — worklet body; one scheduleOnRN hop per native scroll event.
73
+ const eventHandler = useEvent<NativeScrollEvent>((event) => {
74
+ 'worklet'
75
+ const xOffset = event.contentOffset.x
76
+ const yOffset = event.contentOffset.y
77
+ const xMax = Math.max(0, event.contentSize.width - event.layoutMeasurement.width)
78
+ const yMax = Math.max(0, event.contentSize.height - event.layoutMeasurement.height)
79
+ scheduleOnRN(applyOnJS, xOffset, yOffset, xMax, yMax)
80
+ }, SCROLL_EVENT_NAMES) as unknown as {
81
+ workletEventHandler: {
82
+ registerForEvents: (tag: number) => void
83
+ unregisterFromEvents: (tag: number) => void
84
+ }
85
+ }
86
+
87
+ const containerRef = useRef(gated.container)
88
+ containerRef.current = gated.container
89
+
90
+ // Severity authority for the hydrated path (single throw — avoid layout+effect AggregateError):
91
+ // missing / non-scrollable / unbindable (no observe) fail loud here. Pending waits for settle.
92
+ useLayoutEffect(() => {
93
+ const resolved = resolveNativeScrollSource({ container: containerRef.current }, gate)
94
+ if (resolved.kind === 'scrollable' && !isBindableScrollContainer(containerRef.current)) {
95
+ refuseUnbindableScrollContainer(gate)
96
+ }
97
+ })
98
+
99
+ // Bindable animated refs observe-wait immediately (even while current is null). Plain
100
+ // refs keep the pin's one-microtask sibling-hydrate window, then refuse unhydrated.
101
+ useEffect(() => {
102
+ const container = containerRef.current as AnimatedScrollContainer | null | undefined
103
+ if (container === null || container === undefined) return
104
+
105
+ let cleanup: (() => void) | undefined
106
+ let cancelled = false
107
+
108
+ const runSettle = () => {
109
+ if (cancelled) return
110
+ try {
111
+ cleanup = settleNativeScrollBinding(container, gate, (tag) => {
112
+ eventHandler.workletEventHandler.registerForEvents(tag)
113
+ return () => {
114
+ eventHandler.workletEventHandler.unregisterFromEvents(tag)
115
+ }
116
+ })
117
+ } catch (error) {
118
+ // Surface outside the microtask as a render throw (act-observable).
119
+ setSettleError(error instanceof Error ? error : new Error(String(error)))
120
+ }
121
+ }
122
+
123
+ if (isBindableScrollContainer(container)) {
124
+ runSettle()
125
+ return () => {
126
+ cancelled = true
127
+ cleanup?.()
128
+ }
129
+ }
130
+
131
+ if (container.current === null || container.current === undefined) {
132
+ queueMicrotask(runSettle)
133
+ return () => {
134
+ cancelled = true
135
+ cleanup?.()
136
+ }
137
+ }
138
+ return
139
+ }, [gated.container, eventHandler])
140
+
141
+ return values
142
+ }
143
+
144
+ /** Test seam: drive the four values as a native scroll event would (no React re-render). */
145
+ export function applyScrollOffsetsForTests(
146
+ values: ScrollMotionValues,
147
+ offsets: {
148
+ readonly xOffset: number
149
+ readonly yOffset: number
150
+ readonly xMaxOffset: number
151
+ readonly yMaxOffset: number
152
+ },
153
+ ): void {
154
+ applyScrollOffsets(values, offsets)
155
+ }
156
+
157
+ export type ScrollValueBundle = {
158
+ readonly scrollX: MotionValue<number>
159
+ readonly scrollY: MotionValue<number>
160
+ readonly scrollXProgress: MotionValue<number>
161
+ readonly scrollYProgress: MotionValue<number>
162
+ }
@@ -0,0 +1,29 @@
1
+ // U7i (REQ-API-037(e)) — useViewportScroll, the pin's deprecated zero-argument alias of
2
+ // useScroll(). Pin: framer-motion@12.42.2 value/scroll/use-viewport-scroll.ts — dev warnOnce
3
+ // then `return useScroll()`. Native inherits the container-required refusal (no window
4
+ // viewport analog). Shipping the alias is not a viewport claim.
5
+
6
+ import { useScroll, type ScrollMotionValues } from './useScroll'
7
+
8
+ declare const __DEV__: boolean | undefined
9
+
10
+ const warnedKeys = new Set<string>()
11
+ function warnOnce(condition: boolean, message: string): void {
12
+ if (condition || warnedKeys.has(message)) return
13
+ warnedKeys.add(message)
14
+ console.warn(message)
15
+ }
16
+
17
+ /**
18
+ * @deprecated useViewportScroll is deprecated. Convert to useScroll()
19
+ */
20
+ export function useViewportScroll(): ScrollMotionValues {
21
+ if (typeof __DEV__ === 'undefined' || __DEV__ !== false) {
22
+ warnOnce(false, 'useViewportScroll is deprecated. Convert to useScroll().')
23
+ }
24
+ return useScroll()
25
+ }
26
+
27
+ export function resetUseViewportScrollWarningForTests(): void {
28
+ warnedKeys.clear()
29
+ }