@gtkx/cli 0.9.3 → 0.10.0

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.
@@ -0,0 +1,531 @@
1
+ # GTKX Widget Reference
2
+
3
+ ## Container Widgets
4
+
5
+ ### GtkBox
6
+ Linear layout container.
7
+
8
+ ```tsx
9
+ <GtkBox orientation={Gtk.Orientation.VERTICAL} spacing={12}>
10
+ Child 1
11
+ Child 2
12
+ </GtkBox>
13
+ ```
14
+
15
+ Props:
16
+ - `orientation`: `Gtk.Orientation.HORIZONTAL` | `Gtk.Orientation.VERTICAL`
17
+ - `spacing`: number (pixels between children)
18
+ - `homogeneous`: boolean (equal child sizes)
19
+
20
+ ### GtkGrid
21
+ 2D grid layout with explicit positioning.
22
+
23
+ ```tsx
24
+ <GtkGrid rowSpacing={5} columnSpacing={5}>
25
+ <GridChild column={0} row={0}>Top-left</GridChild>
26
+ <GridChild column={1} row={0} columnSpan={2}>Spans 2 columns</GridChild>
27
+ </GtkGrid>
28
+ ```
29
+
30
+ GridChild props (consumed, not passed to GTK):
31
+ - `column`: number (0-indexed)
32
+ - `row`: number (0-indexed)
33
+ - `columnSpan`: number (default 1)
34
+ - `rowSpan`: number (default 1)
35
+
36
+ ### GtkStack
37
+ Shows one child at a time, switchable by name.
38
+
39
+ ```tsx
40
+ <GtkStack visibleChildName="page1">
41
+ <StackPage name="page1" title="First" iconName="document-new">
42
+ <Content1 />
43
+ </StackPage>
44
+ <StackPage name="page2" title="Second">
45
+ <Content2 />
46
+ </StackPage>
47
+ </GtkStack>
48
+ ```
49
+
50
+ StackPage props (consumed):
51
+ - `name`: string (required, unique identifier)
52
+ - `title`: string (display title)
53
+ - `iconName`: string (icon name)
54
+ - `needsAttention`: boolean (show attention indicator)
55
+ - `visible`: boolean (visibility in switchers)
56
+ - `useUnderline`: boolean (mnemonic underlines in title)
57
+ - `badgeNumber`: number (badge on indicator, AdwViewStack only)
58
+
59
+ ### GtkNotebook
60
+ Tabbed container with visible tabs.
61
+
62
+ ```tsx
63
+ <GtkNotebook>
64
+ <Notebook.Page label="Tab 1">
65
+ <Content1 />
66
+ </Notebook.Page>
67
+ <Notebook.Page label="Tab 2">
68
+ <Content2 />
69
+ </Notebook.Page>
70
+ </GtkNotebook>
71
+ ```
72
+
73
+ Notebook.Page props (consumed):
74
+ - `label`: string (tab label, optional when using Notebook.PageTab)
75
+
76
+ Custom tab widgets with Notebook.PageTab:
77
+ ```tsx
78
+ <Notebook.Page>
79
+ <Notebook.PageTab>
80
+ <GtkBox orientation={Gtk.Orientation.HORIZONTAL} spacing={4}>
81
+ <GtkImage iconName="folder-symbolic" />
82
+ <GtkLabel label="Files" />
83
+ </GtkBox>
84
+ </Notebook.PageTab>
85
+ <Content />
86
+ </Notebook.Page>
87
+ ```
88
+
89
+ ### GtkPaned
90
+ Resizable split container with draggable divider.
91
+
92
+ ```tsx
93
+ <GtkPaned
94
+ orientation={Gtk.Orientation.HORIZONTAL}
95
+ position={280}
96
+ shrinkStartChild={false}
97
+ shrinkEndChild={false}
98
+ >
99
+ <Slot for={GtkPaned} id="startChild">
100
+ <SidePanel />
101
+ </Slot>
102
+ <Slot for={GtkPaned} id="endChild">
103
+ <MainContent />
104
+ </Slot>
105
+ </GtkPaned>
106
+ ```
107
+
108
+ Props:
109
+ - `orientation`: `Gtk.Orientation.HORIZONTAL` | `Gtk.Orientation.VERTICAL`
110
+ - `position`: number (divider position in pixels)
111
+ - `shrinkStartChild`: boolean
112
+ - `shrinkEndChild`: boolean
113
+
114
+ ## Virtual Scrolling Widgets
115
+
116
+ ### ListView
117
+ High-performance scrollable list with virtual rendering and selection support.
118
+
119
+ ```tsx
120
+ <ListView<Item>
121
+ vexpand
122
+ selected={[selectedId]}
123
+ selectionMode={Gtk.SelectionMode.SINGLE}
124
+ onSelectionChanged={(ids) => setSelectedId(ids[0])}
125
+ renderItem={(item) => (
126
+ <GtkLabel label={item?.text ?? ""} />
127
+ )}
128
+ >
129
+ {items.map(item => (
130
+ <ListItem key={item.id} id={item.id} value={item} />
131
+ ))}
132
+ </ListView>
133
+ ```
134
+
135
+ Props:
136
+ - `renderItem`: `(item: T | null) => ReactElement` (required)
137
+ - `selected`: string[] (array of selected item IDs)
138
+ - `selectionMode`: `Gtk.SelectionMode.SINGLE` | `MULTIPLE` | `NONE`
139
+ - `onSelectionChanged`: `(ids: string[]) => void`
140
+
141
+ ListItem props:
142
+ - `id`: string (required, unique identifier for selection)
143
+ - `value`: T (the data item)
144
+
145
+ ### GridView
146
+ Grid-based virtual scrolling. Same API as ListView but renders items in a grid.
147
+
148
+ ```tsx
149
+ <GridView<Item>
150
+ vexpand
151
+ minColumns={2}
152
+ maxColumns={4}
153
+ renderItem={(item) => (
154
+ <GtkBox orientation={Gtk.Orientation.VERTICAL}>
155
+ <GtkImage iconName={item?.icon ?? "image-missing"} />
156
+ <GtkLabel label={item?.name ?? ""} />
157
+ </GtkBox>
158
+ )}
159
+ >
160
+ {items.map(item => (
161
+ <ListItem key={item.id} id={item.id} value={item} />
162
+ ))}
163
+ </GridView>
164
+ ```
165
+
166
+ ### GtkColumnView
167
+ Table with sortable columns.
168
+
169
+ ```tsx
170
+ <GtkColumnView
171
+ sortColumn="name"
172
+ sortOrder={Gtk.SortType.ASCENDING}
173
+ onSortChange={(column, order) => handleSort(column, order)}
174
+ >
175
+ <ColumnViewColumn<Item>
176
+ title="Name"
177
+ id="name"
178
+ expand
179
+ resizable
180
+ sortable
181
+ renderCell={(item) => (
182
+ <GtkLabel label={item?.name ?? ""} />
183
+ )}
184
+ />
185
+ {items.map(item => (
186
+ <ListItem key={item.id} id={item.id} value={item} />
187
+ ))}
188
+ </GtkColumnView>
189
+ ```
190
+
191
+ ColumnViewColumn props:
192
+ - `title`: string (column header)
193
+ - `id`: string (used for sorting)
194
+ - `expand`: boolean (fill available space)
195
+ - `resizable`: boolean (user can resize)
196
+ - `sortable`: boolean (clicking header triggers sort)
197
+ - `fixedWidth`: number (fixed width in pixels)
198
+ - `renderCell`: `(item: T | null) => ReactElement`
199
+
200
+ ### GtkDropDown
201
+ String selection dropdown with controlled state.
202
+
203
+ ```tsx
204
+ <GtkDropDown selectedId={selectedId} onSelectionChanged={setSelectedId}>
205
+ {options.map(opt => (
206
+ <SimpleListItem key={opt.id} id={opt.id} value={opt.label} />
207
+ ))}
208
+ </GtkDropDown>
209
+ ```
210
+
211
+ Props:
212
+ - `selectedId`: string (controlled selected item ID)
213
+ - `onSelectionChanged`: `(id: string) => void`
214
+
215
+ SimpleListItem props:
216
+ - `id`: string (unique identifier)
217
+ - `value`: string (display text)
218
+
219
+ ### GtkOverlay
220
+ Stack widgets on top of each other. First child is the base layer.
221
+
222
+ ```tsx
223
+ <GtkOverlay>
224
+ <GtkButton label="Main" widthRequest={120} heightRequest={40} />
225
+ <GtkLabel
226
+ label="3"
227
+ cssClasses={["badge"]}
228
+ halign={Gtk.Align.END}
229
+ valign={Gtk.Align.START}
230
+ marginEnd={4}
231
+ marginTop={4}
232
+ />
233
+ </GtkOverlay>
234
+ ```
235
+
236
+ Use `halign` and `valign` to position overlay children at corners or edges.
237
+
238
+ ## Header Widgets
239
+
240
+ ### GtkHeaderBar
241
+ Title bar with packed widgets at start and end.
242
+
243
+ ```tsx
244
+ <GtkHeaderBar>
245
+ <Pack.Start>
246
+ <GtkButton iconName="go-previous-symbolic" />
247
+ </Pack.Start>
248
+ <Slot for={GtkHeaderBar} id="titleWidget">
249
+ <GtkLabel label="My App" cssClasses={["title"]} />
250
+ </Slot>
251
+ <Pack.End>
252
+ <GtkMenuButton iconName="open-menu-symbolic" />
253
+ </Pack.End>
254
+ </GtkHeaderBar>
255
+ ```
256
+
257
+ ### GtkActionBar
258
+ Bottom action bar with start/end packing.
259
+
260
+ ```tsx
261
+ <GtkActionBar>
262
+ <Pack.Start>
263
+ <GtkButton label="Cancel" />
264
+ </Pack.Start>
265
+ <Pack.End>
266
+ <GtkButton label="Save" cssClasses={["suggested-action"]} />
267
+ </Pack.End>
268
+ </GtkActionBar>
269
+ ```
270
+
271
+ ## Input Widgets
272
+
273
+ ### GtkEntry
274
+ Single-line text input. Requires two-way binding for controlled behavior.
275
+
276
+ ```tsx
277
+ const [text, setText] = useState("");
278
+
279
+ <GtkEntry
280
+ text={text}
281
+ onChanged={(entry) => setText(entry.getText())}
282
+ placeholderText="Enter text..."
283
+ />
284
+ ```
285
+
286
+ ### GtkToggleButton
287
+ Toggle button with controlled state. Auto-prevents signal feedback loops.
288
+
289
+ ```tsx
290
+ const [active, setActive] = useState(false);
291
+
292
+ <GtkToggleButton
293
+ active={active}
294
+ onToggled={() => setActive(!active)}
295
+ label="Toggle me"
296
+ />
297
+ ```
298
+
299
+ ## Display Widgets
300
+
301
+ ### GtkLabel
302
+ ```tsx
303
+ <GtkLabel label="Hello World" halign={Gtk.Align.START} wrap useMarkup />
304
+ ```
305
+
306
+ ### GtkButton
307
+ ```tsx
308
+ <GtkButton label="Click me" onClicked={() => handleClick()} iconName="document-new" />
309
+ ```
310
+
311
+ ### GtkMenuButton
312
+ ```tsx
313
+ <GtkMenuButton label="Options" iconName="open-menu">
314
+ <Slot for={GtkMenuButton} id="popover">
315
+ <GtkPopoverMenu>
316
+ <Menu.Item id="action" label="Action" onActivate={handle} />
317
+ </GtkPopoverMenu>
318
+ </Slot>
319
+ </GtkMenuButton>
320
+ ```
321
+
322
+ ## Menu Widgets
323
+
324
+ ### GtkPopoverMenu
325
+ ```tsx
326
+ <GtkPopoverMenu>
327
+ <Menu.Section>
328
+ <Menu.Item id="new" label="New" onActivate={handleNew} accels="<Control>n" />
329
+ </Menu.Section>
330
+ <Menu.Section>
331
+ <Menu.Submenu label="File">
332
+ <Menu.Item id="open" label="Open" onActivate={handleOpen} />
333
+ <Menu.Item id="save" label="Save" onActivate={handleSave} />
334
+ </Menu.Submenu>
335
+ </Menu.Section>
336
+ <Menu.Section>
337
+ <Menu.Item id="quit" label="Quit" onActivate={quit} accels="<Control>q" />
338
+ </Menu.Section>
339
+ </GtkPopoverMenu>
340
+ ```
341
+
342
+ ### Menu.Item
343
+ Props:
344
+ - `id`: string (required, unique identifier)
345
+ - `label`: string
346
+ - `onActivate`: `() => void`
347
+ - `accels`: string | string[] (e.g., "<Control>n")
348
+
349
+ ### Menu.Section
350
+ Groups menu items with optional label.
351
+
352
+ ### Menu.Submenu
353
+ Nested submenu.
354
+
355
+ ## Window Widgets
356
+
357
+ ### GtkApplicationWindow
358
+ ```tsx
359
+ <GtkApplicationWindow
360
+ title="My App"
361
+ defaultWidth={800}
362
+ defaultHeight={600}
363
+ showMenubar
364
+ onCloseRequest={quit}
365
+ >
366
+ <MainContent />
367
+ </GtkApplicationWindow>
368
+ ```
369
+
370
+ ## Adwaita (Libadwaita) Widgets
371
+
372
+ Import from `@gtkx/ffi/adw` for enums and `@gtkx/react` for components.
373
+
374
+ ### AdwApplicationWindow
375
+ Modern application window with required content slot.
376
+
377
+ ```tsx
378
+ <AdwApplicationWindow title="App" defaultWidth={800} defaultHeight={600} onCloseRequest={quit}>
379
+ <Slot for={AdwApplicationWindow} id="content">
380
+ <AdwToolbarView>...</AdwToolbarView>
381
+ </Slot>
382
+ </AdwApplicationWindow>
383
+ ```
384
+
385
+ ### AdwToolbarView
386
+ Layout container for apps with top/bottom toolbars.
387
+
388
+ ```tsx
389
+ <AdwToolbarView>
390
+ <Toolbar.Top>
391
+ <AdwHeaderBar />
392
+ </Toolbar.Top>
393
+ <MainContent />
394
+ <Toolbar.Bottom>
395
+ <GtkActionBar />
396
+ </Toolbar.Bottom>
397
+ </AdwToolbarView>
398
+ ```
399
+
400
+ ### AdwHeaderBar
401
+ Modern header bar with title widget slot.
402
+
403
+ ```tsx
404
+ <AdwHeaderBar>
405
+ <GtkButton iconName="go-previous-symbolic" />
406
+ <Slot for={AdwHeaderBar} id="titleWidget">
407
+ <AdwWindowTitle title="App" subtitle="Description" />
408
+ </Slot>
409
+ </AdwHeaderBar>
410
+ ```
411
+
412
+ ### AdwStatusPage
413
+ Welcome, error, or empty state pages.
414
+
415
+ ```tsx
416
+ <AdwStatusPage
417
+ iconName="applications-system-symbolic"
418
+ title="Welcome"
419
+ description="Description text"
420
+ vexpand
421
+ >
422
+ <GtkButton label="Get Started" cssClasses={["suggested-action", "pill"]} />
423
+ </AdwStatusPage>
424
+ ```
425
+
426
+ ### AdwBanner
427
+ Dismissable notification banner.
428
+
429
+ Props:
430
+ - `title`: string
431
+ - `buttonLabel`: string
432
+ - `revealed`: boolean
433
+ - `onButtonClicked`: `() => void`
434
+
435
+ ### AdwPreferencesPage / AdwPreferencesGroup
436
+ Settings UI layout.
437
+
438
+ ```tsx
439
+ <AdwPreferencesPage title="Settings">
440
+ <AdwPreferencesGroup title="Section" description="Help text">
441
+ <AdwActionRow title="Item" subtitle="Description" />
442
+ <AdwSwitchRow title="Toggle" active={value} onActivate={toggle} />
443
+ </AdwPreferencesGroup>
444
+ </AdwPreferencesPage>
445
+ ```
446
+
447
+ ### AdwActionRow
448
+ List row with title, subtitle, and optional widgets.
449
+
450
+ ```tsx
451
+ <AdwActionRow title="Setting" subtitle="Description">
452
+ <Slot for={AdwActionRow} id="activatableWidget">
453
+ <GtkImage iconName="go-next-symbolic" />
454
+ </Slot>
455
+ </AdwActionRow>
456
+ ```
457
+
458
+ ### AdwSwitchRow
459
+ Toggle switch in a list row.
460
+
461
+ Props:
462
+ - `title`: string
463
+ - `subtitle`: string
464
+ - `active`: boolean
465
+ - `onActivate`: `() => void`
466
+
467
+ ### AdwExpanderRow
468
+ Expandable row with nested children.
469
+
470
+ ```tsx
471
+ <AdwExpanderRow title="Advanced" subtitle="More options" iconName="preferences-system-symbolic">
472
+ <AdwActionRow title="Child 1" />
473
+ <AdwSwitchRow title="Child 2" active />
474
+ </AdwExpanderRow>
475
+ ```
476
+
477
+ ### AdwEntryRow / AdwPasswordEntryRow
478
+ Text input in a list row.
479
+
480
+ ```tsx
481
+ <AdwEntryRow title="Username" text={text} onChanged={(e) => setText(e.getText())} />
482
+ <AdwPasswordEntryRow title="Password" />
483
+ ```
484
+
485
+ ### AdwButtonRow
486
+ Button styled as a list row.
487
+
488
+ ```tsx
489
+ <AdwButtonRow title="Add Item" startIconName="list-add-symbolic" />
490
+ ```
491
+
492
+ ### AdwClamp
493
+ Limits content width for readability.
494
+
495
+ ```tsx
496
+ <AdwClamp maximumSize={600}>
497
+ <GtkBox>...</GtkBox>
498
+ </AdwClamp>
499
+ ```
500
+
501
+ ### AdwAvatar
502
+ User avatar with initials fallback.
503
+
504
+ Props:
505
+ - `size`: number (32, 48, 64, 80, etc.)
506
+ - `text`: string (for initials)
507
+ - `showInitials`: boolean
508
+
509
+ ### AdwSpinner
510
+ Loading spinner.
511
+
512
+ ```tsx
513
+ <AdwSpinner widthRequest={32} heightRequest={32} />
514
+ ```
515
+
516
+ ### AdwWindowTitle
517
+ Title and subtitle for header bars.
518
+
519
+ ```tsx
520
+ <AdwWindowTitle title="App Name" subtitle="Page description" />
521
+ ```
522
+
523
+ ## Common Props
524
+
525
+ All widgets support:
526
+ - `hexpand` / `vexpand`: boolean (expand to fill space)
527
+ - `halign` / `valign`: `Gtk.Align.START` | `CENTER` | `END` | `FILL`
528
+ - `marginStart` / `marginEnd` / `marginTop` / `marginBottom`: number
529
+ - `sensitive`: boolean (enabled/disabled)
530
+ - `visible`: boolean
531
+ - `cssClasses`: string[]
@@ -0,0 +1,19 @@
1
+ /** @type {import('jest').Config} */
2
+ export default {
3
+ preset: "ts-jest/presets/default-esm",
4
+ testEnvironment: "node",
5
+ testMatch: ["**/tests/**/*.test.ts"],
6
+ extensionsToTreatAsEsm: [".ts", ".tsx"],
7
+ moduleNameMapper: {
8
+ "^(\\.{1,2}/.*)\\.js$": "$1",
9
+ },
10
+ transform: {
11
+ "^.+\\.tsx?$": [
12
+ "ts-jest",
13
+ {
14
+ useESM: true,
15
+ tsconfig: "tsconfig.json",
16
+ },
17
+ ],
18
+ },
19
+ };
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ["tests/**/*.test.{ts,tsx}"],
6
+ globals: false,
7
+ bail: 1,
8
+ pool: "forks",
9
+ },
10
+ esbuild: {
11
+ jsx: "automatic",
12
+ },
13
+ });
@@ -0,0 +1,4 @@
1
+ node_modules/
2
+ dist/
3
+ *.log
4
+ .DS_Store
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "<%= name %>",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "gtkx dev src/dev.tsx",
8
+ "build": "tsc -b",
9
+ "start": "node dist/index.js"<% if (testing !== 'none') { %>,
10
+ "test": "<% if (testing === 'vitest') { %>GDK_BACKEND=x11 GSK_RENDERER=cairo LIBGL_ALWAYS_SOFTWARE=1 xvfb-run -a vitest<% } else if (testing === 'jest') { %>GDK_BACKEND=x11 GSK_RENDERER=cairo LIBGL_ALWAYS_SOFTWARE=1 xvfb-run -a jest<% } else if (testing === 'node') { %>GDK_BACKEND=x11 GSK_RENDERER=cairo LIBGL_ALWAYS_SOFTWARE=1 xvfb-run -a node --import tsx --test tests/**/*.test.ts<% } %>"<% } %>
11
+ },
12
+ "gtkx": {
13
+ "appId": "<%= appId %>"
14
+ }
15
+ }
@@ -0,0 +1,17 @@
1
+ import { useState } from "react";
2
+ import * as Gtk from "@gtkx/ffi/gtk";
3
+ import { GtkApplicationWindow, GtkBox, GtkButton, GtkLabel, quit } from "@gtkx/react";
4
+
5
+ export default function App() {
6
+ const [count, setCount] = useState(0);
7
+
8
+ return (
9
+ <GtkApplicationWindow title="<%= title %>" defaultWidth={400} defaultHeight={300} onCloseRequest={quit}>
10
+ <GtkBox orientation={Gtk.Orientation.VERTICAL} spacing={20} marginTop={40} marginStart={40} marginEnd={40}>
11
+ Welcome to GTKX!
12
+ <GtkLabel label={`Count: ${count}`} />
13
+ <GtkButton label="Increment" onClicked={() => setCount((c) => c + 1)} />
14
+ </GtkBox>
15
+ </GtkApplicationWindow>
16
+ );
17
+ }
@@ -0,0 +1,5 @@
1
+ import pkg from "../package.json" with { type: "json" };
2
+
3
+ export { default } from "./app.js";
4
+
5
+ export const appId = pkg.gtkx.appId;
@@ -0,0 +1,4 @@
1
+ import { render } from "@gtkx/react";
2
+ import App from "./app.js";
3
+
4
+ render(<App />, "<%= appId %>");
@@ -0,0 +1,27 @@
1
+ <% if (testing === 'vitest') { -%>
2
+ import { describe, it, expect, afterEach } from "vitest";
3
+ <% } else if (testing === 'jest') { -%>
4
+ import { describe, it, expect, afterEach } from "@jest/globals";
5
+ <% } else { -%>
6
+ import { describe, it, after } from "node:test";
7
+ import { strict as assert } from "node:assert";
8
+ <% } -%>
9
+ import * as Gtk from "@gtkx/ffi/gtk";
10
+ import { cleanup, render, screen } from "@gtkx/testing";
11
+ import App from "../src/app.js";
12
+
13
+ <%= testing === 'node' ? 'after' : 'afterEach' %>(async () => {
14
+ await cleanup();
15
+ });
16
+
17
+ describe("App", () => {
18
+ it("renders the increment button", async () => {
19
+ await render(<App />, { wrapper: false });
20
+ const button = await screen.findByRole(Gtk.AccessibleRole.BUTTON, { name: "Increment" });
21
+ <% if (testing === 'node') { -%>
22
+ assert.ok(button, "Button should be rendered");
23
+ <% } else { -%>
24
+ expect(button).toBeDefined();
25
+ <% } -%>
26
+ });
27
+ });
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "jsx": "react-jsx",
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "resolveJsonModule": true,
10
+ "outDir": "dist",
11
+ "rootDir": "src"
12
+ },
13
+ "include": ["src/**/*"]
14
+ }