gpui-react 0.1.7

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/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # React-GPUI Renderer
2
+
3
+ A React renderer for GPUI (Zed's GPU-accelerated UI framework) using Bun native FFI.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ React Component (JSX)
9
+
10
+ React Reconciler (src/reconciler/host-config.ts)
11
+
12
+ Element Store (src/reconciler/element-store.ts)
13
+
14
+ Bun FFI (src/reconciler/gpui-binding.ts)
15
+
16
+ Rust FFI (rust/src/lib.rs)
17
+
18
+ GPUI Runtime (rust/src/renderer.rs)
19
+
20
+ GPU Rendering (GPUI)
21
+ ```
22
+
23
+ ## Project Structure
24
+
25
+ ```
26
+ gpui-react/
27
+ ├── rust/ # Rust FFI library (cdylib)
28
+ │ └── src/ # FFI exports, GPUI rendering, command bus
29
+ ├── src/reconciler/ # React reconciler, FFI bindings, element store
30
+ └── demo/ # Demo applications (5 entry points)
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ ### 1. Build Rust Library
36
+
37
+ ```bash
38
+ cd rust && cargo build --release
39
+ ```
40
+
41
+ ### 2. Run Demos
42
+
43
+ ```bash
44
+ bun run demo # Basic elements
45
+ bun run event-demo # Event handling
46
+ bun run styled-demo # Styled components
47
+ bun run flex-demo # Flexbox layout
48
+ bun run elements-demo # Span, image elements
49
+ ```
50
+
51
+ ## API Reference
52
+
53
+ ### createRoot(container)
54
+
55
+ Create a GPUI rendering root for a container element.
56
+
57
+ ```typescript
58
+ import { createRoot } from 'gpui-react';
59
+
60
+ const container = document.getElementById('root');
61
+ const root = createRoot(container);
62
+ root.render(<App />);
63
+ ```
64
+
65
+ ### Supported Elements
66
+
67
+ | Element | GPUI Mapping | Notes |
68
+ |---------|--------------|-------|
69
+ | `div` | `div()` | Block container |
70
+ | `span` | `span()` | Inline container, contains text children |
71
+ | `text` | Text nodes | Always child of span |
72
+
73
+ ### Event Handlers
74
+
75
+ ```jsx
76
+ <div onClick={handleClick} onHover={handleHover}>
77
+ <span>Click me</span>
78
+ </div>
79
+ ```
80
+ 的恩托斯
81
+ **Supported:** `onClick`, `onHover`, `onMouseEnter`, `onMouseLeave`
82
+
83
+ ### Styles
84
+
85
+ ```jsx
86
+ <div style={{
87
+ color: '#fff',
88
+ backgroundColor: '#333',
89
+ fontSize: 16,
90
+ width: 200,
91
+ height: 100,
92
+ margin: 10,
93
+ padding: 20,
94
+ borderRadius: 8,
95
+ display: 'flex',
96
+ flexDirection: 'row',
97
+ justifyContent: 'center',
98
+ alignItems: 'center',
99
+ gap: 8
100
+ }}>
101
+ Content
102
+ </div>
103
+ ```
104
+
105
+ ## Documentation
106
+
107
+ - **[AGENTS.md](./AGENTS.md)** - Root knowledge base
108
+ - **[src/reconciler/AGENTS.md](./src/reconciler/AGENTS.md)** - React reconciler layer
109
+ - **[rust/src/AGENTS.md](./rust/src/AGENTS.md)** - Rust FFI layer
110
+ - **[ROADMAP.md](./ROADMAP.md)** - Development roadmap
111
+
112
+ ## Requirements
113
+
114
+ - [Bun](https://bun.sh) ≥ 1.0
115
+ - [Rust](https://rust-lang.org) ≥ 1.70
116
+ - GPUI dependencies (downloaded automatically on first cargo build)
@@ -0,0 +1,2 @@
1
+ export * from "./reconciler/renderer";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from "./reconciler/renderer";
@@ -0,0 +1,7 @@
1
+ interface AppContext {
2
+ windowId: number;
3
+ }
4
+ export declare const AppContext: import("react").Context<AppContext>;
5
+ export declare const useAppContext: () => AppContext;
6
+ export {};
7
+ //# sourceMappingURL=ctx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ctx.d.ts","sourceRoot":"","sources":["../../src/reconciler/ctx.ts"],"names":[],"mappings":"AAEA,UAAU,UAAU;IAChB,QAAQ,EAAE,MAAM,CAAA;CACnB;AAED,eAAO,MAAM,UAAU,qCAErB,CAAA;AAEF,eAAO,MAAM,aAAa,kBAEzB,CAAA"}
@@ -0,0 +1,7 @@
1
+ import { createContext, useContext } from "react";
2
+ export const AppContext = createContext({
3
+ windowId: 0
4
+ });
5
+ export const useAppContext = () => {
6
+ return useContext(AppContext);
7
+ };
@@ -0,0 +1,22 @@
1
+ export interface ElementData {
2
+ globalId: number;
3
+ type: string;
4
+ text?: string;
5
+ children: number[];
6
+ style?: Record<string, any>;
7
+ eventHandlers?: Record<string, number>;
8
+ }
9
+ export declare class ElementStore {
10
+ private store;
11
+ private nextId;
12
+ private rootId;
13
+ reset(): void;
14
+ createElement(type: string, text?: string, style?: Record<string, any>): number;
15
+ getStore(element: ElementData): ElementStore | undefined;
16
+ appendChild(parentId: number, childId: number): void;
17
+ removeChild(parentId: number, childId: number): void;
18
+ setContainerChild(childId: number): void;
19
+ getElement(globalId: number): ElementData | undefined;
20
+ getRoot(): ElementData;
21
+ }
22
+ //# sourceMappingURL=element-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element-store.d.ts","sourceRoot":"","sources":["../../src/reconciler/element-store.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAID,qBAAa,YAAY;IACvB,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,MAAM,CAAuB;IAErC,KAAK,IAAI,IAAI;IAMb,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM;IAe/E,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,YAAY,GAAG,SAAS;IAIxD,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAOpD,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAUpD,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAOxC,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAIrD,OAAO,IAAI,WAAW;CAMvB"}
@@ -0,0 +1,61 @@
1
+ import { trace } from "./logging";
2
+ const STORE_SYMBOL = Symbol('store');
3
+ export class ElementStore {
4
+ store = new Map();
5
+ nextId = 2;
6
+ rootId = null;
7
+ reset() {
8
+ this.store.clear();
9
+ this.nextId = 2;
10
+ this.rootId = null;
11
+ }
12
+ createElement(type, text, style) {
13
+ const globalId = this.nextId++;
14
+ const element = {
15
+ globalId,
16
+ type,
17
+ text,
18
+ style,
19
+ children: [],
20
+ };
21
+ Object.defineProperty(element, STORE_SYMBOL, { value: this, writable: false, enumerable: false });
22
+ this.store.set(globalId, element);
23
+ trace("createElement", { type, globalId, style });
24
+ return globalId;
25
+ }
26
+ getStore(element) {
27
+ return element[STORE_SYMBOL];
28
+ }
29
+ appendChild(parentId, childId) {
30
+ const parent = this.store.get(parentId);
31
+ if (!parent)
32
+ throw new Error(`Parent element ${parentId} not found`);
33
+ parent.children.push(childId);
34
+ trace("appendChild", { parentId, childId });
35
+ }
36
+ removeChild(parentId, childId) {
37
+ const parent = this.store.get(parentId);
38
+ if (!parent)
39
+ throw new Error(`Parent element ${parentId} not found`);
40
+ const index = parent.children.indexOf(childId);
41
+ if (index !== -1) {
42
+ parent.children.splice(index, 1);
43
+ trace("removeChild", { parentId, childId });
44
+ }
45
+ }
46
+ setContainerChild(childId) {
47
+ if (this.rootId === null) {
48
+ this.rootId = childId;
49
+ trace("setContainerChild - rootId", this.rootId);
50
+ }
51
+ }
52
+ getElement(globalId) {
53
+ return this.store.get(globalId);
54
+ }
55
+ getRoot() {
56
+ if (this.rootId === null) {
57
+ throw new Error("No root element created yet");
58
+ }
59
+ return this.store.get(this.rootId);
60
+ }
61
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Event Types for React-GPUI Renderer
3
+ */
4
+ export type EventHandler = (event: Event) => void;
5
+ export type MouseEvent = {
6
+ type: 'click' | 'hover' | 'mouseenter' | 'mouseleave';
7
+ target: number;
8
+ button?: number;
9
+ clientX?: number;
10
+ clientY?: number;
11
+ };
12
+ export type Event = MouseEvent;
13
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/reconciler/events.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;AAElD,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,YAAY,GAAG,YAAY,CAAC;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG,UAAU,CAAC"}
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Event Types for React-GPUI Renderer
3
+ */
4
+ export {};
@@ -0,0 +1,7 @@
1
+ export declare let currentWindowId: number;
2
+ export declare function init(): void;
3
+ export declare function createWindow(width: number, height: number, title?: string): number;
4
+ export declare function renderFrame(windowId: number, element: any): void;
5
+ export declare function triggerRender(windowId: number): void;
6
+ export declare function batchElementUpdates(windowId: number, elements: any[]): void;
7
+ //# sourceMappingURL=gpui-binding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gpui-binding.d.ts","sourceRoot":"","sources":["../../src/reconciler/gpui-binding.ts"],"names":[],"mappings":"AAiDA,eAAO,IAAI,eAAe,EAAE,MAAU,CAAC;AA8BvC,wBAAgB,IAAI,IAAI,IAAI,CAM3B;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAalF;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,IAAI,CAsEhE;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAQpD;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,IAAI,CAiC3E"}
@@ -0,0 +1,179 @@
1
+ import { dlopen, FFIType, suffix, ptr } from "bun:ffi";
2
+ import { join } from "path";
3
+ import { sleep } from "bun";
4
+ import { info, debug, trace } from "./logging";
5
+ const libName = `libgpui_renderer.${suffix}`;
6
+ const libPath = join(import.meta.dir, "../native/linux-x64", libName);
7
+ info(`Loading GPUI library from: ${libPath}`);
8
+ const liveBuffers = [];
9
+ const lib = dlopen(libPath, {
10
+ gpui_init: {
11
+ args: [FFIType.ptr],
12
+ returns: FFIType.void,
13
+ },
14
+ gpui_create_window: {
15
+ args: [FFIType.f32, FFIType.f32, FFIType.ptr, FFIType.ptr],
16
+ returns: FFIType.void,
17
+ },
18
+ gpui_is_ready: {
19
+ args: [],
20
+ returns: FFIType.bool,
21
+ },
22
+ gpui_render_frame: {
23
+ args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr],
24
+ returns: FFIType.void,
25
+ },
26
+ gpui_trigger_render: {
27
+ args: [FFIType.ptr, FFIType.ptr],
28
+ returns: FFIType.void,
29
+ },
30
+ gpui_free_result: {
31
+ args: [FFIType.ptr],
32
+ returns: FFIType.void,
33
+ },
34
+ gpui_batch_update_elements: {
35
+ args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr],
36
+ returns: FFIType.void,
37
+ },
38
+ });
39
+ if (!lib.symbols) {
40
+ throw new Error("Failed to load GPUI library");
41
+ }
42
+ const FFI_RESULT_SIZE = 16;
43
+ export let currentWindowId = 0;
44
+ function checkResult(resultBuffer) {
45
+ const status = new DataView(resultBuffer.buffer).getInt32(0, true);
46
+ if (status !== 0) {
47
+ const errorPtr = new DataView(resultBuffer.buffer).getInt32(8, true);
48
+ lib.symbols.gpui_free_result(resultBuffer);
49
+ throw new Error(`GPUI operation failed: error ptr=${errorPtr}`);
50
+ }
51
+ const errorCheck = new DataView(resultBuffer.buffer).getInt32(8, true);
52
+ if (errorCheck !== 0) {
53
+ lib.symbols.gpui_free_result(resultBuffer);
54
+ }
55
+ }
56
+ function checkWindowCreateResult(resultBuffer) {
57
+ const status = new DataView(resultBuffer.buffer).getInt32(0, true);
58
+ if (status !== 0) {
59
+ const errorPtr = new DataView(resultBuffer.buffer).getInt32(8, true);
60
+ lib.symbols.gpui_free_result(resultBuffer);
61
+ throw new Error(`GPUI window creation failed: error ptr=${errorPtr}`);
62
+ }
63
+ const windowId = new DataView(resultBuffer.buffer).getBigUint64(8, true);
64
+ return Number(windowId);
65
+ }
66
+ export function init() {
67
+ const resultBuffer = new Uint8Array(FFI_RESULT_SIZE);
68
+ lib.symbols.gpui_init(resultBuffer);
69
+ checkResult(resultBuffer);
70
+ waitGpuiReady();
71
+ }
72
+ export function createWindow(width, height, title) {
73
+ const resultBuffer = new Uint8Array(FFI_RESULT_SIZE);
74
+ // 编码 title 字符串,添加 null 终止符
75
+ const titleStr = title || "React-GPUI";
76
+ const titleBuffer = new TextEncoder().encode(titleStr + "\0");
77
+ liveBuffers.push(titleBuffer.buffer); // 保持引用防止 GC
78
+ const titlePtr = ptr(titleBuffer);
79
+ lib.symbols.gpui_create_window(width, height, titlePtr, resultBuffer);
80
+ currentWindowId = checkWindowCreateResult(resultBuffer);
81
+ info(`Created window with id: ${currentWindowId}`);
82
+ return currentWindowId;
83
+ }
84
+ export function renderFrame(windowId, element) {
85
+ trace("renderFrame called");
86
+ debug("Element", element);
87
+ liveBuffers.length = 0;
88
+ const windowIdBuffer = new ArrayBuffer(8);
89
+ new DataView(windowIdBuffer).setBigInt64(0, BigInt(windowId), true);
90
+ liveBuffers.push(windowIdBuffer);
91
+ const windowIdPtr = ptr(windowIdBuffer);
92
+ const globalIdBuffer = new ArrayBuffer(8);
93
+ new DataView(globalIdBuffer).setBigInt64(0, BigInt(element.globalId), true);
94
+ liveBuffers.push(globalIdBuffer);
95
+ const globalIdPtr = ptr(globalIdBuffer);
96
+ const typeBuffer = new TextEncoder().encode(element.type + "\0");
97
+ liveBuffers.push(typeBuffer.buffer);
98
+ const typePtr = ptr(typeBuffer);
99
+ const textContent = element.text || " ";
100
+ const textBuffer = new TextEncoder().encode(textContent + "\0");
101
+ liveBuffers.push(textBuffer.buffer);
102
+ const textPtr = ptr(textBuffer);
103
+ const childrenArray = element.children || [];
104
+ const childCountBuffer = new ArrayBuffer(8);
105
+ new DataView(childCountBuffer).setBigInt64(0, BigInt(childrenArray.length), true);
106
+ liveBuffers.push(childCountBuffer);
107
+ const childCountPtr = ptr(childCountBuffer);
108
+ const childrenByteLength = Math.max(childrenArray.length * 8, 8);
109
+ const childrenBuffer = new ArrayBuffer(childrenByteLength);
110
+ if (childrenArray.length > 0) {
111
+ const childrenView = new BigInt64Array(childrenBuffer);
112
+ for (let i = 0; i < childrenArray.length; i++) {
113
+ childrenView[i] = BigInt(childrenArray[i]);
114
+ }
115
+ }
116
+ liveBuffers.push(childrenBuffer);
117
+ const childrenPtr = ptr(childrenBuffer);
118
+ debug("FFI params", {
119
+ windowId,
120
+ globalId: element.globalId,
121
+ type: element.type,
122
+ text: textContent,
123
+ childCount: childrenArray.length,
124
+ children: childrenArray
125
+ });
126
+ const resultBuffer = new Uint8Array(8);
127
+ lib.symbols.gpui_render_frame(windowIdPtr, globalIdPtr, typePtr, textPtr, childCountPtr, childrenPtr, resultBuffer);
128
+ const status = new DataView(resultBuffer.buffer).getInt32(0, true);
129
+ if (status !== 0) {
130
+ throw new Error(`GPUI render failed with status: ${status}`);
131
+ }
132
+ trace("renderFrame completed");
133
+ }
134
+ export function triggerRender(windowId) {
135
+ const windowIdBuffer = new ArrayBuffer(8);
136
+ new DataView(windowIdBuffer).setBigInt64(0, BigInt(windowId), true);
137
+ liveBuffers.push(windowIdBuffer);
138
+ const windowIdPtr = ptr(windowIdBuffer);
139
+ const triggerBuffer = new Uint8Array(8);
140
+ lib.symbols.gpui_trigger_render(windowIdPtr, triggerBuffer);
141
+ }
142
+ export function batchElementUpdates(windowId, elements) {
143
+ if (elements.length === 0) {
144
+ return;
145
+ }
146
+ info(`Batching ${elements.length} element updates for window ${windowId}`);
147
+ liveBuffers.length = 0;
148
+ const windowIdBuffer = new ArrayBuffer(8);
149
+ new DataView(windowIdBuffer).setBigInt64(0, BigInt(windowId), true);
150
+ liveBuffers.push(windowIdBuffer);
151
+ const windowIdPtr = ptr(windowIdBuffer);
152
+ const countBuffer = new ArrayBuffer(8);
153
+ new DataView(countBuffer).setBigInt64(0, BigInt(elements.length), true);
154
+ liveBuffers.push(countBuffer);
155
+ const countPtr = ptr(countBuffer);
156
+ const elementsJsonString = JSON.stringify(elements);
157
+ debug("batchElementUpdates - elements JSON", elementsJsonString.substring(0, 500));
158
+ const elementsBuffer = new TextEncoder().encode(elementsJsonString + "\0");
159
+ liveBuffers.push(elementsBuffer.buffer);
160
+ const elementsPtr = ptr(elementsBuffer);
161
+ const resultBuffer = new Uint8Array(8);
162
+ lib.symbols.gpui_batch_update_elements(windowIdPtr, countPtr, elementsPtr, resultBuffer);
163
+ const triggerBuffer = new Uint8Array(8);
164
+ lib.symbols.gpui_trigger_render(windowIdPtr, triggerBuffer);
165
+ info(`Batch update completed for ${elements.length} elements`);
166
+ }
167
+ function waitGpuiReady() {
168
+ let delay = 1;
169
+ const maxDelay = 100;
170
+ const maxTotalWait = 5000;
171
+ const startTime = Date.now();
172
+ while (Date.now() - startTime < maxTotalWait) {
173
+ if (lib.symbols.gpui_is_ready()) {
174
+ break;
175
+ }
176
+ sleep(Math.min(delay, maxDelay));
177
+ delay *= 2;
178
+ }
179
+ }
@@ -0,0 +1,33 @@
1
+ import { ElementStore } from "./element-store";
2
+ import { HostConfig } from "react-reconciler";
3
+ type Type = string;
4
+ type Props = any;
5
+ type Container = ElementStore;
6
+ type SuspenseInstance = never;
7
+ type HydratableInstance = never;
8
+ type FormInstance = never;
9
+ type PublicInstance = Element;
10
+ type HostContext = object;
11
+ type ChildSet = never;
12
+ type TimeoutHandle = number;
13
+ type NoTimeout = -1;
14
+ type TransitionStatus = any;
15
+ export interface Instance {
16
+ id: number;
17
+ type: string;
18
+ text?: string;
19
+ children: Instance[];
20
+ style?: Record<string, any>;
21
+ eventHandlers?: Record<string, number>;
22
+ store: ElementStore;
23
+ }
24
+ export interface TextInstance {
25
+ id: number;
26
+ type: "text";
27
+ text: string;
28
+ children: [];
29
+ store: ElementStore;
30
+ }
31
+ export declare const hostConfig: HostConfig<Type, Props, Container, Instance, TextInstance, SuspenseInstance, HydratableInstance, FormInstance, PublicInstance, HostContext, ChildSet, TimeoutHandle, NoTimeout, TransitionStatus>;
32
+ export {};
33
+ //# sourceMappingURL=host-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-config.d.ts","sourceRoot":"","sources":["../../src/reconciler/host-config.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,YAAY,EAAc,MAAM,iBAAiB,CAAC;AAI1D,OAAO,EAAC,UAAU,EAAe,MAAM,kBAAkB,CAAC;AAM1D,KAAK,IAAI,GAAG,MAAM,CAAC;AACnB,KAAK,KAAK,GAAG,GAAG,CAAC;AACjB,KAAK,SAAS,GAAG,YAAY,CAAC;AAC9B,KAAK,gBAAgB,GAAG,KAAK,CAAC;AAC9B,KAAK,kBAAkB,GAAG,KAAK,CAAC;AAChC,KAAK,YAAY,GAAG,KAAK,CAAC;AAC1B,KAAK,cAAc,GAAG,OAAO,CAAC;AAC9B,KAAK,WAAW,GAAG,MAAM,CAAC;AAC1B,KAAK,QAAQ,GAAG,KAAK,CAAC;AACtB,KAAK,aAAa,GAAG,MAAM,CAAC;AAC5B,KAAK,SAAS,GAAG,CAAC,CAAC,CAAC;AACpB,KAAK,gBAAgB,GAAG,GAAG,CAAC;AAG5B,MAAM,WAAW,QAAQ;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,KAAK,EAAE,YAAY,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,EAAE,CAAC;IACb,KAAK,EAAE,YAAY,CAAC;CACvB;AA6ED,eAAO,MAAM,UAAU,EAAE,UAAU,CAC/B,IAAI,EACJ,KAAK,EACL,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACZ,cAAc,EACd,WAAW,EACX,QAAQ,EACR,aAAa,EACb,SAAS,EACT,gBAAgB,CA0SnB,CAAA"}
@@ -0,0 +1,270 @@
1
+ import { renderFrame, batchElementUpdates, currentWindowId } from "./gpui-binding";
2
+ import { mapStyleToProps } from "./styles";
3
+ import { DefaultEventPriority, NoEventPriority } from "react-reconciler/constants";
4
+ import { trace, info, warn } from "./logging";
5
+ let nextEventId = 0;
6
+ const eventHandlers = new Map();
7
+ const pendingUpdates = [];
8
+ function registerEventHandler(handler) {
9
+ const id = nextEventId++;
10
+ eventHandlers.set(id, handler);
11
+ return id;
12
+ }
13
+ function extractStyleProps(props) {
14
+ const styleProps = {};
15
+ if (props.style) {
16
+ Object.assign(styleProps, props.style);
17
+ }
18
+ if (props.className) {
19
+ warn("className not yet supported, use style prop instead");
20
+ }
21
+ if (props.onClick) {
22
+ styleProps.onClick = props.onClick;
23
+ }
24
+ if (props.onHover) {
25
+ styleProps.onHover = props.onHover;
26
+ }
27
+ if (props.onMouseEnter) {
28
+ styleProps.onMouseEnter = props.onMouseEnter;
29
+ }
30
+ if (props.onMouseLeave) {
31
+ styleProps.onMouseLeave = props.onMouseLeave;
32
+ }
33
+ return styleProps;
34
+ }
35
+ function extractEventHandlers(props) {
36
+ const handlers = {};
37
+ if (props.onClick) {
38
+ handlers['onClick'] = registerEventHandler(props.onClick);
39
+ }
40
+ if (props.onHover) {
41
+ handlers['onHover'] = registerEventHandler(props.onHover);
42
+ }
43
+ if (props.onMouseEnter) {
44
+ handlers['onMouseEnter'] = registerEventHandler(props.onMouseEnter);
45
+ }
46
+ if (props.onMouseLeave) {
47
+ handlers['onMouseLeave'] = registerEventHandler(props.onMouseLeave);
48
+ }
49
+ return handlers;
50
+ }
51
+ function queueElementUpdate(element) {
52
+ if (!element)
53
+ return;
54
+ const existingIndex = pendingUpdates.findIndex((e) => e.globalId === element.globalId);
55
+ if (existingIndex !== -1) {
56
+ pendingUpdates[existingIndex] = element;
57
+ }
58
+ else {
59
+ pendingUpdates.push(element);
60
+ }
61
+ }
62
+ let currentUpdatePriority = 0;
63
+ export const hostConfig = {
64
+ supportsMutation: true,
65
+ supportsPersistence: false,
66
+ supportsHydration: false,
67
+ isPrimaryRenderer: true,
68
+ getPublicInstance(_instance) {
69
+ return document.createElement("div");
70
+ },
71
+ getRootHostContext(_rootContainer) {
72
+ return {};
73
+ },
74
+ getChildHostContext(_parentHostContext, _type, _rootContainer) {
75
+ return {};
76
+ },
77
+ prepareForCommit(_containerInfo) {
78
+ return {};
79
+ },
80
+ resetAfterCommit(containerInfo) {
81
+ if (pendingUpdates.length > 0) {
82
+ info(`Processing ${pendingUpdates.length} batched updates`);
83
+ batchElementUpdates(currentWindowId, pendingUpdates);
84
+ pendingUpdates.length = 0;
85
+ }
86
+ const root = containerInfo.getRoot();
87
+ trace("resetAfterCommit - root element", root);
88
+ renderFrame(currentWindowId, root);
89
+ setImmediate(() => {
90
+ if (typeof window !== 'undefined' && window.__gpuiTrigger) {
91
+ window.__gpuiTrigger();
92
+ }
93
+ });
94
+ },
95
+ shouldSetTextContent(_type, _props) {
96
+ return false;
97
+ },
98
+ resetTextContent(_instance) {
99
+ },
100
+ createTextInstance(text, rootContainer, _hostContext, _internalHandle) {
101
+ const styleProps = extractStyleProps({ style: {} });
102
+ const styles = mapStyleToProps(styleProps);
103
+ const id = rootContainer.createElement("text", String(text), styles);
104
+ trace("createTextInstance", { text, id, styles });
105
+ const instance = {
106
+ id,
107
+ type: "text",
108
+ text: String(text),
109
+ children: [],
110
+ store: rootContainer,
111
+ };
112
+ queueElementUpdate(rootContainer.getElement(id));
113
+ return instance;
114
+ },
115
+ commitTextUpdate(textInstance, oldText, newText) {
116
+ textInstance.text = String(newText);
117
+ trace("commitTextUpdate", { oldText, newText });
118
+ const element = textInstance.store.getElement(textInstance.id);
119
+ if (element) {
120
+ element.text = String(newText);
121
+ queueElementUpdate(element);
122
+ }
123
+ },
124
+ createInstance(type, props, rootContainer, _hostContext, _internalHandle) {
125
+ const styleProps = extractStyleProps(props);
126
+ const styles = mapStyleToProps(styleProps);
127
+ const eventHandlers = extractEventHandlers(props);
128
+ const id = rootContainer.createElement(type, undefined, { ...styles, eventHandlers });
129
+ trace("createInstance", { type, id, styles, eventHandlers });
130
+ const instance = {
131
+ id,
132
+ type,
133
+ children: [],
134
+ style: styles,
135
+ eventHandlers,
136
+ store: rootContainer,
137
+ };
138
+ queueElementUpdate(rootContainer.getElement(id));
139
+ return instance;
140
+ },
141
+ appendInitialChild(parentInstance, child) {
142
+ trace("appendInitialChild", { parent: parentInstance, child });
143
+ parentInstance.children.push(child);
144
+ parentInstance.store.appendChild(parentInstance.id, child.id);
145
+ queueElementUpdate(parentInstance.store.getElement(parentInstance.id));
146
+ },
147
+ appendChild(parentInstance, child) {
148
+ trace("appendChild", { parent: parentInstance, child });
149
+ parentInstance.children.push(child);
150
+ parentInstance.store.appendChild(parentInstance.id, child.id);
151
+ queueElementUpdate(parentInstance.store.getElement(parentInstance.id));
152
+ },
153
+ appendChildToContainer(container, child) {
154
+ trace("appendChildToContainer", { container, child });
155
+ const childInstance = child;
156
+ container.setContainerChild(childInstance.id);
157
+ queueElementUpdate(container.getElement(childInstance.id));
158
+ },
159
+ insertBefore(parentInstance, child, beforeChild) {
160
+ const childInstance = child;
161
+ const beforeChildInstance = beforeChild;
162
+ const beforeIndex = parentInstance.children.indexOf(beforeChildInstance);
163
+ if (beforeIndex !== -1) {
164
+ parentInstance.children.splice(beforeIndex, 0, childInstance);
165
+ }
166
+ else {
167
+ parentInstance.children.push(childInstance);
168
+ }
169
+ queueElementUpdate(parentInstance.store.getElement(parentInstance.id));
170
+ },
171
+ insertInContainerBefore(_container, child, beforeChild) {
172
+ trace("insertInContainerBefore", { child, beforeChild });
173
+ },
174
+ removeChild(parentInstance, child) {
175
+ const childInstance = child;
176
+ const index = parentInstance.children.indexOf(childInstance);
177
+ if (index !== -1) {
178
+ parentInstance.children.splice(index, 1);
179
+ }
180
+ parentInstance.store.removeChild(parentInstance.id, childInstance.id);
181
+ queueElementUpdate(parentInstance.store.getElement(parentInstance.id));
182
+ },
183
+ removeChildFromContainer(_container, child) {
184
+ trace("removeChildFromContainer", { container: _container, child });
185
+ const childInstance = child;
186
+ _container.removeChild(_container.getRoot().globalId, childInstance.id);
187
+ },
188
+ commitUpdate(instance, _type, _prevProps, nextProps, _internalHandle) {
189
+ if (nextProps && typeof nextProps.children === "string") {
190
+ instance.text = String(nextProps.children);
191
+ const element = instance.store.getElement(instance.id);
192
+ if (element) {
193
+ queueElementUpdate(element);
194
+ }
195
+ }
196
+ },
197
+ finalizeInitialChildren(_instance, _type, _props, _rootContainer, _hostContext) {
198
+ return false;
199
+ },
200
+ clearContainer(_container) {
201
+ },
202
+ hideInstance(_instance) {
203
+ },
204
+ unhideInstance(_instance, _props) {
205
+ },
206
+ detachDeletedInstance(instance) {
207
+ trace("detachDeletedInstance", { instance });
208
+ },
209
+ prepareScopeUpdate(_scopeInstance, _instance) {
210
+ },
211
+ getInstanceFromScope(_scopeInstance) {
212
+ return null;
213
+ },
214
+ scheduleTimeout(fn, delay) {
215
+ return setTimeout(fn, delay);
216
+ },
217
+ cancelTimeout(id) {
218
+ clearTimeout(id);
219
+ },
220
+ noTimeout: -1,
221
+ preparePortalMount(_containerInfo) {
222
+ },
223
+ getInstanceFromNode(_node) {
224
+ return null;
225
+ },
226
+ beforeActiveInstanceBlur() {
227
+ },
228
+ afterActiveInstanceBlur() {
229
+ },
230
+ NotPendingTransition: null,
231
+ HostTransitionContext: null,
232
+ resetFormInstance(_form) {
233
+ },
234
+ requestPostPaintCallback(_callback) {
235
+ },
236
+ shouldAttemptEagerTransition() {
237
+ return false;
238
+ },
239
+ trackSchedulerEvent() {
240
+ },
241
+ resolveEventType() {
242
+ return null;
243
+ },
244
+ resolveEventTimeStamp() {
245
+ return Date.now();
246
+ },
247
+ maySuspendCommit(_type, _props) {
248
+ return false;
249
+ },
250
+ preloadInstance(_type, _props) {
251
+ return false;
252
+ },
253
+ startSuspendingCommit() {
254
+ },
255
+ suspendInstance(_type, _props) {
256
+ },
257
+ waitForCommitToBeReady() {
258
+ return null;
259
+ },
260
+ setCurrentUpdatePriority(newPriority) {
261
+ currentUpdatePriority = newPriority;
262
+ },
263
+ getCurrentUpdatePriority: () => currentUpdatePriority,
264
+ resolveUpdatePriority() {
265
+ if (currentUpdatePriority !== NoEventPriority) {
266
+ return currentUpdatePriority;
267
+ }
268
+ return DefaultEventPriority;
269
+ },
270
+ };
@@ -0,0 +1,10 @@
1
+ type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace';
2
+ export declare function initLogging(): void;
3
+ export declare function log(level: LogLevel, message: string, ...args: unknown[]): void;
4
+ export declare function trace(message: string, ...args: unknown[]): void;
5
+ export declare function debug(message: string, ...args: unknown[]): void;
6
+ export declare function info(message: string, ...args: unknown[]): void;
7
+ export declare function warn(message: string, ...args: unknown[]): void;
8
+ export declare function error(message: string, ...args: unknown[]): void;
9
+ export {};
10
+ //# sourceMappingURL=logging.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../../src/reconciler/logging.ts"],"names":[],"mappings":"AAAA,KAAK,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAkC9D,wBAAgB,WAAW,IAAI,IAAI,CAGlC;AAED,wBAAgB,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAoB9E;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAE/D;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAE/D;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAE9D;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAE9D;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAE/D"}
@@ -0,0 +1,68 @@
1
+ const LOG_LEVELS = ['error', 'warn', 'info', 'debug', 'trace'];
2
+ function getLogLevel() {
3
+ const envLevel = process.env.RUST_LOG?.toLowerCase();
4
+ if (envLevel && LOG_LEVELS.includes(envLevel)) {
5
+ return envLevel;
6
+ }
7
+ return 'info';
8
+ }
9
+ function getLocalTime() {
10
+ const now = new Date();
11
+ const year = now.getFullYear();
12
+ const month = String(now.getMonth() + 1).padStart(2, '0');
13
+ const day = String(now.getDate()).padStart(2, '0');
14
+ const hours = String(now.getHours()).padStart(2, '0');
15
+ const minutes = String(now.getMinutes()).padStart(2, '0');
16
+ const seconds = String(now.getSeconds()).padStart(2, '0');
17
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
18
+ }
19
+ function shouldLog(level) {
20
+ const currentLevel = getLogLevel();
21
+ return LOG_LEVELS.indexOf(level) <= LOG_LEVELS.indexOf(currentLevel);
22
+ }
23
+ function formatMessage(level, message, args) {
24
+ const timestamp = getLocalTime();
25
+ const argsStr = args && args.length > 0 ? '\n' + args.map(a => JSON.stringify(a, null, 2)).join('\n') : '';
26
+ return `${timestamp} ${level.toUpperCase()} ${message}${argsStr}`;
27
+ }
28
+ export function initLogging() {
29
+ const level = getLogLevel();
30
+ console.log(`${getLocalTime()} INFO init: Logging system initialized (level: ${level})`);
31
+ }
32
+ export function log(level, message, ...args) {
33
+ if (!shouldLog(level))
34
+ return;
35
+ const formatted = formatMessage(level, message, args);
36
+ switch (level) {
37
+ case 'error':
38
+ console.error(formatted);
39
+ break;
40
+ case 'warn':
41
+ console.warn(formatted);
42
+ break;
43
+ case 'info':
44
+ console.log(formatted);
45
+ break;
46
+ case 'debug':
47
+ console.debug(formatted);
48
+ break;
49
+ case 'trace':
50
+ console.trace(formatted);
51
+ break;
52
+ }
53
+ }
54
+ export function trace(message, ...args) {
55
+ log('trace', message, ...args);
56
+ }
57
+ export function debug(message, ...args) {
58
+ log('debug', message, ...args);
59
+ }
60
+ export function info(message, ...args) {
61
+ log('info', message, ...args);
62
+ }
63
+ export function warn(message, ...args) {
64
+ log('warn', message, ...args);
65
+ }
66
+ export function error(message, ...args) {
67
+ log('error', message, ...args);
68
+ }
@@ -0,0 +1,6 @@
1
+ import ReactReconciler from "react-reconciler";
2
+ import React from "react";
3
+ import { ElementStore } from "./element-store";
4
+ export declare const reconciler: ReactReconciler.Reconciler<ElementStore, import("./host-config").Instance, import("./host-config").TextInstance, never, never, Element>;
5
+ export declare function _render(element: React.ReactNode, root: ElementStore): any;
6
+ //# sourceMappingURL=reconciler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconciler.d.ts","sourceRoot":"","sources":["../../src/reconciler/reconciler.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,kBAAkB,CAAC;AAE/C,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAc,YAAY,EAAC,MAAM,iBAAiB,CAAC;AAG1D,eAAO,MAAM,UAAU,yIAA8B,CAAA;AAGrD,wBAAgB,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE,YAAY,OAkBnE"}
@@ -0,0 +1,9 @@
1
+ import ReactReconciler from "react-reconciler";
2
+ import { hostConfig } from "./host-config";
3
+ import { ConcurrentRoot } from "react-reconciler/constants";
4
+ export const reconciler = ReactReconciler(hostConfig);
5
+ export function _render(element, root) {
6
+ const container = reconciler.createContainer(root, ConcurrentRoot, null, false, null, "", console.error, console.error, console.error, console.error, null);
7
+ reconciler.updateContainer(element, container, null, () => { });
8
+ return container;
9
+ }
@@ -0,0 +1,12 @@
1
+ import * as React from "react";
2
+ export type Root = {
3
+ render: (children: React.ReactNode) => void;
4
+ unmount: () => void;
5
+ };
6
+ export type RootProps = {
7
+ width: number;
8
+ height: number;
9
+ title?: string;
10
+ };
11
+ export declare function createRoot({ width, height, title }: RootProps): Root;
12
+ //# sourceMappingURL=renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/reconciler/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,MAAM,MAAM,IAAI,GAAG;IACf,MAAM,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,KAAK,IAAI,CAAC;IAC5C,OAAO,EAAE,MAAM,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB,CAAA;AAED,wBAAgB,UAAU,CAAC,EACI,KAAK,EAAE,MAAM,EAAE,KAAK,EACvB,EAAE,SAAS,GAAG,IAAI,CAuB7C"}
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+ import { _render, reconciler } from "./reconciler";
3
+ import { init, createWindow } from "./gpui-binding";
4
+ import { ElementStore } from "./element-store";
5
+ import { AppContext } from "./ctx";
6
+ export function createRoot({ width, height, title }) {
7
+ let container = null;
8
+ init();
9
+ const windowId = createWindow(width, height, title);
10
+ console.log("Created window with id:", windowId);
11
+ const elementStore = new ElementStore();
12
+ return {
13
+ render(node) {
14
+ container = _render(React.createElement(AppContext.Provider, { value: { windowId } }, node), elementStore);
15
+ },
16
+ unmount() {
17
+ reconciler.updateContainer(null, container, null, () => {
18
+ });
19
+ },
20
+ };
21
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Style Utilities for React-GPUI Renderer
3
+ *
4
+ * Handles conversion between React style props and GPUI style values
5
+ */
6
+ export interface StyleProps {
7
+ color?: string;
8
+ backgroundColor?: string;
9
+ borderColor?: string;
10
+ fontSize?: number | string;
11
+ fontWeight?: string | number;
12
+ width?: number | string;
13
+ height?: number | string;
14
+ margin?: number | string;
15
+ marginTop?: number | string;
16
+ marginRight?: number | string;
17
+ marginBottom?: number | string;
18
+ marginLeft?: number | string;
19
+ padding?: number | string;
20
+ paddingTop?: number | string;
21
+ paddingRight?: number | string;
22
+ paddingBottom?: number | string;
23
+ paddingLeft?: number | string;
24
+ display?: 'flex' | 'block' | 'inline' | 'inline-block';
25
+ flexDirection?: 'row' | 'column';
26
+ justifyContent?: 'flex-start' | 'center' | 'flex-end' | 'space-between' | 'space-around' | 'space-evenly';
27
+ alignItems?: 'flex-start' | 'center' | 'flex-end' | 'stretch';
28
+ gap?: number | string;
29
+ borderRadius?: number | string;
30
+ opacity?: number;
31
+ src?: string;
32
+ alt?: string;
33
+ onClick?: (event: MouseEvent) => void;
34
+ onHover?: (event: MouseEvent) => void;
35
+ onMouseEnter?: (event: MouseEvent) => void;
36
+ onMouseLeave?: (event: MouseEvent) => void;
37
+ }
38
+ /**
39
+ * Parse CSS color string to GPUI RGB value
40
+ * Supports: hex (#RRGGBB, #RGB), rgb(r, g, b), rgba(r, g, b, a), named colors
41
+ */
42
+ export declare function parseColor(color: string): number;
43
+ /**
44
+ * Parse size value to pixels
45
+ * Supports: px, em, rem, %, number (assumed px)
46
+ */
47
+ export declare function parseSize(size: number | string): number;
48
+ /**
49
+ * Parse font weight
50
+ */
51
+ export declare function parseFontWeight(weight: string | number): string;
52
+ /**
53
+ * Parse margin/padding shorthand
54
+ * Supports: 1-4 values (CSS-like syntax)
55
+ * Returns: [top, right, bottom, left]
56
+ */
57
+ export declare function parseSpacing(value: number | string): [number, number, number, number];
58
+ /**
59
+ * Parse margin/padding individual values
60
+ */
61
+ export declare function parseSpacingIndividual(top?: number | string, right?: number | string, bottom?: number | string, left?: number | string): [number, number, number, number];
62
+ /**
63
+ * Map React style props to GPUI style object
64
+ */
65
+ export declare function mapStyleToProps(props: StyleProps): Record<string, any>;
66
+ //# sourceMappingURL=styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/reconciler/styles.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,cAAc,CAAC;IACvD,aAAa,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IACjC,cAAc,CAAC,EAAE,YAAY,GAAG,QAAQ,GAAG,UAAU,GAAG,eAAe,GAAG,cAAc,GAAG,cAAc,CAAC;IAC1G,UAAU,CAAC,EAAE,YAAY,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;IAC9D,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IAC3C,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CAC5C;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CA2ChD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAgCvD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAc/D;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAGrF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EACrB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EACvB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EACxB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GACrB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAOlC;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAsFtE"}
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Style Utilities for React-GPUI Renderer
3
+ *
4
+ * Handles conversion between React style props and GPUI style values
5
+ */
6
+ /**
7
+ * Parse CSS color string to GPUI RGB value
8
+ * Supports: hex (#RRGGBB, #RGB), rgb(r, g, b), rgba(r, g, b, a), named colors
9
+ */
10
+ export function parseColor(color) {
11
+ if (!color) {
12
+ return 0x000000;
13
+ }
14
+ if (color.startsWith('#')) {
15
+ const hex = color.slice(1);
16
+ if (hex.length === 3) {
17
+ const r = parseInt(hex[0] + hex[0], 16);
18
+ const g = parseInt(hex[1] + hex[1], 16);
19
+ const b = parseInt(hex[2] + hex[2], 16);
20
+ return (r << 16) | (g << 8) | b;
21
+ }
22
+ else if (hex.length === 6) {
23
+ const r = parseInt(hex.slice(0, 2), 16);
24
+ const g = parseInt(hex.slice(2, 4), 16);
25
+ const b = parseInt(hex.slice(4, 6), 16);
26
+ return (r << 16) | (g << 8) | b;
27
+ }
28
+ }
29
+ const rgbMatch = color.match(/rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/);
30
+ if (rgbMatch) {
31
+ const r = parseInt(rgbMatch[1], 10);
32
+ const g = parseInt(rgbMatch[2], 10);
33
+ const b = parseInt(rgbMatch[3], 10);
34
+ return (r << 16) | (g << 8) | b;
35
+ }
36
+ const rgbaMatch = color.match(/rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*[\d.]+\s*\)/);
37
+ if (rgbaMatch) {
38
+ const r = parseInt(rgbaMatch[1], 10);
39
+ const g = parseInt(rgbaMatch[2], 10);
40
+ const b = parseInt(rgbaMatch[3], 10);
41
+ return (r << 16) | (g << 8) | b;
42
+ }
43
+ const namedColor = NAMED_COLORS[color.toLowerCase()];
44
+ if (namedColor !== undefined) {
45
+ return namedColor;
46
+ }
47
+ console.warn(`Unknown color format: ${color}, using black`);
48
+ return 0x000000;
49
+ }
50
+ /**
51
+ * Parse size value to pixels
52
+ * Supports: px, em, rem, %, number (assumed px)
53
+ */
54
+ export function parseSize(size) {
55
+ if (typeof size === 'number') {
56
+ return size;
57
+ }
58
+ if (typeof size === 'string') {
59
+ const s = size.trim();
60
+ if (s.endsWith('px')) {
61
+ return parseFloat(s.slice(0, -2));
62
+ }
63
+ if (s.endsWith('em')) {
64
+ return parseFloat(s.slice(0, -2)) * 16;
65
+ }
66
+ if (s.endsWith('rem')) {
67
+ return parseFloat(s.slice(0, -3)) * 16;
68
+ }
69
+ if (s.endsWith('%')) {
70
+ return parseFloat(s.slice(0, -1));
71
+ }
72
+ const parsed = parseFloat(s);
73
+ if (!isNaN(parsed)) {
74
+ return parsed;
75
+ }
76
+ }
77
+ console.warn(`Unknown size format: ${size}, using 0`);
78
+ return 0;
79
+ }
80
+ /**
81
+ * Parse font weight
82
+ */
83
+ export function parseFontWeight(weight) {
84
+ if (typeof weight === 'number') {
85
+ return weight.toString();
86
+ }
87
+ const normalized = weight.toLowerCase();
88
+ const weightMap = {
89
+ 'normal': '400',
90
+ 'bold': '700',
91
+ 'lighter': '300',
92
+ 'bolder': '900',
93
+ };
94
+ return weightMap[normalized] || normalized;
95
+ }
96
+ /**
97
+ * Parse margin/padding shorthand
98
+ * Supports: 1-4 values (CSS-like syntax)
99
+ * Returns: [top, right, bottom, left]
100
+ */
101
+ export function parseSpacing(value) {
102
+ const parsed = parseSize(value);
103
+ return [parsed, parsed, parsed, parsed];
104
+ }
105
+ /**
106
+ * Parse margin/padding individual values
107
+ */
108
+ export function parseSpacingIndividual(top, right, bottom, left) {
109
+ return [
110
+ top !== undefined ? parseSize(top) : 0,
111
+ right !== undefined ? parseSize(right) : 0,
112
+ bottom !== undefined ? parseSize(bottom) : 0,
113
+ left !== undefined ? parseSize(left) : 0,
114
+ ];
115
+ }
116
+ /**
117
+ * Map React style props to GPUI style object
118
+ */
119
+ export function mapStyleToProps(props) {
120
+ const result = {};
121
+ if (props.color) {
122
+ result.textColor = parseColor(props.color);
123
+ }
124
+ if (props.backgroundColor) {
125
+ result.bgColor = parseColor(props.backgroundColor);
126
+ }
127
+ if (props.borderColor) {
128
+ result.borderColor = parseColor(props.borderColor);
129
+ }
130
+ if (props.fontSize) {
131
+ result.textSize = parseSize(props.fontSize);
132
+ }
133
+ if (props.fontWeight) {
134
+ result.fontWeight = parseFontWeight(props.fontWeight);
135
+ }
136
+ if (props.width) {
137
+ result.width = parseSize(props.width);
138
+ }
139
+ if (props.height) {
140
+ result.height = parseSize(props.height);
141
+ }
142
+ if (props.margin !== undefined) {
143
+ const [mt, mr, mb, ml] = parseSpacing(props.margin);
144
+ result.marginTop = mt;
145
+ result.marginRight = mr;
146
+ result.marginBottom = mb;
147
+ result.marginLeft = ml;
148
+ }
149
+ else {
150
+ if (props.marginTop !== undefined)
151
+ result.marginTop = parseSize(props.marginTop);
152
+ if (props.marginRight !== undefined)
153
+ result.marginRight = parseSize(props.marginRight);
154
+ if (props.marginBottom !== undefined)
155
+ result.marginBottom = parseSize(props.marginBottom);
156
+ if (props.marginLeft !== undefined)
157
+ result.marginLeft = parseSize(props.marginLeft);
158
+ }
159
+ if (props.padding !== undefined) {
160
+ const [pt, pr, pb, pl] = parseSpacing(props.padding);
161
+ result.paddingTop = pt;
162
+ result.paddingRight = pr;
163
+ result.paddingBottom = pb;
164
+ result.paddingLeft = pl;
165
+ }
166
+ else {
167
+ if (props.paddingTop !== undefined)
168
+ result.paddingTop = parseSize(props.paddingTop);
169
+ if (props.paddingRight !== undefined)
170
+ result.paddingRight = parseSize(props.paddingRight);
171
+ if (props.paddingBottom !== undefined)
172
+ result.paddingBottom = parseSize(props.paddingBottom);
173
+ if (props.paddingLeft !== undefined)
174
+ result.paddingLeft = parseSize(props.paddingLeft);
175
+ }
176
+ if (props.display) {
177
+ result.display = props.display;
178
+ }
179
+ if (props.flexDirection) {
180
+ result.flexDirection = props.flexDirection;
181
+ }
182
+ if (props.justifyContent) {
183
+ result.justifyContent = props.justifyContent;
184
+ }
185
+ if (props.alignItems) {
186
+ result.alignItems = props.alignItems;
187
+ }
188
+ if (props.gap !== undefined) {
189
+ result.gap = parseSize(props.gap);
190
+ }
191
+ if (props.borderRadius) {
192
+ result.borderRadius = parseSize(props.borderRadius);
193
+ }
194
+ if (props.opacity !== undefined) {
195
+ result.opacity = Math.max(0, Math.min(1, props.opacity));
196
+ }
197
+ return result;
198
+ }
199
+ /**
200
+ * Named color map (common colors only for MVP)
201
+ */
202
+ const NAMED_COLORS = {
203
+ 'black': 0x000000,
204
+ 'white': 0xffffff,
205
+ 'red': 0xff0000,
206
+ 'green': 0x00ff00,
207
+ 'blue': 0x0000ff,
208
+ 'yellow': 0xffff00,
209
+ 'cyan': 0x00ffff,
210
+ 'magenta': 0xff00ff,
211
+ 'gray': 0x808080,
212
+ 'grey': 0x808080,
213
+ 'transparent': 0x000000,
214
+ };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "gpui-react",
3
+ "version": "0.1.7",
4
+ "module": "src/index.ts",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist/",
9
+ "src/native/"
10
+ ],
11
+ "devDependencies": {
12
+ "@types/react": "^19.0.0",
13
+ "@types/react-reconciler": "^0.32.0",
14
+ "bun-types": "latest",
15
+ "typescript": "^5.9.3",
16
+ "react": ">=19.0.0"
17
+ },
18
+ "scripts": {
19
+ "build": "bun run build:rust && bun run build:ts && bun run copy:native",
20
+ "build:rust": "cd rust && cargo build --release",
21
+ "build:ts": "tsc",
22
+ "copy:native": "node scripts/copy-native.js",
23
+ "build:all": "bun run build && echo 'Build complete for distribution'",
24
+ "release": "node scripts/release.js",
25
+ "release:dry-run": "node scripts/release.js patch --dry-run",
26
+ "release:status": "node scripts/release.js --status",
27
+ "demo": "bun run demo/index.ts",
28
+ "styled-demo": "bun run demo/styled-index.ts",
29
+ "flex-demo": "bun run demo/flex-index.ts",
30
+ "elements-demo": "bun run demo/elements-index.ts",
31
+ "event-demo": "bun run demo/event-index.ts",
32
+ "test": "bun run src/reconciler/__tests__/element-store.test.ts",
33
+ "dev": "bun run build:rust && bun run demo",
34
+ "postinstall": "node scripts/postinstall.js"
35
+ },
36
+ "type": "module",
37
+ "dependencies": {
38
+ "react-reconciler": "^0.32.0"
39
+ },
40
+ "engines": {
41
+ "bun": ">=1.2.0",
42
+ "node": ">=18.0.0"
43
+ }
44
+ }