@gtkx/react 0.9.1 → 0.9.3
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 +4 -14
- package/dist/batch.d.ts +0 -15
- package/dist/batch.js +0 -15
- package/dist/codegen/jsx-generator.d.ts +1 -25
- package/dist/codegen/jsx-generator.js +14 -51
- package/dist/factory.d.ts +0 -7
- package/dist/factory.js +0 -4
- package/dist/fiber-root.d.ts +0 -5
- package/dist/fiber-root.js +0 -5
- package/dist/generated/jsx.d.ts +2331 -2312
- package/dist/generated/jsx.js +1664 -1656
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/node.d.ts +0 -4
- package/dist/node.js +0 -4
- package/dist/nodes/menu.js +8 -8
- package/dist/nodes/paged-stack.d.ts +0 -12
- package/dist/nodes/paged-stack.js +0 -4
- package/dist/nodes/selectable-list.js +3 -3
- package/dist/nodes/string-list-container.js +2 -2
- package/dist/nodes/toggle-button.d.ts +0 -8
- package/dist/nodes/toggle-button.js +0 -8
- package/dist/nodes/toolbar-view.d.ts +0 -4
- package/dist/nodes/toolbar-view.js +0 -4
- package/dist/nodes/virtual-item.d.ts +0 -5
- package/dist/nodes/virtual-item.js +0 -5
- package/dist/nodes/widget.d.ts +0 -5
- package/dist/nodes/widget.js +0 -5
- package/dist/nodes/window.js +3 -3
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ GTKX lets you build native Linux desktop applications using React and TypeScript
|
|
|
22
22
|
## Features
|
|
23
23
|
|
|
24
24
|
- **React** — Hooks, state, props, and components you already know
|
|
25
|
-
- **
|
|
25
|
+
- **HMR** — Edit code and see changes instantly via Vite
|
|
26
26
|
- **Native** — Direct FFI bindings to GTK4 via Rust and libffi
|
|
27
27
|
- **CLI** — `npx @gtkx/cli@latest create` scaffolds a ready-to-go project
|
|
28
28
|
- **CSS-in-JS** — Emotion-style `css` template literals for GTK styling
|
|
@@ -41,14 +41,7 @@ Edit your code and see changes instantly—no restart needed.
|
|
|
41
41
|
### Example
|
|
42
42
|
|
|
43
43
|
```tsx
|
|
44
|
-
import {
|
|
45
|
-
render,
|
|
46
|
-
ApplicationWindow,
|
|
47
|
-
Box,
|
|
48
|
-
Button,
|
|
49
|
-
Label,
|
|
50
|
-
quit,
|
|
51
|
-
} from "@gtkx/react";
|
|
44
|
+
import { render, ApplicationWindow, Box, Button, quit } from "@gtkx/react";
|
|
52
45
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
53
46
|
import { useState } from "react";
|
|
54
47
|
|
|
@@ -58,7 +51,7 @@ const App = () => {
|
|
|
58
51
|
return (
|
|
59
52
|
<ApplicationWindow title="Counter" onCloseRequest={quit}>
|
|
60
53
|
<Box orientation={Gtk.Orientation.VERTICAL} spacing={12}>
|
|
61
|
-
|
|
54
|
+
{`Count: ${count}`}
|
|
62
55
|
<Button label="Increment" onClicked={() => setCount((c) => c + 1)} />
|
|
63
56
|
</Box>
|
|
64
57
|
</ApplicationWindow>
|
|
@@ -84,8 +77,6 @@ const primary = css`
|
|
|
84
77
|
<Button label="Click me" cssClasses={[primary]} />;
|
|
85
78
|
```
|
|
86
79
|
|
|
87
|
-
GTK also provides built-in classes like `suggested-action`, `destructive-action`, `card`, and `heading`.
|
|
88
|
-
|
|
89
80
|
## Testing
|
|
90
81
|
|
|
91
82
|
```tsx
|
|
@@ -109,9 +100,8 @@ test("increments count", async () => {
|
|
|
109
100
|
|
|
110
101
|
## Requirements
|
|
111
102
|
|
|
112
|
-
- Node.js 20+
|
|
103
|
+
- Node.js 20+ (Deno support experimental)
|
|
113
104
|
- GTK4 Runtime (`gtk4` on Fedora, `libgtk-4-1` on Ubuntu)
|
|
114
|
-
- Linux
|
|
115
105
|
|
|
116
106
|
## Contributing
|
|
117
107
|
|
package/dist/batch.d.ts
CHANGED
|
@@ -1,20 +1,5 @@
|
|
|
1
1
|
type FlushCallback = () => void;
|
|
2
|
-
/**
|
|
3
|
-
* Marks the beginning of a React reconciler commit phase.
|
|
4
|
-
* While in commit, flush callbacks are deferred until endCommit is called.
|
|
5
|
-
* Supports nested commits through depth tracking.
|
|
6
|
-
*/
|
|
7
2
|
export declare const beginCommit: () => void;
|
|
8
|
-
/**
|
|
9
|
-
* Marks the end of a React reconciler commit phase.
|
|
10
|
-
* Executes all pending flush callbacks that were deferred during the commit
|
|
11
|
-
* only when the outermost commit ends (depth reaches 0).
|
|
12
|
-
* If called without a matching beginCommit, resets the state (useful for test cleanup).
|
|
13
|
-
*/
|
|
14
3
|
export declare const endCommit: () => void;
|
|
15
|
-
/**
|
|
16
|
-
* Schedules a callback to be executed, deferring it if currently in a commit phase.
|
|
17
|
-
* This ensures GTK state updates happen after React has finished its batch of changes.
|
|
18
|
-
*/
|
|
19
4
|
export declare const scheduleFlush: (callback: FlushCallback) => void;
|
|
20
5
|
export {};
|
package/dist/batch.js
CHANGED
|
@@ -2,20 +2,9 @@ const state = {
|
|
|
2
2
|
depth: 0,
|
|
3
3
|
pendingFlushes: new Set(),
|
|
4
4
|
};
|
|
5
|
-
/**
|
|
6
|
-
* Marks the beginning of a React reconciler commit phase.
|
|
7
|
-
* While in commit, flush callbacks are deferred until endCommit is called.
|
|
8
|
-
* Supports nested commits through depth tracking.
|
|
9
|
-
*/
|
|
10
5
|
export const beginCommit = () => {
|
|
11
6
|
state.depth++;
|
|
12
7
|
};
|
|
13
|
-
/**
|
|
14
|
-
* Marks the end of a React reconciler commit phase.
|
|
15
|
-
* Executes all pending flush callbacks that were deferred during the commit
|
|
16
|
-
* only when the outermost commit ends (depth reaches 0).
|
|
17
|
-
* If called without a matching beginCommit, resets the state (useful for test cleanup).
|
|
18
|
-
*/
|
|
19
8
|
export const endCommit = () => {
|
|
20
9
|
if (state.depth <= 0) {
|
|
21
10
|
state.depth = 0;
|
|
@@ -32,10 +21,6 @@ export const endCommit = () => {
|
|
|
32
21
|
});
|
|
33
22
|
}
|
|
34
23
|
};
|
|
35
|
-
/**
|
|
36
|
-
* Schedules a callback to be executed, deferring it if currently in a commit phase.
|
|
37
|
-
* This ensures GTK state updates happen after React has finished its batch of changes.
|
|
38
|
-
*/
|
|
39
24
|
export const scheduleFlush = (callback) => {
|
|
40
25
|
if (state.depth > 0) {
|
|
41
26
|
state.pendingFlushes.add(callback);
|
|
@@ -1,24 +1,11 @@
|
|
|
1
1
|
import type { GirClass, GirNamespace, TypeMapper, TypeRegistry } from "@gtkx/gir";
|
|
2
|
-
/**
|
|
3
|
-
* Configuration options for the JSX type generator.
|
|
4
|
-
*/
|
|
5
2
|
type JsxGeneratorOptions = {
|
|
6
|
-
/** Optional Prettier configuration for formatting output. */
|
|
7
3
|
prettierConfig?: unknown;
|
|
8
4
|
};
|
|
9
|
-
/**
|
|
10
|
-
* Result of the JSX generation containing both public and internal files.
|
|
11
|
-
*/
|
|
12
5
|
type JsxGeneratorResult = {
|
|
13
|
-
/** Public JSX types and components for user consumption. */
|
|
14
6
|
jsx: string;
|
|
15
|
-
/** Internal metadata for reconciler use (not exported to users). */
|
|
16
7
|
internal: string;
|
|
17
8
|
};
|
|
18
|
-
/**
|
|
19
|
-
* Generates JSX type definitions for React components from GTK widget classes.
|
|
20
|
-
* Creates TypeScript interfaces for props and augments React's JSX namespace.
|
|
21
|
-
*/
|
|
22
9
|
export declare class JsxGenerator {
|
|
23
10
|
private typeMapper;
|
|
24
11
|
private typeRegistry;
|
|
@@ -30,19 +17,8 @@ export declare class JsxGenerator {
|
|
|
30
17
|
private widgetSignalNames;
|
|
31
18
|
private currentNamespace;
|
|
32
19
|
private widgetNamespaceMap;
|
|
33
|
-
/**
|
|
34
|
-
* Creates a new JSX generator.
|
|
35
|
-
* @param typeMapper - TypeMapper for converting GIR types to TypeScript
|
|
36
|
-
* @param typeRegistry - TypeRegistry for cross-namespace type resolution
|
|
37
|
-
* @param classMap - Combined class map with fully qualified names
|
|
38
|
-
* @param options - Generator configuration options
|
|
39
|
-
*/
|
|
40
20
|
constructor(typeMapper: TypeMapper, typeRegistry: TypeRegistry, classMap: Map<string, GirClass>, options?: JsxGeneratorOptions);
|
|
41
|
-
|
|
42
|
-
* Generates JSX type definitions for all widgets in the given namespaces.
|
|
43
|
-
* @param namespaces - The parsed GIR namespaces (GTK must be first)
|
|
44
|
-
* @returns Generated TypeScript code as public jsx.ts and internal.ts files
|
|
45
|
-
*/
|
|
21
|
+
private formatDoc;
|
|
46
22
|
generate(namespaces: GirNamespace[]): Promise<JsxGeneratorResult>;
|
|
47
23
|
private generateImports;
|
|
48
24
|
private generateInternalImports;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { toCamelCase, toPascalCase } from "@gtkx/gir";
|
|
1
|
+
import { formatDoc as formatDocBase, toCamelCase, toPascalCase } from "@gtkx/gir";
|
|
2
2
|
import { format } from "prettier";
|
|
3
3
|
const LIST_WIDGETS = new Set(["ListView", "GridView"]);
|
|
4
4
|
const COLUMN_VIEW_WIDGET = "ColumnView";
|
|
@@ -40,34 +40,6 @@ const isNotebookWidget = (widgetName) => widgetName === NOTEBOOK_WIDGET;
|
|
|
40
40
|
const isStackWidget = (widgetName) => STACK_WIDGETS.has(widgetName);
|
|
41
41
|
const isPopoverMenuWidget = (widgetName) => widgetName === POPOVER_MENU_WIDGET;
|
|
42
42
|
const isToolbarViewWidget = (widgetName) => widgetName === TOOLBAR_VIEW_WIDGET;
|
|
43
|
-
const sanitizeDoc = (doc) => {
|
|
44
|
-
let result = doc;
|
|
45
|
-
result = result.replace(/<picture[^>]*>[\s\S]*?<\/picture>/gi, "");
|
|
46
|
-
result = result.replace(/<img[^>]*>/gi, "");
|
|
47
|
-
result = result.replace(/<source[^>]*>/gi, "");
|
|
48
|
-
result = result.replace(/!\[[^\]]*\]\([^)]+\.png\)/gi, "");
|
|
49
|
-
result = result.replace(/<kbd>([^<]*)<\/kbd>/gi, "`$1`");
|
|
50
|
-
result = result.replace(/<kbd>/gi, "`");
|
|
51
|
-
result = result.replace(/<\/kbd>/gi, "`");
|
|
52
|
-
result = result.replace(/\[([^\]]+)\]\([^)]+\.html[^)]*\)/gi, "$1");
|
|
53
|
-
result = result.replace(/@(\w+)\s/g, "`$1` ");
|
|
54
|
-
result = result.replace(/<(\/?)(child|object|property|signal|template)>/gi, "`<$1$2>`");
|
|
55
|
-
return result.trim();
|
|
56
|
-
};
|
|
57
|
-
const formatDoc = (doc, indent = "") => {
|
|
58
|
-
if (!doc)
|
|
59
|
-
return "";
|
|
60
|
-
const sanitized = sanitizeDoc(doc);
|
|
61
|
-
if (!sanitized)
|
|
62
|
-
return "";
|
|
63
|
-
const lines = sanitized.split("\n").map((line) => line.trim());
|
|
64
|
-
const firstLine = lines[0] ?? "";
|
|
65
|
-
if (lines.length === 1 && firstLine.length < 80) {
|
|
66
|
-
return `${indent}/** ${firstLine} */\n`;
|
|
67
|
-
}
|
|
68
|
-
const formattedLines = lines.map((line) => `${indent} * ${line}`);
|
|
69
|
-
return `${indent}/**\n${formattedLines.join("\n")}\n${indent} */\n`;
|
|
70
|
-
};
|
|
71
43
|
const isWidgetSubclass = (typeName, classMap, visited = new Set()) => {
|
|
72
44
|
const className = typeName.includes(".") ? typeName.split(".")[1] : typeName;
|
|
73
45
|
if (!className || visited.has(className))
|
|
@@ -80,10 +52,6 @@ const isWidgetSubclass = (typeName, classMap, visited = new Set()) => {
|
|
|
80
52
|
return true;
|
|
81
53
|
return cls.parent ? isWidgetSubclass(cls.parent, classMap, visited) : false;
|
|
82
54
|
};
|
|
83
|
-
/**
|
|
84
|
-
* Generates JSX type definitions for React components from GTK widget classes.
|
|
85
|
-
* Creates TypeScript interfaces for props and augments React's JSX namespace.
|
|
86
|
-
*/
|
|
87
55
|
export class JsxGenerator {
|
|
88
56
|
typeMapper;
|
|
89
57
|
typeRegistry;
|
|
@@ -95,24 +63,19 @@ export class JsxGenerator {
|
|
|
95
63
|
widgetSignalNames = new Set();
|
|
96
64
|
currentNamespace = "";
|
|
97
65
|
widgetNamespaceMap = new Map();
|
|
98
|
-
/**
|
|
99
|
-
* Creates a new JSX generator.
|
|
100
|
-
* @param typeMapper - TypeMapper for converting GIR types to TypeScript
|
|
101
|
-
* @param typeRegistry - TypeRegistry for cross-namespace type resolution
|
|
102
|
-
* @param classMap - Combined class map with fully qualified names
|
|
103
|
-
* @param options - Generator configuration options
|
|
104
|
-
*/
|
|
105
66
|
constructor(typeMapper, typeRegistry, classMap, options = {}) {
|
|
106
67
|
this.typeMapper = typeMapper;
|
|
107
68
|
this.typeRegistry = typeRegistry;
|
|
108
69
|
this.classMap = classMap;
|
|
109
70
|
this.options = options;
|
|
110
71
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
72
|
+
formatDoc(doc, indent = "") {
|
|
73
|
+
return formatDocBase(doc, indent, {
|
|
74
|
+
namespace: this.currentNamespace,
|
|
75
|
+
escapeXmlTags: true,
|
|
76
|
+
linkStyle: "prefixed",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
116
79
|
async generate(namespaces) {
|
|
117
80
|
for (const ns of namespaces) {
|
|
118
81
|
for (const iface of ns.interfaces) {
|
|
@@ -181,7 +144,7 @@ ${widgetPropsContent}
|
|
|
181
144
|
}
|
|
182
145
|
generateWidgetPropsContent(widgetClass) {
|
|
183
146
|
const lines = [];
|
|
184
|
-
const widgetDoc = widgetClass?.doc ? formatDoc(widgetClass.doc) : "";
|
|
147
|
+
const widgetDoc = widgetClass?.doc ? this.formatDoc(widgetClass.doc) : "";
|
|
185
148
|
if (widgetDoc) {
|
|
186
149
|
lines.push(widgetDoc.trimEnd());
|
|
187
150
|
}
|
|
@@ -191,7 +154,7 @@ ${widgetPropsContent}
|
|
|
191
154
|
const propName = toCamelCase(prop.name);
|
|
192
155
|
const tsType = this.toJsxPropertyType(this.typeMapper.mapType(prop.type).ts, "Gtk");
|
|
193
156
|
if (prop.doc) {
|
|
194
|
-
lines.push(formatDoc(prop.doc, "\t").trimEnd());
|
|
157
|
+
lines.push(this.formatDoc(prop.doc, "\t").trimEnd());
|
|
195
158
|
}
|
|
196
159
|
lines.push(`\t${propName}?: ${tsType};`);
|
|
197
160
|
}
|
|
@@ -199,7 +162,7 @@ ${widgetPropsContent}
|
|
|
199
162
|
lines.push("");
|
|
200
163
|
for (const signal of widgetClass.signals) {
|
|
201
164
|
if (signal.doc) {
|
|
202
|
-
lines.push(formatDoc(signal.doc, "\t").trimEnd());
|
|
165
|
+
lines.push(this.formatDoc(signal.doc, "\t").trimEnd());
|
|
203
166
|
}
|
|
204
167
|
lines.push(`\t${this.generateSignalHandler(signal, "Widget")}`);
|
|
205
168
|
}
|
|
@@ -354,7 +317,7 @@ ${widgetPropsContent}
|
|
|
354
317
|
const isRequiredByConstructor = requiredCtorParams.has(prop.name);
|
|
355
318
|
const isRequired = isRequiredByConstructor;
|
|
356
319
|
if (prop.doc) {
|
|
357
|
-
lines.push(formatDoc(prop.doc, "\t").trimEnd());
|
|
320
|
+
lines.push(this.formatDoc(prop.doc, "\t").trimEnd());
|
|
358
321
|
}
|
|
359
322
|
lines.push(`\t${propName}${isRequired ? "" : "?"}: ${tsType};`);
|
|
360
323
|
}
|
|
@@ -377,7 +340,7 @@ ${widgetPropsContent}
|
|
|
377
340
|
lines.push("");
|
|
378
341
|
for (const signal of specificSignals) {
|
|
379
342
|
if (signal.doc) {
|
|
380
|
-
lines.push(formatDoc(signal.doc, "\t").trimEnd());
|
|
343
|
+
lines.push(this.formatDoc(signal.doc, "\t").trimEnd());
|
|
381
344
|
}
|
|
382
345
|
lines.push(`\t${this.generateSignalHandler(signal, widget.name)}`);
|
|
383
346
|
}
|
|
@@ -716,7 +679,7 @@ ${widgetPropsContent}
|
|
|
716
679
|
isNotebookWidget(widget.name) ||
|
|
717
680
|
isStackWidget(widget.name) ||
|
|
718
681
|
isPopoverMenuWidget(widget.name);
|
|
719
|
-
const docComment = widget.doc ? formatDoc(widget.doc).trimEnd() : "";
|
|
682
|
+
const docComment = widget.doc ? this.formatDoc(widget.doc).trimEnd() : "";
|
|
720
683
|
if (hasMeaningfulSlots) {
|
|
721
684
|
if (isListWidget(widget.name) ||
|
|
722
685
|
isColumnViewWidget(widget.name) ||
|
package/dist/factory.d.ts
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import type * as Gtk from "@gtkx/ffi/gtk";
|
|
2
2
|
import type { Node } from "./node.js";
|
|
3
3
|
import { type ROOT_NODE_CONTAINER } from "./nodes/root.js";
|
|
4
|
-
/**
|
|
5
|
-
* Generic props type for React component properties passed to GTK nodes.
|
|
6
|
-
*/
|
|
7
4
|
export type Props = Record<string, unknown>;
|
|
8
5
|
export { ROOT_NODE_CONTAINER } from "./nodes/root.js";
|
|
9
|
-
/**
|
|
10
|
-
* Creates a Node instance for the given JSX element type.
|
|
11
|
-
* Matches the type against registered node classes and initializes with props.
|
|
12
|
-
*/
|
|
13
6
|
export declare const createNode: (type: string, props: Props, widget?: Gtk.Widget | typeof ROOT_NODE_CONTAINER) => Node;
|
package/dist/factory.js
CHANGED
|
@@ -63,10 +63,6 @@ const CONTAINER_NODES = [
|
|
|
63
63
|
HeaderBarNode,
|
|
64
64
|
];
|
|
65
65
|
const NODE_CLASSES = [RootNode, ...VIRTUAL_NODES, ...SPECIALIZED_NODES, ...CONTAINER_NODES, WidgetNode];
|
|
66
|
-
/**
|
|
67
|
-
* Creates a Node instance for the given JSX element type.
|
|
68
|
-
* Matches the type against registered node classes and initializes with props.
|
|
69
|
-
*/
|
|
70
66
|
export const createNode = (type, props, widget) => {
|
|
71
67
|
for (const NodeClass of NODE_CLASSES) {
|
|
72
68
|
if (NodeClass.matches(type, widget)) {
|
package/dist/fiber-root.d.ts
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
1
|
import type * as Gtk from "@gtkx/ffi/gtk";
|
|
2
2
|
import type Reconciler from "react-reconciler";
|
|
3
|
-
/**
|
|
4
|
-
* Creates a new fiber root container for rendering React elements.
|
|
5
|
-
* @param container - Optional GTK widget to use as the container. If not provided,
|
|
6
|
-
* uses the ROOT_NODE_CONTAINER sentinel for virtual roots.
|
|
7
|
-
*/
|
|
8
3
|
export declare const createFiberRoot: (container?: Gtk.Widget) => Reconciler.FiberRoot;
|
package/dist/fiber-root.js
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { ROOT_NODE_CONTAINER } from "./factory.js";
|
|
2
2
|
import { reconciler } from "./reconciler.js";
|
|
3
|
-
/**
|
|
4
|
-
* Creates a new fiber root container for rendering React elements.
|
|
5
|
-
* @param container - Optional GTK widget to use as the container. If not provided,
|
|
6
|
-
* uses the ROOT_NODE_CONTAINER sentinel for virtual roots.
|
|
7
|
-
*/
|
|
8
3
|
export const createFiberRoot = (container) => {
|
|
9
4
|
const instance = reconciler.getInstance();
|
|
10
5
|
return instance.createContainer(container ?? ROOT_NODE_CONTAINER, 0, null, false, null, "", (error) => console.error("Fiber root render error:", error), () => { }, () => { }, () => { }, null);
|