@gtkx/react 0.12.0 → 0.13.0

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.
Files changed (81) hide show
  1. package/README.md +1 -1
  2. package/dist/generated/jsx.d.ts +32 -18
  3. package/dist/host-config.js +3 -3
  4. package/dist/jsx.d.ts +530 -372
  5. package/dist/jsx.js +401 -353
  6. package/dist/nodes/action-row-child.d.ts +4 -11
  7. package/dist/nodes/action-row-child.js +10 -66
  8. package/dist/nodes/action-row.js +21 -4
  9. package/dist/nodes/application.js +22 -3
  10. package/dist/nodes/autowrapped.js +13 -3
  11. package/dist/nodes/calendar-mark.d.ts +15 -0
  12. package/dist/nodes/calendar-mark.js +29 -0
  13. package/dist/nodes/calendar.d.ts +1 -0
  14. package/dist/nodes/calendar.js +70 -0
  15. package/dist/nodes/column-view-column.d.ts +1 -0
  16. package/dist/nodes/column-view-column.js +4 -0
  17. package/dist/nodes/column-view.js +24 -7
  18. package/dist/nodes/expander-row-child.d.ts +15 -0
  19. package/dist/nodes/expander-row-child.js +20 -0
  20. package/dist/nodes/expander-row.d.ts +1 -0
  21. package/dist/nodes/expander-row.js +54 -0
  22. package/dist/nodes/fixed-child.js +10 -8
  23. package/dist/nodes/grid-child.js +10 -8
  24. package/dist/nodes/index.d.ts +12 -0
  25. package/dist/nodes/index.js +12 -0
  26. package/dist/nodes/internal/list-item-renderer.d.ts +3 -0
  27. package/dist/nodes/internal/list-item-renderer.js +25 -9
  28. package/dist/nodes/internal/list-store.js +2 -2
  29. package/dist/nodes/internal/tree-list-item-renderer.d.ts +8 -0
  30. package/dist/nodes/internal/tree-list-item-renderer.js +68 -24
  31. package/dist/nodes/internal/tree-store.js +3 -4
  32. package/dist/nodes/level-bar-offset.d.ts +13 -0
  33. package/dist/nodes/level-bar-offset.js +35 -0
  34. package/dist/nodes/level-bar.d.ts +1 -0
  35. package/dist/nodes/level-bar.js +82 -0
  36. package/dist/nodes/list-view.js +14 -7
  37. package/dist/nodes/menu.js +4 -4
  38. package/dist/nodes/models/list.d.ts +2 -1
  39. package/dist/nodes/models/list.js +21 -12
  40. package/dist/nodes/models/menu.d.ts +1 -0
  41. package/dist/nodes/models/menu.js +24 -17
  42. package/dist/nodes/models/tree-list.d.ts +2 -1
  43. package/dist/nodes/models/tree-list.js +43 -24
  44. package/dist/nodes/navigation-page.d.ts +16 -0
  45. package/dist/nodes/navigation-page.js +58 -0
  46. package/dist/nodes/navigation-view.d.ts +1 -0
  47. package/dist/nodes/navigation-view.js +105 -0
  48. package/dist/nodes/notebook-page-tab.js +1 -1
  49. package/dist/nodes/notebook-page.js +3 -2
  50. package/dist/nodes/notebook.js +3 -3
  51. package/dist/nodes/overlay-child.js +29 -14
  52. package/dist/nodes/pack-child.d.ts +4 -11
  53. package/dist/nodes/pack-child.js +10 -66
  54. package/dist/nodes/pack.js +21 -4
  55. package/dist/nodes/popover-menu.js +15 -12
  56. package/dist/nodes/scale-mark.d.ts +17 -0
  57. package/dist/nodes/scale-mark.js +38 -0
  58. package/dist/nodes/scale.d.ts +1 -0
  59. package/dist/nodes/scale.js +70 -0
  60. package/dist/nodes/simple-list-view.js +3 -3
  61. package/dist/nodes/slot.d.ts +2 -1
  62. package/dist/nodes/slot.js +2 -2
  63. package/dist/nodes/stack-page.js +7 -7
  64. package/dist/nodes/stack.js +5 -5
  65. package/dist/nodes/toggle-group.d.ts +1 -0
  66. package/dist/nodes/toggle-group.js +48 -0
  67. package/dist/nodes/toggle.d.ts +15 -0
  68. package/dist/nodes/toggle.js +70 -0
  69. package/dist/nodes/toolbar-child.js +18 -16
  70. package/dist/nodes/tree-list-view.js +16 -7
  71. package/dist/nodes/virtual-child.d.ts +18 -0
  72. package/dist/nodes/virtual-child.js +62 -0
  73. package/dist/nodes/widget.js +22 -8
  74. package/dist/nodes/window.d.ts +22 -0
  75. package/dist/nodes/window.js +11 -2
  76. package/dist/render.d.ts +3 -5
  77. package/dist/render.js +3 -5
  78. package/dist/scheduler.d.ts +13 -1
  79. package/dist/scheduler.js +26 -6
  80. package/dist/types.d.ts +25 -0
  81. package/package.json +3 -3
