@gtkx/react 0.3.5 → 0.4.1
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 +2 -6
- package/dist/factory.js +2 -0
- package/dist/node.js +6 -0
- package/dist/nodes/menu.js +5 -1
- package/dist/nodes/toggle-button.d.ts +25 -0
- package/dist/nodes/toggle-button.js +77 -0
- package/dist/reconciler.js +3 -0
- package/dist/render.js +5 -3
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ const App = () => {
|
|
|
57
57
|
|
|
58
58
|
return (
|
|
59
59
|
<ApplicationWindow title="Counter" onCloseRequest={quit}>
|
|
60
|
-
<Box orientation={Orientation.VERTICAL} spacing={12}
|
|
60
|
+
<Box orientation={Orientation.VERTICAL} spacing={12}>
|
|
61
61
|
<Label.Root label={`Count: ${count}`} />
|
|
62
62
|
<Button label="Increment" onClicked={() => setCount((c) => c + 1)} />
|
|
63
63
|
</Box>
|
|
@@ -117,10 +117,6 @@ User events: `click`, `dblClick`, `type`, `clear`, `tab`, `selectOptions`
|
|
|
117
117
|
| [gtk4-demo](examples/gtk4-demo) | Widget showcase |
|
|
118
118
|
| [todo](examples/todo) | Todo app with tests |
|
|
119
119
|
|
|
120
|
-
```bash
|
|
121
|
-
cd examples/gtk4-demo && pnpm dev
|
|
122
|
-
```
|
|
123
|
-
|
|
124
120
|
## Packages
|
|
125
121
|
|
|
126
122
|
| Package | Description |
|
|
@@ -136,7 +132,7 @@ cd examples/gtk4-demo && pnpm dev
|
|
|
136
132
|
## Requirements
|
|
137
133
|
|
|
138
134
|
- Node.js 20+
|
|
139
|
-
- GTK4 (`gtk4
|
|
135
|
+
- GTK4 Runtime (`gtk4` on Fedora, `libgtk-4-1` on Ubuntu)
|
|
140
136
|
- Linux
|
|
141
137
|
|
|
142
138
|
## Contributing
|
package/dist/factory.js
CHANGED
|
@@ -13,6 +13,7 @@ import { RootNode } from "./nodes/root.js";
|
|
|
13
13
|
import { SlotNode } from "./nodes/slot.js";
|
|
14
14
|
import { StackNode, StackPageNode } from "./nodes/stack.js";
|
|
15
15
|
import { TextViewNode } from "./nodes/text-view.js";
|
|
16
|
+
import { ToggleButtonNode } from "./nodes/toggle-button.js";
|
|
16
17
|
import { WidgetNode } from "./nodes/widget.js";
|
|
17
18
|
import { WindowNode } from "./nodes/window.js";
|
|
18
19
|
export { ROOT_NODE_CONTAINER } from "./nodes/root.js";
|
|
@@ -33,6 +34,7 @@ const SPECIALIZED_NODES = [
|
|
|
33
34
|
WindowNode,
|
|
34
35
|
AboutDialogNode,
|
|
35
36
|
TextViewNode,
|
|
37
|
+
ToggleButtonNode,
|
|
36
38
|
ApplicationMenuNode,
|
|
37
39
|
PopoverMenuRootNode,
|
|
38
40
|
PopoverMenuBarNode,
|
package/dist/node.js
CHANGED
|
@@ -136,9 +136,15 @@ export class Node {
|
|
|
136
136
|
for (const { key } of signalUpdates) {
|
|
137
137
|
this.disconnectSignal(this.propKeyToEventName(key));
|
|
138
138
|
}
|
|
139
|
+
if (propertyUpdates.length > 0) {
|
|
140
|
+
widget.freezeNotify();
|
|
141
|
+
}
|
|
139
142
|
for (const { key, newValue } of propertyUpdates) {
|
|
140
143
|
this.setProperty(widget, key, newValue);
|
|
141
144
|
}
|
|
145
|
+
if (propertyUpdates.length > 0) {
|
|
146
|
+
widget.thawNotify();
|
|
147
|
+
}
|
|
142
148
|
for (const { key, newValue } of signalUpdates) {
|
|
143
149
|
if (typeof newValue === "function") {
|
|
144
150
|
this.connectSignal(widget, this.propKeyToEventName(key), newValue);
|
package/dist/nodes/menu.js
CHANGED
|
@@ -177,7 +177,11 @@ export class MenuItemNode extends Node {
|
|
|
177
177
|
this.onActivateCallback?.();
|
|
178
178
|
});
|
|
179
179
|
const app = getCurrentApp();
|
|
180
|
-
|
|
180
|
+
const action = getInterface(this.action, Gio.Action);
|
|
181
|
+
if (!action) {
|
|
182
|
+
throw new Error("Failed to get Gio.Action interface from SimpleAction");
|
|
183
|
+
}
|
|
184
|
+
app.addAction(action);
|
|
181
185
|
this.entry.action = `app.${this.actionName}`;
|
|
182
186
|
if (this.currentAccels) {
|
|
183
187
|
this.updateAccels(this.currentAccels);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type * as Gtk from "@gtkx/ffi/gtk";
|
|
2
|
+
import type { Props } from "../factory.js";
|
|
3
|
+
import { Node } from "../node.js";
|
|
4
|
+
type ToggleButtonState = {
|
|
5
|
+
lastPropsActive: boolean | undefined;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Specialized node for GtkToggleButton that prevents signal feedback loops.
|
|
9
|
+
*
|
|
10
|
+
* When multiple ToggleButtons share the same state (controlled components),
|
|
11
|
+
* React syncing the `active` prop via setActive() triggers the `toggled` signal.
|
|
12
|
+
* This node guards against that by tracking the expected active state and
|
|
13
|
+
* suppressing callbacks when the signal was caused by a programmatic update.
|
|
14
|
+
*/
|
|
15
|
+
export declare class ToggleButtonNode extends Node<Gtk.ToggleButton, ToggleButtonState> {
|
|
16
|
+
static matches(type: string): boolean;
|
|
17
|
+
initialize(props: Props): void;
|
|
18
|
+
attachToParent(parent: Node): void;
|
|
19
|
+
attachToParentBefore(parent: Node, before: Node): void;
|
|
20
|
+
detachFromParent(parent: Node): void;
|
|
21
|
+
protected consumedProps(): Set<string>;
|
|
22
|
+
updateProps(_oldProps: Props, newProps: Props): void;
|
|
23
|
+
protected connectSignal(widget: Gtk.Widget, eventName: string, handler: (...args: unknown[]) => unknown): void;
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { isChildContainer } from "../container-interfaces.js";
|
|
2
|
+
import { Node } from "../node.js";
|
|
3
|
+
/**
|
|
4
|
+
* Specialized node for GtkToggleButton that prevents signal feedback loops.
|
|
5
|
+
*
|
|
6
|
+
* When multiple ToggleButtons share the same state (controlled components),
|
|
7
|
+
* React syncing the `active` prop via setActive() triggers the `toggled` signal.
|
|
8
|
+
* This node guards against that by tracking the expected active state and
|
|
9
|
+
* suppressing callbacks when the signal was caused by a programmatic update.
|
|
10
|
+
*/
|
|
11
|
+
export class ToggleButtonNode extends Node {
|
|
12
|
+
static matches(type) {
|
|
13
|
+
return type === "ToggleButton.Root";
|
|
14
|
+
}
|
|
15
|
+
initialize(props) {
|
|
16
|
+
this.state = { lastPropsActive: undefined };
|
|
17
|
+
super.initialize(props);
|
|
18
|
+
}
|
|
19
|
+
attachToParent(parent) {
|
|
20
|
+
if (isChildContainer(parent)) {
|
|
21
|
+
parent.attachChild(this.widget);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
super.attachToParent(parent);
|
|
25
|
+
}
|
|
26
|
+
attachToParentBefore(parent, before) {
|
|
27
|
+
if (isChildContainer(parent)) {
|
|
28
|
+
const beforeWidget = before.getWidget();
|
|
29
|
+
if (beforeWidget) {
|
|
30
|
+
parent.insertChildBefore(this.widget, beforeWidget);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
parent.attachChild(this.widget);
|
|
34
|
+
}
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
super.attachToParentBefore(parent, before);
|
|
38
|
+
}
|
|
39
|
+
detachFromParent(parent) {
|
|
40
|
+
if (isChildContainer(parent)) {
|
|
41
|
+
parent.detachChild(this.widget);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
super.detachFromParent(parent);
|
|
45
|
+
}
|
|
46
|
+
consumedProps() {
|
|
47
|
+
return new Set(["children", "active"]);
|
|
48
|
+
}
|
|
49
|
+
updateProps(_oldProps, newProps) {
|
|
50
|
+
const widget = this.getWidget();
|
|
51
|
+
if (!widget) {
|
|
52
|
+
super.updateProps(_oldProps, newProps);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const newActive = newProps.active;
|
|
56
|
+
if (typeof newActive === "boolean") {
|
|
57
|
+
this.state.lastPropsActive = newActive;
|
|
58
|
+
if (widget.getActive() !== newActive) {
|
|
59
|
+
widget.setActive(newActive);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
super.updateProps(_oldProps, newProps);
|
|
63
|
+
}
|
|
64
|
+
connectSignal(widget, eventName, handler) {
|
|
65
|
+
if (eventName === "toggled") {
|
|
66
|
+
const wrappedHandler = (...args) => {
|
|
67
|
+
if (this.widget?.getActive() === this.state.lastPropsActive) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
return handler(...args);
|
|
71
|
+
};
|
|
72
|
+
super.connectSignal(widget, eventName, wrappedHandler);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
super.connectSignal(widget, eventName, handler);
|
|
76
|
+
}
|
|
77
|
+
}
|
package/dist/reconciler.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { beginBatch, endBatch } from "@gtkx/ffi";
|
|
1
2
|
import React from "react";
|
|
2
3
|
import ReactReconciler from "react-reconciler";
|
|
3
4
|
import { beginCommit, endCommit } from "./batch.js";
|
|
@@ -56,11 +57,13 @@ class Reconciler {
|
|
|
56
57
|
parent.insertBefore(child, beforeChild);
|
|
57
58
|
},
|
|
58
59
|
prepareForCommit: () => {
|
|
60
|
+
beginBatch();
|
|
59
61
|
beginCommit();
|
|
60
62
|
return null;
|
|
61
63
|
},
|
|
62
64
|
resetAfterCommit: () => {
|
|
63
65
|
endCommit();
|
|
66
|
+
endBatch();
|
|
64
67
|
},
|
|
65
68
|
commitTextUpdate: (textInstance, oldText, newText) => {
|
|
66
69
|
textInstance.updateProps({ label: oldText }, { label: newText });
|
package/dist/render.js
CHANGED
|
@@ -24,8 +24,10 @@ export let container = null;
|
|
|
24
24
|
export const render = (element, appId, flags) => {
|
|
25
25
|
start(appId, flags);
|
|
26
26
|
const instance = reconciler.getInstance();
|
|
27
|
-
container = instance.createContainer(ROOT_NODE_CONTAINER, 0, null, false, null, "", (error
|
|
28
|
-
|
|
29
|
-
}, (
|
|
27
|
+
container = instance.createContainer(ROOT_NODE_CONTAINER, 0, null, false, null, "", (error) => {
|
|
28
|
+
throw error;
|
|
29
|
+
}, (error) => {
|
|
30
|
+
console.error("Error caught by ErrorBoundary:", error);
|
|
31
|
+
}, () => { }, () => { }, null);
|
|
30
32
|
instance.updateContainer(element, container, null, () => { });
|
|
31
33
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gtkx/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Build GTK4 desktop applications with React and TypeScript",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gtk",
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"react-reconciler": "0.33.0",
|
|
39
|
-
"@gtkx/ffi": "0.
|
|
39
|
+
"@gtkx/ffi": "0.4.1"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@gtkx/gir": "0.
|
|
42
|
+
"@gtkx/gir": "0.4.1"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"react": "^19"
|