@gtkx/react 0.1.11
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/LICENSE +373 -0
- package/README.md +390 -0
- package/dist/codegen/jsx-generator.d.ts +37 -0
- package/dist/codegen/jsx-generator.js +554 -0
- package/dist/factory.d.ts +3 -0
- package/dist/factory.js +59 -0
- package/dist/generated/jsx.d.ts +1598 -0
- package/dist/generated/jsx.js +264 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +14 -0
- package/dist/node.d.ts +13 -0
- package/dist/node.js +1 -0
- package/dist/nodes/action-bar.d.ts +27 -0
- package/dist/nodes/action-bar.js +88 -0
- package/dist/nodes/dialog.d.ts +19 -0
- package/dist/nodes/dialog.js +87 -0
- package/dist/nodes/dropdown.d.ts +41 -0
- package/dist/nodes/dropdown.js +163 -0
- package/dist/nodes/grid.d.ts +41 -0
- package/dist/nodes/grid.js +140 -0
- package/dist/nodes/list.d.ts +46 -0
- package/dist/nodes/list.js +165 -0
- package/dist/nodes/notebook.d.ts +25 -0
- package/dist/nodes/notebook.js +88 -0
- package/dist/nodes/overlay.d.ts +29 -0
- package/dist/nodes/overlay.js +109 -0
- package/dist/nodes/slot.d.ts +17 -0
- package/dist/nodes/slot.js +55 -0
- package/dist/nodes/text.d.ts +16 -0
- package/dist/nodes/text.js +31 -0
- package/dist/nodes/widget.d.ts +19 -0
- package/dist/nodes/widget.js +136 -0
- package/dist/portal.d.ts +3 -0
- package/dist/portal.js +11 -0
- package/dist/reconciler.d.ts +20 -0
- package/dist/reconciler.js +111 -0
- package/dist/render.d.ts +5 -0
- package/dist/render.js +12 -0
- package/dist/signal-utils.d.ts +4 -0
- package/dist/signal-utils.js +7 -0
- package/dist/types.d.ts +13 -0
- package/dist/types.js +1 -0
- package/dist/widget-capabilities.d.ts +46 -0
- package/dist/widget-capabilities.js +32 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
# @gtkx/react
|
|
2
|
+
|
|
3
|
+
React integration layer for GTKX. This package provides a custom React reconciler that renders React components as native GTK4 widgets.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @gtkx/react react
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Peer Dependencies
|
|
12
|
+
|
|
13
|
+
- `react` ^19.0.0
|
|
14
|
+
|
|
15
|
+
### System Requirements
|
|
16
|
+
|
|
17
|
+
- Linux with GTK4 libraries
|
|
18
|
+
- Node.js 20+
|
|
19
|
+
- Rust toolchain (for building `@gtkx/native`)
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Fedora
|
|
23
|
+
sudo dnf install gtk4-devel
|
|
24
|
+
|
|
25
|
+
# Ubuntu/Debian
|
|
26
|
+
sudo apt install libgtk-4-dev
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { ApplicationWindow, Button, Box, Label, quit, render } from "@gtkx/react";
|
|
33
|
+
import * as Gtk from "@gtkx/ffi/gtk";
|
|
34
|
+
import { useState } from "react";
|
|
35
|
+
|
|
36
|
+
const Counter = () => {
|
|
37
|
+
const [count, setCount] = useState(0);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Box valign={Gtk.Align.CENTER} halign={Gtk.Align.CENTER} spacing={10}>
|
|
41
|
+
<Label.Root label={`Count: ${count}`} cssClasses={["title-2"]} />
|
|
42
|
+
<Button label="Increment" onClicked={() => setCount(c => c + 1)} />
|
|
43
|
+
</Box>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Export app instance for use in dialogs
|
|
48
|
+
export const app = render(
|
|
49
|
+
<ApplicationWindow title="My App" defaultWidth={800} defaultHeight={600} onCloseRequest={quit}>
|
|
50
|
+
<Counter />
|
|
51
|
+
</ApplicationWindow>,
|
|
52
|
+
"com.example.myapp"
|
|
53
|
+
);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Run with:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx tsx src/index.tsx
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## API
|
|
63
|
+
|
|
64
|
+
### `render(element, applicationId)`
|
|
65
|
+
|
|
66
|
+
Renders a React element tree as a GTK4 application. Returns the GTK Application instance.
|
|
67
|
+
|
|
68
|
+
- `element` — Root React element (typically `ApplicationWindow`)
|
|
69
|
+
- `applicationId` — Unique identifier in reverse-DNS format (e.g., `com.example.app`)
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
export const app = render(<App />, "com.example.myapp");
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### `quit()`
|
|
76
|
+
|
|
77
|
+
Signals the application to close. Returns `false` (useful as a direct event handler for `onCloseRequest`).
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
<ApplicationWindow onCloseRequest={quit}>...</ApplicationWindow>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### `createPortal(element)`
|
|
84
|
+
|
|
85
|
+
Renders a React element outside the normal component tree (useful for dialogs).
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
{showDialog && createPortal(<AboutDialog ... />)}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### `createRef()`
|
|
92
|
+
|
|
93
|
+
Creates a reference for FFI output parameters.
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import { createRef } from "@gtkx/react";
|
|
97
|
+
|
|
98
|
+
const ref = createRef();
|
|
99
|
+
someGtkFunction(ref);
|
|
100
|
+
console.log(ref.value);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Widgets
|
|
104
|
+
|
|
105
|
+
### Container Widgets
|
|
106
|
+
|
|
107
|
+
**ApplicationWindow** — Main application window
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
<ApplicationWindow title="Window Title" defaultWidth={800} defaultHeight={600} onCloseRequest={quit}>
|
|
111
|
+
{children}
|
|
112
|
+
</ApplicationWindow>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Box** — Arranges children in a row or column
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
<Box orientation={Gtk.Orientation.VERTICAL} spacing={10}>
|
|
119
|
+
<Button label="First" />
|
|
120
|
+
<Button label="Second" />
|
|
121
|
+
</Box>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Grid** — Arranges children in a grid layout
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
<Grid.Root columnSpacing={10} rowSpacing={10}>
|
|
128
|
+
<Grid.Child column={0} row={0}><Button label="(0,0)" /></Grid.Child>
|
|
129
|
+
<Grid.Child column={1} row={0}><Button label="(1,0)" /></Grid.Child>
|
|
130
|
+
<Grid.Child column={0} row={1} columnSpan={2}>
|
|
131
|
+
<Button label="Spans 2 columns" hexpand />
|
|
132
|
+
</Grid.Child>
|
|
133
|
+
</Grid.Root>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**ScrolledWindow** — Adds scrollbars to content
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
<ScrolledWindow vexpand hexpand>
|
|
140
|
+
<TextView />
|
|
141
|
+
</ScrolledWindow>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Paned** — Resizable split container
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
<Paned.Root wideHandle>
|
|
148
|
+
<Paned.StartChild>
|
|
149
|
+
<Box>{/* Left */}</Box>
|
|
150
|
+
</Paned.StartChild>
|
|
151
|
+
<Paned.EndChild>
|
|
152
|
+
<Box>{/* Right */}</Box>
|
|
153
|
+
</Paned.EndChild>
|
|
154
|
+
</Paned.Root>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Input Widgets
|
|
158
|
+
|
|
159
|
+
**Button** — Clickable button
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
<Button label="Click me" onClicked={() => console.log("Clicked!")} />
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**ToggleButton** — Button with on/off state
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
<ToggleButton.Root label={active ? "ON" : "OFF"} active={active} onToggled={() => setActive(a => !a)} />
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**CheckButton** — Checkbox with label
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
<CheckButton.Root label="Accept terms" active={checked} onToggled={() => setChecked(c => !c)} />
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Switch** — On/off toggle (must return `true` from `onStateSet`)
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
<Switch
|
|
181
|
+
active={enabled}
|
|
182
|
+
onStateSet={() => {
|
|
183
|
+
setEnabled(e => !e);
|
|
184
|
+
return true;
|
|
185
|
+
}}
|
|
186
|
+
/>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Entry** — Single-line text input
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
<Entry placeholderText="Enter text..." />
|
|
193
|
+
<PasswordEntry placeholderText="Password..." />
|
|
194
|
+
<SearchEntry placeholderText="Search..." />
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**SpinButton** — Numeric input with increment/decrement
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
const adjustment = useRef(new Gtk.Adjustment({ value: 50, lower: 0, upper: 100, stepIncrement: 1 }));
|
|
201
|
+
<SpinButton adjustment={adjustment.current.ptr} onValueChanged={() => setValue(adjustment.current.getValue())} />
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Scale** — Horizontal or vertical slider
|
|
205
|
+
|
|
206
|
+
```tsx
|
|
207
|
+
<Scale hexpand drawValue adjustment={adjustment.current.ptr} />
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Display Widgets
|
|
211
|
+
|
|
212
|
+
**Label** — Text display
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
<Label.Root label="Hello, World!" cssClasses={["title-2"]} />
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**ProgressBar** — Progress indicator
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
<ProgressBar fraction={0.5} showText />
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Spinner** — Loading indicator
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
<Spinner spinning={isLoading} />
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Layout Widgets
|
|
231
|
+
|
|
232
|
+
**HeaderBar** — Title bar with buttons
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
<HeaderBar.Root>
|
|
236
|
+
<HeaderBar.TitleWidget>
|
|
237
|
+
<Label.Root label="App Title" />
|
|
238
|
+
</HeaderBar.TitleWidget>
|
|
239
|
+
</HeaderBar.Root>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**CenterBox** — Positions children at start, center, and end
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
<CenterBox.Root>
|
|
246
|
+
<CenterBox.StartWidget><Button label="Left" /></CenterBox.StartWidget>
|
|
247
|
+
<CenterBox.CenterWidget><Label.Root label="Center" /></CenterBox.CenterWidget>
|
|
248
|
+
<CenterBox.EndWidget><Button label="Right" /></CenterBox.EndWidget>
|
|
249
|
+
</CenterBox.Root>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### List Widgets
|
|
253
|
+
|
|
254
|
+
**ListBox** — Simple vertical list
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
<ListBox selectionMode={Gtk.SelectionMode.SINGLE}>
|
|
258
|
+
<ListBoxRow><Label.Root label="Item 1" /></ListBoxRow>
|
|
259
|
+
<ListBoxRow><Label.Root label="Item 2" /></ListBoxRow>
|
|
260
|
+
</ListBox>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**DropDown** — Dropdown selector
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
<DropDown.Root
|
|
267
|
+
itemLabel={(item) => item.name}
|
|
268
|
+
onSelectionChanged={(item, index) => setSelected(item)}
|
|
269
|
+
>
|
|
270
|
+
{items.map(item => <DropDown.Item key={item.id} item={item} />)}
|
|
271
|
+
</DropDown.Root>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**ListView** — Virtualized list for large datasets
|
|
275
|
+
|
|
276
|
+
```tsx
|
|
277
|
+
<ListView.Root renderItem={(item) => {
|
|
278
|
+
if (!item) return new Gtk.Box();
|
|
279
|
+
return new Gtk.Label({ label: item.name });
|
|
280
|
+
}}>
|
|
281
|
+
{items.map(item => <ListView.Item item={item} key={item.id} />)}
|
|
282
|
+
</ListView.Root>
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Dialog Widgets
|
|
286
|
+
|
|
287
|
+
**AboutDialog** — Application about dialog (React component)
|
|
288
|
+
|
|
289
|
+
```tsx
|
|
290
|
+
{showAbout && createPortal(
|
|
291
|
+
<AboutDialog
|
|
292
|
+
programName="My App"
|
|
293
|
+
version="1.0.0"
|
|
294
|
+
onCloseRequest={() => { setShowAbout(false); return false; }}
|
|
295
|
+
/>
|
|
296
|
+
)}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**Async Dialogs** — AlertDialog, FileDialog, ColorDialog, FontDialog (imperative)
|
|
300
|
+
|
|
301
|
+
```tsx
|
|
302
|
+
import { app } from "./index.js";
|
|
303
|
+
|
|
304
|
+
const openFile = async () => {
|
|
305
|
+
const dialog = new Gtk.FileDialog();
|
|
306
|
+
dialog.setTitle("Open File");
|
|
307
|
+
try {
|
|
308
|
+
const file = await dialog.open(app.getActiveWindow());
|
|
309
|
+
console.log(file.getPath());
|
|
310
|
+
} catch {
|
|
311
|
+
// Cancelled
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Named Slots
|
|
317
|
+
|
|
318
|
+
Some GTK widgets have named child positions, handled via compound components:
|
|
319
|
+
|
|
320
|
+
```tsx
|
|
321
|
+
<Frame.Root>
|
|
322
|
+
<Frame.LabelWidget>
|
|
323
|
+
<Label.Root label="Custom Label" />
|
|
324
|
+
</Frame.LabelWidget>
|
|
325
|
+
<Frame.Child>
|
|
326
|
+
<Box>{/* Content */}</Box>
|
|
327
|
+
</Frame.Child>
|
|
328
|
+
</Frame.Root>
|
|
329
|
+
|
|
330
|
+
<Expander.Root label="Click to expand">
|
|
331
|
+
<Expander.Child>
|
|
332
|
+
<Box>Hidden content</Box>
|
|
333
|
+
</Expander.Child>
|
|
334
|
+
</Expander.Root>
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Using GTK Enums
|
|
338
|
+
|
|
339
|
+
Import enums from `@gtkx/ffi/gtk`:
|
|
340
|
+
|
|
341
|
+
```tsx
|
|
342
|
+
import * as Gtk from "@gtkx/ffi/gtk";
|
|
343
|
+
|
|
344
|
+
<Box orientation={Gtk.Orientation.VERTICAL} />
|
|
345
|
+
<ListBox selectionMode={Gtk.SelectionMode.SINGLE} />
|
|
346
|
+
<Revealer transitionType={Gtk.RevealerTransitionType.SLIDE_DOWN} />
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Hooks Support
|
|
350
|
+
|
|
351
|
+
Standard React hooks work as expected:
|
|
352
|
+
|
|
353
|
+
```tsx
|
|
354
|
+
import { useState, useEffect, useRef } from "react";
|
|
355
|
+
|
|
356
|
+
const Counter = () => {
|
|
357
|
+
const [count, setCount] = useState(0);
|
|
358
|
+
|
|
359
|
+
useEffect(() => {
|
|
360
|
+
console.log(`Count: ${count}`);
|
|
361
|
+
}, [count]);
|
|
362
|
+
|
|
363
|
+
return (
|
|
364
|
+
<Box spacing={10}>
|
|
365
|
+
<Label.Root label={`Count: ${count}`} />
|
|
366
|
+
<Button label="Increment" onClicked={() => setCount(c => c + 1)} />
|
|
367
|
+
</Box>
|
|
368
|
+
);
|
|
369
|
+
};
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Architecture
|
|
373
|
+
|
|
374
|
+
```
|
|
375
|
+
┌─────────────────────────────────┐
|
|
376
|
+
│ Your React Application │
|
|
377
|
+
├─────────────────────────────────┤
|
|
378
|
+
│ @gtkx/react (React Reconciler) │
|
|
379
|
+
├─────────────────────────────────┤
|
|
380
|
+
│ @gtkx/ffi (TypeScript FFI) │
|
|
381
|
+
├─────────────────────────────────┤
|
|
382
|
+
│ @gtkx/native (Rust Bridge) │
|
|
383
|
+
├─────────────────────────────────┤
|
|
384
|
+
│ GTK4 / GLib │
|
|
385
|
+
└─────────────────────────────────┘
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## License
|
|
389
|
+
|
|
390
|
+
[MPL-2.0](../../LICENSE)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { GirClass, GirNamespace, TypeMapper } from "@gtkx/gir";
|
|
2
|
+
export interface JsxGeneratorOptions {
|
|
3
|
+
prettierConfig?: unknown;
|
|
4
|
+
}
|
|
5
|
+
export declare class JsxGenerator {
|
|
6
|
+
private typeMapper;
|
|
7
|
+
private options;
|
|
8
|
+
private classMap;
|
|
9
|
+
private interfaceMap;
|
|
10
|
+
private namespace;
|
|
11
|
+
private usedExternalNamespaces;
|
|
12
|
+
private widgetPropertyNames;
|
|
13
|
+
private widgetSignalNames;
|
|
14
|
+
constructor(typeMapper: TypeMapper, options?: JsxGeneratorOptions);
|
|
15
|
+
generate(namespace: GirNamespace, classMap: Map<string, GirClass>): Promise<string>;
|
|
16
|
+
private generateImports;
|
|
17
|
+
private generateCommonTypes;
|
|
18
|
+
private generateWidgetPropsContent;
|
|
19
|
+
private buildContainerMetadata;
|
|
20
|
+
private findWidgets;
|
|
21
|
+
private analyzeContainerCapabilities;
|
|
22
|
+
private generateWidgetPropsInterfaces;
|
|
23
|
+
private generateWidgetProps;
|
|
24
|
+
private getParentPropsName;
|
|
25
|
+
private getRequiredConstructorParams;
|
|
26
|
+
private getConstructorParams;
|
|
27
|
+
private generateConstructorArgsMetadata;
|
|
28
|
+
private getAncestorInterfaces;
|
|
29
|
+
private findInheritedProperty;
|
|
30
|
+
private generateSignalHandler;
|
|
31
|
+
private getSignalParamFfiType;
|
|
32
|
+
private addNamespacePrefix;
|
|
33
|
+
private buildSignalHandlerType;
|
|
34
|
+
private generateExports;
|
|
35
|
+
private generateJsxNamespace;
|
|
36
|
+
private formatCode;
|
|
37
|
+
}
|