@@ -0,0 +1,62 @@
1
+ import { isObjectEqual } from "@gtkx/ffi";
2
+ import { CommitPriority, scheduleAfterCommit } from "../scheduler.js";
3
+ import { VirtualNode } from "./virtual.js";
4
+ import { WidgetNode } from "./widget.js";
5
+ export class VirtualChildNode extends VirtualNode {
6
+ parent;
7
+ children = [];
8
+ setParent(newParent) {
9
+ this.parent = newParent;
10
+ }
11
+ unmount() {
12
+ const parent = this.parent;
13
+ const childrenToRemove = [...this.children];
14
+ if (parent && childrenToRemove.length > 0) {
15
+ scheduleAfterCommit(() => {
16
+ for (const widget of childrenToRemove) {
17
+ const currentParent = widget.getParent();
18
+ if (currentParent && isObjectEqual(currentParent, parent)) {
19
+ parent.remove(widget);
20
+ }
21
+ }
22
+ }, CommitPriority.HIGH);
23
+ }
24
+ this.children = [];
25
+ this.parent = undefined;
26
+ super.unmount();
27
+ }
28
+ appendChild(child) {
29
+ if (!(child instanceof WidgetNode)) {
30
+ throw new Error(`Cannot append '${child.typeName}' to '${this.typeName}': expected Widget`);
31
+ }
32
+ const widget = child.container;
33
+ this.children.push(widget);
34
+ scheduleAfterCommit(() => {
35
+ if (this.parent) {
36
+ this.attachChild(this.parent, widget);
37
+ }
38
+ });
39
+ }
40
+ insertBefore(child) {
41
+ this.appendChild(child);
42
+ }
43
+ removeChild(child) {
44
+ if (!(child instanceof WidgetNode)) {
45
+ throw new Error(`Cannot remove '${child.typeName}' from '${this.typeName}': expected Widget`);
46
+ }
47
+ const widget = child.container;
48
+ const parent = this.parent;
49
+ const index = this.children.indexOf(widget);
50
+ if (index !== -1) {
51
+ this.children.splice(index, 1);
52
+ }
53
+ scheduleAfterCommit(() => {
54
+ if (parent) {
55
+ const currentParent = widget.getParent();
56
+ if (currentParent && isObjectEqual(currentParent, parent)) {
57
+ parent.remove(widget);
58
+ }
59
+ }
60
+ }, CommitPriority.HIGH);
61
+ }
62
+ }
@@ -93,23 +93,23 @@ export class WidgetNode extends Node {
93
93
  ...Object.keys(filterProps(oldProps ?? {}, ["children"])),
94
94
  ...Object.keys(filterProps(newProps ?? {}, ["children"])),
95
95
  ]);
96
+ const pendingSignals = [];
96
97
  for (const name of propNames) {
97
98
  const oldValue = oldProps?.[name];
98
99
  const newValue = newProps[name];
99
100
  if (oldValue === newValue)
100
101
  continue;
101
102
  if (EVENT_CONTROLLER_PROPS.has(name)) {
102
- this.updateEventControllerProp(name, newValue ?? null);
103
+ pendingSignals.push({ name, newValue });
103
104
  continue;
104
105
  }
105
106
  if (name === "onNotify") {
106
- this.updateNotifyHandler(newValue ?? null);
107
+ pendingSignals.push({ name, newValue });
107
108
  continue;
108
109
  }
109
110
  const signalName = this.propNameToSignalName(name);
110
111
  if (resolveSignal(this.container, signalName)) {
111
- const handler = typeof newValue === "function" ? newValue : undefined;
112
- signalStore.set(this, this.container, signalName, handler);
112
+ pendingSignals.push({ name, newValue });
113
113
  }
114
114
  else if (newValue !== undefined) {
115
115
  const isEditableText = name === "text" && isEditable(this.container);
@@ -122,8 +122,22 @@ export class WidgetNode extends Node {
122
122
  this.setProperty(name, newValue);
123
123
  }
124
124
  }
125
+ for (const { name, newValue } of pendingSignals) {
126
+ if (EVENT_CONTROLLER_PROPS.has(name)) {
127
+ this.updateEventControllerProp(name, newValue ?? null);
128
+ }
129
+ else if (name === "onNotify") {
130
+ this.updateNotifyHandler(newValue ?? null);
131
+ }
132
+ else {
133
+ const signalName = this.propNameToSignalName(name);
134
+ const handler = typeof newValue === "function" ? newValue : undefined;
135
+ signalStore.set(this, this.container, signalName, handler);
136
+ }
137
+ }
125
138
  }
