@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,210 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync, readFileSync, statSync, watch, type FSWatcher } from "node:fs";
3
+ import { createConnection } from "node:net";
4
+ import { extname, join, relative, resolve, sep } from "node:path";
5
+
6
+ export type HotReloadOptions = {
7
+ socketPath?: string;
8
+ roots: string[];
9
+ entryPath?: string;
10
+ buildCommand?: string;
11
+ bundlePath?: string;
12
+ ignores?: string[];
13
+ extensions?: string[];
14
+ debounceMs?: number;
15
+ label?: string;
16
+ once?: boolean;
17
+ };
18
+
19
+ const DEFAULT_IGNORES = ["node_modules", ".git", ".gpui", ".gpui-out", "dist"];
20
+ const DEFAULT_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json", ".css"];
21
+ const SOCKET_READY_TIMEOUT_MS = 5_000;
22
+ const BUNDLE_SCRIPT = join(import.meta.dirname, "..", "scripts", "bundle-app.mjs");
23
+
24
+ export async function runHotReload(options: HotReloadOptions): Promise<number> {
25
+ const socketPath = options.socketPath || process.env.RNGPUI_CONTROL_SOCKET;
26
+ if (!socketPath) {
27
+ console.error(" dev needs --socket <control.sock> or RNGPUI_CONTROL_SOCKET");
28
+ return 1;
29
+ }
30
+ if (!options.buildCommand && !options.entryPath) {
31
+ console.error(" dev needs --launch <entry.tsx> or --build <shell-command>");
32
+ return 1;
33
+ }
34
+ if (!options.bundlePath) {
35
+ console.error(" dev needs --bundle <bundle.js>");
36
+ return 1;
37
+ }
38
+
39
+ const label = options.label || "rngpui-hot";
40
+ const run = () => buildAndPush({ ...options, socketPath, label });
41
+ if (options.once) return (await run()) ? 0 : 1;
42
+
43
+ const roots = [
44
+ ...new Set(options.roots.map((root) => resolve(root)).filter((root) => existsSync(root) && statSync(root).isDirectory())),
45
+ ];
46
+ if (roots.length === 0) {
47
+ console.error(" dev needs at least one existing --root <dir>");
48
+ return 1;
49
+ }
50
+
51
+ const ignores = new Set([...DEFAULT_IGNORES, ...(options.ignores ?? [])].filter(Boolean));
52
+ const extensions = new Set(normalizeExtensions(options.extensions?.length ? options.extensions : DEFAULT_EXTENSIONS));
53
+ const debounceMs = options.debounceMs ?? 80;
54
+ const watchers: FSWatcher[] = [];
55
+ let timer: ReturnType<typeof setTimeout> | null = null;
56
+ let building = false;
57
+ let pending = false;
58
+ let lastTrigger = "";
59
+
60
+ const ignored = (path: string) => path.split(sep).some((part) => ignores.has(part));
61
+ const schedule = (path: string) => {
62
+ if (ignored(path) || !extensions.has(extname(path))) return;
63
+ lastTrigger = relative(process.cwd(), path);
64
+ pending = true;
65
+ if (timer) clearTimeout(timer);
66
+ timer = setTimeout(runPending, debounceMs);
67
+ };
68
+ const runPending = async () => {
69
+ timer = null;
70
+ if (building || !pending) return;
71
+ pending = false;
72
+ building = true;
73
+ console.log(`[${label}] ${lastTrigger || "source change"} -> hot update`);
74
+ try {
75
+ await run();
76
+ } catch (error) {
77
+ console.error(`[${label}] hot update error: ${error instanceof Error ? error.message : String(error)}`);
78
+ } finally {
79
+ building = false;
80
+ if (pending) void runPending();
81
+ }
82
+ };
83
+ for (const root of roots) {
84
+ const watcher = watch(root, { recursive: true }, (_event, file) => {
85
+ if (file) schedule(resolve(root, String(file)));
86
+ });
87
+ watcher.on("error", (error) => {
88
+ console.error(`[${label}] watch error ${root}: ${error instanceof Error ? error.message : String(error)}`);
89
+ });
90
+ watchers.push(watcher);
91
+ }
92
+
93
+ console.log(`[${label}] Fast Refresh armed: ${roots.map((root) => relative(process.cwd(), root) || root).join(", ")}`);
94
+ console.log(`[${label}] control socket: ${socketPath}`);
95
+ await new Promise<void>((resolveDone) => {
96
+ const close = () => {
97
+ closeWatchers(watchers);
98
+ resolveDone();
99
+ };
100
+ process.once("SIGINT", close);
101
+ process.once("SIGTERM", close);
102
+ });
103
+ return 0;
104
+ }
105
+
106
+ async function buildAndPush(options: HotReloadOptions & { socketPath: string; label: string }) {
107
+ const started = Date.now();
108
+ const env = { ...process.env, NODE_ENV: "development", RNGPUI_HOT_UPDATE: "1" };
109
+ const status = options.entryPath
110
+ ? await runProcess(process.execPath, [BUNDLE_SCRIPT, resolve(options.entryPath), resolve(options.bundlePath!)], env)
111
+ : await runProcess("/bin/sh", ["-lc", options.buildCommand!], env);
112
+ if (status !== 0) {
113
+ console.error(`[${options.label}] build failed with status ${status}; current app remains active`);
114
+ return false;
115
+ }
116
+ const bundlePath = resolve(options.bundlePath!);
117
+ const code = readFileSync(bundlePath, "utf8");
118
+ let response: { ok: boolean; error?: string };
119
+ try {
120
+ response = await requestSocketWithRetry<{ ok: boolean; error?: string }>(
121
+ options.socketPath,
122
+ {
123
+ $cmd: "hotEval",
124
+ url: bundlePath,
125
+ code,
126
+ },
127
+ SOCKET_READY_TIMEOUT_MS,
128
+ );
129
+ } catch (error) {
130
+ console.error(`[${options.label}] hot update request failed: ${error instanceof Error ? error.message : String(error)}`);
131
+ return false;
132
+ }
133
+ if (response.ok) {
134
+ console.log(`[${options.label}] hot update applied in ${Date.now() - started}ms`);
135
+ return true;
136
+ }
137
+ console.error(`[${options.label}] hot update failed: ${response.error || "unknown error"}`);
138
+ return false;
139
+ }
140
+
141
+ function runProcess(command: string, args: string[], env: NodeJS.ProcessEnv): Promise<number> {
142
+ return new Promise((resolveStatus, reject) => {
143
+ const child = spawn(command, args, { cwd: process.cwd(), stdio: "inherit", env });
144
+ child.once("error", reject);
145
+ child.once("exit", (code, signal) => resolveStatus(code ?? (signal ? 1 : 0)));
146
+ });
147
+ }
148
+
149
+ function requestSocket<T>(socketPath: string, body: object): Promise<T> {
150
+ return new Promise((resolveRequest, reject) => {
151
+ const socket = createConnection(socketPath);
152
+ let buffer = "";
153
+ const timer = setTimeout(() => {
154
+ socket.destroy();
155
+ reject(new Error(`control request timed out on ${socketPath}`));
156
+ }, 10_000);
157
+ socket.on("connect", () => {
158
+ socket.write(JSON.stringify(body) + "\n");
159
+ });
160
+ socket.on("data", (chunk) => {
161
+ buffer += chunk.toString("utf8");
162
+ const idx = buffer.indexOf("\n");
163
+ if (idx < 0) return;
164
+ clearTimeout(timer);
165
+ socket.end();
166
+ resolveRequest(JSON.parse(buffer.slice(0, idx)) as T);
167
+ });
168
+ socket.on("error", (error) => {
169
+ clearTimeout(timer);
170
+ reject(error);
171
+ });
172
+ });
173
+ }
174
+
175
+ async function requestSocketWithRetry<T>(socketPath: string, body: object, timeoutMs: number): Promise<T> {
176
+ const deadline = Date.now() + timeoutMs;
177
+ let lastError: unknown;
178
+ while (Date.now() < deadline) {
179
+ try {
180
+ return await requestSocket<T>(socketPath, body);
181
+ } catch (error) {
182
+ if (!isTransientSocketError(error)) throw error;
183
+ lastError = error;
184
+ await sleep(50);
185
+ }
186
+ }
187
+ throw lastError instanceof Error ? lastError : new Error(`control socket was not ready: ${socketPath}`);
188
+ }
189
+
190
+ function isTransientSocketError(error: unknown): boolean {
191
+ const code = typeof error === "object" && error ? (error as { code?: unknown }).code : undefined;
192
+ return code === "ENOENT" || code === "ECONNREFUSED";
193
+ }
194
+
195
+ function sleep(ms: number) {
196
+ return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
197
+ }
198
+
199
+ function normalizeExtensions(values: string[]): string[] {
200
+ return values
201
+ .flatMap((value) => value.split(","))
202
+ .map((value) => value.trim())
203
+ .filter(Boolean)
204
+ .map((value) => (value.startsWith(".") ? value : `.${value}`));
205
+ }
206
+
207
+ function closeWatchers(watchers: FSWatcher[]) {
208
+ for (const watcher of watchers) watcher.close();
209
+ watchers.length = 0;
210
+ }
package/cli/run.ts ADDED
@@ -0,0 +1,66 @@
1
+ import { spawn } from "node:child_process";
2
+ import { mkdtempSync, rmSync } from "node:fs";
3
+ import { constants, tmpdir } from "node:os";
4
+ import { join, resolve } from "node:path";
5
+ import { resolveServiceBinary, uiRuntimeEnv } from "../scripts/service-binary.mjs";
6
+ import { bundleApp } from "./bundle";
7
+
8
+ export type RunOptions = {
9
+ entry?: string;
10
+ bundle?: string;
11
+ development: boolean;
12
+ size?: string;
13
+ };
14
+
15
+ const FORWARDED_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"] as const;
16
+
17
+ export async function runApp(options: RunOptions): Promise<number> {
18
+ if (!options.entry && !options.bundle) {
19
+ console.error(" run needs an entry (rngpui run app.tsx) or --bundle <app.js>");
20
+ return 1;
21
+ }
22
+ const windowSize = parseSize(options.size);
23
+ const workdir = options.bundle ? null : mkdtempSync(join(tmpdir(), "rngpui-run-"));
24
+ const cleanup = () => {
25
+ if (workdir) rmSync(workdir, { recursive: true, force: true });
26
+ };
27
+ try {
28
+ const bundlePath = options.bundle
29
+ ? resolve(options.bundle)
30
+ : bundleApp(options.entry!, join(workdir!, "app.js"), { development: options.development, inheritOutput: true });
31
+ const service = resolveServiceBinary();
32
+ console.error(`[rngpui] ${service.path} (${service.source})`);
33
+ const child = spawn(service.path, [], {
34
+ stdio: "inherit",
35
+ env: {
36
+ ...process.env,
37
+ ...uiRuntimeEnv(service.path),
38
+ RNGPUI_BUNDLE: bundlePath,
39
+ ...(windowSize ? { RNGPUI_WINDOW_SIZE: windowSize } : {}),
40
+ },
41
+ });
42
+ const forward = (signal: NodeJS.Signals) => child.kill(signal);
43
+ for (const signal of FORWARDED_SIGNALS) process.on(signal, forward);
44
+ const code = await new Promise<number>((resolveExit) => {
45
+ child.on("error", (error) => {
46
+ console.error(` failed to start ${service.path}: ${error.message}`);
47
+ resolveExit(1);
48
+ });
49
+ child.on("exit", (exitCode, signal) => {
50
+ const signalCode = signal ? 128 + (constants.signals[signal] ?? 0) : 1;
51
+ resolveExit(exitCode ?? signalCode);
52
+ });
53
+ });
54
+ for (const signal of FORWARDED_SIGNALS) process.off(signal, forward);
55
+ return code;
56
+ } finally {
57
+ cleanup();
58
+ }
59
+ }
60
+
61
+ function parseSize(size: string | undefined): string | null {
62
+ if (!size) return null;
63
+ const match = /^(\d+)x(\d+)$/.exec(size);
64
+ if (!match) throw new Error(`--size must look like 1280x860, got ${size}`);
65
+ return `${match[1]},${match[2]}`;
66
+ }
@@ -0,0 +1,205 @@
1
+ // Selector resolution for the `rngpui` CLI, modeled on sootsim's tap-id / tap-text
2
+ // ergonomics. A selector is one of:
3
+ // #<globalId> exact node id
4
+ // <substring> case-insensitive substring match on testID / accessibility
5
+ // identifier / nativeID / label / text / type (in that priority)
6
+ // <x>,<y> a literal window-coordinate point (handled by the caller)
7
+ //
8
+ // Resolution prefers the SMALLEST visible matching node (the most specific target),
9
+ // matching how a human reads "the composer input" as the input itself, not its
10
+ // container.
11
+
12
+ import type { DumpNode } from "./host";
13
+
14
+ export type ResolvedNode = {
15
+ node: DumpNode;
16
+ matchedField: string;
17
+ matchedValue: string;
18
+ };
19
+
20
+ export function parsePoint(selector: string): { x: number; y: number } | null {
21
+ const m = /^(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)$/.exec(selector.trim());
22
+ if (!m) return null;
23
+ return { x: Number(m[1]), y: Number(m[2]) };
24
+ }
25
+
26
+ export function isVisible(node: DumpNode): boolean {
27
+ const b = node.bounds;
28
+ return !!b && b.width > 0.5 && b.height > 0.5;
29
+ }
30
+
31
+ function fieldsOf(node: DumpNode): Array<{ field: string; value: string }> {
32
+ const out: Array<{ field: string; value: string }> = [];
33
+ const a = node.accessibility ?? {};
34
+ if (a.testID) out.push({ field: "testID", value: a.testID });
35
+ if (a.identifier) out.push({ field: "identifier", value: a.identifier });
36
+ if (a.nativeID) out.push({ field: "nativeID", value: a.nativeID });
37
+ if (a.label) out.push({ field: "label", value: a.label });
38
+ if (typeof node.text === "string" && node.text.trim()) out.push({ field: "text", value: node.text });
39
+ if (typeof node.value === "string" && node.value.trim()) out.push({ field: "value", value: node.value });
40
+ out.push({ field: "type", value: node.type });
41
+ return out;
42
+ }
43
+
44
+ function area(node: DumpNode): number {
45
+ const b = node.bounds;
46
+ return b ? b.width * b.height : Infinity;
47
+ }
48
+
49
+ export function* walk(root: DumpNode): Generator<DumpNode> {
50
+ yield root;
51
+ for (const child of root.children ?? []) yield* walk(child);
52
+ }
53
+
54
+ export function findById(root: DumpNode, id: number): DumpNode | null {
55
+ for (const node of walk(root)) if (node.globalId === id) return node;
56
+ return null;
57
+ }
58
+
59
+ // Resolve a selector to the best-matching node. Ranks by field priority (testID >
60
+ // identifier > nativeID > label > text > value > type), then prefers visible nodes,
61
+ // then the smallest area (most specific). Returns all candidates for diagnostics.
62
+ export function resolve(root: DumpNode, selector: string): { best: ResolvedNode | null; candidates: ResolvedNode[] } {
63
+ if (selector.startsWith("#")) {
64
+ const id = Number(selector.slice(1));
65
+ const node = Number.isFinite(id) ? findById(root, id) : null;
66
+ const best = node ? { node, matchedField: "globalId", matchedValue: String(id) } : null;
67
+ return { best, candidates: best ? [best] : [] };
68
+ }
69
+
70
+ const needle = selector.toLowerCase();
71
+ const fieldRank: Record<string, number> = {
72
+ testID: 0,
73
+ identifier: 1,
74
+ nativeID: 2,
75
+ label: 3,
76
+ text: 4,
77
+ value: 5,
78
+ type: 6,
79
+ };
80
+
81
+ const candidates: Array<ResolvedNode & { rank: number; exact: boolean }> = [];
82
+ for (const node of walk(root)) {
83
+ for (const { field, value } of fieldsOf(node)) {
84
+ const v = value.toLowerCase();
85
+ if (v.includes(needle)) {
86
+ candidates.push({
87
+ node,
88
+ matchedField: field,
89
+ matchedValue: value,
90
+ rank: fieldRank[field] ?? 9,
91
+ exact: v === needle,
92
+ });
93
+ break; // one match per node, highest-priority field
94
+ }
95
+ }
96
+ }
97
+
98
+ candidates.sort((a, b) => {
99
+ // exact match beats substring
100
+ if (a.exact !== b.exact) return a.exact ? -1 : 1;
101
+ // higher-priority field
102
+ if (a.rank !== b.rank) return a.rank - b.rank;
103
+ // visible beats not
104
+ const av = isVisible(a.node) ? 0 : 1;
105
+ const bv = isVisible(b.node) ? 0 : 1;
106
+ if (av !== bv) return av - bv;
107
+ // smallest area (most specific)
108
+ return area(a.node) - area(b.node);
109
+ });
110
+
111
+ const best = candidates[0] ? { node: candidates[0].node, matchedField: candidates[0].matchedField, matchedValue: candidates[0].matchedValue } : null;
112
+ return { best, candidates: candidates.map((c) => ({ node: c.node, matchedField: c.matchedField, matchedValue: c.matchedValue })) };
113
+ }
114
+
115
+ // Topmost painted surface at a window point. Later or higher-z siblings paint above
116
+ // earlier siblings; a transparent layout wrapper does not cover an underlay surface.
117
+ export function nodeAtPoint(root: DumpNode, x: number, y: number): DumpNode | null {
118
+ let fallback: { node: DumpNode; depth: number; z: number } | null = null;
119
+
120
+ // visit() walks topmost siblings FIRST, so on a full depth/z tie the
121
+ // earlier-seen candidate is the topmost in paint order — never replace it.
122
+ const rememberFallback = (node: DumpNode, depth: number) => {
123
+ const z = zIndexOf(node);
124
+ const candidate = { node, depth, z };
125
+ if (
126
+ !fallback ||
127
+ candidate.depth > fallback.depth ||
128
+ (candidate.depth === fallback.depth && candidate.z > fallback.z)
129
+ ) {
130
+ fallback = candidate;
131
+ }
132
+ };
133
+
134
+ const visit = (node: DumpNode, depth: number): DumpNode | null => {
135
+ // gpui inherits group opacity — an opacity:0 ancestor hides its whole
136
+ // subtree, so nothing under it can be the painted surface at the point.
137
+ if (Number(node.style?.opacity ?? 1) === 0) return null;
138
+ const inside = containsPoint(node, x, y);
139
+ if (inside) rememberFallback(node, depth);
140
+
141
+ const clipsChildren = ["hidden", "scroll", "auto"].includes(String(node.style?.overflow ?? ""));
142
+ if (inside || !clipsChildren) {
143
+ const children = [...(node.children ?? [])]
144
+ .map((child, index) => ({ child, index, z: zIndexOf(child) }))
145
+ .sort((a, b) => a.z - b.z || a.index - b.index);
146
+ for (let i = children.length - 1; i >= 0; i--) {
147
+ const hit = visit(children[i].child, depth + 1);
148
+ if (hit) return hit;
149
+ }
150
+ }
151
+
152
+ return inside && paintsSurface(node) ? node : null;
153
+ };
154
+
155
+ return visit(root, 0) ?? fallback?.node ?? null;
156
+ }
157
+
158
+ function containsPoint(node: DumpNode, x: number, y: number): boolean {
159
+ if (!isVisible(node)) return false;
160
+ const b = node.bounds!;
161
+ return x >= b.x && x <= b.x + b.width && y >= b.y && y <= b.y + b.height;
162
+ }
163
+
164
+ function zIndexOf(node: DumpNode): number {
165
+ const z = Number(node.style?.zIndex ?? 0);
166
+ return Number.isFinite(z) ? z : 0;
167
+ }
168
+
169
+ function borderWidthOf(style: Record<string, unknown>): number {
170
+ return Math.max(
171
+ Number(style.borderWidth ?? 0) || 0,
172
+ Number(style.borderTopWidth ?? 0) || 0,
173
+ Number(style.borderRightWidth ?? 0) || 0,
174
+ Number(style.borderBottomWidth ?? 0) || 0,
175
+ Number(style.borderLeftWidth ?? 0) || 0,
176
+ );
177
+ }
178
+
179
+ function paintsSurface(node: DumpNode): boolean {
180
+ if (!isVisible(node)) return false;
181
+ if (["webview", "image", "svg", "text", "textinput", "textarea"].includes(node.type)) return true;
182
+
183
+ const style = node.style ?? {};
184
+ if (style.backgroundImage) return true;
185
+ if (isVisibleColor(style.backgroundColor)) return true;
186
+ if (isVisibleColor(style.borderColor) && borderWidthOf(style) > 0) return true;
187
+ return typeof style.boxShadow === "string" && style.boxShadow !== "" && style.boxShadow !== "none";
188
+ }
189
+
190
+ function isVisibleColor(value: unknown): boolean {
191
+ if (typeof value !== "string") return false;
192
+ const color = value.trim().toLowerCase();
193
+ if (!color || color === "transparent") return false;
194
+ const hex8 = /^#([0-9a-f]{8})$/.exec(color);
195
+ if (hex8) return hex8[1].slice(6) !== "00";
196
+ const hex4 = /^#([0-9a-f]{4})$/.exec(color);
197
+ if (hex4) return hex4[1].slice(3) !== "0";
198
+ return !/rgba\([^)]*,\s*0(?:\.0+)?\s*\)$/.test(color);
199
+ }
200
+
201
+ export function centerOf(node: DumpNode): { x: number; y: number } | null {
202
+ const b = node.bounds;
203
+ if (!b) return null;
204
+ return { x: b.x + b.width / 2, y: b.y + b.height / 2 };
205
+ }
@@ -0,0 +1,120 @@
1
+ type EndCallback = (result: {
2
+ finished: boolean;
3
+ }) => void;
4
+ interface CompositeAnimation {
5
+ start(cb?: EndCallback): void;
6
+ stop(): void;
7
+ reset(): void;
8
+ }
9
+ export declare class AnimatedValue {
10
+ _value: number;
11
+ private _listeners;
12
+ private _nextListenerId;
13
+ constructor(value?: number);
14
+ setValue(value: number): void;
15
+ setOffset(_offset: number): void;
16
+ flattenOffset(): void;
17
+ extractOffset(): void;
18
+ addListener(_cb: (state: {
19
+ value: number;
20
+ }) => void): string;
21
+ removeListener(_id: string): void;
22
+ removeAllListeners(): void;
23
+ stopAnimation(cb?: (value: number) => void): void;
24
+ resetAnimation(cb?: (value: number) => void): void;
25
+ interpolate(config: InterpolationConfig): AnimatedInterpolation;
26
+ __getValue(): number;
27
+ private _emit;
28
+ }
29
+ type InterpolationConfig = {
30
+ inputRange: number[];
31
+ outputRange: Array<number | string>;
32
+ extrapolate?: "extend" | "clamp" | "identity";
33
+ extrapolateLeft?: "extend" | "clamp" | "identity";
34
+ extrapolateRight?: "extend" | "clamp" | "identity";
35
+ };
36
+ export declare class AnimatedInterpolation {
37
+ private parent;
38
+ private config;
39
+ constructor(parent: AnimatedValue, config: InterpolationConfig);
40
+ addListener(cb: (state: {
41
+ value: number;
42
+ }) => void): string;
43
+ removeListener(id: string): void;
44
+ __getValue(): number | string;
45
+ }
46
+ declare class AnimatedValueXY {
47
+ x: AnimatedValue;
48
+ y: AnimatedValue;
49
+ constructor(value?: {
50
+ x?: number;
51
+ y?: number;
52
+ });
53
+ setValue(value: {
54
+ x: number;
55
+ y: number;
56
+ }): void;
57
+ getLayout(): {
58
+ left: AnimatedValue;
59
+ top: AnimatedValue;
60
+ };
61
+ getTranslateTransform(): ({
62
+ translateX: AnimatedValue;
63
+ translateY?: undefined;
64
+ } | {
65
+ translateY: AnimatedValue;
66
+ translateX?: undefined;
67
+ })[];
68
+ }
69
+ type AnimationConfig = {
70
+ toValue?: unknown;
71
+ duration?: number;
72
+ delay?: number;
73
+ easing?: (value: number) => number;
74
+ };
75
+ export declare const Animated: {
76
+ View: (props: Record<string, unknown>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
77
+ Text: (props: Record<string, unknown>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
78
+ Image: (props: Record<string, unknown>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
79
+ ScrollView: (props: Record<string, unknown>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
80
+ Value: typeof AnimatedValue;
81
+ ValueXY: typeof AnimatedValueXY;
82
+ timing: (value: unknown, config: AnimationConfig) => CompositeAnimation;
83
+ spring: (value: unknown, config: AnimationConfig) => CompositeAnimation;
84
+ decay: (value: unknown, config: AnimationConfig) => CompositeAnimation;
85
+ parallel: (anims: CompositeAnimation[]) => CompositeAnimation;
86
+ sequence: (anims: CompositeAnimation[]) => CompositeAnimation;
87
+ stagger: (_ms: number, anims: CompositeAnimation[]) => CompositeAnimation;
88
+ loop: (anim: CompositeAnimation, config?: {
89
+ iterations?: number;
90
+ }) => CompositeAnimation;
91
+ delay: (ms: number) => CompositeAnimation;
92
+ event: (_argMapping: unknown, _config?: unknown) => () => void;
93
+ createAnimatedComponent: <T>(component: T) => T;
94
+ add: (a: AnimatedValue) => AnimatedValue;
95
+ subtract: (a: AnimatedValue) => AnimatedValue;
96
+ multiply: (a: AnimatedValue) => AnimatedValue;
97
+ divide: (a: AnimatedValue) => AnimatedValue;
98
+ modulo: (a: AnimatedValue) => AnimatedValue;
99
+ diffClamp: () => AnimatedValue;
100
+ };
101
+ export declare const Easing: {
102
+ linear: (t: number) => number;
103
+ ease: (t: number) => number;
104
+ quad: (t: number) => number;
105
+ cubic: (t: number) => number;
106
+ poly: (n: number) => (t: number) => number;
107
+ sin: (t: number) => number;
108
+ circle: (t: number) => number;
109
+ exp: (t: number) => number;
110
+ elastic: () => (t: number) => number;
111
+ back: () => (t: number) => number;
112
+ bounce: (t: number) => number;
113
+ bezier: () => (t: number) => number;
114
+ in: (fn: (t: number) => number) => (t: number) => number;
115
+ out: (fn: (t: number) => number) => (t: number) => number;
116
+ inOut: (fn: (t: number) => number) => (t: number) => number;
117
+ step0: (n: number) => 0 | 1;
118
+ step1: (n: number) => 0 | 1;
119
+ };
120
+ export {};
@@ -0,0 +1,28 @@
1
+ import type { ChatListHandle } from "./chat-list-types";
2
+ export declare const ChatList: import("react").ForwardRefExoticComponent<import("./types").ViewProps & {
3
+ fontSize?: number;
4
+ fontFamily?: string;
5
+ textColor?: string;
6
+ mutedColor?: string;
7
+ linkColor?: string;
8
+ selectionColor?: string;
9
+ separatorColor?: string;
10
+ alternateBackground?: string;
11
+ scrollbarColor?: string;
12
+ disabledOpacity?: number;
13
+ historicalOpacity?: number;
14
+ showTimestamps?: boolean;
15
+ emoteScale?: number;
16
+ animateEmotes?: boolean;
17
+ maxMessages?: number;
18
+ pauseOnHover?: boolean;
19
+ onPress?: (event: {
20
+ nativeEvent: import("./chat-list-types").ChatListPressEvent;
21
+ }) => void;
22
+ onHover?: (event: {
23
+ nativeEvent: import("./chat-list-types").ChatListHoverEvent | null;
24
+ }) => void;
25
+ onScrollState?: (event: {
26
+ nativeEvent: import("./chat-list-types").ChatListScrollState;
27
+ }) => void;
28
+ } & import("react").RefAttributes<ChatListHandle>>;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * RN-compatible Dimensions API, backed by the GPUI window's real size.
3
+ *
4
+ * The service emits a `{type:"resize",width,height}` event whenever the window
5
+ * content box changes; the runtime calls `_setWindow(...)`, which updates the
6
+ * cached size and fires every `change` listener — exactly like RN's Dimensions.
7
+ */
8
+ export type ScaledSize = {
9
+ width: number;
10
+ height: number;
11
+ scale: number;
12
+ fontScale: number;
13
+ };
14
+ type ChangeHandler = (dims: {
15
+ window: ScaledSize;
16
+ screen: ScaledSize;
17
+ }) => void;
18
+ export declare const Dimensions: {
19
+ get(dim: "window" | "screen"): ScaledSize;
20
+ set(_dims: Record<string, ScaledSize>): void;
21
+ addEventListener(type: "change", handler: ChangeHandler): {
22
+ remove: () => void;
23
+ };
24
+ removeEventListener(type: "change", handler: ChangeHandler): void;
25
+ /** internal: called by the runtime when the window reports a new size.
26
+ * Debounced to avoid rapid re-renders during OS-level window resize drag,
27
+ * which can fire at 120Hz. Pass false while seeding the first render, before
28
+ * subscribers exist. Returns true if the size actually changed. */
29
+ _setWindow(width: number, height: number, notifyListeners?: boolean): boolean;
30
+ };
31
+ export {};
@@ -0,0 +1,18 @@
1
+ /**
2
+ * StyleSheet — RN-compatible. `create` is an identity helper; `flatten` collapses
3
+ * arrays; `normalize` expands RN shorthands (paddingHorizontal, shadow*, …) into
4
+ * the flat style the GPUI bridge understands.
5
+ */
6
+ import type { ViewStyle, TextStyle, ImageStyle, StyleProp } from "./types";
7
+ type AnyStyle = Record<string, unknown>;
8
+ export declare const StyleSheet: {
9
+ create<T extends Record<string, ViewStyle | TextStyle | ImageStyle>>(styles: T): T;
10
+ hairlineWidth: number;
11
+ absoluteFill: ViewStyle;
12
+ absoluteFillObject: ViewStyle;
13
+ flatten<T>(style?: StyleProp<T>): T;
14
+ };
15
+ /** Expand RN shorthands → the flat camelCase keys the Rust bridge reads. */
16
+ export declare function normalizeStyle(style: StyleProp<ViewStyle | TextStyle | ImageStyle>): AnyStyle | undefined;
17
+ export declare function cssColorString(value: unknown): string | undefined;
18
+ export {};