@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,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
|
+
}
|