@gtkx/cli 0.18.9 → 0.20.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.
- package/dist/create.js +1 -1
- package/dist/vite-plugin-gtkx-assets.d.ts +14 -5
- package/dist/vite-plugin-gtkx-assets.d.ts.map +1 -1
- package/dist/vite-plugin-gtkx-assets.js +37 -6
- package/dist/vite-plugin-gtkx-assets.js.map +1 -1
- package/env.d.ts +11 -0
- package/package.json +13 -10
- package/src/create.ts +1 -1
- package/src/vite-plugin-gtkx-assets.ts +43 -6
- package/templates/claude/EXAMPLES.md.ejs +37 -46
- package/templates/claude/WIDGETS.md.ejs +39 -37
- package/templates/package.json.ejs +1 -0
- package/templates/src/gtkx-env.d.ts.ejs +1 -0
- package/templates/src/vite-env.d.ts.ejs +0 -1
package/dist/create.js
CHANGED
|
@@ -126,7 +126,7 @@ const scaffoldProject = (projectPath, resolved) => {
|
|
|
126
126
|
writeFileSync(join(projectPath, "src", "app.tsx"), renderFile("src/app.tsx.ejs", context));
|
|
127
127
|
writeFileSync(join(projectPath, "src", "dev.tsx"), renderFile("src/dev.tsx.ejs", context));
|
|
128
128
|
writeFileSync(join(projectPath, "src", "index.tsx"), renderFile("src/index.tsx.ejs", context));
|
|
129
|
-
writeFileSync(join(projectPath, "src", "
|
|
129
|
+
writeFileSync(join(projectPath, "src", "gtkx-env.d.ts"), renderFile("src/gtkx-env.d.ts.ejs", context));
|
|
130
130
|
writeFileSync(join(projectPath, ".gitignore"), renderFile("gitignore.ejs", context));
|
|
131
131
|
if (claudeSkills) {
|
|
132
132
|
const skillsDir = join(projectPath, ".claude", "skills", "developing-gtkx-apps");
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import type { Plugin } from "vite";
|
|
2
2
|
/**
|
|
3
|
-
* Vite plugin that resolves static asset imports to filesystem paths
|
|
3
|
+
* Vite plugin that resolves static asset imports to filesystem paths
|
|
4
|
+
* and handles CSS imports for GTK applications.
|
|
4
5
|
*
|
|
5
|
-
* In dev mode, asset imports resolve to the absolute
|
|
6
|
-
* In build mode, Vite's built-in asset pipeline handles
|
|
7
|
-
* hashing; the `renderBuiltUrl` config in the builder
|
|
8
|
-
* URL to a filesystem path via `import.meta.url`.
|
|
6
|
+
* **Non-CSS assets:** In dev mode, asset imports resolve to the absolute
|
|
7
|
+
* source file path. In build mode, Vite's built-in asset pipeline handles
|
|
8
|
+
* emission and hashing; the `renderBuiltUrl` config in the builder
|
|
9
|
+
* converts the URL to a filesystem path via `import.meta.url`.
|
|
10
|
+
*
|
|
11
|
+
* **CSS imports (`import "./style.css"`):** Transformed into a module that
|
|
12
|
+
* calls `injectGlobal` from `@gtkx/css` with the file's contents, injecting
|
|
13
|
+
* the styles into the GTK CSS provider at runtime.
|
|
14
|
+
*
|
|
15
|
+
* **CSS URL imports (`import path from "./style.css?url"`):** Handled by
|
|
16
|
+
* Vite's built-in `?url` mechanism, which emits the file as an asset and
|
|
17
|
+
* resolves it to a filesystem path via `renderBuiltUrl`.
|
|
9
18
|
*/
|
|
10
19
|
export declare function gtkxAssets(): Plugin;
|
|
11
20
|
//# sourceMappingURL=vite-plugin-gtkx-assets.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin-gtkx-assets.d.ts","sourceRoot":"","sources":["../src/vite-plugin-gtkx-assets.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"vite-plugin-gtkx-assets.d.ts","sourceRoot":"","sources":["../src/vite-plugin-gtkx-assets.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAMnC;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,UAAU,IAAI,MAAM,CA4CnC"}
|
|
@@ -1,21 +1,52 @@
|
|
|
1
|
-
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
const ASSET_RE = /\.(png|jpe?g|gif|svg|webp|webm|mp4|ogg|mp3|wav|flac|aac|woff2?|eot|ttf|otf|ico|avif|data)$/i;
|
|
3
|
+
const CSS_RE = /\.css$/i;
|
|
4
|
+
const VIRTUAL_PREFIX = "\0gtkx:";
|
|
2
5
|
/**
|
|
3
|
-
* Vite plugin that resolves static asset imports to filesystem paths
|
|
6
|
+
* Vite plugin that resolves static asset imports to filesystem paths
|
|
7
|
+
* and handles CSS imports for GTK applications.
|
|
4
8
|
*
|
|
5
|
-
* In dev mode, asset imports resolve to the absolute
|
|
6
|
-
* In build mode, Vite's built-in asset pipeline handles
|
|
7
|
-
* hashing; the `renderBuiltUrl` config in the builder
|
|
8
|
-
* URL to a filesystem path via `import.meta.url`.
|
|
9
|
+
* **Non-CSS assets:** In dev mode, asset imports resolve to the absolute
|
|
10
|
+
* source file path. In build mode, Vite's built-in asset pipeline handles
|
|
11
|
+
* emission and hashing; the `renderBuiltUrl` config in the builder
|
|
12
|
+
* converts the URL to a filesystem path via `import.meta.url`.
|
|
13
|
+
*
|
|
14
|
+
* **CSS imports (`import "./style.css"`):** Transformed into a module that
|
|
15
|
+
* calls `injectGlobal` from `@gtkx/css` with the file's contents, injecting
|
|
16
|
+
* the styles into the GTK CSS provider at runtime.
|
|
17
|
+
*
|
|
18
|
+
* **CSS URL imports (`import path from "./style.css?url"`):** Handled by
|
|
19
|
+
* Vite's built-in `?url` mechanism, which emits the file as an asset and
|
|
20
|
+
* resolves it to a filesystem path via `renderBuiltUrl`.
|
|
9
21
|
*/
|
|
10
22
|
export function gtkxAssets() {
|
|
11
23
|
let isBuild = false;
|
|
12
24
|
return {
|
|
13
25
|
name: "gtkx:assets",
|
|
14
26
|
enforce: "pre",
|
|
27
|
+
config() {
|
|
28
|
+
return {
|
|
29
|
+
assetsInclude: [ASSET_RE],
|
|
30
|
+
};
|
|
31
|
+
},
|
|
15
32
|
configResolved(config) {
|
|
16
33
|
isBuild = config.command === "build";
|
|
17
34
|
},
|
|
35
|
+
async resolveId(source, importer, options) {
|
|
36
|
+
if (!CSS_RE.test(source)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const resolved = await this.resolve(source, importer, { ...options, skipSelf: true });
|
|
40
|
+
if (!resolved || resolved.external)
|
|
41
|
+
return;
|
|
42
|
+
return `${VIRTUAL_PREFIX + resolved.id}?inject`;
|
|
43
|
+
},
|
|
18
44
|
load(id) {
|
|
45
|
+
if (id.startsWith(VIRTUAL_PREFIX) && id.endsWith("?inject")) {
|
|
46
|
+
const filePath = id.slice(VIRTUAL_PREFIX.length, -"?inject".length);
|
|
47
|
+
const content = readFileSync(filePath, "utf-8");
|
|
48
|
+
return [`import { injectGlobal } from "@gtkx/css";`, `injectGlobal(${JSON.stringify(content)});`].join("\n");
|
|
49
|
+
}
|
|
19
50
|
if (isBuild || !ASSET_RE.test(id)) {
|
|
20
51
|
return;
|
|
21
52
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin-gtkx-assets.js","sourceRoot":"","sources":["../src/vite-plugin-gtkx-assets.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"vite-plugin-gtkx-assets.js","sourceRoot":"","sources":["../src/vite-plugin-gtkx-assets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGvC,MAAM,QAAQ,GAAG,6FAA6F,CAAC;AAC/G,MAAM,MAAM,GAAG,SAAS,CAAC;AACzB,MAAM,cAAc,GAAG,SAAS,CAAC;AAEjC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,UAAU;IACtB,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,OAAO;QACH,IAAI,EAAE,aAAa;QACnB,OAAO,EAAE,KAAK;QAEd,MAAM;YACF,OAAO;gBACH,aAAa,EAAE,CAAC,QAAQ,CAAC;aAC5B,CAAC;QACN,CAAC;QAED,cAAc,CAAC,MAAM;YACjB,OAAO,GAAG,MAAM,CAAC,OAAO,KAAK,OAAO,CAAC;QACzC,CAAC;QAED,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO;YACrC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;gBACvB,OAAO;YACX,CAAC;YAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;YACtF,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ;gBAAE,OAAO;YAE3C,OAAO,GAAG,cAAc,GAAG,QAAQ,CAAC,EAAE,SAAS,CAAC;QACpD,CAAC;QAED,IAAI,CAAC,EAAE;YACH,IAAI,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC1D,MAAM,QAAQ,GAAG,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;gBACpE,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBAChD,OAAO,CAAC,2CAA2C,EAAE,gBAAgB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAClG,IAAI,CACP,CAAC;YACN,CAAC;YAED,IAAI,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;gBAChC,OAAO;YACX,CAAC;YAED,OAAO,kBAAkB,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,CAAC;QACnD,CAAC;KACJ,CAAC;AACN,CAAC"}
|
package/env.d.ts
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gtkx/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "CLI for GTKX - create and develop GTK4 React applications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gtkx",
|
|
@@ -45,7 +45,8 @@
|
|
|
45
45
|
"./refresh-runtime": {
|
|
46
46
|
"types": "./dist/refresh-runtime.d.ts",
|
|
47
47
|
"default": "./dist/refresh-runtime.js"
|
|
48
|
-
}
|
|
48
|
+
},
|
|
49
|
+
"./env": "./env.d.ts"
|
|
49
50
|
},
|
|
50
51
|
"bin": {
|
|
51
52
|
"gtkx": "bin/gtkx.js"
|
|
@@ -54,29 +55,30 @@
|
|
|
54
55
|
"files": [
|
|
55
56
|
"bin",
|
|
56
57
|
"dist",
|
|
58
|
+
"env.d.ts",
|
|
57
59
|
"src",
|
|
58
60
|
"templates"
|
|
59
61
|
],
|
|
60
62
|
"dependencies": {
|
|
61
|
-
"@clack/prompts": "^1.0.
|
|
63
|
+
"@clack/prompts": "^1.0.1",
|
|
62
64
|
"@swc/core": "^1.15.11",
|
|
63
|
-
"citty": "^0.2.
|
|
65
|
+
"citty": "^0.2.1",
|
|
64
66
|
"ejs": "^4.0.1",
|
|
65
67
|
"react-refresh": "^0.18.0",
|
|
66
68
|
"vite": "^7.3.1",
|
|
67
|
-
"@gtkx/ffi": "0.
|
|
68
|
-
"@gtkx/mcp": "0.
|
|
69
|
-
"@gtkx/react": "0.
|
|
69
|
+
"@gtkx/ffi": "0.20.0",
|
|
70
|
+
"@gtkx/mcp": "0.20.0",
|
|
71
|
+
"@gtkx/react": "0.20.0"
|
|
70
72
|
},
|
|
71
73
|
"devDependencies": {
|
|
72
74
|
"@types/ejs": "^3.1.5",
|
|
73
75
|
"@types/react-refresh": "^0.14.7",
|
|
74
76
|
"memfs": "^4.56.10",
|
|
75
|
-
"@gtkx/testing": "0.
|
|
77
|
+
"@gtkx/testing": "0.20.0"
|
|
76
78
|
},
|
|
77
79
|
"peerDependencies": {
|
|
78
80
|
"react": "^19",
|
|
79
|
-
"@gtkx/testing": "0.
|
|
81
|
+
"@gtkx/testing": "0.20.0"
|
|
80
82
|
},
|
|
81
83
|
"peerDependenciesMeta": {
|
|
82
84
|
"@gtkx/testing": {
|
|
@@ -85,6 +87,7 @@
|
|
|
85
87
|
},
|
|
86
88
|
"scripts": {
|
|
87
89
|
"build": "tsc -b",
|
|
88
|
-
"test": "vitest run"
|
|
90
|
+
"test": "vitest run",
|
|
91
|
+
"typecheck": "tsc -b --emitDeclarationOnly"
|
|
89
92
|
}
|
|
90
93
|
}
|
package/src/create.ts
CHANGED
|
@@ -196,7 +196,7 @@ const scaffoldProject = (projectPath: string, resolved: ResolvedOptions): void =
|
|
|
196
196
|
writeFileSync(join(projectPath, "src", "app.tsx"), renderFile("src/app.tsx.ejs", context));
|
|
197
197
|
writeFileSync(join(projectPath, "src", "dev.tsx"), renderFile("src/dev.tsx.ejs", context));
|
|
198
198
|
writeFileSync(join(projectPath, "src", "index.tsx"), renderFile("src/index.tsx.ejs", context));
|
|
199
|
-
writeFileSync(join(projectPath, "src", "
|
|
199
|
+
writeFileSync(join(projectPath, "src", "gtkx-env.d.ts"), renderFile("src/gtkx-env.d.ts.ejs", context));
|
|
200
200
|
writeFileSync(join(projectPath, ".gitignore"), renderFile("gitignore.ejs", context));
|
|
201
201
|
|
|
202
202
|
if (claudeSkills) {
|
|
@@ -1,14 +1,26 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
1
2
|
import type { Plugin } from "vite";
|
|
2
3
|
|
|
3
|
-
const ASSET_RE = /\.(png|jpe?g|gif|svg|webp|webm|mp4|ogg|mp3|wav|flac|aac|woff2?|eot|ttf|otf|ico|avif)$/i;
|
|
4
|
+
const ASSET_RE = /\.(png|jpe?g|gif|svg|webp|webm|mp4|ogg|mp3|wav|flac|aac|woff2?|eot|ttf|otf|ico|avif|data)$/i;
|
|
5
|
+
const CSS_RE = /\.css$/i;
|
|
6
|
+
const VIRTUAL_PREFIX = "\0gtkx:";
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
|
-
* Vite plugin that resolves static asset imports to filesystem paths
|
|
9
|
+
* Vite plugin that resolves static asset imports to filesystem paths
|
|
10
|
+
* and handles CSS imports for GTK applications.
|
|
7
11
|
*
|
|
8
|
-
* In dev mode, asset imports resolve to the absolute
|
|
9
|
-
* In build mode, Vite's built-in asset pipeline handles
|
|
10
|
-
* hashing; the `renderBuiltUrl` config in the builder
|
|
11
|
-
* URL to a filesystem path via `import.meta.url`.
|
|
12
|
+
* **Non-CSS assets:** In dev mode, asset imports resolve to the absolute
|
|
13
|
+
* source file path. In build mode, Vite's built-in asset pipeline handles
|
|
14
|
+
* emission and hashing; the `renderBuiltUrl` config in the builder
|
|
15
|
+
* converts the URL to a filesystem path via `import.meta.url`.
|
|
16
|
+
*
|
|
17
|
+
* **CSS imports (`import "./style.css"`):** Transformed into a module that
|
|
18
|
+
* calls `injectGlobal` from `@gtkx/css` with the file's contents, injecting
|
|
19
|
+
* the styles into the GTK CSS provider at runtime.
|
|
20
|
+
*
|
|
21
|
+
* **CSS URL imports (`import path from "./style.css?url"`):** Handled by
|
|
22
|
+
* Vite's built-in `?url` mechanism, which emits the file as an asset and
|
|
23
|
+
* resolves it to a filesystem path via `renderBuiltUrl`.
|
|
12
24
|
*/
|
|
13
25
|
export function gtkxAssets(): Plugin {
|
|
14
26
|
let isBuild = false;
|
|
@@ -17,11 +29,36 @@ export function gtkxAssets(): Plugin {
|
|
|
17
29
|
name: "gtkx:assets",
|
|
18
30
|
enforce: "pre",
|
|
19
31
|
|
|
32
|
+
config() {
|
|
33
|
+
return {
|
|
34
|
+
assetsInclude: [ASSET_RE],
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
|
|
20
38
|
configResolved(config) {
|
|
21
39
|
isBuild = config.command === "build";
|
|
22
40
|
},
|
|
23
41
|
|
|
42
|
+
async resolveId(source, importer, options) {
|
|
43
|
+
if (!CSS_RE.test(source)) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const resolved = await this.resolve(source, importer, { ...options, skipSelf: true });
|
|
48
|
+
if (!resolved || resolved.external) return;
|
|
49
|
+
|
|
50
|
+
return `${VIRTUAL_PREFIX + resolved.id}?inject`;
|
|
51
|
+
},
|
|
52
|
+
|
|
24
53
|
load(id) {
|
|
54
|
+
if (id.startsWith(VIRTUAL_PREFIX) && id.endsWith("?inject")) {
|
|
55
|
+
const filePath = id.slice(VIRTUAL_PREFIX.length, -"?inject".length);
|
|
56
|
+
const content = readFileSync(filePath, "utf-8");
|
|
57
|
+
return [`import { injectGlobal } from "@gtkx/css";`, `injectGlobal(${JSON.stringify(content)});`].join(
|
|
58
|
+
"\n",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
25
62
|
if (isBuild || !ASSET_RE.test(id)) {
|
|
26
63
|
return;
|
|
27
64
|
}
|
|
@@ -101,7 +101,7 @@ const LoginForm = () => {
|
|
|
101
101
|
|
|
102
102
|
```tsx
|
|
103
103
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
104
|
-
import { GtkBox, GtkButton, GtkEntry, GtkLabel, GtkScrolledWindow
|
|
104
|
+
import { GtkBox, GtkButton, GtkEntry, GtkLabel, GtkScrolledWindow } from "@gtkx/react";
|
|
105
105
|
import { useCallback, useState } from "react";
|
|
106
106
|
|
|
107
107
|
interface Todo {
|
|
@@ -133,17 +133,14 @@ const TodoList = () => {
|
|
|
133
133
|
</GtkBox>
|
|
134
134
|
<GtkScrolledWindow vexpand cssClasses={["card"]}>
|
|
135
135
|
<GtkListView
|
|
136
|
-
|
|
136
|
+
items={todos.map((todo) => ({ id: todo.id, value: todo }))}
|
|
137
|
+
renderItem={(todo: Todo) => (
|
|
137
138
|
<GtkBox spacing={8} marginStart={12} marginEnd={12} marginTop={8} marginBottom={8}>
|
|
138
|
-
<GtkLabel label={todo
|
|
139
|
-
<GtkButton iconName="edit-delete-symbolic" cssClasses={["flat"]} onClicked={() =>
|
|
139
|
+
<GtkLabel label={todo.text} hexpand halign={Gtk.Align.START} />
|
|
140
|
+
<GtkButton iconName="edit-delete-symbolic" cssClasses={["flat"]} onClicked={() => deleteTodo(todo.id)} />
|
|
140
141
|
</GtkBox>
|
|
141
142
|
)}
|
|
142
|
-
|
|
143
|
-
{todos.map((todo) => (
|
|
144
|
-
<x.ListItem key={todo.id} id={todo.id} value={todo} />
|
|
145
|
-
))}
|
|
146
|
-
</GtkListView>
|
|
143
|
+
/>
|
|
147
144
|
</GtkScrolledWindow>
|
|
148
145
|
</GtkBox>
|
|
149
146
|
);
|
|
@@ -158,7 +155,7 @@ const TodoList = () => {
|
|
|
158
155
|
|
|
159
156
|
```tsx
|
|
160
157
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
161
|
-
import { GtkBox, GtkLabel, GtkPaned, GtkScrolledWindow, GtkStack, x } from "@gtkx/react";
|
|
158
|
+
import { GtkBox, GtkLabel, GtkListView, GtkPaned, GtkScrolledWindow, GtkStack, x } from "@gtkx/react";
|
|
162
159
|
import { useState } from "react";
|
|
163
160
|
|
|
164
161
|
interface Page {
|
|
@@ -183,14 +180,11 @@ const SidebarNav = () => {
|
|
|
183
180
|
selected={[currentPage]}
|
|
184
181
|
selectionMode={Gtk.SelectionMode.SINGLE}
|
|
185
182
|
onSelectionChanged={(ids) => setCurrentPage(ids[0])}
|
|
186
|
-
|
|
187
|
-
|
|
183
|
+
items={pages.map((page) => ({ id: page.id, value: page }))}
|
|
184
|
+
renderItem={(page: Page) => (
|
|
185
|
+
<GtkLabel label={page.name} halign={Gtk.Align.START} marginStart={12} marginTop={8} marginBottom={8} />
|
|
188
186
|
)}
|
|
189
|
-
|
|
190
|
-
{pages.map((page) => (
|
|
191
|
-
<x.ListItem key={page.id} id={page.id} value={page} />
|
|
192
|
-
))}
|
|
193
|
-
</GtkListView>
|
|
187
|
+
/>
|
|
194
188
|
</GtkScrolledWindow>
|
|
195
189
|
</x.Slot>
|
|
196
190
|
<x.Slot for={GtkPaned} id="endChild">
|
|
@@ -339,13 +333,16 @@ const FileTable = () => {
|
|
|
339
333
|
|
|
340
334
|
return (
|
|
341
335
|
<GtkScrolledWindow vexpand cssClasses={["card"]}>
|
|
342
|
-
<GtkColumnView
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
{
|
|
347
|
-
|
|
348
|
-
|
|
336
|
+
<GtkColumnView
|
|
337
|
+
estimatedRowHeight={48}
|
|
338
|
+
sortColumn={sortColumn}
|
|
339
|
+
sortOrder={sortOrder}
|
|
340
|
+
onSortChanged={handleSort}
|
|
341
|
+
items={sortedFiles.map((file) => ({ id: file.id, value: file }))}
|
|
342
|
+
>
|
|
343
|
+
<x.ColumnViewColumn title="Name" id="name" expand sortable renderCell={(f: FileItem) => <GtkLabel label={f.name} />} />
|
|
344
|
+
<x.ColumnViewColumn title="Size" id="size" fixedWidth={100} sortable renderCell={(f: FileItem) => <GtkLabel label={`${f.size} KB`} />} />
|
|
345
|
+
<x.ColumnViewColumn title="Modified" id="modified" fixedWidth={120} sortable renderCell={(f: FileItem) => <GtkLabel label={f.modified} />} />
|
|
349
346
|
</GtkColumnView>
|
|
350
347
|
</GtkScrolledWindow>
|
|
351
348
|
);
|
|
@@ -398,7 +395,7 @@ const MenuDemo = () => {
|
|
|
398
395
|
|
|
399
396
|
```tsx
|
|
400
397
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
401
|
-
import { AdwSpinner, GtkBox, GtkLabel,
|
|
398
|
+
import { AdwSpinner, GtkBox, GtkLabel, GtkListView, GtkScrolledWindow } from "@gtkx/react";
|
|
402
399
|
import { useEffect, useState } from "react";
|
|
403
400
|
|
|
404
401
|
interface User {
|
|
@@ -442,17 +439,14 @@ const AsyncList = () => {
|
|
|
442
439
|
return (
|
|
443
440
|
<GtkScrolledWindow vexpand>
|
|
444
441
|
<GtkListView
|
|
445
|
-
|
|
442
|
+
items={users.map((user) => ({ id: user.id, value: user }))}
|
|
443
|
+
renderItem={(user: User) => (
|
|
446
444
|
<GtkBox orientation={Gtk.Orientation.VERTICAL} marginStart={12} marginTop={8} marginBottom={8}>
|
|
447
|
-
<GtkLabel label={user
|
|
448
|
-
<GtkLabel label={user
|
|
445
|
+
<GtkLabel label={user.name} halign={Gtk.Align.START} cssClasses={["heading"]} />
|
|
446
|
+
<GtkLabel label={user.email} halign={Gtk.Align.START} cssClasses={["dim-label"]} />
|
|
449
447
|
</GtkBox>
|
|
450
448
|
)}
|
|
451
|
-
|
|
452
|
-
{users.map((user) => (
|
|
453
|
-
<x.ListItem key={user.id} id={user.id} value={user} />
|
|
454
|
-
))}
|
|
455
|
-
</GtkListView>
|
|
449
|
+
/>
|
|
456
450
|
</GtkScrolledWindow>
|
|
457
451
|
);
|
|
458
452
|
};
|
|
@@ -632,7 +626,7 @@ const SplitViewDemo = () => {
|
|
|
632
626
|
|
|
633
627
|
```tsx
|
|
634
628
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
635
|
-
import { GtkBox, GtkImage, GtkLabel, GtkListView, GtkScrolledWindow
|
|
629
|
+
import { GtkBox, GtkImage, GtkLabel, GtkListView, GtkScrolledWindow } from "@gtkx/react";
|
|
636
630
|
import { useState } from "react";
|
|
637
631
|
|
|
638
632
|
interface FileNode {
|
|
@@ -667,21 +661,18 @@ const FileBrowser = () => {
|
|
|
667
661
|
selectionMode={Gtk.SelectionMode.SINGLE}
|
|
668
662
|
selected={selected ? [selected] : []}
|
|
669
663
|
onSelectionChanged={(ids) => setSelected(ids[0] ?? null)}
|
|
670
|
-
|
|
664
|
+
items={files.map((file) => ({
|
|
665
|
+
id: file.id,
|
|
666
|
+
value: file,
|
|
667
|
+
children: file.children?.map((child) => ({ id: child.id, value: child })),
|
|
668
|
+
}))}
|
|
669
|
+
renderItem={(item: FileNode) => (
|
|
671
670
|
<GtkBox spacing={8}>
|
|
672
|
-
<GtkImage iconName={item
|
|
673
|
-
<GtkLabel label={item
|
|
671
|
+
<GtkImage iconName={item.isDirectory ? "folder-symbolic" : "text-x-generic-symbolic"} />
|
|
672
|
+
<GtkLabel label={item.name} halign={Gtk.Align.START} />
|
|
674
673
|
</GtkBox>
|
|
675
674
|
)}
|
|
676
|
-
|
|
677
|
-
{files.map((file) => (
|
|
678
|
-
<x.ListItem key={file.id} id={file.id} value={file}>
|
|
679
|
-
{file.children?.map((child) => (
|
|
680
|
-
<x.ListItem key={child.id} id={child.id} value={child} />
|
|
681
|
-
))}
|
|
682
|
-
</x.ListItem>
|
|
683
|
-
))}
|
|
684
|
-
</GtkListView>
|
|
675
|
+
/>
|
|
685
676
|
</GtkScrolledWindow>
|
|
686
677
|
);
|
|
687
678
|
};
|
|
@@ -130,7 +130,7 @@ Scrollable container.
|
|
|
130
130
|
|
|
131
131
|
## Virtual Lists
|
|
132
132
|
|
|
133
|
-
All virtual list widgets use `
|
|
133
|
+
All virtual list widgets use an `items` data prop and a `renderItem` function. Items are `{ id: string, value: T }` objects.
|
|
134
134
|
|
|
135
135
|
### GtkListView
|
|
136
136
|
High-performance scrollable list with selection.
|
|
@@ -142,10 +142,9 @@ High-performance scrollable list with selection.
|
|
|
142
142
|
selected={selectedId ? [selectedId] : []}
|
|
143
143
|
selectionMode={Gtk.SelectionMode.SINGLE}
|
|
144
144
|
onSelectionChanged={(ids) => setSelectedId(ids[0])}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
</GtkListView>
|
|
145
|
+
items={items.map(item => ({ id: item.id, value: item }))}
|
|
146
|
+
renderItem={(item: Item) => <GtkLabel label={item.name} />}
|
|
147
|
+
/>
|
|
149
148
|
```
|
|
150
149
|
|
|
151
150
|
### GtkGridView
|
|
@@ -156,37 +155,41 @@ Grid-based virtual scrolling.
|
|
|
156
155
|
estimatedItemHeight={100}
|
|
157
156
|
minColumns={2}
|
|
158
157
|
maxColumns={4}
|
|
159
|
-
|
|
158
|
+
items={items.map(item => ({ id: item.id, value: item }))}
|
|
159
|
+
renderItem={(item: Item) => (
|
|
160
160
|
<GtkBox orientation={Gtk.Orientation.VERTICAL}>
|
|
161
|
-
<GtkImage iconName={item
|
|
162
|
-
<GtkLabel label={item
|
|
161
|
+
<GtkImage iconName={item.icon} />
|
|
162
|
+
<GtkLabel label={item.name} />
|
|
163
163
|
</GtkBox>
|
|
164
164
|
)}
|
|
165
|
-
|
|
166
|
-
{items.map(item => <x.ListItem key={item.id} id={item.id} value={item} />)}
|
|
167
|
-
</GtkGridView>
|
|
165
|
+
/>
|
|
168
166
|
```
|
|
169
167
|
|
|
170
168
|
### GtkColumnView
|
|
171
169
|
Table with sortable columns.
|
|
172
170
|
|
|
173
171
|
```tsx
|
|
174
|
-
<GtkColumnView
|
|
175
|
-
|
|
172
|
+
<GtkColumnView
|
|
173
|
+
estimatedRowHeight={48}
|
|
174
|
+
sortColumn="name"
|
|
175
|
+
sortOrder={Gtk.SortType.ASCENDING}
|
|
176
|
+
onSortChanged={handleSort}
|
|
177
|
+
items={items.map(item => ({ id: item.id, value: item }))}
|
|
178
|
+
>
|
|
179
|
+
<x.ColumnViewColumn
|
|
176
180
|
title="Name"
|
|
177
181
|
id="name"
|
|
178
182
|
expand
|
|
179
183
|
resizable
|
|
180
184
|
sortable
|
|
181
|
-
renderCell={(item) => <GtkLabel label={item
|
|
185
|
+
renderCell={(item: Item) => <GtkLabel label={item.name} />}
|
|
182
186
|
/>
|
|
183
|
-
<x.ColumnViewColumn
|
|
187
|
+
<x.ColumnViewColumn
|
|
184
188
|
title="Size"
|
|
185
189
|
id="size"
|
|
186
190
|
fixedWidth={100}
|
|
187
|
-
renderCell={(item) => <GtkLabel label={`${item
|
|
191
|
+
renderCell={(item: Item) => <GtkLabel label={`${item.size} KB`} />}
|
|
188
192
|
/>
|
|
189
|
-
{items.map(item => <x.ListItem key={item.id} id={item.id} value={item} />)}
|
|
190
193
|
</GtkColumnView>
|
|
191
194
|
```
|
|
192
195
|
|
|
@@ -194,13 +197,15 @@ Table with sortable columns.
|
|
|
194
197
|
Selection dropdown.
|
|
195
198
|
|
|
196
199
|
```tsx
|
|
197
|
-
<GtkDropDown
|
|
198
|
-
|
|
199
|
-
|
|
200
|
+
<GtkDropDown
|
|
201
|
+
selectedId={selectedId}
|
|
202
|
+
onSelectionChanged={setSelectedId}
|
|
203
|
+
items={options.map(opt => ({ id: opt.id, value: opt.label }))}
|
|
204
|
+
/>
|
|
200
205
|
```
|
|
201
206
|
|
|
202
207
|
### GtkListView (tree mode)
|
|
203
|
-
Hierarchical tree with expand/collapse.
|
|
208
|
+
Hierarchical tree with expand/collapse. Items with nested `children` arrays trigger tree behavior.
|
|
204
209
|
|
|
205
210
|
```tsx
|
|
206
211
|
<GtkListView
|
|
@@ -210,24 +215,21 @@ Hierarchical tree with expand/collapse. Nesting `x.ListItem` children triggers t
|
|
|
210
215
|
selectionMode={Gtk.SelectionMode.SINGLE}
|
|
211
216
|
selected={selectedId ? [selectedId] : []}
|
|
212
217
|
onSelectionChanged={(ids) => setSelectedId(ids[0])}
|
|
213
|
-
|
|
218
|
+
items={files.map(file => ({
|
|
219
|
+
id: file.id,
|
|
220
|
+
value: file,
|
|
221
|
+
children: file.children?.map(child => ({ id: child.id, value: child })),
|
|
222
|
+
}))}
|
|
223
|
+
renderItem={(item: FileNode, row) => (
|
|
214
224
|
<GtkBox spacing={8}>
|
|
215
|
-
<GtkImage iconName={item
|
|
216
|
-
<GtkLabel label={item
|
|
225
|
+
<GtkImage iconName={item.isDirectory ? "folder-symbolic" : "text-x-generic-symbolic"} />
|
|
226
|
+
<GtkLabel label={item.name} />
|
|
217
227
|
</GtkBox>
|
|
218
228
|
)}
|
|
219
|
-
|
|
220
|
-
{files.map(file => (
|
|
221
|
-
<x.ListItem key={file.id} id={file.id} value={file}>
|
|
222
|
-
{file.children?.map(child => (
|
|
223
|
-
<x.ListItem key={child.id} id={child.id} value={child} />
|
|
224
|
-
))}
|
|
225
|
-
</x.ListItem>
|
|
226
|
-
))}
|
|
227
|
-
</GtkListView>
|
|
229
|
+
/>
|
|
228
230
|
```
|
|
229
231
|
|
|
230
|
-
**ListItem
|
|
232
|
+
**ListItem data props:** `id`, `value`, `children` (nested items for tree mode), `hideExpander`, `indentForDepth`, `indentForIcon`, `section` (for sectioned lists)
|
|
231
233
|
|
|
232
234
|
---
|
|
233
235
|
|
|
@@ -799,14 +801,14 @@ Text content is provided as direct children. Use `x.TextTag` for formatting and
|
|
|
799
801
|
|
|
800
802
|
## Keyboard Shortcuts
|
|
801
803
|
|
|
802
|
-
Attach shortcuts with
|
|
804
|
+
Attach shortcuts with `<GtkShortcutController>` and `x.Shortcut`:
|
|
803
805
|
|
|
804
806
|
```tsx
|
|
805
807
|
<GtkBox orientation={Gtk.Orientation.VERTICAL} spacing={12} focusable>
|
|
806
|
-
<
|
|
808
|
+
<GtkShortcutController scope={Gtk.ShortcutScope.LOCAL}>
|
|
807
809
|
<x.Shortcut trigger="<Control>equal" onActivate={() => setCount((c) => c + 1)} />
|
|
808
810
|
<x.Shortcut trigger="<Control>minus" onActivate={() => setCount((c) => c - 1)} />
|
|
809
|
-
</
|
|
811
|
+
</GtkShortcutController>
|
|
810
812
|
<GtkLabel label={`Count: ${count}`} />
|
|
811
813
|
</GtkBox>
|
|
812
814
|
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="@gtkx/cli/env" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/// <reference types="vite/client" />
|