126
139
  updateEventControllerProp(propName, handler) {
140
+ const wrappedHandler = handler ? (_self, ...args) => handler(...args) : undefined;
127
141
  switch (propName) {
128
142
  case "onEnter":
129
143
  case "onLeave":
@@ -133,7 +147,7 @@ export class WidgetNode extends Node {
133
147
  this.container.addController(this.motionController);
134
148
  }
135
149
  const signalName = propName === "onEnter" ? "enter" : propName === "onLeave" ? "leave" : "motion";
136
- signalStore.set(this, this.motionController, signalName, handler);
150
+ signalStore.set(this, this.motionController, signalName, wrappedHandler);
137
151
  break;
138
152
  }
139
153
  case "onPressed":
@@ -143,7 +157,7 @@ export class WidgetNode extends Node {
143
157
  this.container.addController(this.clickController);
144
158
  }
145
159
  const signalName = propName === "onPressed" ? "pressed" : "released";
146
- signalStore.set(this, this.clickController, signalName, handler);
160
+ signalStore.set(this, this.clickController, signalName, wrappedHandler);
147
161
  break;
148
162
  }
149
163
  case "onKeyPressed":
@@ -153,7 +167,7 @@ export class WidgetNode extends Node {
153
167
  this.container.addController(this.keyController);
154
168
  }
155
169
  const signalName = propName === "onKeyPressed" ? "key-pressed" : "key-released";
156
- signalStore.set(this, this.keyController, signalName, handler);
170
+ signalStore.set(this, this.keyController, signalName, wrappedHandler);
157
171
  break;
158
172
  }
159
173
  case "onScroll": {
@@ -161,7 +175,7 @@ export class WidgetNode extends Node {
161
175
  this.scrollController = new Gtk.EventControllerScroll(Gtk.EventControllerScrollFlags.BOTH_AXES);
162
176
  this.container.addController(this.scrollController);
163
177
  }
164
- signalStore.set(this, this.scrollController, "scroll", handler);
178
+ signalStore.set(this, this.scrollController, "scroll", wrappedHandler);
165
179
  break;
166
180
  }
167
181
  }
@@ -1 +1,23 @@
1
+ import * as Gtk from "@gtkx/ffi/gtk";
2
+ import type { Node } from "../node.js";
3
+ import type { Container, ContainerClass, Props } from "../types.js";
4
+ import { WidgetNode } from "./widget.js";
5
+ type WindowProps = Props & {
6
+ defaultWidth?: number;
7
+ defaultHeight?: number;
8
+ onClose?: () => void;
9
+ };
10
+ export declare class WindowNode extends WidgetNode<Gtk.Window, WindowProps> {
11
+ static priority: number;
12
+ private menu;
13
+ static matches(_type: string, containerOrClass?: Container | ContainerClass | null): boolean;
14
+ static createContainer(props: Props, containerClass: typeof Gtk.Window, rootContainer?: Container): Gtk.Window;
15
+ constructor(typeName: string, props: WindowProps, container: Gtk.Window, rootContainer?: Container);
16
+ appendChild(child: Node): void;
17
+ removeChild(child: Node): void;
18
+ insertBefore(child: Node, before: Node): void;
19
+ mount(): void;
20
+ unmount(): void;
21
+ updateProps(oldProps: WindowProps | null, newProps: WindowProps): void;
22
+ }
1
23
  export {};
@@ -1,11 +1,12 @@
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 { filterProps, isContainerType } from "./internal/utils.js";
5
6
  import { Menu } from "./models/menu.js";
6
7
  import { WidgetNode } from "./widget.js";
