@felipe-software/react-native-gpui 0.0.0-stage → 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.
Files changed (72) hide show
  1. package/.reanimated-prebuilt/react-native-reanimated.mjs +29373 -0
  2. package/LICENSE +21 -0
  3. package/README.md +657 -2
  4. package/cli/bin.ts +379 -0
  5. package/cli/bundle.ts +32 -0
  6. package/cli/commands/do.ts +213 -0
  7. package/cli/commands/flow.ts +283 -0
  8. package/cli/commands/get.ts +584 -0
  9. package/cli/commands/shot.ts +312 -0
  10. package/cli/commands/trace.ts +293 -0
  11. package/cli/host.ts +714 -0
  12. package/cli/hotReload.ts +210 -0
  13. package/cli/run.ts +66 -0
  14. package/cli/selectors.ts +205 -0
  15. package/dist/Animated.d.ts +120 -0
  16. package/dist/ChatList.d.ts +28 -0
  17. package/dist/Dimensions.d.ts +31 -0
  18. package/dist/StyleSheet.d.ts +18 -0
  19. package/dist/Window.d.ts +33 -0
  20. package/dist/apis.d.ts +165 -0
  21. package/dist/app-state.d.ts +27 -0
  22. package/dist/chat-list-types.d.ts +136 -0
  23. package/dist/colors.d.ts +32 -0
  24. package/dist/commands.d.ts +230 -0
  25. package/dist/components.d.ts +419 -0
  26. package/dist/hotModules.d.ts +1 -0
  27. package/dist/index.d.ts +31 -0
  28. package/dist/index.development.js +14876 -0
  29. package/dist/index.js +12 -0
  30. package/dist/keyboard.d.ts +140 -0
  31. package/dist/native-menu.d.ts +7 -0
  32. package/dist/platform-driver.d.ts +32 -0
  33. package/dist/platform-services.d.ts +35 -0
  34. package/dist/portal.d.ts +43 -0
  35. package/dist/raf.d.ts +4 -0
  36. package/dist/reanimated/colors-processor-shim.d.ts +10 -0
  37. package/dist/reanimated/reanimated-host.d.ts +10 -0
  38. package/dist/reanimated/seam.d.ts +34 -0
  39. package/dist/reanimated/ui-entry.d.ts +2 -0
  40. package/dist/reanimated/worklet-channel.d.ts +10 -0
  41. package/dist/reanimated/worklet-runtime.d.ts +466 -0
  42. package/dist/reanimated/worklets.d.ts +199 -0
  43. package/dist/reconciler.d.ts +102 -0
  44. package/dist/refresh.d.ts +1 -0
  45. package/dist/render.d.ts +37 -0
  46. package/dist/runtime.d.ts +235 -0
  47. package/dist/surfaces.d.ts +51 -0
  48. package/dist/types.d.ts +276 -0
  49. package/dist/ui-runtime.js +42276 -0
  50. package/dist/window-context.d.ts +7 -0
  51. package/dist/window-host.d.ts +41 -0
  52. package/dist/wire-delta.d.ts +20 -0
  53. package/package.json +176 -5
  54. package/scripts/bundle-app.mjs +88 -0
  55. package/scripts/conformance-utils.mjs +293 -0
  56. package/scripts/hot-update-alias-plugin.mjs +88 -0
  57. package/scripts/native-package-exports-plugin.mjs +235 -0
  58. package/scripts/pixel.mjs +152 -0
  59. package/scripts/png.mjs +198 -0
  60. package/scripts/react-refresh-swc.mjs +35 -0
  61. package/scripts/reanimated-bun-plugin.mjs +188 -0
  62. package/scripts/service-binary.d.mts +27 -0
  63. package/scripts/service-binary.mjs +100 -0
  64. package/src/reanimated/colors-processor-shim.ts +119 -0
  65. package/src/reanimated/reanimated-host.ts +69 -0
  66. package/src/reanimated/rngpui-reanimated-prebuilt.d.ts +7 -0
  67. package/src/reanimated/seam.ts +566 -0
  68. package/src/reanimated/ui-entry.ts +80 -0
  69. package/src/reanimated/worklet-channel.ts +64 -0
  70. package/src/reanimated/worklet-runtime.ts +2569 -0
  71. package/src/reanimated/worklets.ts +1334 -0
  72. package/src/refresh.ts +33 -0
