@gtkx/react 0.10.2 → 0.10.4
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/host-config.d.ts +0 -1
- package/dist/host-config.js +3 -6
- package/dist/jsx.d.ts +0 -47
- package/dist/jsx.js +0 -23
- package/dist/node.d.ts +0 -2
- package/dist/node.js +2 -4
- package/dist/nodes/column-view-column.js +1 -1
- package/dist/nodes/column-view.js +2 -1
- package/dist/nodes/index.d.ts +0 -2
- package/dist/nodes/index.js +0 -2
- package/dist/nodes/internal/list-item-renderer.d.ts +1 -3
- package/dist/nodes/internal/list-item-renderer.js +6 -7
- package/dist/nodes/internal/signal-store.d.ts +11 -5
- package/dist/nodes/internal/signal-store.js +50 -31
- package/dist/nodes/internal/tree-list-item-renderer.d.ts +1 -3
- package/dist/nodes/internal/tree-list-item-renderer.js +6 -7
- package/dist/nodes/list-view.js +1 -1
- package/dist/nodes/models/list.js +3 -2
- package/dist/nodes/models/menu.js +5 -4
- package/dist/nodes/models/tree-list.js +3 -2
- package/dist/nodes/simple-list-view.js +2 -1
- package/dist/nodes/tree-list-view.js +1 -1
- package/dist/nodes/widget.d.ts +1 -0
- package/dist/nodes/widget.js +23 -13
- package/package.json +3 -3
- package/README.md +0 -103
- package/dist/nodes/toast-overlay.d.ts +0 -1
- package/dist/nodes/toast-overlay.js +0 -35
- package/dist/nodes/toast.d.ts +0 -17
- package/dist/nodes/toast.js +0 -77
package/dist/host-config.d.ts
CHANGED
|
@@ -5,7 +5,6 @@ import type { Container, Props } from "./types.js";
|
|
|
5
5
|
declare global {
|
|
6
6
|
var __GTKX_CONTAINER_NODE_CACHE__: Map<number, Node> | undefined;
|
|
7
7
|
}
|
|
8
|
-
export declare const isCommitting: () => boolean;
|
|
9
8
|
type TextInstance = Node;
|
|
10
9
|
type SuspenseInstance = never;
|
|
11
10
|
type HydratableInstance = never;
|
package/dist/host-config.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { beginBatch, endBatch, getObjectId } from "@gtkx/ffi";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { createNode } from "./factory.js";
|
|
4
|
-
import {
|
|
4
|
+
import { signalStore } from "./nodes/internal/signal-store.js";
|
|
5
5
|
import { flushAfterCommit } from "./scheduler.js";
|
|
6
6
|
if (!globalThis.__GTKX_CONTAINER_NODE_CACHE__) {
|
|
7
7
|
globalThis.__GTKX_CONTAINER_NODE_CACHE__ = new Map();
|
|
8
8
|
}
|
|
9
|
-
let committing = false;
|
|
10
|
-
export const isCommitting = () => committing;
|
|
11
9
|
const containerNodeCache = globalThis.__GTKX_CONTAINER_NODE_CACHE__;
|
|
12
10
|
const getOrCreateContainerNode = (container) => {
|
|
13
11
|
const id = getObjectId(container.id);
|
|
@@ -73,15 +71,14 @@ export function createHostConfig() {
|
|
|
73
71
|
parent.insertBefore(child, beforeChild);
|
|
74
72
|
},
|
|
75
73
|
prepareForCommit: () => {
|
|
76
|
-
|
|
74
|
+
signalStore.blockAll();
|
|
77
75
|
beginBatch();
|
|
78
76
|
return null;
|
|
79
77
|
},
|
|
80
78
|
resetAfterCommit: () => {
|
|
81
79
|
endBatch();
|
|
82
|
-
|
|
80
|
+
signalStore.unblockAll();
|
|
83
81
|
flushAfterCommit();
|
|
84
|
-
committing = false;
|
|
85
82
|
},
|
|
86
83
|
commitTextUpdate: (textInstance, oldText, newText) => {
|
|
87
84
|
textInstance.updateProps({ label: oldText }, { label: newText });
|
package/dist/jsx.d.ts
CHANGED
|
@@ -105,29 +105,6 @@ export type FixedChildProps = SlotProps & {
|
|
|
105
105
|
/** Y coordinate in pixels */
|
|
106
106
|
y?: number;
|
|
107
107
|
};
|
|
108
|
-
/**
|
|
109
|
-
* Props for declarative toast notifications.
|
|
110
|
-
*
|
|
111
|
-
* @see {@link Toast} for usage
|
|
112
|
-
*/
|
|
113
|
-
export type ToastProps = {
|
|
114
|
-
/** The toast message text */
|
|
115
|
-
title: string;
|
|
116
|
-
/** Timeout in seconds (0 for indefinite) */
|
|
117
|
-
timeout?: number;
|
|
118
|
-
/** Priority level for interrupting other toasts */
|
|
119
|
-
priority?: import("@gtkx/ffi/adw").ToastPriority;
|
|
120
|
-
/** Label for the action button */
|
|
121
|
-
buttonLabel?: string;
|
|
122
|
-
/** Action name to trigger when button is clicked */
|
|
123
|
-
actionName?: string;
|
|
124
|
-
/** Whether to use Pango markup in the title */
|
|
125
|
-
useMarkup?: boolean;
|
|
126
|
-
/** Callback when the toast button is clicked */
|
|
127
|
-
onButtonClicked?: () => void;
|
|
128
|
-
/** Callback when the toast is dismissed */
|
|
129
|
-
onDismissed?: () => void;
|
|
130
|
-
};
|
|
131
108
|
/**
|
|
132
109
|
* Props for custom list view rendering.
|
|
133
110
|
*
|
|
@@ -336,29 +313,6 @@ export declare const GridChild: "GridChild";
|
|
|
336
313
|
* ```
|
|
337
314
|
*/
|
|
338
315
|
export declare const FixedChild: "FixedChild";
|
|
339
|
-
/**
|
|
340
|
-
* Element type for declarative toast notifications within an AdwToastOverlay.
|
|
341
|
-
*
|
|
342
|
-
* When mounted, shows the toast. When unmounted, the toast auto-dismisses.
|
|
343
|
-
* Toasts can have an optional action button and callbacks.
|
|
344
|
-
*
|
|
345
|
-
* @example
|
|
346
|
-
* ```tsx
|
|
347
|
-
* <AdwToastOverlay>
|
|
348
|
-
* <MyContent />
|
|
349
|
-
* {showToast && (
|
|
350
|
-
* <Toast
|
|
351
|
-
* title="File saved"
|
|
352
|
-
* timeout={3}
|
|
353
|
-
* buttonLabel="Undo"
|
|
354
|
-
* onButtonClicked={handleUndo}
|
|
355
|
-
* onDismissed={() => setShowToast(false)}
|
|
356
|
-
* />
|
|
357
|
-
* )}
|
|
358
|
-
* </AdwToastOverlay>
|
|
359
|
-
* ```
|
|
360
|
-
*/
|
|
361
|
-
export declare const Toast: "Toast";
|
|
362
316
|
/**
|
|
363
317
|
* Element types for pages within a GtkNotebook (tabbed interface).
|
|
364
318
|
*
|
|
@@ -686,7 +640,6 @@ declare global {
|
|
|
686
640
|
StackPage: StackPageProps;
|
|
687
641
|
GridChild: GridChildProps;
|
|
688
642
|
FixedChild: FixedChildProps;
|
|
689
|
-
Toast: ToastProps;
|
|
690
643
|
"Notebook.Page": NotebookPageProps;
|
|
691
644
|
"Notebook.PageTab": NotebookPageTabProps;
|
|
692
645
|
ListItem: ListItemProps;
|
package/dist/jsx.js
CHANGED
|
@@ -73,29 +73,6 @@ export const GridChild = "GridChild";
|
|
|
73
73
|
* ```
|
|
74
74
|
*/
|
|
75
75
|
export const FixedChild = "FixedChild";
|
|
76
|
-
/**
|
|
77
|
-
* Element type for declarative toast notifications within an AdwToastOverlay.
|
|
78
|
-
*
|
|
79
|
-
* When mounted, shows the toast. When unmounted, the toast auto-dismisses.
|
|
80
|
-
* Toasts can have an optional action button and callbacks.
|
|
81
|
-
*
|
|
82
|
-
* @example
|
|
83
|
-
* ```tsx
|
|
84
|
-
* <AdwToastOverlay>
|
|
85
|
-
* <MyContent />
|
|
86
|
-
* {showToast && (
|
|
87
|
-
* <Toast
|
|
88
|
-
* title="File saved"
|
|
89
|
-
* timeout={3}
|
|
90
|
-
* buttonLabel="Undo"
|
|
91
|
-
* onButtonClicked={handleUndo}
|
|
92
|
-
* onDismissed={() => setShowToast(false)}
|
|
93
|
-
* />
|
|
94
|
-
* )}
|
|
95
|
-
* </AdwToastOverlay>
|
|
96
|
-
* ```
|
|
97
|
-
*/
|
|
98
|
-
export const Toast = "Toast";
|
|
99
76
|
/**
|
|
100
77
|
* Element types for pages within a GtkNotebook (tabbed interface).
|
|
101
78
|
*
|
package/dist/node.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { SignalStore } from "./nodes/internal/signal-store.js";
|
|
2
1
|
import type { Container, ContainerClass, Props } from "./types.js";
|
|
3
2
|
export declare class Node<T = unknown, P = Props> {
|
|
4
3
|
static priority: number;
|
|
@@ -6,7 +5,6 @@ export declare class Node<T = unknown, P = Props> {
|
|
|
6
5
|
static createContainer(_props: Props, _containerClass: ContainerClass, _rootContainer?: Container): unknown;
|
|
7
6
|
container: T;
|
|
8
7
|
typeName: string;
|
|
9
|
-
protected signalStore: SignalStore;
|
|
10
8
|
constructor(typeName: string, _props: P | undefined, container: T, _rootContainer?: Container);
|
|
11
9
|
appendChild(_child: Node): void;
|
|
12
10
|
removeChild(_child: Node): void;
|
package/dist/node.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { signalStore } from "./nodes/internal/signal-store.js";
|
|
2
2
|
export class Node {
|
|
3
3
|
static priority = 0;
|
|
4
4
|
static matches(_type, _containerOrClass) {
|
|
@@ -9,11 +9,9 @@ export class Node {
|
|
|
9
9
|
}
|
|
10
10
|
container;
|
|
11
11
|
typeName;
|
|
12
|
-
signalStore;
|
|
13
12
|
constructor(typeName, _props = {}, container, _rootContainer) {
|
|
14
13
|
this.typeName = typeName;
|
|
15
14
|
this.container = container;
|
|
16
|
-
this.signalStore = new SignalStore();
|
|
17
15
|
}
|
|
18
16
|
appendChild(_child) { }
|
|
19
17
|
removeChild(_child) { }
|
|
@@ -21,6 +19,6 @@ export class Node {
|
|
|
21
19
|
updateProps(_oldProps, _newProps) { }
|
|
22
20
|
mount() { }
|
|
23
21
|
unmount() {
|
|
24
|
-
|
|
22
|
+
signalStore.clear(this);
|
|
25
23
|
}
|
|
26
24
|
}
|
|
@@ -11,7 +11,7 @@ export class ColumnViewColumnNode extends VirtualNode {
|
|
|
11
11
|
itemRenderer;
|
|
12
12
|
constructor(typeName, props, container, rootContainer) {
|
|
13
13
|
super(typeName, props, container, rootContainer);
|
|
14
|
-
this.itemRenderer = new ListItemRenderer(
|
|
14
|
+
this.itemRenderer = new ListItemRenderer();
|
|
15
15
|
this.column = new Gtk.ColumnViewColumn();
|
|
16
16
|
this.column.setFactory(this.itemRenderer.getFactory());
|
|
17
17
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
2
2
|
import { registerNodeClass } from "../registry.js";
|
|
3
3
|
import { ColumnViewColumnNode } from "./column-view-column.js";
|
|
4
|
+
import { signalStore } from "./internal/signal-store.js";
|
|
4
5
|
import { filterProps, isContainerType } from "./internal/utils.js";
|
|
5
6
|
import { ListItemNode } from "./list-item.js";
|
|
6
7
|
import { List } from "./models/list.js";
|
|
@@ -79,7 +80,7 @@ class ColumnViewNode extends WidgetNode {
|
|
|
79
80
|
this.handleSortChange = () => {
|
|
80
81
|
onSortChange?.(sorter.getPrimarySortColumn()?.getId() ?? null, sorter.getPrimarySortOrder());
|
|
81
82
|
};
|
|
82
|
-
|
|
83
|
+
signalStore.set(this, sorter, "changed", this.handleSortChange);
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
if (!oldProps || oldProps.sortColumn !== newProps.sortColumn || oldProps.sortOrder !== newProps.sortOrder) {
|
package/dist/nodes/index.d.ts
CHANGED
|
@@ -21,8 +21,6 @@ import "./simple-list-view.js";
|
|
|
21
21
|
import "./slot.js";
|
|
22
22
|
import "./stack-page.js";
|
|
23
23
|
import "./stack.js";
|
|
24
|
-
import "./toast-overlay.js";
|
|
25
|
-
import "./toast.js";
|
|
26
24
|
import "./toolbar-child.js";
|
|
27
25
|
import "./tree-list-item.js";
|
|
28
26
|
import "./tree-list-view.js";
|
package/dist/nodes/index.js
CHANGED
|
@@ -21,8 +21,6 @@ import "./simple-list-view.js";
|
|
|
21
21
|
import "./slot.js";
|
|
22
22
|
import "./stack-page.js";
|
|
23
23
|
import "./stack.js";
|
|
24
|
-
import "./toast-overlay.js";
|
|
25
|
-
import "./toast.js";
|
|
26
24
|
import "./toolbar-child.js";
|
|
27
25
|
import "./tree-list-item.js";
|
|
28
26
|
import "./tree-list-view.js";
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
3
|
import type { ListStore } from "./list-store.js";
|
|
4
|
-
import type { SignalStore } from "./signal-store.js";
|
|
5
4
|
export type RenderItemFn<T> = (item: T | null) => ReactNode;
|
|
6
5
|
export declare class ListItemRenderer {
|
|
7
6
|
private factory;
|
|
8
7
|
private store?;
|
|
9
8
|
private fiberRoots;
|
|
10
9
|
private renderFn?;
|
|
11
|
-
|
|
12
|
-
constructor(signalStore: SignalStore);
|
|
10
|
+
constructor();
|
|
13
11
|
getFactory(): Gtk.SignalListItemFactory;
|
|
14
12
|
setRenderFn(renderFn?: RenderItemFn<unknown>): void;
|
|
15
13
|
setStore(store?: ListStore): void;
|
|
@@ -2,14 +2,13 @@ import { getObjectId } from "@gtkx/ffi";
|
|
|
2
2
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
3
3
|
import { createFiberRoot } from "../../fiber-root.js";
|
|
4
4
|
import { reconciler } from "../../reconciler.js";
|
|
5
|
+
import { signalStore } from "./signal-store.js";
|
|
5
6
|
export class ListItemRenderer {
|
|
6
7
|
factory;
|
|
7
8
|
store;
|
|
8
9
|
fiberRoots = new Map();
|
|
9
10
|
renderFn = () => null;
|
|
10
|
-
|
|
11
|
-
constructor(signalStore) {
|
|
12
|
-
this.signalStore = signalStore;
|
|
11
|
+
constructor() {
|
|
13
12
|
this.factory = new Gtk.SignalListItemFactory();
|
|
14
13
|
this.initialize();
|
|
15
14
|
}
|
|
@@ -29,7 +28,7 @@ export class ListItemRenderer {
|
|
|
29
28
|
return this.store;
|
|
30
29
|
}
|
|
31
30
|
initialize() {
|
|
32
|
-
|
|
31
|
+
signalStore.set(this, this.factory, "setup", (_self, listItem) => {
|
|
33
32
|
const ptr = getObjectId(listItem.id);
|
|
34
33
|
const box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
|
|
35
34
|
listItem.setChild(box);
|
|
@@ -38,7 +37,7 @@ export class ListItemRenderer {
|
|
|
38
37
|
const element = this.renderFn?.(null);
|
|
39
38
|
reconciler.getInstance().updateContainer(element, fiberRoot, null, () => { });
|
|
40
39
|
});
|
|
41
|
-
|
|
40
|
+
signalStore.set(this, this.factory, "bind", (_self, listItem) => {
|
|
42
41
|
const ptr = getObjectId(listItem.id);
|
|
43
42
|
const fiberRoot = this.fiberRoots.get(ptr);
|
|
44
43
|
if (!fiberRoot)
|
|
@@ -48,14 +47,14 @@ export class ListItemRenderer {
|
|
|
48
47
|
const element = this.renderFn?.(item);
|
|
49
48
|
reconciler.getInstance().updateContainer(element, fiberRoot, null, () => { });
|
|
50
49
|
});
|
|
51
|
-
|
|
50
|
+
signalStore.set(this, this.factory, "unbind", (_self, listItem) => {
|
|
52
51
|
const ptr = getObjectId(listItem.id);
|
|
53
52
|
const fiberRoot = this.fiberRoots.get(ptr);
|
|
54
53
|
if (!fiberRoot)
|
|
55
54
|
return;
|
|
56
55
|
reconciler.getInstance().updateContainer(null, fiberRoot, null, () => { });
|
|
57
56
|
});
|
|
58
|
-
|
|
57
|
+
signalStore.set(this, this.factory, "teardown", (_self, listItem) => {
|
|
59
58
|
const ptr = getObjectId(listItem.id);
|
|
60
59
|
const fiberRoot = this.fiberRoots.get(ptr);
|
|
61
60
|
if (fiberRoot) {
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import * as GObject from "@gtkx/ffi/gobject";
|
|
2
|
+
type SignalOwner = object;
|
|
2
3
|
export type SignalHandler = (...args: any[]) => any;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
private
|
|
4
|
+
declare class SignalStore {
|
|
5
|
+
private ownerHandlers;
|
|
6
|
+
private blockedHandlers;
|
|
7
|
+
private getOwnerMap;
|
|
6
8
|
private disconnect;
|
|
7
9
|
private connect;
|
|
8
|
-
set(obj: GObject.GObject, signal: string, handler?: SignalHandler): void;
|
|
9
|
-
clear(): void;
|
|
10
|
+
set(owner: SignalOwner, obj: GObject.GObject, signal: string, handler?: SignalHandler): void;
|
|
11
|
+
clear(owner: SignalOwner): void;
|
|
12
|
+
blockAll(): void;
|
|
13
|
+
unblockAll(): void;
|
|
10
14
|
}
|
|
15
|
+
export declare const signalStore: SignalStore;
|
|
16
|
+
export {};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { getObjectId } from "@gtkx/ffi";
|
|
2
2
|
import * as GObject from "@gtkx/ffi/gobject";
|
|
3
|
-
import { isCommitting } from "../../host-config.js";
|
|
4
3
|
const LIFECYCLE_SIGNALS = new Set([
|
|
5
4
|
"realize",
|
|
6
5
|
"unrealize",
|
|
@@ -16,49 +15,69 @@ const LIFECYCLE_SIGNALS = new Set([
|
|
|
16
15
|
"unbind",
|
|
17
16
|
"teardown",
|
|
18
17
|
]);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
class SignalStore {
|
|
19
|
+
ownerHandlers = new Map();
|
|
20
|
+
blockedHandlers = new Set();
|
|
21
|
+
getOwnerMap(owner) {
|
|
22
|
+
let map = this.ownerHandlers.get(owner);
|
|
23
|
+
if (!map) {
|
|
24
|
+
map = new Map();
|
|
25
|
+
this.ownerHandlers.set(owner, map);
|
|
26
|
+
}
|
|
27
|
+
return map;
|
|
28
|
+
}
|
|
29
|
+
disconnect(owner, obj, signal) {
|
|
26
30
|
const objectId = getObjectId(obj.id);
|
|
27
31
|
const key = `${objectId}:${signal}`;
|
|
28
|
-
const
|
|
32
|
+
const ownerMap = this.ownerHandlers.get(owner);
|
|
33
|
+
const existing = ownerMap?.get(key);
|
|
29
34
|
if (existing) {
|
|
30
35
|
GObject.signalHandlerDisconnect(existing.obj, existing.handlerId);
|
|
31
|
-
|
|
36
|
+
ownerMap?.delete(key);
|
|
32
37
|
}
|
|
33
38
|
}
|
|
34
|
-
connect(obj, signal, handler) {
|
|
39
|
+
connect(owner, obj, signal, handler) {
|
|
35
40
|
const objectId = getObjectId(obj.id);
|
|
36
41
|
const key = `${objectId}:${signal}`;
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
const handlerId = obj.connect(signal, handler);
|
|
43
|
+
this.getOwnerMap(owner).set(key, { obj, handlerId });
|
|
44
|
+
}
|
|
45
|
+
set(owner, obj, signal, handler) {
|
|
46
|
+
this.disconnect(owner, obj, signal);
|
|
47
|
+
if (handler) {
|
|
48
|
+
this.connect(owner, obj, signal, handler);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
clear(owner) {
|
|
52
|
+
const ownerMap = this.ownerHandlers.get(owner);
|
|
53
|
+
if (ownerMap) {
|
|
54
|
+
for (const { obj, handlerId } of ownerMap.values()) {
|
|
55
|
+
GObject.signalHandlerDisconnect(obj, handlerId);
|
|
40
56
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
57
|
+
this.ownerHandlers.delete(owner);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
blockAll() {
|
|
61
|
+
this.blockedHandlers.clear();
|
|
62
|
+
for (const ownerMap of this.ownerHandlers.values()) {
|
|
63
|
+
for (const [key, { obj, handlerId }] of ownerMap.entries()) {
|
|
64
|
+
if (LIFECYCLE_SIGNALS.has(key.split(":")[1] ?? "")) {
|
|
65
|
+
continue;
|
|
44
66
|
}
|
|
45
|
-
|
|
67
|
+
GObject.signalHandlerBlock(obj, handlerId);
|
|
68
|
+
this.blockedHandlers.add(handlerId);
|
|
46
69
|
}
|
|
47
|
-
return handler(...args);
|
|
48
|
-
};
|
|
49
|
-
const handlerId = obj.connect(signal, wrappedHandler);
|
|
50
|
-
this.signalHandlers.set(key, { obj, handlerId });
|
|
51
|
-
}
|
|
52
|
-
set(obj, signal, handler) {
|
|
53
|
-
this.disconnect(obj, signal);
|
|
54
|
-
if (handler) {
|
|
55
|
-
this.connect(obj, signal, handler);
|
|
56
70
|
}
|
|
57
71
|
}
|
|
58
|
-
|
|
59
|
-
for (const
|
|
60
|
-
|
|
72
|
+
unblockAll() {
|
|
73
|
+
for (const ownerMap of this.ownerHandlers.values()) {
|
|
74
|
+
for (const { obj, handlerId } of ownerMap.values()) {
|
|
75
|
+
if (this.blockedHandlers.has(handlerId)) {
|
|
76
|
+
GObject.signalHandlerUnblock(obj, handlerId);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
61
79
|
}
|
|
62
|
-
this.
|
|
80
|
+
this.blockedHandlers.clear();
|
|
63
81
|
}
|
|
64
82
|
}
|
|
83
|
+
export const signalStore = new SignalStore();
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
|
-
import type { SignalStore } from "./signal-store.js";
|
|
4
3
|
import type { TreeStore } from "./tree-store.js";
|
|
5
4
|
export type TreeRenderItemFn<T> = (item: T | null, row: Gtk.TreeListRow | null) => ReactNode;
|
|
6
5
|
export declare class TreeListItemRenderer {
|
|
@@ -8,8 +7,7 @@ export declare class TreeListItemRenderer {
|
|
|
8
7
|
private store?;
|
|
9
8
|
private fiberRoots;
|
|
10
9
|
private renderFn?;
|
|
11
|
-
|
|
12
|
-
constructor(signalStore: SignalStore);
|
|
10
|
+
constructor();
|
|
13
11
|
getFactory(): Gtk.SignalListItemFactory;
|
|
14
12
|
setRenderFn(renderFn?: TreeRenderItemFn<unknown>): void;
|
|
15
13
|
setStore(store?: TreeStore): void;
|
|
@@ -2,14 +2,13 @@ import { getObjectId } from "@gtkx/ffi";
|
|
|
2
2
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
3
3
|
import { createFiberRoot } from "../../fiber-root.js";
|
|
4
4
|
import { reconciler } from "../../reconciler.js";
|
|
5
|
+
import { signalStore } from "./signal-store.js";
|
|
5
6
|
export class TreeListItemRenderer {
|
|
6
7
|
factory;
|
|
7
8
|
store;
|
|
8
9
|
fiberRoots = new Map();
|
|
9
10
|
renderFn = () => null;
|
|
10
|
-
|
|
11
|
-
constructor(signalStore) {
|
|
12
|
-
this.signalStore = signalStore;
|
|
11
|
+
constructor() {
|
|
13
12
|
this.factory = new Gtk.SignalListItemFactory();
|
|
14
13
|
this.initialize();
|
|
15
14
|
}
|
|
@@ -29,7 +28,7 @@ export class TreeListItemRenderer {
|
|
|
29
28
|
return this.store;
|
|
30
29
|
}
|
|
31
30
|
initialize() {
|
|
32
|
-
|
|
31
|
+
signalStore.set(this, this.factory, "setup", (_self, listItem) => {
|
|
33
32
|
const ptr = getObjectId(listItem.id);
|
|
34
33
|
const expander = new Gtk.TreeExpander();
|
|
35
34
|
const box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
|
|
@@ -40,7 +39,7 @@ export class TreeListItemRenderer {
|
|
|
40
39
|
const element = this.renderFn?.(null, null);
|
|
41
40
|
reconciler.getInstance().updateContainer(element, fiberRoot, null, () => { });
|
|
42
41
|
});
|
|
43
|
-
|
|
42
|
+
signalStore.set(this, this.factory, "bind", (_self, listItem) => {
|
|
44
43
|
const ptr = getObjectId(listItem.id);
|
|
45
44
|
const fiberRoot = this.fiberRoots.get(ptr);
|
|
46
45
|
if (!fiberRoot)
|
|
@@ -69,7 +68,7 @@ export class TreeListItemRenderer {
|
|
|
69
68
|
const element = this.renderFn?.(itemData?.value ?? null, treeListRow);
|
|
70
69
|
reconciler.getInstance().updateContainer(element, fiberRoot, null, () => { });
|
|
71
70
|
});
|
|
72
|
-
|
|
71
|
+
signalStore.set(this, this.factory, "unbind", (_self, listItem) => {
|
|
73
72
|
const ptr = getObjectId(listItem.id);
|
|
74
73
|
const fiberRoot = this.fiberRoots.get(ptr);
|
|
75
74
|
if (!fiberRoot)
|
|
@@ -78,7 +77,7 @@ export class TreeListItemRenderer {
|
|
|
78
77
|
expander.setListRow(undefined);
|
|
79
78
|
reconciler.getInstance().updateContainer(null, fiberRoot, null, () => { });
|
|
80
79
|
});
|
|
81
|
-
|
|
80
|
+
signalStore.set(this, this.factory, "teardown", (_self, listItem) => {
|
|
82
81
|
const ptr = getObjectId(listItem.id);
|
|
83
82
|
const fiberRoot = this.fiberRoots.get(ptr);
|
|
84
83
|
if (fiberRoot) {
|
package/dist/nodes/list-view.js
CHANGED
|
@@ -16,7 +16,7 @@ class ListViewNode extends WidgetNode {
|
|
|
16
16
|
constructor(typeName, props, container, rootContainer) {
|
|
17
17
|
super(typeName, props, container, rootContainer);
|
|
18
18
|
this.list = new List();
|
|
19
|
-
this.itemRenderer = new ListItemRenderer(
|
|
19
|
+
this.itemRenderer = new ListItemRenderer();
|
|
20
20
|
this.itemRenderer.setStore(this.list.getStore());
|
|
21
21
|
this.container.setFactory(this.itemRenderer.getFactory());
|
|
22
22
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
2
2
|
import { ListStore } from "../internal/list-store.js";
|
|
3
|
+
import { signalStore } from "../internal/signal-store.js";
|
|
3
4
|
import { ListItemNode } from "../list-item.js";
|
|
4
5
|
import { VirtualNode } from "../virtual.js";
|
|
5
6
|
export class List extends VirtualNode {
|
|
@@ -42,7 +43,7 @@ export class List extends VirtualNode {
|
|
|
42
43
|
updateProps(oldProps, newProps) {
|
|
43
44
|
super.updateProps(oldProps, newProps);
|
|
44
45
|
if (!oldProps || oldProps.selectionMode !== newProps.selectionMode) {
|
|
45
|
-
|
|
46
|
+
signalStore.set(this, this.selectionModel, "selection-changed", undefined);
|
|
46
47
|
this.selectionModel = this.createSelectionModel(newProps.selectionMode);
|
|
47
48
|
}
|
|
48
49
|
if (!oldProps ||
|
|
@@ -52,7 +53,7 @@ export class List extends VirtualNode {
|
|
|
52
53
|
this.handleSelectionChange = () => {
|
|
53
54
|
onSelectionChanged?.(this.getSelection());
|
|
54
55
|
};
|
|
55
|
-
|
|
56
|
+
signalStore.set(this, this.selectionModel, "selection-changed", newProps.onSelectionChanged ? this.handleSelectionChange : undefined);
|
|
56
57
|
}
|
|
57
58
|
if (!oldProps || oldProps.selected !== newProps.selected || oldProps.selectionMode !== newProps.selectionMode) {
|
|
58
59
|
this.setSelection(newProps.selected);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { batch } from "@gtkx/ffi";
|
|
2
2
|
import * as Gio from "@gtkx/ffi/gio";
|
|
3
3
|
import { scheduleAfterCommit } from "../../scheduler.js";
|
|
4
|
+
import { signalStore } from "../internal/signal-store.js";
|
|
4
5
|
import { VirtualNode } from "../virtual.js";
|
|
5
6
|
export class Menu extends VirtualNode {
|
|
6
7
|
actionMap;
|
|
@@ -66,10 +67,10 @@ export class Menu extends VirtualNode {
|
|
|
66
67
|
createAction() {
|
|
67
68
|
batch(() => {
|
|
68
69
|
if (this.action) {
|
|
69
|
-
|
|
70
|
+
signalStore.set(this, this.action, "activate", undefined);
|
|
70
71
|
}
|
|
71
72
|
this.action = new Gio.SimpleAction(this.getId());
|
|
72
|
-
|
|
73
|
+
signalStore.set(this, this.action, "activate", this.getOnActivate());
|
|
73
74
|
this.getActionMap().addAction(this.action);
|
|
74
75
|
if (this.application && this.props.accels) {
|
|
75
76
|
this.application.setAccelsForAction(this.getActionName(), this.getAccels());
|
|
@@ -83,7 +84,7 @@ export class Menu extends VirtualNode {
|
|
|
83
84
|
}
|
|
84
85
|
if (this.action) {
|
|
85
86
|
this.getActionMap().removeAction(this.getId());
|
|
86
|
-
|
|
87
|
+
signalStore.set(this, this.action, "activate", undefined);
|
|
87
88
|
this.action = undefined;
|
|
88
89
|
}
|
|
89
90
|
});
|
|
@@ -230,7 +231,7 @@ export class Menu extends VirtualNode {
|
|
|
230
231
|
return;
|
|
231
232
|
}
|
|
232
233
|
if (oldProps.onActivate !== newProps.onActivate) {
|
|
233
|
-
|
|
234
|
+
signalStore.set(this, this.getAction(), "activate", newProps.onActivate);
|
|
234
235
|
}
|
|
235
236
|
if (oldProps.accels !== newProps.accels) {
|
|
236
237
|
if (this.application) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
2
|
+
import { signalStore } from "../internal/signal-store.js";
|
|
2
3
|
import { TreeStore } from "../internal/tree-store.js";
|
|
3
4
|
import { TreeListItemNode } from "../tree-list-item.js";
|
|
4
5
|
import { VirtualNode } from "../virtual.js";
|
|
@@ -77,7 +78,7 @@ export class TreeList extends VirtualNode {
|
|
|
77
78
|
this.treeListModel.setAutoexpand(newProps.autoexpand ?? false);
|
|
78
79
|
}
|
|
79
80
|
if (!oldProps || oldProps.selectionMode !== newProps.selectionMode) {
|
|
80
|
-
|
|
81
|
+
signalStore.set(this, this.selectionModel, "selection-changed", undefined);
|
|
81
82
|
this.selectionModel = this.createSelectionModel(newProps.selectionMode);
|
|
82
83
|
this.selectionModel.setModel(this.treeListModel);
|
|
83
84
|
}
|
|
@@ -88,7 +89,7 @@ export class TreeList extends VirtualNode {
|
|
|
88
89
|
this.handleSelectionChange = () => {
|
|
89
90
|
onSelectionChanged?.(this.getSelection());
|
|
90
91
|
};
|
|
91
|
-
|
|
92
|
+
signalStore.set(this, this.selectionModel, "selection-changed", newProps.onSelectionChanged ? this.handleSelectionChange : undefined);
|
|
92
93
|
}
|
|
93
94
|
if (!oldProps || oldProps.selected !== newProps.selected || oldProps.selectionMode !== newProps.selectionMode) {
|
|
94
95
|
this.setSelection(newProps.selected);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as Adw from "@gtkx/ffi/adw";
|
|
2
2
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
3
3
|
import { registerNodeClass } from "../registry.js";
|
|
4
|
+
import { signalStore } from "./internal/signal-store.js";
|
|
4
5
|
import { SimpleListStore } from "./internal/simple-list-store.js";
|
|
5
6
|
import { filterProps, isContainerType } from "./internal/utils.js";
|
|
6
7
|
import { SimpleListItemNode } from "./simple-list-item.js";
|
|
@@ -28,7 +29,7 @@ class SimpleListViewNode extends WidgetNode {
|
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
: undefined;
|
|
31
|
-
|
|
32
|
+
signalStore.set(this, this.container, "notify::selected", handleSelectionChange);
|
|
32
33
|
}
|
|
33
34
|
if (!oldProps || oldProps.selectedId !== newProps.selectedId) {
|
|
34
35
|
const index = newProps.selectedId !== undefined ? this.store.getIndexById(newProps.selectedId) : undefined;
|
|
@@ -20,7 +20,7 @@ class TreeListViewNode extends WidgetNode {
|
|
|
20
20
|
const listView = container ?? new Gtk.ListView();
|
|
21
21
|
super(typeName, props, listView, rootContainer);
|
|
22
22
|
this.treeList = new TreeList(props.autoexpand, props.selectionMode);
|
|
23
|
-
this.itemRenderer = new TreeListItemRenderer(
|
|
23
|
+
this.itemRenderer = new TreeListItemRenderer();
|
|
24
24
|
this.itemRenderer.setStore(this.treeList.getStore());
|
|
25
25
|
this.container.setFactory(this.itemRenderer.getFactory());
|
|
26
26
|
}
|
package/dist/nodes/widget.d.ts
CHANGED
package/dist/nodes/widget.js
CHANGED
|
@@ -4,6 +4,7 @@ import { CONSTRUCTOR_PROPS, PROPS, SIGNALS } from "../generated/internal.js";
|
|
|
4
4
|
import { Node } from "../node.js";
|
|
5
5
|
import { registerNodeClass } from "../registry.js";
|
|
6
6
|
import { hasSingleContent, isAddable, isAppendable, isEditable, isInsertable, isRemovable, isReorderable, isSingleChild, } from "./internal/predicates.js";
|
|
7
|
+
import { signalStore } from "./internal/signal-store.js";
|
|
7
8
|
import { filterProps, isContainerType } from "./internal/utils.js";
|
|
8
9
|
import { SlotNode } from "./slot.js";
|
|
9
10
|
const EVENT_CONTROLLER_PROPS = new Set([
|
|
@@ -173,9 +174,16 @@ export class WidgetNode extends Node {
|
|
|
173
174
|
const signalName = this.propNameToSignalName(name);
|
|
174
175
|
if (signals.has(signalName)) {
|
|
175
176
|
const handler = typeof newValue === "function" ? newValue : undefined;
|
|
176
|
-
|
|
177
|
+
signalStore.set(this, this.container, signalName, handler);
|
|
177
178
|
}
|
|
178
179
|
else if (newValue !== undefined) {
|
|
180
|
+
const isEditableText = name === "text" && isEditable(this.container);
|
|
181
|
+
if (isEditableText && oldValue !== undefined) {
|
|
182
|
+
const currentValue = this.getProperty(name);
|
|
183
|
+
if (oldValue !== currentValue) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
179
187
|
this.setProperty(name, newValue);
|
|
180
188
|
}
|
|
181
189
|
}
|
|
@@ -190,7 +198,7 @@ export class WidgetNode extends Node {
|
|
|
190
198
|
this.container.addController(this.motionController);
|
|
191
199
|
}
|
|
192
200
|
const signalName = propName === "onEnter" ? "enter" : propName === "onLeave" ? "leave" : "motion";
|
|
193
|
-
|
|
201
|
+
signalStore.set(this, this.motionController, signalName, handler);
|
|
194
202
|
break;
|
|
195
203
|
}
|
|
196
204
|
case "onPressed":
|
|
@@ -200,7 +208,7 @@ export class WidgetNode extends Node {
|
|
|
200
208
|
this.container.addController(this.clickController);
|
|
201
209
|
}
|
|
202
210
|
const signalName = propName === "onPressed" ? "pressed" : "released";
|
|
203
|
-
|
|
211
|
+
signalStore.set(this, this.clickController, signalName, handler);
|
|
204
212
|
break;
|
|
205
213
|
}
|
|
206
214
|
case "onKeyPressed":
|
|
@@ -210,7 +218,7 @@ export class WidgetNode extends Node {
|
|
|
210
218
|
this.container.addController(this.keyController);
|
|
211
219
|
}
|
|
212
220
|
const signalName = propName === "onKeyPressed" ? "key-pressed" : "key-released";
|
|
213
|
-
|
|
221
|
+
signalStore.set(this, this.keyController, signalName, handler);
|
|
214
222
|
break;
|
|
215
223
|
}
|
|
216
224
|
case "onScroll": {
|
|
@@ -218,7 +226,7 @@ export class WidgetNode extends Node {
|
|
|
218
226
|
this.scrollController = new Gtk.EventControllerScroll(Gtk.EventControllerScrollFlags.BOTH_AXES);
|
|
219
227
|
this.container.addController(this.scrollController);
|
|
220
228
|
}
|
|
221
|
-
|
|
229
|
+
signalStore.set(this, this.scrollController, "scroll", handler);
|
|
222
230
|
break;
|
|
223
231
|
}
|
|
224
232
|
case "onNotify": {
|
|
@@ -227,7 +235,7 @@ export class WidgetNode extends Node {
|
|
|
227
235
|
handler(obj, pspec.getName());
|
|
228
236
|
}
|
|
229
237
|
: undefined;
|
|
230
|
-
|
|
238
|
+
signalStore.set(this, this.container, "notify", wrappedHandler);
|
|
231
239
|
break;
|
|
232
240
|
}
|
|
233
241
|
}
|
|
@@ -239,6 +247,15 @@ export class WidgetNode extends Node {
|
|
|
239
247
|
.toLowerCase()
|
|
240
248
|
.replace(/^-/, "");
|
|
241
249
|
}
|
|
250
|
+
getProperty(key) {
|
|
251
|
+
const WidgetClass = this.container.constructor;
|
|
252
|
+
const [getterName] = PROPS[WidgetClass.glibTypeName]?.[key] || [];
|
|
253
|
+
const getter = getterName ? this.container[getterName] : undefined;
|
|
254
|
+
if (getter && typeof getter === "function") {
|
|
255
|
+
return getter.call(this.container);
|
|
256
|
+
}
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
242
259
|
setProperty(key, value) {
|
|
243
260
|
const WidgetClass = this.container.constructor;
|
|
244
261
|
const [getterName, setterName] = PROPS[WidgetClass.glibTypeName]?.[key] || [];
|
|
@@ -254,14 +271,7 @@ export class WidgetNode extends Node {
|
|
|
254
271
|
}
|
|
255
272
|
}
|
|
256
273
|
if (setter && typeof setter === "function") {
|
|
257
|
-
const editable = isEditable(this.container) ? this.container : null;
|
|
258
|
-
const shouldPreserveCursor = key === "text" && editable !== null;
|
|
259
|
-
const cursorPosition = shouldPreserveCursor ? editable.getPosition() : 0;
|
|
260
274
|
setter.call(this.container, value);
|
|
261
|
-
if (shouldPreserveCursor && editable !== null) {
|
|
262
|
-
const textLength = editable.getText().length;
|
|
263
|
-
editable.setPosition(Math.min(cursorPosition, textLength));
|
|
264
|
-
}
|
|
265
275
|
}
|
|
266
276
|
}
|
|
267
277
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gtkx/react",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.4",
|
|
4
4
|
"description": "Build GTK4 desktop applications with React and TypeScript",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gtk",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"react-reconciler": "^0.33.0",
|
|
39
|
-
"@gtkx/ffi": "0.10.
|
|
40
|
-
"@gtkx/gir": "0.10.
|
|
39
|
+
"@gtkx/ffi": "0.10.4",
|
|
40
|
+
"@gtkx/gir": "0.10.4"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/react-reconciler": "^0.32.3",
|
package/README.md
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
<p align="center">
|
|
2
|
-
<img src="https://raw.githubusercontent.com/eugeniodepalo/gtkx/main/logo.svg" alt="GTKX" width="80" height="80">
|
|
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://www.npmjs.com/package/@gtkx/react"><img src="https://img.shields.io/npm/v/@gtkx/react.svg" alt="npm version"></a>
|
|
13
|
-
<a href="https://github.com/eugeniodepalo/gtkx/actions"><img src="https://img.shields.io/github/actions/workflow/status/eugeniodepalo/gtkx/ci.yml" alt="CI"></a>
|
|
14
|
-
<a href="https://github.com/eugeniodepalo/gtkx/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MPL--2.0-blue.svg" alt="License"></a>
|
|
15
|
-
<a href="https://github.com/eugeniodepalo/gtkx/discussions"><img src="https://img.shields.io/badge/discussions-GitHub-blue" alt="GitHub Discussions"></a>
|
|
16
|
-
</p>
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
GTKX lets you write Linux desktop applications using React. Your components render as native GTK4 widgets through a Rust FFI bridge—no webviews, no Electron, just native performance with the developer experience you already know.
|
|
21
|
-
|
|
22
|
-
## Quick Start
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
npx @gtkx/cli create my-app
|
|
26
|
-
cd my-app
|
|
27
|
-
npm run dev
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Example
|
|
31
|
-
|
|
32
|
-
```tsx
|
|
33
|
-
import {
|
|
34
|
-
GtkApplicationWindow,
|
|
35
|
-
GtkBox,
|
|
36
|
-
GtkButton,
|
|
37
|
-
GtkLabel,
|
|
38
|
-
quit,
|
|
39
|
-
render,
|
|
40
|
-
} from "@gtkx/react";
|
|
41
|
-
import * as Gtk from "@gtkx/ffi/gtk";
|
|
42
|
-
import { useState } from "react";
|
|
43
|
-
|
|
44
|
-
const App = () => {
|
|
45
|
-
const [count, setCount] = useState(0);
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<GtkApplicationWindow
|
|
49
|
-
title="Counter"
|
|
50
|
-
defaultWidth={300}
|
|
51
|
-
defaultHeight={200}
|
|
52
|
-
onCloseRequest={quit}
|
|
53
|
-
>
|
|
54
|
-
<GtkBox
|
|
55
|
-
orientation={Gtk.Orientation.VERTICAL}
|
|
56
|
-
spacing={20}
|
|
57
|
-
valign={Gtk.Align.CENTER}
|
|
58
|
-
>
|
|
59
|
-
<GtkLabel label={`Count: ${count}`} cssClasses={["title-1"]} />
|
|
60
|
-
<GtkButton label="Increment" onClicked={() => setCount((c) => c + 1)} />
|
|
61
|
-
</GtkBox>
|
|
62
|
-
</GtkApplicationWindow>
|
|
63
|
-
);
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
render(<App />, "com.example.counter");
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Features
|
|
70
|
-
|
|
71
|
-
- **React 19** — Hooks, concurrent features, and the component model you know
|
|
72
|
-
- **Native GTK4 widgets** — Real native controls, not web components in a webview
|
|
73
|
-
- **Adwaita support** — Modern GNOME styling with Libadwaita components
|
|
74
|
-
- **Hot Module Replacement** — Fast refresh during development
|
|
75
|
-
- **TypeScript first** — Full type safety with auto-generated bindings
|
|
76
|
-
- **CSS-in-JS styling** — Familiar styling patterns adapted for GTK
|
|
77
|
-
- **Testing utilities** — Component testing similar to Testing Library
|
|
78
|
-
|
|
79
|
-
## Examples
|
|
80
|
-
|
|
81
|
-
Explore complete applications in the [`examples/`](./examples) directory:
|
|
82
|
-
|
|
83
|
-
- **[gtk-demo](./examples/gtk-demo)** — Full replica of the official GTK demo app
|
|
84
|
-
- **[hello-world](./examples/hello-world)** — Minimal application showing a counter
|
|
85
|
-
- **[todo](./examples/todo)** — Full-featured todo application with Adwaita styling and testing
|
|
86
|
-
- **[deploying](./examples/deploying)** — Example of packaging and distributing a GTKX app
|
|
87
|
-
|
|
88
|
-
## Documentation
|
|
89
|
-
|
|
90
|
-
Visit [https://eugeniodepalo.github.io/gtkx](https://eugeniodepalo.github.io/gtkx/) for the full documentation.
|
|
91
|
-
|
|
92
|
-
## Contributing
|
|
93
|
-
|
|
94
|
-
Contributions are welcome! Please see the [contributing guidelines](./CONTRIBUTING.md) and check out the [good first issues](https://github.com/eugeniodepalo/gtkx/labels/good%20first%20issue).
|
|
95
|
-
|
|
96
|
-
## Community
|
|
97
|
-
|
|
98
|
-
- [GitHub Discussions](https://github.com/eugeniodepalo/gtkx/discussions) — Questions, ideas, and general discussion
|
|
99
|
-
- [Issue Tracker](https://github.com/eugeniodepalo/gtkx/issues) — Bug reports and feature requests
|
|
100
|
-
|
|
101
|
-
## License
|
|
102
|
-
|
|
103
|
-
[MPL-2.0](./LICENSE)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import * as Adw from "@gtkx/ffi/adw";
|
|
2
|
-
import { registerNodeClass } from "../registry.js";
|
|
3
|
-
import { isContainerType } from "./internal/utils.js";
|
|
4
|
-
import { ToastNode } from "./toast.js";
|
|
5
|
-
import { WidgetNode } from "./widget.js";
|
|
6
|
-
class ToastOverlayNode extends WidgetNode {
|
|
7
|
-
static priority = 1;
|
|
8
|
-
static matches(_type, containerOrClass) {
|
|
9
|
-
return isContainerType(Adw.ToastOverlay, containerOrClass);
|
|
10
|
-
}
|
|
11
|
-
appendChild(child) {
|
|
12
|
-
if (child instanceof ToastNode) {
|
|
13
|
-
child.setParent(this.container);
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
if (child instanceof WidgetNode) {
|
|
17
|
-
this.container.setChild(child.container);
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
throw new Error(`Cannot append '${child.typeName}' to 'ToastOverlay': expected Widget or Toast`);
|
|
21
|
-
}
|
|
22
|
-
insertBefore(child, _before) {
|
|
23
|
-
this.appendChild(child);
|
|
24
|
-
}
|
|
25
|
-
removeChild(child) {
|
|
26
|
-
if (child instanceof ToastNode) {
|
|
27
|
-
child.unmount();
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
if (child instanceof WidgetNode) {
|
|
31
|
-
this.container.setChild(undefined);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
registerNodeClass(ToastOverlayNode);
|
package/dist/nodes/toast.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import * as Adw from "@gtkx/ffi/adw";
|
|
2
|
-
import type { ToastProps } from "../jsx.js";
|
|
3
|
-
import { VirtualNode } from "./virtual.js";
|
|
4
|
-
type Props = ToastProps;
|
|
5
|
-
export declare class ToastNode extends VirtualNode<Props> {
|
|
6
|
-
static priority: number;
|
|
7
|
-
static matches(type: string): boolean;
|
|
8
|
-
private toast?;
|
|
9
|
-
private parent?;
|
|
10
|
-
setParent(parent?: Adw.ToastOverlay): void;
|
|
11
|
-
private createToast;
|
|
12
|
-
private showToast;
|
|
13
|
-
updateProps(oldProps: Props | null, newProps: Props): void;
|
|
14
|
-
mount(): void;
|
|
15
|
-
unmount(): void;
|
|
16
|
-
}
|
|
17
|
-
export {};
|
package/dist/nodes/toast.js
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import * as Adw from "@gtkx/ffi/adw";
|
|
2
|
-
import { registerNodeClass } from "../registry.js";
|
|
3
|
-
import { VirtualNode } from "./virtual.js";
|
|
4
|
-
export class ToastNode extends VirtualNode {
|
|
5
|
-
static priority = 1;
|
|
6
|
-
static matches(type) {
|
|
7
|
-
return type === "Toast";
|
|
8
|
-
}
|
|
9
|
-
toast;
|
|
10
|
-
parent;
|
|
11
|
-
setParent(parent) {
|
|
12
|
-
this.parent = parent;
|
|
13
|
-
}
|
|
14
|
-
createToast() {
|
|
15
|
-
const toast = new Adw.Toast(this.props.title);
|
|
16
|
-
if (this.props.timeout !== undefined) {
|
|
17
|
-
toast.setTimeout(this.props.timeout);
|
|
18
|
-
}
|
|
19
|
-
if (this.props.priority !== undefined) {
|
|
20
|
-
toast.setPriority(this.props.priority);
|
|
21
|
-
}
|
|
22
|
-
if (this.props.buttonLabel) {
|
|
23
|
-
toast.setButtonLabel(this.props.buttonLabel);
|
|
24
|
-
}
|
|
25
|
-
if (this.props.actionName) {
|
|
26
|
-
toast.setActionName(this.props.actionName);
|
|
27
|
-
}
|
|
28
|
-
if (this.props.useMarkup !== undefined) {
|
|
29
|
-
toast.setUseMarkup(this.props.useMarkup);
|
|
30
|
-
}
|
|
31
|
-
return toast;
|
|
32
|
-
}
|
|
33
|
-
showToast() {
|
|
34
|
-
if (!this.parent || this.toast)
|
|
35
|
-
return;
|
|
36
|
-
this.toast = this.createToast();
|
|
37
|
-
if (this.props.onButtonClicked) {
|
|
38
|
-
this.signalStore.set(this.toast, "button-clicked", () => {
|
|
39
|
-
this.props.onButtonClicked?.();
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
this.signalStore.set(this.toast, "dismissed", () => {
|
|
43
|
-
this.props.onDismissed?.();
|
|
44
|
-
});
|
|
45
|
-
this.parent.addToast(this.toast);
|
|
46
|
-
}
|
|
47
|
-
updateProps(oldProps, newProps) {
|
|
48
|
-
super.updateProps(oldProps, newProps);
|
|
49
|
-
if (!this.toast)
|
|
50
|
-
return;
|
|
51
|
-
if (!oldProps || oldProps.title !== newProps.title) {
|
|
52
|
-
this.toast.setTitle(newProps.title);
|
|
53
|
-
}
|
|
54
|
-
if (!oldProps || oldProps.buttonLabel !== newProps.buttonLabel) {
|
|
55
|
-
this.toast.setButtonLabel(newProps.buttonLabel);
|
|
56
|
-
}
|
|
57
|
-
if (!oldProps || oldProps.actionName !== newProps.actionName) {
|
|
58
|
-
this.toast.setActionName(newProps.actionName);
|
|
59
|
-
}
|
|
60
|
-
if (!oldProps || oldProps.useMarkup !== newProps.useMarkup) {
|
|
61
|
-
this.toast.setUseMarkup(newProps.useMarkup ?? false);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
mount() {
|
|
65
|
-
super.mount();
|
|
66
|
-
this.showToast();
|
|
67
|
-
}
|
|
68
|
-
unmount() {
|
|
69
|
-
if (this.toast) {
|
|
70
|
-
this.toast.dismiss();
|
|
71
|
-
this.props.onDismissed?.();
|
|
72
|
-
}
|
|
73
|
-
this.parent = undefined;
|
|
74
|
-
super.unmount();
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
registerNodeClass(ToastNode);
|