@felipe-software/react-native-gpui 0.0.0-placeholder → 1.0.1
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/.reanimated-prebuilt/react-native-reanimated.mjs +29373 -0
- package/LICENSE +21 -0
- package/README.md +658 -1
- package/cli/bin.ts +379 -0
- package/cli/bundle.ts +32 -0
- package/cli/commands/do.ts +213 -0
- package/cli/commands/flow.ts +283 -0
- package/cli/commands/get.ts +584 -0
- package/cli/commands/shot.ts +312 -0
- package/cli/commands/trace.ts +293 -0
- package/cli/host.ts +714 -0
- package/cli/hotReload.ts +210 -0
- package/cli/run.ts +66 -0
- package/cli/selectors.ts +205 -0
- package/dist/Animated.d.ts +120 -0
- package/dist/ChatList.d.ts +28 -0
- package/dist/Dimensions.d.ts +31 -0
- package/dist/StyleSheet.d.ts +18 -0
- package/dist/Window.d.ts +33 -0
- package/dist/apis.d.ts +165 -0
- package/dist/app-state.d.ts +27 -0
- package/dist/chat-list-types.d.ts +136 -0
- package/dist/colors.d.ts +32 -0
- package/dist/commands.d.ts +230 -0
- package/dist/components.d.ts +419 -0
- package/dist/hotModules.d.ts +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.development.js +14876 -0
- package/dist/index.js +12 -0
- package/dist/keyboard.d.ts +140 -0
- package/dist/native-menu.d.ts +7 -0
- package/dist/platform-driver.d.ts +32 -0
- package/dist/platform-services.d.ts +35 -0
- package/dist/portal.d.ts +43 -0
- package/dist/raf.d.ts +4 -0
- package/dist/reanimated/colors-processor-shim.d.ts +10 -0
- package/dist/reanimated/reanimated-host.d.ts +10 -0
- package/dist/reanimated/seam.d.ts +34 -0
- package/dist/reanimated/ui-entry.d.ts +2 -0
- package/dist/reanimated/worklet-channel.d.ts +10 -0
- package/dist/reanimated/worklet-runtime.d.ts +466 -0
- package/dist/reanimated/worklets.d.ts +199 -0
- package/dist/reconciler.d.ts +102 -0
- package/dist/refresh.d.ts +1 -0
- package/dist/render.d.ts +37 -0
- package/dist/runtime.d.ts +235 -0
- package/dist/surfaces.d.ts +51 -0
- package/dist/types.d.ts +276 -0
- package/dist/ui-runtime.js +42276 -0
- package/dist/window-context.d.ts +7 -0
- package/dist/window-host.d.ts +41 -0
- package/dist/wire-delta.d.ts +20 -0
- package/package.json +177 -1
- package/scripts/bundle-app.mjs +88 -0
- package/scripts/conformance-utils.mjs +293 -0
- package/scripts/hot-update-alias-plugin.mjs +88 -0
- package/scripts/native-package-exports-plugin.mjs +235 -0
- package/scripts/pixel.mjs +152 -0
- package/scripts/png.mjs +198 -0
- package/scripts/react-refresh-swc.mjs +35 -0
- package/scripts/reanimated-bun-plugin.mjs +188 -0
- package/scripts/service-binary.d.mts +27 -0
- package/scripts/service-binary.mjs +100 -0
- package/src/reanimated/colors-processor-shim.ts +119 -0
- package/src/reanimated/reanimated-host.ts +69 -0
- package/src/reanimated/rngpui-reanimated-prebuilt.d.ts +7 -0
- package/src/reanimated/seam.ts +566 -0
- package/src/reanimated/ui-entry.ts +80 -0
- package/src/reanimated/worklet-channel.ts +64 -0
- package/src/reanimated/worklet-runtime.ts +2569 -0
- package/src/reanimated/worklets.ts +1334 -0
- package/src/refresh.ts +33 -0
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
// react-native-reanimated — gpui's NATIVE-SEAM-ONLY override.
|
|
2
|
+
//
|
|
3
|
+
// react-native-reanimated is mostly pure JS: useSharedValue, useAnimatedStyle,
|
|
4
|
+
// useDerivedValue, withTiming/withSpring, createAnimatedComponent, the animation
|
|
5
|
+
// drivers — all of it resolves from node_modules unchanged. We replace ONLY the thin
|
|
6
|
+
// native seam: the turbomodule (`specs/NativeReanimatedModule`) that installs
|
|
7
|
+
// `__reanimatedModuleProxy` + `global._*` functions, and the
|
|
8
|
+
// `platformFunctions/{scrollTo,measure,setNativeProps,dispatchCommand,setGestureState}`
|
|
9
|
+
// (the redirect aliases in the bundler point upstream's imports here).
|
|
10
|
+
//
|
|
11
|
+
// The single fast path is `engineUpdateProps`: upstream calls `global._updateProps(ops)`
|
|
12
|
+
// every animation frame; we coalesce all ops within one rAF tick into ONE host crossing
|
|
13
|
+
// (`__rngpui_setNodeStyle`), which lands in Rust's animated-style overlay and re-renders
|
|
14
|
+
// WITHOUT a React re-commit. That is off-thread-style animation: the spring driver and
|
|
15
|
+
// the useAnimatedStyle mapper run in the UI JavaScriptCore runtime, and only the resulting
|
|
16
|
+
// style deltas cross to native.
|
|
17
|
+
//
|
|
18
|
+
// CRITICAL ordering: __RUNTIME_KIND=2 and RN$Bridgeless=true MUST be installed before
|
|
19
|
+
// reanimated first evaluates, or runtimeKind.ts defaults to ReactNative (1), defineAnimation
|
|
20
|
+
// returns the bare animation FUNCTION instead of calling it, and `withSpring(...)` leaks a
|
|
21
|
+
// function into a style value → "Invalid value function(){…} for setWidth". installReanimated
|
|
22
|
+
// NativeSeam() runs at module load (this file is evaluated by the bundler's seam redirect
|
|
23
|
+
// before upstream's core.ts imports resolve).
|
|
24
|
+
|
|
25
|
+
import { registerWorkletBuiltin } from './worklet-runtime'
|
|
26
|
+
|
|
27
|
+
interface MeasuredDimensions {
|
|
28
|
+
x: number
|
|
29
|
+
y: number
|
|
30
|
+
width: number
|
|
31
|
+
height: number
|
|
32
|
+
pageX: number
|
|
33
|
+
pageY: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface StyleProps {
|
|
37
|
+
[key: string]: unknown
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface SerializableLike {
|
|
41
|
+
__init?: () => unknown
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type WorkletFn = (...args: unknown[]) => unknown
|
|
45
|
+
|
|
46
|
+
// =============================================================================
|
|
47
|
+
// viewTag → globalId registry. `createAnimatedComponent`'s ref resolves to the
|
|
48
|
+
// reconciler's `Instance` (getPublicInstance returns it), whose `.id` IS the
|
|
49
|
+
// `globalId` Rust keys on. Animated components register their host id here on mount
|
|
50
|
+
// (via the patched reanimated component below) so `_updateProps` can map an operation's
|
|
51
|
+
// shadowNodeWrapper/viewTag back to the globalId the overlay writes.
|
|
52
|
+
// =============================================================================
|
|
53
|
+
|
|
54
|
+
const VIEW_TAG_TO_GLOBAL_ID = new Map<number, number>()
|
|
55
|
+
|
|
56
|
+
/** Resolve an upstream `op.shadowNodeWrapper` / viewTag down to a numeric globalId. */
|
|
57
|
+
function resolveGlobalId(target: unknown): number | null {
|
|
58
|
+
if (typeof target === 'function') {
|
|
59
|
+
const resolved = (target as () => unknown)()
|
|
60
|
+
if (resolved !== target) return resolveGlobalId(resolved)
|
|
61
|
+
}
|
|
62
|
+
if (typeof target === 'number') {
|
|
63
|
+
return VIEW_TAG_TO_GLOBAL_ID.get(target) ?? target
|
|
64
|
+
}
|
|
65
|
+
if (target && typeof target === 'object') {
|
|
66
|
+
const candidate = target as {
|
|
67
|
+
__rngpuiGlobalId?: unknown
|
|
68
|
+
id?: unknown
|
|
69
|
+
__nativeTag?: unknown
|
|
70
|
+
_nativeTag?: unknown
|
|
71
|
+
__viewTag?: unknown
|
|
72
|
+
current?: unknown
|
|
73
|
+
getTag?: () => unknown
|
|
74
|
+
}
|
|
75
|
+
if (typeof candidate.__rngpuiGlobalId === 'number') return candidate.__rngpuiGlobalId
|
|
76
|
+
if (typeof candidate.id === 'number') return VIEW_TAG_TO_GLOBAL_ID.get(candidate.id) ?? candidate.id
|
|
77
|
+
const tag = candidate.__viewTag ?? candidate.__nativeTag ?? candidate._nativeTag
|
|
78
|
+
if (typeof tag === 'number') return VIEW_TAG_TO_GLOBAL_ID.get(tag) ?? tag
|
|
79
|
+
if (typeof candidate.getTag === 'function') {
|
|
80
|
+
const got = candidate.getTag()
|
|
81
|
+
if (typeof got === 'number') return VIEW_TAG_TO_GLOBAL_ID.get(got) ?? got
|
|
82
|
+
}
|
|
83
|
+
if (candidate.current != null && candidate.current !== target) {
|
|
84
|
+
return resolveGlobalId(candidate.current)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Register a viewTag → globalId mapping for an animated component's lifetime. */
|
|
91
|
+
export function registerAnimatedViewTag(viewTag: number, globalId: number): void {
|
|
92
|
+
VIEW_TAG_TO_GLOBAL_ID.set(viewTag, globalId)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function unregisterAnimatedViewTag(viewTag: number): void {
|
|
96
|
+
VIEW_TAG_TO_GLOBAL_ID.delete(viewTag)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
declare const __rngpui_setNodeStyle: ((json: string) => void) | undefined
|
|
100
|
+
declare const __rngpui_scrollTo: ((json: string) => void) | undefined
|
|
101
|
+
|
|
102
|
+
// =============================================================================
|
|
103
|
+
// rAF coalescing. Every `_updateProps` op within one frame accumulates here; we
|
|
104
|
+
// flush ONCE per rAF tick, in ONE `__rngpui_setNodeStyle` host call (one cx.notify).
|
|
105
|
+
// =============================================================================
|
|
106
|
+
|
|
107
|
+
const PENDING_OPS = new Map<number, Record<string, unknown>>()
|
|
108
|
+
let flushScheduled = false
|
|
109
|
+
|
|
110
|
+
function scheduleFlush(): void {
|
|
111
|
+
if (flushScheduled) return
|
|
112
|
+
flushScheduled = true
|
|
113
|
+
const raf = (globalThis as { requestAnimationFrame?: typeof requestAnimationFrame }).requestAnimationFrame
|
|
114
|
+
if (typeof raf === 'function') {
|
|
115
|
+
raf.call(globalThis, flushUpdatedProps)
|
|
116
|
+
} else {
|
|
117
|
+
setTimeout(flushUpdatedProps, 16)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function flushUpdatedProps(): void {
|
|
122
|
+
flushScheduled = false
|
|
123
|
+
if (PENDING_OPS.size === 0) return
|
|
124
|
+
const ops: Array<[number, Record<string, unknown>]> = []
|
|
125
|
+
for (const [id, style] of PENDING_OPS) ops.push([id, style])
|
|
126
|
+
PENDING_OPS.clear()
|
|
127
|
+
if (typeof __rngpui_setNodeStyle === 'function') {
|
|
128
|
+
__rngpui_setNodeStyle(JSON.stringify(ops))
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// =============================================================================
|
|
133
|
+
// __reanimatedModuleProxy + `global._*`. Upstream's NativeReanimatedModule installs
|
|
134
|
+
// these on real iOS; we wire the equivalents at module load so upstream's core.ts /
|
|
135
|
+
// mappers.ts see fully-wired globals by the time they import this seam.
|
|
136
|
+
// =============================================================================
|
|
137
|
+
|
|
138
|
+
let proxyInstalled = false
|
|
139
|
+
|
|
140
|
+
function installReanimatedNativeSeam(): void {
|
|
141
|
+
if (proxyInstalled) return
|
|
142
|
+
proxyInstalled = true
|
|
143
|
+
|
|
144
|
+
const g = globalThis as Record<string, unknown> & {
|
|
145
|
+
global?: typeof globalThis
|
|
146
|
+
_tagToJSPropNamesMapping?: Record<number, Record<string, boolean>>
|
|
147
|
+
_getAnimationTimestamp?: () => number
|
|
148
|
+
_updateProps?: (operations: Array<{ shadowNodeWrapper: unknown; updates: StyleProps }>) => void
|
|
149
|
+
_updatePropsFabric?: (operations: Array<{ shadowNodeWrapper: unknown; updates: StyleProps }>) => void
|
|
150
|
+
_updatePropsPaper?: (operations: Array<{ tag: unknown; name?: unknown; updates: StyleProps }>) => void
|
|
151
|
+
_maybeFlushUIUpdatesQueue?: () => void
|
|
152
|
+
_scrollTo?: (...args: unknown[]) => void
|
|
153
|
+
_scrollToPaper?: (...args: unknown[]) => void
|
|
154
|
+
_measure?: (node: unknown) => MeasuredDimensions | null
|
|
155
|
+
_measureFabric?: (node: unknown) => MeasuredDimensions | null
|
|
156
|
+
_measurePaper?: (node: unknown) => MeasuredDimensions | null
|
|
157
|
+
_dispatchCommand?: (...args: unknown[]) => void
|
|
158
|
+
_dispatchCommandFabric?: (...args: unknown[]) => void
|
|
159
|
+
_dispatchCommandPaper?: (...args: unknown[]) => void
|
|
160
|
+
_scheduleHostFunctionOnJS?: (fn: WorkletFn, args?: unknown[]) => void
|
|
161
|
+
_scheduleRemoteFunctionOnJS?: (fn: WorkletFn, args?: unknown[]) => void
|
|
162
|
+
_setGestureState?: (handlerTag: number, newState: number) => void
|
|
163
|
+
_notifyAboutProgress?: (tag: number, value: unknown) => void
|
|
164
|
+
_notifyAboutEnd?: (tag: number, removeView: boolean) => void
|
|
165
|
+
__reanimatedModuleProxy?: ReanimatedModuleProxy
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// worklet bodies reference Node-style `global`; alias to globalThis so closure
|
|
169
|
+
// bindings resolve in either context.
|
|
170
|
+
if (typeof (globalThis as { global?: unknown }).global === 'undefined') {
|
|
171
|
+
;(globalThis as { global?: unknown }).global = globalThis
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// version-shim getters/setters: read undefined (passes assertSingleReanimatedInstance)
|
|
175
|
+
// and accept any write (records the active guest version, mirrored to _CPP so
|
|
176
|
+
// checkCppVersion sees a matching native side).
|
|
177
|
+
let activeReanimatedJsVersion: string | undefined
|
|
178
|
+
Object.defineProperty(globalThis, '_REANIMATED_VERSION_JS', {
|
|
179
|
+
get: () => undefined,
|
|
180
|
+
set: (version) => {
|
|
181
|
+
activeReanimatedJsVersion = typeof version === 'string' ? version : activeReanimatedJsVersion
|
|
182
|
+
},
|
|
183
|
+
configurable: true,
|
|
184
|
+
enumerable: false,
|
|
185
|
+
})
|
|
186
|
+
Object.defineProperty(globalThis, '_REANIMATED_VERSION_CPP', {
|
|
187
|
+
get: () => activeReanimatedJsVersion,
|
|
188
|
+
set: (version) => {
|
|
189
|
+
activeReanimatedJsVersion = typeof version === 'string' ? version : activeReanimatedJsVersion
|
|
190
|
+
},
|
|
191
|
+
configurable: true,
|
|
192
|
+
enumerable: false,
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
// Reanimated 4 requires the New Architecture; claim Bridgeless/Fabric.
|
|
196
|
+
;(globalThis as { RN$Bridgeless?: boolean }).RN$Bridgeless ??= true
|
|
197
|
+
|
|
198
|
+
// __RUNTIME_KIND — MUST be UI (2) before reanimated evaluates (see header).
|
|
199
|
+
if ((globalThis as { __RUNTIME_KIND?: number }).__RUNTIME_KIND !== 2) {
|
|
200
|
+
;(globalThis as { __RUNTIME_KIND?: number }).__RUNTIME_KIND = 2
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
g._tagToJSPropNamesMapping ??= {}
|
|
204
|
+
g._getAnimationTimestamp ??= () => performance.now()
|
|
205
|
+
|
|
206
|
+
// the fast path + the rest of the seam.
|
|
207
|
+
g._updateProps ??= (operations) => engineUpdateProps(operations)
|
|
208
|
+
g._updatePropsFabric ??= (operations) => engineUpdateProps(operations)
|
|
209
|
+
g._updatePropsPaper ??= (operations) =>
|
|
210
|
+
engineUpdateProps(operations.map((op) => ({ shadowNodeWrapper: op.tag, updates: op.updates })))
|
|
211
|
+
// reanimated's native renderer queues prop writes, then flushes that queue after
|
|
212
|
+
// each mapper pass. engineUpdateProps sends the write immediately, so there is no
|
|
213
|
+
// second queue to flush.
|
|
214
|
+
g._maybeFlushUIUpdatesQueue ??= () => {}
|
|
215
|
+
|
|
216
|
+
// measure / scroll / dispatchCommand — gpui's reconciler owns layout, not the
|
|
217
|
+
// worklet UI runtime; resolve against the live node graph through the host id.
|
|
218
|
+
g._measure ??= (node) => engineMeasure(node)
|
|
219
|
+
g._measureFabric ??= (node) => engineMeasure(node)
|
|
220
|
+
g._measurePaper ??= (node) => engineMeasure(node)
|
|
221
|
+
g._scrollTo ??= (node, x, y) => engineScrollTo(node, x, y)
|
|
222
|
+
g._scrollToPaper ??= (node, x, y) => engineScrollTo(node, x, y)
|
|
223
|
+
g._dispatchCommand ??= () => {}
|
|
224
|
+
g._dispatchCommandFabric ??= () => {}
|
|
225
|
+
g._dispatchCommandPaper ??= () => {}
|
|
226
|
+
g._scheduleHostFunctionOnJS ??= (fn, args = []) => {
|
|
227
|
+
queueMicrotask(() => {
|
|
228
|
+
if (typeof fn === 'function') fn(...args)
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
g._scheduleRemoteFunctionOnJS ??= (fn, args = []) => {
|
|
232
|
+
queueMicrotask(() => {
|
|
233
|
+
if (typeof fn === 'function') fn(...args)
|
|
234
|
+
})
|
|
235
|
+
}
|
|
236
|
+
g._setGestureState ??= () => {}
|
|
237
|
+
g._notifyAboutProgress ??= () => {}
|
|
238
|
+
g._notifyAboutEnd ??= () => {}
|
|
239
|
+
|
|
240
|
+
ensureFrameCallbackRegistry()
|
|
241
|
+
|
|
242
|
+
g.__reanimatedModuleProxy ??= createReanimatedModuleProxy()
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function engineMeasure(_node: unknown): MeasuredDimensions | null {
|
|
246
|
+
// gpui resolves measure through the reconciler's measureInWindow path, not the
|
|
247
|
+
// worklet UI runtime — Tamagui's animation driver doesn't depend on this. Return
|
|
248
|
+
// null (upstream callers tolerate it) rather than guess a rect.
|
|
249
|
+
return null
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function engineScrollTo(animatedRef: unknown, x: unknown, y: unknown): void {
|
|
253
|
+
const globalId = resolveGlobalId(animatedRef)
|
|
254
|
+
if (SEAM_DEBUG) {
|
|
255
|
+
console.error(
|
|
256
|
+
`[seam] scrollTo target=${String(globalId)} x=${String(x)} y=${String(y)} host=${typeof __rngpui_scrollTo}`,
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
if (
|
|
260
|
+
globalId == null ||
|
|
261
|
+
typeof x !== 'number' ||
|
|
262
|
+
!Number.isFinite(x) ||
|
|
263
|
+
typeof y !== 'number' ||
|
|
264
|
+
!Number.isFinite(y) ||
|
|
265
|
+
typeof __rngpui_scrollTo !== 'function'
|
|
266
|
+
) {
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
__rngpui_scrollTo(JSON.stringify([globalId, x, y]))
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// =============================================================================
|
|
273
|
+
// IReanimatedModule proxy — upstream delegates ~25 methods to it. Most are no-ops on
|
|
274
|
+
// gpui; the live ones are registerEventHandler / subscribeForKeyboardEvents (no-op
|
|
275
|
+
// here for now — Tamagui springs don't use them). A permissive Proxy returns a no-op
|
|
276
|
+
// for any unknown method so older bundles that call legacy methods don't crash.
|
|
277
|
+
// =============================================================================
|
|
278
|
+
|
|
279
|
+
interface ReanimatedModuleProxy {
|
|
280
|
+
[key: string]: (...args: unknown[]) => unknown
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function createReanimatedModuleProxy(): ReanimatedModuleProxy {
|
|
284
|
+
const base: Record<string, (...args: unknown[]) => unknown> = {
|
|
285
|
+
configureLayoutAnimationBatch: () => {},
|
|
286
|
+
setShouldAnimateExitingForTag: () => {},
|
|
287
|
+
getStaticFeatureFlag: () => false,
|
|
288
|
+
setDynamicFeatureFlag: () => {},
|
|
289
|
+
registerSensor: () => -1,
|
|
290
|
+
unregisterSensor: () => {},
|
|
291
|
+
registerEventHandler: () => -1,
|
|
292
|
+
unregisterEventHandler: () => {},
|
|
293
|
+
subscribeForKeyboardEvents: () => -1,
|
|
294
|
+
unsubscribeFromKeyboardEvents: () => {},
|
|
295
|
+
getViewProp: (..._args: unknown[]) => {
|
|
296
|
+
const cb = _args[2]
|
|
297
|
+
if (typeof cb === 'function') (cb as (r: unknown) => void)(null)
|
|
298
|
+
return null
|
|
299
|
+
},
|
|
300
|
+
setViewStyle: () => {},
|
|
301
|
+
markNodeAsRemovable: () => {},
|
|
302
|
+
unmarkNodeAsRemovable: () => {},
|
|
303
|
+
registerCSSKeyframes: () => {},
|
|
304
|
+
unregisterCSSKeyframes: () => {},
|
|
305
|
+
applyCSSAnimations: () => {},
|
|
306
|
+
unregisterCSSAnimations: () => {},
|
|
307
|
+
runCSSTransition: () => {},
|
|
308
|
+
unregisterCSSTransition: () => {},
|
|
309
|
+
getSettledUpdates: () => [],
|
|
310
|
+
// legacy v3 methods some bundles call directly on the proxy
|
|
311
|
+
scheduleOnUI: (worklet: unknown) => {
|
|
312
|
+
const fn =
|
|
313
|
+
typeof worklet === 'function'
|
|
314
|
+
? (worklet as WorkletFn)
|
|
315
|
+
: (worklet as { __init?: () => unknown })?.__init?.()
|
|
316
|
+
if (typeof fn === 'function') {
|
|
317
|
+
queueMicrotask(() => {
|
|
318
|
+
try {
|
|
319
|
+
;(fn as WorkletFn).apply(fn, [])
|
|
320
|
+
} catch (err) {
|
|
321
|
+
console.error('[rngpui reanimated] scheduleOnUI threw:', err)
|
|
322
|
+
}
|
|
323
|
+
})
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
makeShareableClone: (value: unknown) => value,
|
|
327
|
+
makeShareableCloneOnUI: (value: unknown) => value,
|
|
328
|
+
installCoreFunctions: () => {},
|
|
329
|
+
installTurboModule: () => {},
|
|
330
|
+
enableLayoutAnimations: () => {},
|
|
331
|
+
}
|
|
332
|
+
return new Proxy(base as ReanimatedModuleProxy, {
|
|
333
|
+
get(target, prop) {
|
|
334
|
+
const value = (target as Record<PropertyKey, unknown>)[prop as PropertyKey]
|
|
335
|
+
if (value !== undefined) return value
|
|
336
|
+
if (typeof prop === 'string') return () => undefined
|
|
337
|
+
return undefined
|
|
338
|
+
},
|
|
339
|
+
})
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// =============================================================================
|
|
343
|
+
// frame callback registry — upstream useFrameCallback drives off this. A minimal,
|
|
344
|
+
// rAF-backed registry (mirrors reanimated's own UI-runtime shape) so useFrameCallback
|
|
345
|
+
// works; the loop stops when no callbacks are active (idle → 0fps).
|
|
346
|
+
// =============================================================================
|
|
347
|
+
|
|
348
|
+
interface FrameInfo {
|
|
349
|
+
timestamp: number
|
|
350
|
+
timeSincePreviousFrame: number | null
|
|
351
|
+
timeSinceFirstFrame: number
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function requestFrame(callback: (timestamp: number) => void): void {
|
|
355
|
+
const raf = (globalThis as { requestAnimationFrame?: typeof requestAnimationFrame }).requestAnimationFrame
|
|
356
|
+
if (typeof raf === 'function') {
|
|
357
|
+
raf.call(globalThis, callback)
|
|
358
|
+
return
|
|
359
|
+
}
|
|
360
|
+
setTimeout(() => callback(performance.now()), 16)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function ensureFrameCallbackRegistry(): void {
|
|
364
|
+
const g = globalThis as typeof globalThis & {
|
|
365
|
+
global?: typeof globalThis
|
|
366
|
+
_frameCallbackRegistry?: unknown
|
|
367
|
+
}
|
|
368
|
+
if (g._frameCallbackRegistry) return
|
|
369
|
+
|
|
370
|
+
const registry = {
|
|
371
|
+
frameCallbackRegistry: new Map<number, { callback: (f: FrameInfo) => void; startTime: number | null }>(),
|
|
372
|
+
activeFrameCallbacks: new Set<number>(),
|
|
373
|
+
previousFrameTimestamp: null as number | null,
|
|
374
|
+
nextCallId: 0,
|
|
375
|
+
runCallbacks(callId: number) {
|
|
376
|
+
const loop = (timestamp: number) => {
|
|
377
|
+
if (callId !== this.nextCallId) return
|
|
378
|
+
if (this.previousFrameTimestamp === null) this.previousFrameTimestamp = timestamp
|
|
379
|
+
const delta = timestamp - this.previousFrameTimestamp
|
|
380
|
+
this.activeFrameCallbacks.forEach((callbackId) => {
|
|
381
|
+
const details = this.frameCallbackRegistry.get(callbackId)
|
|
382
|
+
if (!details) return
|
|
383
|
+
if (details.startTime === null) {
|
|
384
|
+
details.startTime = timestamp
|
|
385
|
+
details.callback({ timestamp, timeSincePreviousFrame: null, timeSinceFirstFrame: 0 })
|
|
386
|
+
} else {
|
|
387
|
+
details.callback({
|
|
388
|
+
timestamp,
|
|
389
|
+
timeSincePreviousFrame: delta,
|
|
390
|
+
timeSinceFirstFrame: timestamp - details.startTime,
|
|
391
|
+
})
|
|
392
|
+
}
|
|
393
|
+
})
|
|
394
|
+
if (this.activeFrameCallbacks.size > 0) {
|
|
395
|
+
this.previousFrameTimestamp = timestamp
|
|
396
|
+
requestFrame(loop)
|
|
397
|
+
} else {
|
|
398
|
+
this.previousFrameTimestamp = null
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (this.activeFrameCallbacks.size === 1 && callId === this.nextCallId) requestFrame(loop)
|
|
402
|
+
},
|
|
403
|
+
registerFrameCallback(callback: (f: FrameInfo) => void, callbackId: number) {
|
|
404
|
+
this.frameCallbackRegistry.set(callbackId, { callback, startTime: null })
|
|
405
|
+
},
|
|
406
|
+
unregisterFrameCallback(callbackId: number) {
|
|
407
|
+
this.manageStateFrameCallback(callbackId, false)
|
|
408
|
+
this.frameCallbackRegistry.delete(callbackId)
|
|
409
|
+
},
|
|
410
|
+
manageStateFrameCallback(callbackId: number, state: boolean) {
|
|
411
|
+
if (callbackId === -1) return
|
|
412
|
+
if (state) {
|
|
413
|
+
if (!this.activeFrameCallbacks.has(callbackId)) {
|
|
414
|
+
this.activeFrameCallbacks.add(callbackId)
|
|
415
|
+
this.runCallbacks(this.nextCallId)
|
|
416
|
+
}
|
|
417
|
+
return
|
|
418
|
+
}
|
|
419
|
+
const cb = this.frameCallbackRegistry.get(callbackId)
|
|
420
|
+
if (cb) cb.startTime = null
|
|
421
|
+
this.activeFrameCallbacks.delete(callbackId)
|
|
422
|
+
if (this.activeFrameCallbacks.size === 0) this.nextCallId += 1
|
|
423
|
+
},
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
g._frameCallbackRegistry = registry
|
|
427
|
+
if (g.global && g.global !== g) {
|
|
428
|
+
;(g.global as typeof globalThis & { _frameCallbackRegistry?: unknown })._frameCallbackRegistry = registry
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// =============================================================================
|
|
433
|
+
// engineUpdateProps — THE fast path. Upstream's `_updateProps` is RN's "set view
|
|
434
|
+
// props without touching React state". On gpui that's the animated-style overlay:
|
|
435
|
+
// resolve the op's target → globalId, merge its style keys into the pending-ops map,
|
|
436
|
+
// and schedule a single coalesced rAF flush. Normalize the style values the SAME way
|
|
437
|
+
// the reconciler serializes them, so Rust's `from_json` reads them identically.
|
|
438
|
+
// =============================================================================
|
|
439
|
+
|
|
440
|
+
const SEAM_DEBUG =
|
|
441
|
+
typeof process !== 'undefined' && !!(process as { env?: Record<string, string> }).env?.RNGPUI_SEAM_DEBUG
|
|
442
|
+
let updatePropsCalls = 0
|
|
443
|
+
|
|
444
|
+
function engineUpdateProps(operations: Array<{ shadowNodeWrapper: unknown; updates: StyleProps }>): void {
|
|
445
|
+
let any = false
|
|
446
|
+
for (const op of operations) {
|
|
447
|
+
const globalId = resolveGlobalId(op.shadowNodeWrapper)
|
|
448
|
+
if (globalId == null) {
|
|
449
|
+
if (SEAM_DEBUG) {
|
|
450
|
+
console.error(
|
|
451
|
+
`[seam] _updateProps op dropped: unresolved target ${JSON.stringify(op.shadowNodeWrapper)} keys=${Object.keys(op.updates).join(',')}`,
|
|
452
|
+
)
|
|
453
|
+
}
|
|
454
|
+
continue
|
|
455
|
+
}
|
|
456
|
+
const prev = PENDING_OPS.get(globalId) ?? {}
|
|
457
|
+
for (const [key, value] of Object.entries(op.updates)) {
|
|
458
|
+
prev[key] = normalizeUpdatePropValue(key, value)
|
|
459
|
+
if (SEAM_DEBUG && prev[key] === undefined) {
|
|
460
|
+
// an undefined value silently VANISHES at the JSON host crossing — the
|
|
461
|
+
// key never reaches the overlay. always worth a loud line.
|
|
462
|
+
console.error(`[seam] _updateProps key dropped as undefined: ${key} (raw ${typeof value})`)
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
PENDING_OPS.set(globalId, prev)
|
|
466
|
+
any = true
|
|
467
|
+
}
|
|
468
|
+
if (SEAM_DEBUG) {
|
|
469
|
+
updatePropsCalls++
|
|
470
|
+
console.error(`[seam] _updateProps call #${updatePropsCalls} ops=${operations.length} any=${any}`)
|
|
471
|
+
}
|
|
472
|
+
if (any) scheduleFlush()
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// reanimated emits transforms as `transform: [{translateX: n}, …]` and colors as
|
|
476
|
+
// processed ints; pass plain numbers/strings through. Drop the reanimated-internal
|
|
477
|
+
// `_requiresAnimatedComponent` marker. Colors arrive as RN-processed numbers from the
|
|
478
|
+
// worklet color interpolation — convert to a CSS rgba string the Rust color parser reads.
|
|
479
|
+
function normalizeUpdatePropValue(key: string, value: unknown): unknown {
|
|
480
|
+
if (value == null) return value
|
|
481
|
+
if (isColorKey(key) && typeof value === 'number') {
|
|
482
|
+
return processedColorToCss(value)
|
|
483
|
+
}
|
|
484
|
+
return value
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function isColorKey(key: string): boolean {
|
|
488
|
+
return (
|
|
489
|
+
key === 'color' ||
|
|
490
|
+
key === 'backgroundColor' ||
|
|
491
|
+
key === 'borderColor' ||
|
|
492
|
+
key === 'borderTopColor' ||
|
|
493
|
+
key === 'borderRightColor' ||
|
|
494
|
+
key === 'borderBottomColor' ||
|
|
495
|
+
key === 'borderLeftColor' ||
|
|
496
|
+
key === 'tintColor' ||
|
|
497
|
+
key === 'shadowColor'
|
|
498
|
+
)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// RN processes colors to a 32-bit ARGB int on the worklet side (reanimated's color
|
|
502
|
+
// interpolation returns this form). Convert to `rgba(r,g,b,a)` so the Rust css parser
|
|
503
|
+
// reads it.
|
|
504
|
+
function processedColorToCss(argb: number): string {
|
|
505
|
+
const a = ((argb >> 24) & 0xff) / 255
|
|
506
|
+
const r = (argb >> 16) & 0xff
|
|
507
|
+
const g = (argb >> 8) & 0xff
|
|
508
|
+
const b = argb & 0xff
|
|
509
|
+
return `rgba(${r},${g},${b},${a})`
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// =============================================================================
|
|
513
|
+
// install on import — upstream's core.ts/mappers.ts read globals like
|
|
514
|
+
// __reanimatedModuleProxy on load, so install before any upstream import resolves.
|
|
515
|
+
// =============================================================================
|
|
516
|
+
|
|
517
|
+
installReanimatedNativeSeam()
|
|
518
|
+
|
|
519
|
+
// default export — upstream's `specs/NativeReanimatedModule` is
|
|
520
|
+
// `export default TurboModuleRegistry.get('ReanimatedModule')`; the bundler redirect
|
|
521
|
+
// routes that import here.
|
|
522
|
+
export default {
|
|
523
|
+
installTurboModule(): boolean {
|
|
524
|
+
installReanimatedNativeSeam()
|
|
525
|
+
return true
|
|
526
|
+
},
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// platform functions — upstream's `platformFunctions/*` redirect here. These run on
|
|
530
|
+
// the JS thread (single runtime). Mark each as a worklet builtin shape so upstream's
|
|
531
|
+
// areWorkletsEqual doesn't choke.
|
|
532
|
+
function markWorkletBuiltin<T extends (...args: never[]) => unknown>(name: string, fn: T): T {
|
|
533
|
+
Object.defineProperty(fn, '__workletHash', { value: 0, enumerable: false, configurable: true })
|
|
534
|
+
Object.defineProperty(fn, '__initData', {
|
|
535
|
+
value: { code: fn.toString(), location: name },
|
|
536
|
+
enumerable: false,
|
|
537
|
+
configurable: true,
|
|
538
|
+
})
|
|
539
|
+
Object.defineProperty(fn, '__closure', { value: {}, enumerable: false, configurable: true, writable: true })
|
|
540
|
+
registerWorkletBuiltin(name, fn)
|
|
541
|
+
return fn
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function scrollToImpl(animatedRef: unknown, x: number, y: number, _animated: boolean): void {
|
|
545
|
+
engineScrollTo(animatedRef, x, y)
|
|
546
|
+
}
|
|
547
|
+
function measureImpl(animatedRef: unknown): MeasuredDimensions | null {
|
|
548
|
+
return engineMeasure(animatedRef)
|
|
549
|
+
}
|
|
550
|
+
function setNativePropsImpl(animatedRef: unknown, updates: StyleProps): void {
|
|
551
|
+
engineUpdateProps([{ shadowNodeWrapper: animatedRef, updates }])
|
|
552
|
+
}
|
|
553
|
+
function dispatchCommandImpl(): void {}
|
|
554
|
+
function setGestureStateImpl(): void {}
|
|
555
|
+
|
|
556
|
+
export const scrollTo = markWorkletBuiltin('scrollTo', scrollToImpl)
|
|
557
|
+
export const measure = markWorkletBuiltin('measure', measureImpl)
|
|
558
|
+
export const setNativeProps = markWorkletBuiltin('setNativeProps', setNativePropsImpl)
|
|
559
|
+
export const dispatchCommand = markWorkletBuiltin('dispatchCommand', dispatchCommandImpl)
|
|
560
|
+
export const setGestureState = markWorkletBuiltin('setGestureState', setGestureStateImpl)
|
|
561
|
+
|
|
562
|
+
// also expose the proxy install so a caller can re-run it (idempotent).
|
|
563
|
+
export { installReanimatedNativeSeam }
|
|
564
|
+
|
|
565
|
+
// satisfy the unused-binding lint for the optional keyboard handler shape.
|
|
566
|
+
export type { SerializableLike }
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// reanimated worklet/UI runtime entry — bundled standalone as dist/ui-runtime.js
|
|
2
|
+
// and evaluated by the SECOND JavaScriptCore runtime (rust jsc::start_ui, thread
|
|
3
|
+
// "jsc-ui"). See plans/off-thread-reanimated.md.
|
|
4
|
+
//
|
|
5
|
+
// This bundle is app-independent library code. It provides everything a
|
|
6
|
+
// dispatched worklet needs to execute here:
|
|
7
|
+
// - the seam (KIND=2 globals: `_updateProps` → coalesced __rngpui_setNodeStyle,
|
|
8
|
+
// _getAnimationTimestamp, the frame-callback registry) — imported first so the
|
|
9
|
+
// globals exist before upstream reanimated evaluates,
|
|
10
|
+
// - vsync requestAnimationFrame (this runtime arms frame_clock bit 1),
|
|
11
|
+
// - upstream react-native-reanimated core: its valueSetter drives `sv.value =
|
|
12
|
+
// withSpring(...)` animations HERE, frame by frame, off the React thread,
|
|
13
|
+
// - the worklet runtime in 'ui' role (closure materialization, shared-value
|
|
14
|
+
// slots over __rngpui_svSlots, runOnJS bridging back to the React runtime),
|
|
15
|
+
// - the worklet builtin registry, so closures serialized as {kind:'builtin'}
|
|
16
|
+
// (withSpring, withTiming, scrollTo, …) bind to THIS runtime's implementations.
|
|
17
|
+
import './seam'
|
|
18
|
+
import '../raf'
|
|
19
|
+
import {
|
|
20
|
+
Easing,
|
|
21
|
+
withClamp,
|
|
22
|
+
withDecay,
|
|
23
|
+
withDelay,
|
|
24
|
+
withRepeat,
|
|
25
|
+
withSequence,
|
|
26
|
+
withSpring,
|
|
27
|
+
withTiming,
|
|
28
|
+
} from 'react-native-reanimated'
|
|
29
|
+
import { dispatchCommand, measure, scrollTo, setGestureState, setNativeProps } from './seam'
|
|
30
|
+
import { registerWorkletBuiltin } from './worklet-runtime'
|
|
31
|
+
|
|
32
|
+
// worklets.ts (imported by upstream's react-native-worklets redirect) installs the
|
|
33
|
+
// worklet runtime + channel at module load, role-detected from the host fns
|
|
34
|
+
// (__rngpui_jsPost ⇒ 'ui'). By the time this body runs, the runtime is live and
|
|
35
|
+
// __rngpui_peerRecv is wired; all that's left is populating the builtin registry.
|
|
36
|
+
|
|
37
|
+
registerWorkletBuiltin('withTiming', withTiming)
|
|
38
|
+
registerWorkletBuiltin('withSpring', withSpring)
|
|
39
|
+
registerWorkletBuiltin('withDecay', withDecay)
|
|
40
|
+
registerWorkletBuiltin('withDelay', withDelay)
|
|
41
|
+
registerWorkletBuiltin('withRepeat', withRepeat)
|
|
42
|
+
registerWorkletBuiltin('withSequence', withSequence)
|
|
43
|
+
registerWorkletBuiltin('withClamp', withClamp)
|
|
44
|
+
registerWorkletBuiltin('Easing', Easing)
|
|
45
|
+
|
|
46
|
+
// upstream's valueSetter (surfaced by the prebuild under an rngpui name): the
|
|
47
|
+
// shared-value proxies route writes through it so `sv.value = animation` drives
|
|
48
|
+
// the animation HERE — _animation cancel semantics, __frameTimestamp, the
|
|
49
|
+
// requestAnimationFrame step loop, per-frame `_value` writes that fan out to
|
|
50
|
+
// mapper listeners. without it the proxy's setter throws by design.
|
|
51
|
+
import * as upstreamInternals from 'react-native-reanimated'
|
|
52
|
+
;(globalThis as Record<string, unknown>).__rngpuiReanimatedValueSetter = (
|
|
53
|
+
upstreamInternals as unknown as Record<string, unknown>
|
|
54
|
+
).__rngpuiValueSetter
|
|
55
|
+
|
|
56
|
+
// the revive table for stamped animation requests shipped by a React-side
|
|
57
|
+
// `sv.value = withX(...)` write (reanimated-host.ts stamps; worklets.ts
|
|
58
|
+
// HOST_SET_SHARED_VALUE_WORKLET revives by `type` from this global and the
|
|
59
|
+
// animation drives HERE, off the React thread).
|
|
60
|
+
;(globalThis as Record<string, unknown>).__rngpuiReanimatedAnimationFactories = {
|
|
61
|
+
withTiming,
|
|
62
|
+
withSpring,
|
|
63
|
+
withDecay,
|
|
64
|
+
withDelay,
|
|
65
|
+
withRepeat,
|
|
66
|
+
withSequence,
|
|
67
|
+
withClamp,
|
|
68
|
+
}
|
|
69
|
+
registerWorkletBuiltin('scrollTo', scrollTo)
|
|
70
|
+
registerWorkletBuiltin('measure', measure)
|
|
71
|
+
registerWorkletBuiltin('setNativeProps', setNativeProps)
|
|
72
|
+
registerWorkletBuiltin('dispatchCommand', dispatchCommand)
|
|
73
|
+
registerWorkletBuiltin('setGestureState', setGestureState)
|
|
74
|
+
|
|
75
|
+
// readiness beacon for conformance probes (RNGPUI_UI_TRACE) — proves this bundle
|
|
76
|
+
// evaluated on the worklet runtime, not the React one.
|
|
77
|
+
;(globalThis as { __rngpuiUiRuntimeReady?: boolean }).__rngpuiUiRuntimeReady = true
|
|
78
|
+
if (typeof process !== 'undefined' && process.env?.RNGPUI_UI_TRACE) {
|
|
79
|
+
console.error('[ui-runtime] ready (worklet runtime evaluated)')
|
|
80
|
+
}
|