@@ -0,0 +1,35 @@
1
+ import { createRequire } from 'node:module'
2
+
3
+ export function createReactRefreshSwcTransform() {
4
+ const require = createRequire(import.meta.url)
5
+ const swc = require('@swc/core')
6
+
7
+ return async function transformReactRefresh(source, { filename, isTs, isJsx }) {
8
+ const result = await swc.transform(source, {
9
+ filename,
10
+ sourceMaps: false,
11
+ jsc: {
12
+ target: 'es2022',
13
+ parser: isTs
14
+ ? { syntax: 'typescript', tsx: isJsx }
15
+ : { syntax: 'ecmascript', jsx: isJsx },
16
+ transform: {
17
+ react: {
18
+ runtime: 'automatic',
19
+ development: true,
20
+ refresh: {
21
+ refreshReg: '$RNGPUIRefreshReg$',
22
+ refreshSig: '$RefreshSig$',
23
+ emitFullSignatures: false,
24
+ },
25
+ },
26
+ },
27
+ },
28
+ module: {
29
+ type: 'es6',
30
+ },
31
+ })
32
+ const moduleId = JSON.stringify(filename)
33
+ return `const $RNGPUIRefreshReg$ = (type, id) => globalThis.$RefreshReg$(type, ${moduleId} + " " + id);\n${result.code}`
34
+ }
35
+ }
@@ -0,0 +1,188 @@
1
+ // Bun-build plugin: wire real react-native-reanimated@4 + react-native-worklets +
2
+ // @tamagui/animations-reanimated for the embedded JavaScriptCore target.
3
+ //
4
+ // HOW IT WORKS (the winning approach — see scripts/prebuild-reanimated.mjs):
5
+ // reanimated's `lib/module` re-export-barrel graph trips Bun's bundler into a
6
+ // tree-shake-then-dangle bug (`import_xN is not defined` at eval). ESBUILD bundles the
7
+ // same graph correctly. So we PRE-BUNDLE reanimated (and @tamagui/animations-reanimated)
8
+ // into self-contained ESM chunks with esbuild (worklets babel transform + native-seam
9
+ // redirect baked in), keeping `react` / `react-native-worklets` / `react-native-gpui`
10
+ // external. This plugin then simply ALIASES:
11
+ // react-native-reanimated → .reanimated-prebuilt/react-native-reanimated.mjs
12
+ // @tamagui/animations-reanimated → .reanimated-prebuilt/tamagui-animations-reanimated.mjs
13
+ // react-native-worklets → ts/src/reanimated/worklets.ts (cross-runtime adapter,
14
+ // which force-imports the seam → installs the globals)
15
+ // Bun then just includes those single files — no deep tree-shake of reanimated's graph.
16
+ //
17
+ // gpui runs separate React and UI JavaScriptCore runtimes. Worklets and shared values cross
18
+ // through the adapter, and `global._updateProps` is the off-thread fast path
19
+ // (→ `__rngpui_setNodeStyle` → Rust animated-style overlay → cx.notify without a React
20
+ // re-commit). The prebuilt chunks contain the real reanimated + Tamagui driver; the seam
21
+ // and worklets adapter provide the thin native seam.
22
+ //
23
+ // Run `bun scripts/prebuild-reanimated.mjs` whenever react-native-reanimated or
24
+ // @tamagui/animations-reanimated changes (or on postinstall). If the prebuilt chunk is
25
+ // missing, this plugin throws with that instruction.
26
+ //
27
+ // Usage:
28
+ // import { reanimatedBunPlugin } from '<rng>/ts/scripts/reanimated-bun-plugin.mjs'
29
+ // plugins: [reanimatedBunPlugin({ rngTsRoot })]
30
+ // rngTsRoot — absolute path to react-native-gpui/ts (seam/worklets/prebuilt live here)
31
+ // prebuiltDir — override the prebuilt chunk dir (default: <rngTsRoot>/.reanimated-prebuilt)
32
+
33
+ import { resolve, sep } from 'node:path'
34
+ import { existsSync } from 'node:fs'
35
+ import { createRequire } from 'node:module'
36
+
37
+ const LIBRARY_SPECIFIER = /^(?:@felipe-software\/)?react-native-gpui$/
38
+ const BABEL_PACKAGES = '@babel/core @babel/plugin-syntax-jsx @babel/preset-typescript react-native-worklets@0.8.1'
39
+
40
+ function defaultLibraryEntry(rngTsRoot) {
41
+ const source = resolve(rngTsRoot, 'src/index.ts')
42
+ return existsSync(source) ? source : resolve(rngTsRoot, 'dist/index.js')
43
+ }
44
+
45
+ // worklet keywords — gate the babel transform so we only pay for files that use them.
46
+ const WORKLET_KEYWORDS =
47
+ /worklet|useAnimatedStyle|useAnimatedProps|useDerivedValue|useAnimatedReaction|useAnimatedGestureHandler|useAnimatedScrollHandler|useFrameCallback|useWorkletCallback|withTiming|withSpring|withDecay|withDelay|withRepeat|runOnUI|createAnimatedPropAdapter/
48
+
49
+ export function reanimatedBunPlugin(opts = {}) {
50
+ const rngTsRoot = opts.rngTsRoot
51
+ if (!rngTsRoot) throw new Error('reanimatedBunPlugin: rngTsRoot is required')
52
+ const resolveRoot = opts.resolveRoot || rngTsRoot
53
+ const workletsPath = resolve(rngTsRoot, 'src/reanimated/worklets.ts')
54
+ const seamPath = resolve(rngTsRoot, 'src/reanimated/seam.ts')
55
+ const reanimatedHostPath = resolve(rngTsRoot, 'src/reanimated/reanimated-host.ts')
56
+ const prebuiltDir = opts.prebuiltDir || resolve(rngTsRoot, '.reanimated-prebuilt')
57
+ const reanimatedChunk = resolve(prebuiltDir, 'react-native-reanimated.mjs')
58
+ const tamaguiReanimatedChunk = resolve(prebuiltDir, 'tamagui-animations-reanimated.mjs')
59
+ // the prebuilt chunks emit external `import { View } from "react-native"`; resolve
60
+ // `react-native` → the gpui RN-compatible index. When the host bundler already aliases
61
+ // it (the gui app does), pass aliasReactNative:false.
62
+ const libraryEntry = opts.libraryEntry || defaultLibraryEntry(rngTsRoot)
63
+ const reactNativeIndex = opts.reactNativeIndex || libraryEntry
64
+ const aliasReactNative = opts.aliasReactNative !== false
65
+ const transformSource = opts.transformSource
66
+ const libraryBuildDir = resolve(rngTsRoot, 'dist') + sep
67
+
68
+ // babel worklets transform — applied to APP + Tamagui-driver source (NOT the prebuilt
69
+ // reanimated chunk, which already ran it, and NOT reanimated node_modules). This is
70
+ // REQUIRED: `useAnimatedStyle`/`useDerivedValue` read `updater.__closure` to extract
71
+ // the SharedValues a worklet depends on (its mapper inputs); without the babel lift
72
+ // the closure is empty, the mapper subscribes to nothing, a `sv.value = …` write never
73
+ // re-runs the mapper, and a Tamagui spring never advances (snaps to its target). The
74
+ // lifted function carries `this.__closure` in `__initData.code` so the UI runtime can
75
+ // deserialize the mapper with the SharedValues it captured.
76
+ const pluginRequires = [...new Set([resolveRoot, rngTsRoot])].map((dir) => createRequire(resolve(dir, 'package.json')))
77
+ let babel = null
78
+ let workletsPluginPath = null
79
+ let jsxSyntax = null
80
+ let presetTs = null
81
+ const loadBabel = (filename) => {
82
+ if (babel) return
83
+ for (const pluginRequire of pluginRequires) {
84
+ try {
85
+ workletsPluginPath = pluginRequire.resolve('react-native-worklets/plugin')
86
+ jsxSyntax = pluginRequire.resolve('@babel/plugin-syntax-jsx')
87
+ presetTs = pluginRequire.resolve('@babel/preset-typescript')
88
+ babel = pluginRequire('@babel/core')
89
+ return
90
+ } catch {}
91
+ }
92
+ throw new Error(
93
+ `[reanimated] ${filename} uses worklets, which need the worklets Babel transform.\n` +
94
+ ` install: bun add -d ${BABEL_PACKAGES}`,
95
+ )
96
+ }
97
+
98
+ return {
99
+ name: 'reanimated prebuilt chunks + worklets seam',
100
+ setup(build) {
101
+ if (aliasReactNative) {
102
+ build.onResolve({ filter: /^react-native$/ }, () => ({ path: reactNativeIndex }))
103
+ build.onResolve({ filter: LIBRARY_SPECIFIER }, () => ({ path: libraryEntry }))
104
+ }
105
+ // react-native-worklets (bare + any subpath) → single-runtime stub. The stub
106
+ // force-imports the seam, which installs reanimated's globals
107
+ // (__reanimatedModuleProxy, global._updateProps, __RUNTIME_KIND=2, RN$Bridgeless)
108
+ // before the reanimated chunk evaluates.
109
+ build.onResolve({ filter: /^react-native-worklets(\/.*)?$/ }, () => ({ path: workletsPath }))
110
+
111
+ // react-native-reanimated → the rngpui host wrapper (stamps the animation
112
+ // factories for the off-thread worklet runtime — see reanimated-host.ts),
113
+ // which itself imports the esbuild-prebuilt chunk via the virtual id below.
114
+ build.onResolve({ filter: /^react-native-reanimated$/ }, () => {
115
+ if (!existsSync(reanimatedChunk)) {
116
+ throw new Error(
117
+ `[reanimated] prebuilt chunk missing: ${reanimatedChunk}\n` +
118
+ ` run: bun scripts/prebuild-reanimated.mjs`,
119
+ )
120
+ }
121
+ return { path: reanimatedHostPath }
122
+ })
123
+ build.onResolve({ filter: /^rngpui-reanimated-prebuilt$/ }, () => ({ path: reanimatedChunk }))
124
+
125
+ // @tamagui/animations-reanimated → the esbuild-prebuilt chunk (when present).
126
+ build.onResolve({ filter: /^@tamagui\/animations-reanimated$/ }, () => {
127
+ if (existsSync(tamaguiReanimatedChunk)) return { path: tamaguiReanimatedChunk }
128
+ // not prebuilt (e.g. a bundle that doesn't use the Tamagui reanimated driver) —
129
+ // let it resolve normally; if the app actually imports it, that surfaces a clear
130
+ // resolve error pointing at the missing chunk.
131
+ return undefined
132
+ })
133
+
134
+ // worklet transform on APP + Tamagui-driver source (see the comment above).
135
+ build.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, async (args) => {
136
+ // skip: the prebuilt chunks (already transformed), the seam/worklets stub, and
137
+ // anything inside reanimated/worklets node_modules (the prebuild owns those).
138
+ if (
139
+ args.path.startsWith(prebuiltDir) ||
140
+ args.path === seamPath ||
141
+ args.path === workletsPath ||
142
+ /node_modules\/(react-native-reanimated|react-native-worklets)\//.test(args.path)
143
+ ) {
144
+ return undefined
145
+ }
146
+ const source = await Bun.file(args.path).text()
147
+ let code = source
148
+ let transformed = false
149
+ const isTsx = args.path.endsWith('.tsx')
150
+ const isTs = /\.(tsx?|mts|cts)$/.test(args.path)
151
+ const isLibraryBuild = args.path.startsWith(libraryBuildDir)
152
+ if (!isLibraryBuild && WORKLET_KEYWORDS.test(source)) {
153
+ loadBabel(args.path)
154
+ const result = await babel.transformAsync(source, {
155
+ filename: args.path,
156
+ babelrc: false,
157
+ configFile: false,
158
+ sourceMaps: false,
159
+ presets: isTs
160
+ ? [[presetTs, { isTSX: isTsx, allExtensions: isTsx, allowDeclareFields: true }]]
161
+ : [],
162
+ plugins: [[jsxSyntax], [workletsPluginPath, { processNestedWorklets: true }]],
163
+ })
164
+ if (result?.code) {
165
+ code = result.code
166
+ transformed = true
167
+ }
168
+ }
169
+ if (transformSource) {
170
+ const result = await transformSource(code, {
171
+ filename: args.path,
172
+ isTs: transformed ? false : isTs,
173
+ isJsx: isTsx || args.path.endsWith('.jsx'),
174
+ })
175
+ if (result !== code) {
176
+ code = result
177
+ transformed = true
178
+ }
179
+ }
180
+ if (!transformed) return undefined
181
+ return {
182
+ contents: code,
183
+ loader: isTsx || args.path.endsWith('.jsx') ? 'jsx' : 'js',
184
+ }
185
+ })
186
+ },
187
+ }
188
+ }
@@ -0,0 +1,27 @@
1
+ export type ServiceBinary = {
2
+ path: string;
3
+ source: string;
4
+ };
5
+
6
+ export type ServiceBinaryOptions = {
7
+ env?: Record<string, string | undefined>;
8
+ root?: string;
9
+ };
10
+
11
+ export type PlatformTarget = {
12
+ os: string;
13
+ cpu: string;
14
+ libc?: string;
15
+ };
16
+
17
+ export declare const MAIN_PACKAGE: string;
18
+ export declare const SERVICE_NAME: string;
19
+ export declare const UI_RUNTIME_NAME: string;
20
+ export declare const packageRoot: string;
21
+ export declare const PLATFORMS: Record<string, PlatformTarget>;
22
+
23
+ export declare function platformKey(platform?: string, arch?: string): string;
24
+ export declare function platformPackageName(key?: string): string;
25
+ export declare function isRepoCheckout(root?: string): boolean;
26
+ export declare function resolveServiceBinary(options?: ServiceBinaryOptions): ServiceBinary;
27
+ export declare function uiRuntimeEnv(binary: string, options?: ServiceBinaryOptions): { RNGPUI_UI_BUNDLE?: string };
@@ -0,0 +1,100 @@
1
+ import { accessSync, chmodSync, constants, existsSync, statSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ export const MAIN_PACKAGE = "@felipe-software/react-native-gpui";
7
+ export const SERVICE_NAME = "rngpui-service";
8
+ export const UI_RUNTIME_NAME = "ui-runtime.js";
9
+ export const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
10
+
11
+ export const PLATFORMS = {
12
+ "linux-x64": { os: "linux", cpu: "x64", libc: "glibc" },
13
+ "linux-arm64": { os: "linux", cpu: "arm64", libc: "glibc" },
14
+ "darwin-arm64": { os: "darwin", cpu: "arm64" },
15
+ };
16
+
17
+ export function platformKey(platform = process.platform, arch = process.arch) {
18
+ return `${platform}-${arch}`;
19
+ }
20
+
21
+ export function platformPackageName(key = platformKey()) {
22
+ return `${MAIN_PACKAGE}-${key}`;
23
+ }
24
+
25
+ export function isRepoCheckout(root = packageRoot) {
26
+ return existsSync(resolve(root, "..", "rust", "Cargo.toml"));
27
+ }
28
+
29
+ export function resolveServiceBinary({ env = process.env, root = packageRoot } = {}) {
30
+ const found = findServiceBinary(env, root);
31
+ if (found.source !== "RNGPUI_SERVICE") ensureExecutable(found.path);
32
+ return found;
33
+ }
34
+
35
+ export function uiRuntimeEnv(binary, { env = process.env, root = packageRoot } = {}) {
36
+ if (env.RNGPUI_UI_BUNDLE) return {};
37
+ if (existsSync(join(dirname(binary), UI_RUNTIME_NAME))) return {};
38
+ const packaged = join(root, "dist", UI_RUNTIME_NAME);
39
+ return existsSync(packaged) ? { RNGPUI_UI_BUNDLE: packaged } : {};
40
+ }
41
+
42
+ function findServiceBinary(env, root) {
43
+ if (env.RNGPUI_SERVICE) {
44
+ const explicit = resolve(env.RNGPUI_SERVICE);
45
+ if (!existsSync(explicit)) throw new Error(`RNGPUI_SERVICE points at ${explicit}, which does not exist`);
46
+ return { path: explicit, source: "RNGPUI_SERVICE" };
47
+ }
48
+ const packaged = platformPackageBinary(root);
49
+ if (packaged) return packaged;
50
+ const local = localBinaries(root).find((candidate) => existsSync(candidate.path));
51
+ if (local) return local;
52
+ throw new Error(missingBinaryMessage(root));
53
+ }
54
+
55
+ function platformPackageBinary(root) {
56
+ const name = platformPackageName();
57
+ try {
58
+ const manifest = createRequire(join(root, "package.json")).resolve(`${name}/package.json`);
59
+ const path = join(dirname(manifest), SERVICE_NAME);
60
+ return existsSync(path) ? { path, source: name } : null;
61
+ } catch {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ function localBinaries(root) {
67
+ const native = { path: join(root, "native", SERVICE_NAME), source: "native/" };
68
+ if (!isRepoCheckout(root)) return [native];
69
+ const target = resolve(root, "..", "rust", "target");
70
+ const release = { path: join(target, "release", SERVICE_NAME), source: "rust/target/release" };
71
+ const debug = { path: join(target, "debug", SERVICE_NAME), source: "rust/target/debug" };
72
+ const newestFirst = [native, release].sort((a, b) => modifiedAt(b.path) - modifiedAt(a.path));
73
+ return [...newestFirst, debug];
74
+ }
75
+
76
+ function modifiedAt(path) {
77
+ try {
78
+ return statSync(path).mtimeMs;
79
+ } catch {
80
+ return 0;
81
+ }
82
+ }
83
+
84
+ function missingBinaryMessage(root) {
85
+ const key = platformKey();
86
+ const supported = Object.keys(PLATFORMS).join(", ");
87
+ if (!PLATFORMS[key]) {
88
+ return `${MAIN_PACKAGE} has no prebuilt ${SERVICE_NAME} for ${key} (prebuilt: ${supported}). Build it from source and set RNGPUI_SERVICE.`;
89
+ }
90
+ const repoHint = isRepoCheckout(root) ? " or run `npm run build:rust && npm run build:native` in ts/" : "";
91
+ return `${SERVICE_NAME} not found: ${platformPackageName(key)} is not installed. It is an optional dependency of ${MAIN_PACKAGE}; reinstall without --omit=optional / --no-optional, set RNGPUI_SERVICE${repoHint}.`;
92
+ }
93
+
94
+ function ensureExecutable(path) {
95
+ try {
96
+ accessSync(path, constants.X_OK);
97
+ } catch {
98
+ chmodSync(path, 0o755);
99
+ }
100
+ }
@@ -0,0 +1,119 @@
1
+ // Shim for react-native-reanimated's `common/style/processors/colors` module.
2
+ //
3
+ // Why: Bun's bundler mis-orders the init of reanimated's color-processor barrel
4
+ // (`config.ts`'s top-level `const colorAttributes = { process: processColor }` reads
5
+ // the binding before Bun assigns it → "Property 'processColorN' doesn't exist" at
6
+ // bundle eval). The original module also pulls a `'worklet'` `PlatformColor` and the
7
+ // deep `Colors.ts` chain. We replace ONLY this leaf with a self-contained module (all
8
+ // exports are hoisted declarations in one file, so there is no cross-module rename for
9
+ // Bun to drop), preserving the exact public surface the barrel re-exports.
10
+ //
11
+ // Faithfulness: `processColor` returns RN's 32-bit ARGB int (the same form upstream's
12
+ // `processColorInitially` produces), so reanimated's color interpolation + the gpui
13
+ // seam's `processedColorToCss` round-trip identically. `processColorsInProps` mutates
14
+ // color-valued style keys in place to that int form, matching upstream.
15
+
16
+ type StyleProps = Record<string, unknown>
17
+
18
+ const COLOR_KEYS = new Set([
19
+ 'color',
20
+ 'backgroundColor',
21
+ 'borderColor',
22
+ 'borderTopColor',
23
+ 'borderRightColor',
24
+ 'borderBottomColor',
25
+ 'borderLeftColor',
26
+ 'borderStartColor',
27
+ 'borderEndColor',
28
+ 'tintColor',
29
+ 'shadowColor',
30
+ 'overlayColor',
31
+ 'textDecorationColor',
32
+ 'textShadowColor',
33
+ ])
34
+
35
+ // RN-style color string/number → 32-bit ARGB int (a>>24, r>>16, g>>8, b). Mirrors
36
+ // react-native's processColor output so reanimated's color interpolation operates on
37
+ // the same numeric form it expects.
38
+ export function processColor(color: unknown): number | null {
39
+ if (color == null) return null
40
+ if (typeof color === 'number') return color >>> 0
41
+ if (typeof color !== 'string') return null
42
+ const s = color.trim()
43
+ let m: RegExpExecArray | null
44
+ if ((m = /^#([0-9a-f]{6})$/i.exec(s))) return (0xff000000 | parseInt(m[1], 16)) >>> 0
45
+ if ((m = /^#([0-9a-f]{8})$/i.exec(s))) {
46
+ const n = parseInt(m[1], 16)
47
+ const a = n & 0xff
48
+ return (((a << 24) | (n >>> 8)) >>> 0) >>> 0
49
+ }
50
+ if ((m = /^#([0-9a-f]{3})$/i.exec(s))) {
51
+ const h = m[1]
52
+ const r = parseInt(h[0] + h[0], 16)
53
+ const g = parseInt(h[1] + h[1], 16)
54
+ const b = parseInt(h[2] + h[2], 16)
55
+ return (0xff000000 | (r << 16) | (g << 8) | b) >>> 0
56
+ }
57
+ if ((m = /^rgba?\(([^)]+)\)$/i.exec(s))) {
58
+ const p = m[1].split(',').map((x) => x.trim())
59
+ const r = parseInt(p[0]) || 0
60
+ const g = parseInt(p[1]) || 0
61
+ const b = parseInt(p[2]) || 0
62
+ let a = p[3] === undefined ? 1 : parseFloat(p[3])
63
+ if (!(a >= 0)) a = 0
64
+ if (a > 1) a = 1
65
+ return ((((a * 255) & 0xff) << 24) | (r << 16) | (g << 8) | b) >>> 0
66
+ }
67
+ // named colors / unknown → opaque black (matches RN's fallback shape closely enough).
68
+ return 0xff000000 >>> 0
69
+ }
70
+
71
+ export function processColorNumber(value: unknown): number | null {
72
+ return processColor(value)
73
+ }
74
+
75
+ export function processColorsInProps(props: StyleProps): void {
76
+ for (const key of Object.keys(props)) {
77
+ if (COLOR_KEYS.has(key)) {
78
+ const processed = processColor(props[key])
79
+ if (processed != null) props[key] = processed
80
+ }
81
+ }
82
+ }
83
+
84
+ // PlatformColor / DynamicColorIOS — gpui has no native semantic colors; identity-ish.
85
+ export function PlatformColor(...names: string[]): unknown {
86
+ return names[0]
87
+ }
88
+
89
+ export function DynamicColorIOS(tuple: { light: unknown; dark: unknown }): unknown {
90
+ return tuple?.light
91
+ }
92
+
93
+ // ── worklet-builtin registration ─────────────────────────────────────────────
94
+ // Upstream's updateProps worklet CAPTURES processColorsInProps in its closure.
95
+ // These shim fns are plain (not babel-workletized), so without a builtin brand
96
+ // the cross-runtime serializer ships them as async jsCallback proxies — on the
97
+ // worklet/UI runtime they then do nothing and every color key crosses as
98
+ // undefined (the dropped-dialog-bg bug). Brand + register them so closures
99
+ // serialize as {kind:'builtin', name} and bind to THIS module's implementation
100
+ // on whichever runtime executes (this shim is baked into the prebuilt chunk,
101
+ // which evaluates on both). Constants mirror worklet-runtime.ts — this file
102
+ // cannot import it (the prebuilt chunk keeps the worklets package external).
103
+ const RNGPUI_BUILTIN_NAME = Symbol.for('rngpui.workletBuiltinName')
104
+ function registerColorBuiltin(name: string, fn: (...args: never[]) => unknown): void {
105
+ try {
106
+ Object.defineProperty(fn, RNGPUI_BUILTIN_NAME, {
107
+ value: name,
108
+ enumerable: false,
109
+ configurable: true,
110
+ writable: false,
111
+ })
112
+ } catch {}
113
+ const g = globalThis as unknown as { __rngpui_worklet_builtins?: Map<string, unknown> }
114
+ if (!g.__rngpui_worklet_builtins) g.__rngpui_worklet_builtins = new Map()
115
+ g.__rngpui_worklet_builtins.set(name, fn)
116
+ }
117
+ registerColorBuiltin('processColor', processColor)
118
+ registerColorBuiltin('processColorNumber', processColorNumber)
119
+ registerColorBuiltin('processColorsInProps', processColorsInProps)
@@ -0,0 +1,69 @@
1
+ // `react-native-reanimated` entry for rngpui bundles — the bundler plugins alias
2
+ // the package here, and `rngpui-reanimated-prebuilt` to the prebuilt upstream
3
+ // chunk. Re-exports upstream untouched EXCEPT, on the React runtime with the
4
+ // worklet bridge present, the animation factories — the producer half of
5
+ // sootsim's guestDecorator (plans/off-thread-reanimated.md):
6
+ //
7
+ // - calling `withTiming(...)` / `withSpring(...)` / … stamps the returned
8
+ // animation object with a non-enumerable descriptor {type, args} so a direct
9
+ // `sv.value = withX(...)` write on the React runtime can be re-created and
10
+ // DRIVEN on the worklet/UI runtime (worklets.ts setValue →
11
+ // dispatchHostValueSet → HOST_SET_SHARED_VALUE_WORKLET revive).
12
+ // - each wrapper registers itself as a worklet builtin, so a mapper closure
13
+ // capturing `withSpring` serializes as {kind:'builtin', name:'withSpring'}
14
+ // and binds to the UI runtime's OWN factory over there (registered raw by
15
+ // ui-entry.ts). Without this, closure serialization would ship the wrapper
16
+ // as code and lose the stamp-free UI-side identity.
17
+ //
18
+ // On the UI runtime (and in bun tests / single-runtime mode) factories pass
19
+ // through untouched — animations are created and driven locally there, no stamp
20
+ // needed. Explicit named exports win over the `export *` per the ES module
21
+ // spec's ambiguity rule, so the wrapped names below shadow the chunk's.
22
+ import * as upstream from 'rngpui-reanimated-prebuilt'
23
+ import { registerWorkletBuiltin } from './worklet-runtime'
24
+
25
+ export * from 'rngpui-reanimated-prebuilt'
26
+ export { default } from 'rngpui-reanimated-prebuilt'
27
+
28
+ const RNGPUI_REANIMATED_ANIMATION_DESCRIPTOR = Symbol.for('rngpui.reanimatedAnimationDescriptor')
29
+
30
+ declare const __rngpui_uiPost: ((arg: string) => void) | undefined
31
+
32
+ // only the React runtime with a live worklet bridge stamps factories.
33
+ const wrapFactories = typeof __rngpui_uiPost === 'function'
34
+
35
+ type AnimationFactory = (...args: unknown[]) => unknown
36
+
37
+ function stamped(name: string, factory: unknown): AnimationFactory {
38
+ const fn = factory as AnimationFactory
39
+ if (!wrapFactories || typeof fn !== 'function') return fn
40
+ const wrapper: AnimationFactory = (...args: unknown[]) => {
41
+ const out = fn(...args)
42
+ if (out && (typeof out === 'object' || typeof out === 'function')) {
43
+ Object.defineProperty(out as object, RNGPUI_REANIMATED_ANIMATION_DESCRIPTOR, {
44
+ value: { type: name, args },
45
+ enumerable: false,
46
+ configurable: true,
47
+ })
48
+ }
49
+ return out
50
+ }
51
+ // keep the worklet metadata visible on the wrapper so upstream's
52
+ // areWorkletsEqual/isWorkletFunction checks treat it like the original.
53
+ for (const key of ['__workletHash', '__initData', '__closure', '__isAnimationDefinition']) {
54
+ const value = (fn as unknown as Record<string, unknown>)[key]
55
+ if (value !== undefined) {
56
+ Object.defineProperty(wrapper, key, { value, enumerable: false, configurable: true, writable: true })
57
+ }
58
+ }
59
+ registerWorkletBuiltin(name, wrapper)
60
+ return wrapper
61
+ }
62
+
63
+ export const withTiming = stamped('withTiming', upstream.withTiming)
64
+ export const withSpring = stamped('withSpring', upstream.withSpring)
65
+ export const withDecay = stamped('withDecay', upstream.withDecay)
66
+ export const withDelay = stamped('withDelay', upstream.withDelay)
67
+ export const withRepeat = stamped('withRepeat', upstream.withRepeat)
68
+ export const withSequence = stamped('withSequence', upstream.withSequence)
69
+ export const withClamp = stamped('withClamp', upstream.withClamp)
@@ -0,0 +1,7 @@
1
+ // virtual module id the bundler plugins map to the esbuild-prebuilt upstream
2
+ // reanimated chunk (.reanimated-prebuilt/react-native-reanimated.mjs). Only
3
+ // reanimated-host.ts imports it; type-wise it IS upstream reanimated.
4
+ declare module 'rngpui-reanimated-prebuilt' {
5
+ export * from 'react-native-reanimated'
6
+ export { default } from 'react-native-reanimated'
7
+ }