@gtkx/react 0.1.23 → 0.1.25
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/codegen/jsx-generator.js +1 -3
- package/dist/factory.d.ts +1 -1
- package/dist/factory.js +4 -4
- package/dist/node.d.ts +3 -2
- package/dist/node.js +22 -5
- package/dist/nodes/list.d.ts +2 -0
- package/dist/nodes/list.js +17 -0
- package/dist/nodes/overlay.d.ts +1 -0
- package/dist/nodes/overlay.js +19 -0
- package/dist/nodes/widget.d.ts +3 -6
- package/dist/nodes/widget.js +37 -20
- package/dist/reconciler.d.ts +1 -0
- package/dist/reconciler.js +13 -11
- package/package.json +4 -4
- package/README.md +0 -208
|
@@ -231,9 +231,7 @@ ${widgetPropsContent}
|
|
|
231
231
|
const parentPropsName = this.getParentPropsName(widget);
|
|
232
232
|
const namedChildPropNames = new Set(metadata.namedChildSlots.map((s) => toCamelCase(s.propertyName)));
|
|
233
233
|
const lines = [];
|
|
234
|
-
|
|
235
|
-
lines.push(formatDoc(widget.doc).trimEnd());
|
|
236
|
-
}
|
|
234
|
+
lines.push(`/** Props for the {@link ${widgetName}} widget. */`);
|
|
237
235
|
lines.push(`export interface ${widgetName}Props extends ${parentPropsName} {`);
|
|
238
236
|
const requiredCtorParams = this.getRequiredConstructorParams(widget);
|
|
239
237
|
const seenProps = new Set();
|
package/dist/factory.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type * as Gtk from "@gtkx/ffi/gtk";
|
|
2
2
|
import type { Node } from "./node.js";
|
|
3
3
|
export type Props = Record<string, unknown>;
|
|
4
|
-
export declare const createNode: (type: string, props: Props, app: Gtk.Application) => Node;
|
|
4
|
+
export declare const createNode: (type: string, props: Props, app: Gtk.Application, existingWidget?: Gtk.Widget) => Node;
|
package/dist/factory.js
CHANGED
|
@@ -5,20 +5,20 @@ import { OverlayNode } from "./nodes/overlay.js";
|
|
|
5
5
|
import { SlotNode } from "./nodes/slot.js";
|
|
6
6
|
import { WidgetNode } from "./nodes/widget.js";
|
|
7
7
|
const NODE_CLASSES = [
|
|
8
|
-
SlotNode,
|
|
9
8
|
ListItemNode,
|
|
10
9
|
DropDownItemNode,
|
|
11
|
-
DropDownNode,
|
|
12
10
|
GridChildNode,
|
|
11
|
+
SlotNode,
|
|
12
|
+
DropDownNode,
|
|
13
13
|
GridNode,
|
|
14
14
|
OverlayNode,
|
|
15
15
|
ListViewNode,
|
|
16
16
|
WidgetNode,
|
|
17
17
|
];
|
|
18
|
-
export const createNode = (type, props, app) => {
|
|
18
|
+
export const createNode = (type, props, app, existingWidget) => {
|
|
19
19
|
for (const NodeClass of NODE_CLASSES) {
|
|
20
20
|
if (NodeClass.matches(type)) {
|
|
21
|
-
return new NodeClass(type, props, app);
|
|
21
|
+
return new NodeClass(type, props, app, existingWidget);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
throw new Error(`No matching node class for type: ${type}`);
|
package/dist/node.d.ts
CHANGED
|
@@ -5,14 +5,15 @@ export declare abstract class Node<T extends Gtk.Widget | undefined = Gtk.Widget
|
|
|
5
5
|
protected signalHandlers: Map<string, number>;
|
|
6
6
|
protected widget: T;
|
|
7
7
|
protected isVirtual(): boolean;
|
|
8
|
-
constructor(type: string, props: Props, app: Gtk.Application);
|
|
8
|
+
constructor(type: string, props: Props, app: Gtk.Application, existingWidget?: Gtk.Widget);
|
|
9
9
|
protected createWidget(type: string, props: Props, app: Gtk.Application): T;
|
|
10
10
|
getWidget(): T;
|
|
11
11
|
appendChild(child: Node): void;
|
|
12
12
|
removeChild(child: Node): void;
|
|
13
|
-
insertBefore(child: Node,
|
|
13
|
+
insertBefore(child: Node, before: Node): void;
|
|
14
14
|
attachToParent(parent: Node): void;
|
|
15
15
|
detachFromParent(parent: Node): void;
|
|
16
|
+
attachToParentBefore(parent: Node, before: Node): void;
|
|
16
17
|
protected consumedProps(): Set<string>;
|
|
17
18
|
updateProps(oldProps: Props, newProps: Props): void;
|
|
18
19
|
protected setSignalProperty(widget: Gtk.Widget, key: string, handler: unknown): void;
|
package/dist/node.js
CHANGED
|
@@ -17,7 +17,11 @@ export class Node {
|
|
|
17
17
|
isVirtual() {
|
|
18
18
|
return false;
|
|
19
19
|
}
|
|
20
|
-
constructor(type, props, app) {
|
|
20
|
+
constructor(type, props, app, existingWidget) {
|
|
21
|
+
if (existingWidget) {
|
|
22
|
+
this.widget = existingWidget;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
21
25
|
this.widget = (this.isVirtual() ? undefined : this.createWidget(type, props, app));
|
|
22
26
|
this.updateProps({}, props);
|
|
23
27
|
}
|
|
@@ -27,7 +31,7 @@ export class Node {
|
|
|
27
31
|
if (!WidgetClass) {
|
|
28
32
|
throw new Error(`Unknown GTK widget type: ${normalizedType}`);
|
|
29
33
|
}
|
|
30
|
-
if (
|
|
34
|
+
if (WidgetClass === Gtk.ApplicationWindow) {
|
|
31
35
|
return new WidgetClass(app);
|
|
32
36
|
}
|
|
33
37
|
return new WidgetClass(...extractConstructorArgs(normalizedType, props));
|
|
@@ -41,8 +45,8 @@ export class Node {
|
|
|
41
45
|
removeChild(child) {
|
|
42
46
|
child.detachFromParent(this);
|
|
43
47
|
}
|
|
44
|
-
insertBefore(child,
|
|
45
|
-
|
|
48
|
+
insertBefore(child, before) {
|
|
49
|
+
child.attachToParentBefore(this, before);
|
|
46
50
|
}
|
|
47
51
|
attachToParent(parent) {
|
|
48
52
|
const parentWidget = parent.getWidget();
|
|
@@ -50,7 +54,7 @@ export class Node {
|
|
|
50
54
|
if (!parentWidget || !widget)
|
|
51
55
|
return;
|
|
52
56
|
if (isAppendable(parentWidget)) {
|
|
53
|
-
|
|
57
|
+
widget.insertBefore(parentWidget, null);
|
|
54
58
|
}
|
|
55
59
|
else if (isSingleChild(parentWidget)) {
|
|
56
60
|
parentWidget.setChild(widget);
|
|
@@ -68,6 +72,19 @@ export class Node {
|
|
|
68
72
|
parentWidget.setChild(null);
|
|
69
73
|
}
|
|
70
74
|
}
|
|
75
|
+
attachToParentBefore(parent, before) {
|
|
76
|
+
const parentWidget = parent.getWidget();
|
|
77
|
+
const widget = this.getWidget();
|
|
78
|
+
const beforeWidget = before.getWidget();
|
|
79
|
+
if (!parentWidget || !widget)
|
|
80
|
+
return;
|
|
81
|
+
if (isAppendable(parentWidget) && beforeWidget) {
|
|
82
|
+
widget.insertBefore(parentWidget, beforeWidget);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
this.attachToParent(parent);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
71
88
|
consumedProps() {
|
|
72
89
|
return new Set(["children"]);
|
|
73
90
|
}
|
package/dist/nodes/list.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare class ListViewNode extends Node<Gtk.ListView> {
|
|
|
11
11
|
private factorySignalHandlers;
|
|
12
12
|
constructor(type: string, props: Props, app: Gtk.Application);
|
|
13
13
|
addItem(item: unknown): void;
|
|
14
|
+
insertItemBefore(item: unknown, beforeItem: unknown): void;
|
|
14
15
|
removeItem(item: unknown): void;
|
|
15
16
|
protected consumedProps(): Set<string>;
|
|
16
17
|
updateProps(oldProps: Props, newProps: Props): void;
|
|
@@ -23,5 +24,6 @@ export declare class ListItemNode extends Node {
|
|
|
23
24
|
constructor(type: string, props: Props, app: Gtk.Application);
|
|
24
25
|
getItem(): unknown;
|
|
25
26
|
attachToParent(parent: Node): void;
|
|
27
|
+
attachToParentBefore(parent: Node, before: Node): void;
|
|
26
28
|
detachFromParent(parent: Node): void;
|
|
27
29
|
}
|
package/dist/nodes/list.js
CHANGED
|
@@ -43,6 +43,15 @@ export class ListViewNode extends Node {
|
|
|
43
43
|
this.items.push(item);
|
|
44
44
|
this.stringList.append("");
|
|
45
45
|
}
|
|
46
|
+
insertItemBefore(item, beforeItem) {
|
|
47
|
+
const beforeIndex = this.items.indexOf(beforeItem);
|
|
48
|
+
if (beforeIndex === -1) {
|
|
49
|
+
this.addItem(item);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
this.items.splice(beforeIndex, 0, item);
|
|
53
|
+
this.stringList.splice(beforeIndex, 0, [""]);
|
|
54
|
+
}
|
|
46
55
|
removeItem(item) {
|
|
47
56
|
const index = this.items.indexOf(item);
|
|
48
57
|
if (index !== -1) {
|
|
@@ -92,6 +101,14 @@ export class ListItemNode extends Node {
|
|
|
92
101
|
parent.addItem(this.item);
|
|
93
102
|
}
|
|
94
103
|
}
|
|
104
|
+
attachToParentBefore(parent, before) {
|
|
105
|
+
if (parent instanceof ListViewNode && before instanceof ListItemNode) {
|
|
106
|
+
parent.insertItemBefore(this.item, before.getItem());
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
this.attachToParent(parent);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
95
112
|
detachFromParent(parent) {
|
|
96
113
|
if (parent instanceof ListViewNode) {
|
|
97
114
|
parent.removeItem(this.item);
|
package/dist/nodes/overlay.d.ts
CHANGED
|
@@ -5,5 +5,6 @@ export declare class OverlayNode extends Node<Gtk.Overlay> {
|
|
|
5
5
|
private mainChild;
|
|
6
6
|
private overlayChildren;
|
|
7
7
|
attachChild(childWidget: Gtk.Widget): void;
|
|
8
|
+
insertChildBefore(childWidget: Gtk.Widget, beforeWidget: Gtk.Widget): void;
|
|
8
9
|
detachChild(childWidget: Gtk.Widget): void;
|
|
9
10
|
}
|
package/dist/nodes/overlay.js
CHANGED
|
@@ -15,6 +15,25 @@ export class OverlayNode extends Node {
|
|
|
15
15
|
this.widget.addOverlay(childWidget);
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
+
insertChildBefore(childWidget, beforeWidget) {
|
|
19
|
+
if (this.mainChild === null) {
|
|
20
|
+
this.mainChild = childWidget;
|
|
21
|
+
this.widget.setChild(childWidget);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (this.mainChild === beforeWidget) {
|
|
25
|
+
this.overlayChildren.unshift(childWidget);
|
|
26
|
+
this.widget.addOverlay(childWidget);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const beforeIndex = this.overlayChildren.indexOf(beforeWidget);
|
|
30
|
+
if (beforeIndex === -1) {
|
|
31
|
+
this.attachChild(childWidget);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
this.overlayChildren.splice(beforeIndex, 0, childWidget);
|
|
35
|
+
this.widget.addOverlay(childWidget);
|
|
36
|
+
}
|
|
18
37
|
detachChild(childWidget) {
|
|
19
38
|
if (this.mainChild === childWidget) {
|
|
20
39
|
this.widget.setChild(null);
|
package/dist/nodes/widget.d.ts
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
2
2
|
import type { Props } from "../factory.js";
|
|
3
3
|
import { Node } from "../node.js";
|
|
4
|
-
export declare class WidgetWrapper extends Node<Gtk.Widget> {
|
|
5
|
-
constructor(widget: Gtk.Widget, app: Gtk.Application);
|
|
6
|
-
protected isVirtual(): boolean;
|
|
7
|
-
}
|
|
8
4
|
export declare class WidgetNode extends Node<Gtk.Widget> {
|
|
9
5
|
static matches(_type: string): boolean;
|
|
10
6
|
attachToParent(parent: Node): void;
|
|
7
|
+
attachToParentBefore(parent: Node, before: Node): void;
|
|
11
8
|
detachFromParent(parent: Node): void;
|
|
12
9
|
protected consumedProps(): Set<string>;
|
|
13
10
|
updateProps(oldProps: Props, newProps: Props): void;
|
|
14
|
-
mount(
|
|
15
|
-
dispose(
|
|
11
|
+
mount(_app: Gtk.Application): void;
|
|
12
|
+
dispose(_app: Gtk.Application): void;
|
|
16
13
|
}
|
package/dist/nodes/widget.js
CHANGED
|
@@ -13,15 +13,6 @@ const COMBINED_PROPS = [
|
|
|
13
13
|
},
|
|
14
14
|
},
|
|
15
15
|
];
|
|
16
|
-
export class WidgetWrapper extends Node {
|
|
17
|
-
constructor(widget, app) {
|
|
18
|
-
super("", {}, app);
|
|
19
|
-
this.widget = widget;
|
|
20
|
-
}
|
|
21
|
-
isVirtual() {
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
16
|
export class WidgetNode extends Node {
|
|
26
17
|
static matches(_type) {
|
|
27
18
|
return true;
|
|
@@ -47,6 +38,40 @@ export class WidgetNode extends Node {
|
|
|
47
38
|
}
|
|
48
39
|
super.attachToParent(parent);
|
|
49
40
|
}
|
|
41
|
+
attachToParentBefore(parent, before) {
|
|
42
|
+
if (this.widget instanceof Gtk.AboutDialog) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (parent instanceof OverlayNode) {
|
|
46
|
+
const beforeWidget = before.getWidget();
|
|
47
|
+
if (beforeWidget) {
|
|
48
|
+
parent.insertChildBefore(this.widget, beforeWidget);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
parent.attachChild(this.widget);
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const parentWidget = parent.getWidget();
|
|
56
|
+
const beforeWidget = before.getWidget();
|
|
57
|
+
if (!parentWidget)
|
|
58
|
+
return;
|
|
59
|
+
if (parentWidget instanceof Gtk.ActionBar) {
|
|
60
|
+
parentWidget.packStart(this.widget);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (parentWidget instanceof Gtk.Notebook && beforeWidget) {
|
|
64
|
+
const beforePageNum = parentWidget.pageNum(beforeWidget);
|
|
65
|
+
if (beforePageNum >= 0) {
|
|
66
|
+
parentWidget.insertPage(this.widget, beforePageNum);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
parentWidget.appendPage(this.widget);
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
super.attachToParentBefore(parent, before);
|
|
74
|
+
}
|
|
50
75
|
detachFromParent(parent) {
|
|
51
76
|
if (this.widget instanceof Gtk.AboutDialog) {
|
|
52
77
|
return;
|
|
@@ -73,7 +98,6 @@ export class WidgetNode extends Node {
|
|
|
73
98
|
}
|
|
74
99
|
consumedProps() {
|
|
75
100
|
const consumed = super.consumedProps();
|
|
76
|
-
consumed.add("application");
|
|
77
101
|
for (const handler of COMBINED_PROPS) {
|
|
78
102
|
for (const prop of handler.props) {
|
|
79
103
|
consumed.add(prop);
|
|
@@ -94,21 +118,14 @@ export class WidgetNode extends Node {
|
|
|
94
118
|
}
|
|
95
119
|
super.updateProps(oldProps, newProps);
|
|
96
120
|
}
|
|
97
|
-
mount(
|
|
98
|
-
if (this.widget instanceof Gtk.AboutDialog) {
|
|
99
|
-
const activeWindow = app.getActiveWindow();
|
|
100
|
-
if (activeWindow) {
|
|
101
|
-
this.widget.setTransientFor(activeWindow);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
121
|
+
mount(_app) {
|
|
104
122
|
if (this.widget instanceof Gtk.Window) {
|
|
105
123
|
this.widget.present();
|
|
106
124
|
}
|
|
107
125
|
}
|
|
108
|
-
dispose(
|
|
109
|
-
super.dispose(app);
|
|
126
|
+
dispose(_app) {
|
|
110
127
|
if (this.widget instanceof Gtk.Window) {
|
|
111
|
-
this.widget.
|
|
128
|
+
this.widget.destroy();
|
|
112
129
|
}
|
|
113
130
|
}
|
|
114
131
|
}
|
package/dist/reconciler.d.ts
CHANGED
package/dist/reconciler.js
CHANGED
|
@@ -2,7 +2,6 @@ import * as Gtk from "@gtkx/ffi/gtk";
|
|
|
2
2
|
import React from "react";
|
|
3
3
|
import ReactReconciler from "react-reconciler";
|
|
4
4
|
import { createNode } from "./factory.js";
|
|
5
|
-
import { WidgetWrapper } from "./nodes/widget.js";
|
|
6
5
|
class Reconciler {
|
|
7
6
|
instance;
|
|
8
7
|
app = null;
|
|
@@ -45,19 +44,16 @@ class Reconciler {
|
|
|
45
44
|
removeChild: (parent, child) => parent.removeChild(child),
|
|
46
45
|
insertBefore: (parent, child, beforeChild) => parent.insertBefore(child, beforeChild),
|
|
47
46
|
removeChildFromContainer: (container, child) => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
47
|
+
const parent = this.createNodeFromContainer(container);
|
|
48
|
+
parent.removeChild(child);
|
|
51
49
|
},
|
|
52
50
|
appendChildToContainer: (container, child) => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
51
|
+
const parent = this.createNodeFromContainer(container);
|
|
52
|
+
parent.appendChild(child);
|
|
56
53
|
},
|
|
57
|
-
insertInContainerBefore: (container, child,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
54
|
+
insertInContainerBefore: (container, child, beforeChild) => {
|
|
55
|
+
const parent = this.createNodeFromContainer(container);
|
|
56
|
+
parent.insertBefore(child, beforeChild);
|
|
61
57
|
},
|
|
62
58
|
prepareForCommit: () => null,
|
|
63
59
|
resetAfterCommit: () => { },
|
|
@@ -104,5 +100,11 @@ class Reconciler {
|
|
|
104
100
|
const context = React.createContext(value);
|
|
105
101
|
return context;
|
|
106
102
|
}
|
|
103
|
+
createNodeFromContainer(container) {
|
|
104
|
+
if (container instanceof Gtk.Widget) {
|
|
105
|
+
return createNode(container.constructor.name, {}, this.getApp(), container);
|
|
106
|
+
}
|
|
107
|
+
return createNode("Application", {}, this.getApp(), container);
|
|
108
|
+
}
|
|
107
109
|
}
|
|
108
110
|
export const reconciler = new Reconciler();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gtkx/react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
4
4
|
"description": "Build GTK4 desktop applications with React and TypeScript",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gtk",
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"react-reconciler": "0.33.0",
|
|
43
|
-
"@gtkx/ffi": "0.1.
|
|
43
|
+
"@gtkx/ffi": "0.1.25"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@gtkx/gir": "0.1.
|
|
46
|
+
"@gtkx/gir": "0.1.25"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"react": "^19"
|
|
@@ -51,6 +51,6 @@
|
|
|
51
51
|
"scripts": {
|
|
52
52
|
"build": "tsc -b && cp ../../README.md .",
|
|
53
53
|
"codegen": "tsx scripts/codegen.ts",
|
|
54
|
-
"test": "GDK_BACKEND=x11 xvfb-run -a vitest run"
|
|
54
|
+
"test": "if [ -n \"$CI\" ]; then vitest run; else GDK_BACKEND=x11 xvfb-run -a vitest run; fi"
|
|
55
55
|
}
|
|
56
56
|
}
|
package/README.md
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
<p align="center">
|
|
2
|
-
<img src="https://raw.githubusercontent.com/eugeniodepalo/gtkx/HEAD/logo.svg" alt="GTKX Logo" width="128" height="128">
|
|
3
|
-
</p>
|
|
4
|
-
|
|
5
|
-
<h1 align="center">GTKX</h1>
|
|
6
|
-
|
|
7
|
-
<p align="center">
|
|
8
|
-
<strong>Build native GTK4 desktop applications with React and TypeScript</strong>
|
|
9
|
-
</p>
|
|
10
|
-
|
|
11
|
-
<p align="center">
|
|
12
|
-
<a href="https://eugeniodepalo.github.io/gtkx">Documentation</a> ·
|
|
13
|
-
<a href="#quick-start">Quick Start</a> ·
|
|
14
|
-
<a href="#examples">Examples</a>
|
|
15
|
-
</p>
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
GTKX bridges React's component model with GTK4's native widget system. Write familiar React code and render it as native Linux desktop applications with full access to GTK4 widgets, signals, and styling.
|
|
20
|
-
|
|
21
|
-
## Features
|
|
22
|
-
|
|
23
|
-
- **React Components** — Use React hooks, state, and component patterns you already know
|
|
24
|
-
- **Type-Safe** — Full TypeScript support with auto-generated types from GTK4 introspection data
|
|
25
|
-
- **Native Performance** — Direct FFI bindings to GTK4 via Rust and libffi
|
|
26
|
-
- **CSS-in-JS Styling** — Emotion-style `css` template literals for GTK widgets
|
|
27
|
-
- **Testing Library** — Familiar `screen`, `userEvent`, and query APIs for testing components
|
|
28
|
-
|
|
29
|
-
## Quick Start
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
# Install dependencies
|
|
33
|
-
pnpm add @gtkx/react react
|
|
34
|
-
|
|
35
|
-
# For styling (optional)
|
|
36
|
-
pnpm add @gtkx/css
|
|
37
|
-
|
|
38
|
-
# For testing (optional)
|
|
39
|
-
pnpm add -D @gtkx/testing
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Create your first app:
|
|
43
|
-
|
|
44
|
-
```tsx
|
|
45
|
-
// index.tsx
|
|
46
|
-
import { render } from "@gtkx/react";
|
|
47
|
-
import { App } from "./app.js";
|
|
48
|
-
|
|
49
|
-
render(<App />, "org.example.MyApp");
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
```tsx
|
|
53
|
-
// app.tsx
|
|
54
|
-
import { ApplicationWindow, Box, Button, Label, quit } from "@gtkx/react";
|
|
55
|
-
import { Orientation } from "@gtkx/ffi/gtk";
|
|
56
|
-
import { useState } from "react";
|
|
57
|
-
|
|
58
|
-
export const App = () => {
|
|
59
|
-
const [count, setCount] = useState(0);
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
<ApplicationWindow
|
|
63
|
-
title="My App"
|
|
64
|
-
defaultWidth={400}
|
|
65
|
-
defaultHeight={300}
|
|
66
|
-
onCloseRequest={quit}
|
|
67
|
-
>
|
|
68
|
-
<Box orientation={Orientation.VERTICAL} spacing={12} margin={20}>
|
|
69
|
-
<Label.Root label={`Count: ${count}`} />
|
|
70
|
-
<Button
|
|
71
|
-
label="Increment"
|
|
72
|
-
onClicked={() => setCount((c) => c + 1)}
|
|
73
|
-
/>
|
|
74
|
-
</Box>
|
|
75
|
-
</ApplicationWindow>
|
|
76
|
-
);
|
|
77
|
-
};
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Run with:
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
pnpm tsx index.tsx
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## Styling
|
|
87
|
-
|
|
88
|
-
Use `@gtkx/css` for CSS-in-JS styling:
|
|
89
|
-
|
|
90
|
-
```tsx
|
|
91
|
-
import { css } from "@gtkx/css";
|
|
92
|
-
import { Button } from "@gtkx/react";
|
|
93
|
-
|
|
94
|
-
const primaryButton = css`
|
|
95
|
-
padding: 16px 32px;
|
|
96
|
-
border-radius: 24px;
|
|
97
|
-
background: linear-gradient(135deg, #3584e4, #9141ac);
|
|
98
|
-
color: white;
|
|
99
|
-
font-weight: bold;
|
|
100
|
-
`;
|
|
101
|
-
|
|
102
|
-
const MyButton = () => (
|
|
103
|
-
<Button label="Click me" cssClasses={[primaryButton]} />
|
|
104
|
-
);
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
GTK also provides built-in CSS classes like `suggested-action`, `destructive-action`, `card`, and `heading`.
|
|
108
|
-
|
|
109
|
-
## Testing
|
|
110
|
-
|
|
111
|
-
Use `@gtkx/testing` for Testing Library-style component tests:
|
|
112
|
-
|
|
113
|
-
```tsx
|
|
114
|
-
import { cleanup, render, screen, userEvent, fireEvent } from "@gtkx/testing";
|
|
115
|
-
import { AccessibleRole } from "@gtkx/ffi/gtk";
|
|
116
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
117
|
-
import { App } from "./app.js";
|
|
118
|
-
|
|
119
|
-
describe("Counter", () => {
|
|
120
|
-
afterEach(() => cleanup());
|
|
121
|
-
|
|
122
|
-
it("increments count when clicking button", async () => {
|
|
123
|
-
render(<App />);
|
|
124
|
-
|
|
125
|
-
const button = await screen.findByRole(AccessibleRole.BUTTON, {
|
|
126
|
-
name: "Increment",
|
|
127
|
-
});
|
|
128
|
-
await userEvent.click(button);
|
|
129
|
-
|
|
130
|
-
await screen.findByText("Count: 1");
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("can also use fireEvent for synchronous events", async () => {
|
|
134
|
-
render(<App />);
|
|
135
|
-
|
|
136
|
-
const button = await screen.findByRole(AccessibleRole.BUTTON, {
|
|
137
|
-
name: "Increment",
|
|
138
|
-
});
|
|
139
|
-
fireEvent.click(button);
|
|
140
|
-
|
|
141
|
-
await screen.findByText("Count: 1");
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### Available APIs
|
|
147
|
-
|
|
148
|
-
**Queries** - Find elements in the rendered tree:
|
|
149
|
-
- `getBy*` / `getAllBy*` - Throws if not found
|
|
150
|
-
- `queryBy*` / `queryAllBy*` - Returns null/empty array if not found
|
|
151
|
-
- `findBy*` / `findAllBy*` - Async, waits for element
|
|
152
|
-
|
|
153
|
-
Query types: `ByRole`, `ByText`, `ByLabelText`, `ByTestId`
|
|
154
|
-
|
|
155
|
-
**User Interactions**:
|
|
156
|
-
- `userEvent.click(element)` - Simulate click
|
|
157
|
-
- `userEvent.dblClick(element)` - Simulate double click
|
|
158
|
-
- `userEvent.type(element, text)` - Type text into input
|
|
159
|
-
- `userEvent.clear(element)` - Clear input text
|
|
160
|
-
- `userEvent.setup()` - Create reusable instance
|
|
161
|
-
|
|
162
|
-
**Low-level Events**:
|
|
163
|
-
- `fireEvent(element, signalName)` - Emit any GTK signal
|
|
164
|
-
- `fireEvent.click(element)` - Emit clicked signal
|
|
165
|
-
- `fireEvent.activate(element)` - Emit activate signal
|
|
166
|
-
|
|
167
|
-
**Utilities**:
|
|
168
|
-
- `waitFor(callback)` - Wait for condition
|
|
169
|
-
- `waitForElementToBeRemoved(element)` - Wait for element removal
|
|
170
|
-
|
|
171
|
-
## Examples
|
|
172
|
-
|
|
173
|
-
### Counter
|
|
174
|
-
|
|
175
|
-
A minimal counter app demonstrating state management:
|
|
176
|
-
|
|
177
|
-
```bash
|
|
178
|
-
turbo start --filter=counter-example
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
### GTK4 Demo
|
|
182
|
-
|
|
183
|
-
A comprehensive showcase of GTK4 widgets and features:
|
|
184
|
-
|
|
185
|
-
```bash
|
|
186
|
-
turbo start --filter=gtk4-demo
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
## Packages
|
|
190
|
-
|
|
191
|
-
| Package | Description |
|
|
192
|
-
|---------|-------------|
|
|
193
|
-
| [@gtkx/react](packages/react) | React reconciler and JSX components |
|
|
194
|
-
| [@gtkx/ffi](packages/ffi) | TypeScript FFI bindings for GTK4 |
|
|
195
|
-
| [@gtkx/native](packages/native) | Rust native module for FFI bridge |
|
|
196
|
-
| [@gtkx/css](packages/css) | CSS-in-JS styling for GTK widgets |
|
|
197
|
-
| [@gtkx/testing](packages/testing) | Testing utilities for GTKX components |
|
|
198
|
-
| [@gtkx/gir](packages/gir) | GObject Introspection parser for codegen |
|
|
199
|
-
|
|
200
|
-
## Requirements
|
|
201
|
-
|
|
202
|
-
- Node.js 20+
|
|
203
|
-
- GTK4 development libraries
|
|
204
|
-
- Linux (GTK4 is Linux-native)
|
|
205
|
-
|
|
206
|
-
## License
|
|
207
|
-
|
|
208
|
-
[MPL-2.0](LICENSE)
|