7
- const PROPS = ["defaultWidth", "defaultHeight"];
8
- class WindowNode extends WidgetNode {
8
+ const PROPS = ["defaultWidth", "defaultHeight", "onClose"];
9
+ export class WindowNode extends WidgetNode {
9
10
  static priority = 1;
10
11
  menu;
11
12
  static matches(_type, containerOrClass) {
@@ -67,6 +68,14 @@ class WindowNode extends WidgetNode {
67
68
  const height = newProps.defaultHeight ?? -1;
68
69
  this.container.setDefaultSize(width, height);
69
70
  }
71
+ if (oldProps?.onClose !== newProps.onClose) {
72
+ const userHandler = newProps.onClose;
73
+ const wrappedHandler = () => {
74
+ userHandler?.();
75
+ return true;
76
+ };
77
+ signalStore.set(this, this.container, "close-request", wrappedHandler);
78
+ }
70
79
  super.updateProps(filterProps(oldProps ?? {}, PROPS), filterProps(newProps, PROPS));
71
80
  }
72
81
  }
package/dist/render.d.ts CHANGED
@@ -69,7 +69,7 @@ export declare const setHotReloading: (value: boolean) => void;
69
69
  * import { render, quit } from "@gtkx/react";
70
70
  *
71
71
  * const App = () => (
72
- * <GtkApplicationWindow title="My App" onCloseRequest={quit}>
72
+ * <GtkApplicationWindow title="My App" onClose={quit}>
73
73
  * <GtkLabel label="Hello, GTKX!" />
74
74
  * </GtkApplicationWindow>
75
75
  * );
@@ -107,16 +107,14 @@ export declare const update: (element: ReactNode) => Promise<void>;
107
107
  * Gracefully shuts down the GTK application.
108
108
  *
109
109
  * Unmounts the React component tree and stops the GTK main loop.
110
- * Typically used as the `onCloseRequest` handler for the application window.
111
- *
112
- * @returns `false` to allow GTK to close the window
110
+ * Typically used as the `onClose` handler for the application window.
113
111
  *
114
112
  * @example
115
113
  * ```tsx
116
114
  * import { quit } from "@gtkx/react";
117
115
  *
118
116
  * const App = () => (
119
- * <GtkApplicationWindow title="My App" onCloseRequest={quit}>
117
+ * <GtkApplicationWindow title="My App" onClose={quit}>
120
118
  * <GtkButton label="Quit" onClicked={quit} />
121
119
  * </GtkApplicationWindow>
122
120
  * );
package/dist/render.js CHANGED
@@ -84,7 +84,7 @@ export const setHotReloading = (value) => {
84
84
  * import { render, quit } from "@gtkx/react";
85
85
  *
86
86
  * const App = () => (
87
- * <GtkApplicationWindow title="My App" onCloseRequest={quit}>
87
+ * <GtkApplicationWindow title="My App" onClose={quit}>
88
88
  * <GtkLabel label="Hello, GTKX!" />
89
89
  * </GtkApplicationWindow>
90
90
  * );
@@ -140,16 +140,14 @@ export const update = (element) => {
140
140
  * Gracefully shuts down the GTK application.
141
141
  *
142
142
  * Unmounts the React component tree and stops the GTK main loop.
143
- * Typically used as the `onCloseRequest` handler for the application window.
144
- *
145
- * @returns `false` to allow GTK to close the window
143
+ * Typically used as the `onClose` handler for the application window.
146
144
  *
147
145
  * @example
148
146
  * ```tsx
149
147
  * import { quit } from "@gtkx/react";
150
148
  *
151
149
  * const App = () => (
152
- * <GtkApplicationWindow title="My App" onCloseRequest={quit}>
150
+ * <GtkApplicationWindow title="My App" onClose={quit}>
153
151
  * <GtkButton label="Quit" onClicked={quit} />
154
152
  * </GtkApplicationWindow>
155
153
  * );
@@ -1,4 +1,16 @@
1
1
  type Callback = () => void;
2
- export declare const scheduleAfterCommit: (callback: Callback) => void;
2
+ export declare enum CommitPriority {
3
+ /** Runs first. Used for widget removals to unparent before reparenting. */
4
+ HIGH = 0,
5
+ /** Runs after HIGH priority. Used for widget additions. */
6
+ NORMAL = 1,
7
+ /** Runs last. Used for model sync operations that need all data to be added first. */
8
+ LOW = 2
9
+ }
10
+ /**
11
+ * Schedule a callback to run after commit with the specified priority.
12
+ * HIGH priority callbacks run before NORMAL priority callbacks.
13
+ */
14
+ export declare const scheduleAfterCommit: (callback: Callback, priority?: CommitPriority) => void;
3
15
  export declare const flushAfterCommit: () => void;
4
16
  export {};
package/dist/scheduler.js CHANGED
@@ -1,10 +1,30 @@
1
- const pendingCallbacks = [];
2
- export const scheduleAfterCommit = (callback) => {
3
- pendingCallbacks.push(callback);
1
+ export var CommitPriority;
2
+ (function (CommitPriority) {
3
+ /** Runs first. Used for widget removals to unparent before reparenting. */
4
+ CommitPriority[CommitPriority["HIGH"] = 0] = "HIGH";
5
+ /** Runs after HIGH priority. Used for widget additions. */
6
+ CommitPriority[CommitPriority["NORMAL"] = 1] = "NORMAL";
7
+ /** Runs last. Used for model sync operations that need all data to be added first. */
8
+ CommitPriority[CommitPriority["LOW"] = 2] = "LOW";
9
+ })(CommitPriority || (CommitPriority = {}));
10
+ const queues = {
11
+ [CommitPriority.HIGH]: [],
12
+ [CommitPriority.NORMAL]: [],
13
+ [CommitPriority.LOW]: [],
14
+ };
15
+ const priorities = [CommitPriority.HIGH, CommitPriority.NORMAL, CommitPriority.LOW];
16
+ /**
17
+ * Schedule a callback to run after commit with the specified priority.
18
+ * HIGH priority callbacks run before NORMAL priority callbacks.
19
+ */
20
+ export const scheduleAfterCommit = (callback, priority = CommitPriority.NORMAL) => {
21
+ queues[priority].push(callback);
4
22
  };
5
23
  export const flushAfterCommit = () => {
6
- const callbacks = pendingCallbacks.splice(0);
7
- for (const callback of callbacks) {
8
- callback();
24
+ for (const priority of priorities) {
25
+ const callbacks = queues[priority].splice(0);
26
+ for (const callback of callbacks) {
27
+ callback();
28
+ }
9
29
  }
10
30
  };
package/dist/types.d.ts CHANGED
@@ -1,4 +1,29 @@
1
+ import type * as Gdk from "@gtkx/ffi/gdk";
1
2
  import type * as Gtk from "@gtkx/ffi/gtk";
2
3
  export type Container = Gtk.Widget | Gtk.Application;
3
4
  export type Props = Record<string, unknown>;
4
5
  export type ContainerClass = typeof Gtk.Widget | typeof Gtk.Application;
6
+ /**
7
+ * Props for EventController-based event handlers.
8
+ *
9
+ * These props attach EventControllers to widgets for handling
10
+ * pointer motion, clicks, and keyboard events.
11
+ */
12
+ export interface EventControllerProps {
13
+ /** Called when the pointer enters the widget */
14
+ onEnter?: (x: number, y: number) => void;
15
+ /** Called when the pointer leaves the widget */
16
+ onLeave?: () => void;
17
+ /** Called when the pointer moves over the widget */
18
+ onMotion?: (x: number, y: number) => void;
19
+ /** Called when a mouse button is pressed */
20
+ onPressed?: (nPress: number, x: number, y: number) => void;
21
+ /** Called when a mouse button is released */
22
+ onReleased?: (nPress: number, x: number, y: number) => void;
23
+ /** Called when a key is pressed (for focusable widgets) */
24
+ onKeyPressed?: (keyval: number, keycode: number, state: Gdk.ModifierType) => boolean;
25
+ /** Called when a key is released */
26
+ onKeyReleased?: (keyval: number, keycode: number, state: Gdk.ModifierType) => void;
27
+ /** Called when the widget is scrolled */
28
+ onScroll?: (dx: number, dy: number) => boolean;
29
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtkx/react",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Build GTK4 desktop applications with React and TypeScript",
5
5
  "keywords": [
6
6
  "gtkx",
@@ -37,8 +37,8 @@
37
37
  ],
38
38
  "dependencies": {
39
39
  "react-reconciler": "^0.33.0",
40
- "@gtkx/ffi": "0.12.0",
41
- "@gtkx/gir": "0.12.0"
40
+ "@gtkx/ffi": "0.13.0",
41
+ "@gtkx/gir": "0.13.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/react-reconciler": "^0.32.3"