@gtkx/react 0.9.1 → 0.9.2
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 +13 -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 +1947 -1928
- package/dist/generated/jsx.js +1308 -1300
- 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,18 @@ 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
|
+
});
|
|
77
|
+
}
|
|
116
78
|
async generate(namespaces) {
|
|
117
79
|
for (const ns of namespaces) {
|
|
118
80
|
for (const iface of ns.interfaces) {
|
|
@@ -181,7 +143,7 @@ ${widgetPropsContent}
|
|
|
181
143
|
}
|
|
182
144
|
generateWidgetPropsContent(widgetClass) {
|
|
183
145
|
const lines = [];
|
|
184
|
-
const widgetDoc = widgetClass?.doc ? formatDoc(widgetClass.doc) : "";
|
|
146
|
+
const widgetDoc = widgetClass?.doc ? this.formatDoc(widgetClass.doc) : "";
|
|
185
147
|
if (widgetDoc) {
|
|
186
148
|
lines.push(widgetDoc.trimEnd());
|
|
187
149
|
}
|
|
@@ -191,7 +153,7 @@ ${widgetPropsContent}
|
|
|
191
153
|
const propName = toCamelCase(prop.name);
|
|
192
154
|
const tsType = this.toJsxPropertyType(this.typeMapper.mapType(prop.type).ts, "Gtk");
|
|
193
155
|
if (prop.doc) {
|
|
194
|
-
lines.push(formatDoc(prop.doc, "\t").trimEnd());
|
|
156
|
+
lines.push(this.formatDoc(prop.doc, "\t").trimEnd());
|
|
195
157
|
}
|
|
196
158
|
lines.push(`\t${propName}?: ${tsType};`);
|
|
197
159
|
}
|
|
@@ -199,7 +161,7 @@ ${widgetPropsContent}
|
|
|
199
161
|
lines.push("");
|
|
200
162
|
for (const signal of widgetClass.signals) {
|
|
201
163
|
if (signal.doc) {
|
|
202
|
-
lines.push(formatDoc(signal.doc, "\t").trimEnd());
|
|
164
|
+
lines.push(this.formatDoc(signal.doc, "\t").trimEnd());
|
|
203
165
|
}
|
|
204
166
|
lines.push(`\t${this.generateSignalHandler(signal, "Widget")}`);
|
|
205
167
|
}
|
|
@@ -354,7 +316,7 @@ ${widgetPropsContent}
|
|
|
354
316
|
const isRequiredByConstructor = requiredCtorParams.has(prop.name);
|
|
355
317
|
const isRequired = isRequiredByConstructor;
|
|
356
318
|
if (prop.doc) {
|
|
357
|
-
lines.push(formatDoc(prop.doc, "\t").trimEnd());
|
|
319
|
+
lines.push(this.formatDoc(prop.doc, "\t").trimEnd());
|
|
358
320
|
}
|
|
359
321
|
lines.push(`\t${propName}${isRequired ? "" : "?"}: ${tsType};`);
|
|
360
322
|
}
|
|
@@ -377,7 +339,7 @@ ${widgetPropsContent}
|
|
|
377
339
|
lines.push("");
|
|
378
340
|
for (const signal of specificSignals) {
|
|
379
341
|
if (signal.doc) {
|
|
380
|
-
lines.push(formatDoc(signal.doc, "\t").trimEnd());
|
|
342
|
+
lines.push(this.formatDoc(signal.doc, "\t").trimEnd());
|
|
381
343
|
}
|
|
382
344
|
lines.push(`\t${this.generateSignalHandler(signal, widget.name)}`);
|
|
383
345
|
}
|
|
@@ -716,7 +678,7 @@ ${widgetPropsContent}
|
|
|
716
678
|
isNotebookWidget(widget.name) ||
|
|
717
679
|
isStackWidget(widget.name) ||
|
|
718
680
|
isPopoverMenuWidget(widget.name);
|
|
719
|
-
const docComment = widget.doc ? formatDoc(widget.doc).trimEnd() : "";
|
|
681
|
+
const docComment = widget.doc ? this.formatDoc(widget.doc).trimEnd() : "";
|
|
720
682
|
if (hasMeaningfulSlots) {
|
|
721
683
|
if (isListWidget(widget.name) ||
|
|
722
684
|
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);
|