@gtkx/react 0.14.0 → 0.16.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 (182) hide show
  1. package/README.md +28 -27
  2. package/dist/errors.js +3 -0
  3. package/dist/factory.d.ts +3 -2
  4. package/dist/factory.js +1 -1
  5. package/dist/generated/internal.d.ts +28 -1
  6. package/dist/generated/internal.js +93 -18
  7. package/dist/generated/jsx.d.ts +1712 -1516
  8. package/dist/generated/jsx.js +475 -0
  9. package/dist/host-config.d.ts +3 -1
  10. package/dist/host-config.js +31 -11
  11. package/dist/jsx.d.ts +147 -97
  12. package/dist/jsx.js +89 -21
  13. package/dist/node.d.ts +3 -1
  14. package/dist/node.js +5 -3
  15. package/dist/nodes/abstract/positional-child.d.ts +9 -0
  16. package/dist/nodes/abstract/positional-child.js +29 -0
  17. package/dist/nodes/abstract/positional-parent.d.ts +18 -0
  18. package/dist/nodes/abstract/positional-parent.js +48 -0
  19. package/dist/nodes/abstract/virtual-container.d.ts +17 -0
  20. package/dist/nodes/abstract/virtual-container.js +59 -0
  21. package/dist/nodes/abstract/virtual-single-child.d.ts +18 -0
  22. package/dist/nodes/abstract/virtual-single-child.js +54 -0
  23. package/dist/nodes/action-row-child.d.ts +0 -13
  24. package/dist/nodes/action-row-child.js +14 -12
  25. package/dist/nodes/action-row.d.ts +6 -1
  26. package/dist/nodes/action-row.js +4 -37
  27. package/dist/nodes/adjustable.d.ts +23 -0
  28. package/dist/nodes/adjustable.js +62 -0
  29. package/dist/nodes/alert-dialog-response.d.ts +1 -0
  30. package/dist/nodes/alert-dialog-response.js +86 -0
  31. package/dist/nodes/animation/animation-controller.d.ts +17 -0
  32. package/dist/nodes/animation/animation-controller.js +107 -0
  33. package/dist/nodes/animation/animation-factory.d.ts +15 -0
  34. package/dist/nodes/animation/animation-factory.js +25 -0
  35. package/dist/nodes/animation/animation-node.d.ts +9 -0
  36. package/dist/nodes/animation/animation-node.js +126 -0
  37. package/dist/nodes/animation/animation-style-sheet.d.ts +16 -0
  38. package/dist/nodes/animation/animation-style-sheet.js +74 -0
  39. package/dist/nodes/animation/index.d.ts +4 -0
  40. package/dist/nodes/animation/index.js +1 -0
  41. package/dist/nodes/animation/property-mapper.d.ts +11 -0
  42. package/dist/nodes/animation/property-mapper.js +36 -0
  43. package/dist/nodes/animation/transform-state.d.ts +11 -0
  44. package/dist/nodes/animation/transform-state.js +57 -0
  45. package/dist/nodes/animation/widget-registry.d.ts +5 -0
  46. package/dist/nodes/animation/widget-registry.js +42 -0
  47. package/dist/nodes/application.js +17 -7
  48. package/dist/nodes/autowrapped.js +37 -43
  49. package/dist/nodes/calendar.js +16 -55
  50. package/dist/nodes/color-dialog-button.d.ts +1 -0
  51. package/dist/nodes/color-dialog-button.js +70 -0
  52. package/dist/nodes/column-view-column.d.ts +4 -3
  53. package/dist/nodes/column-view-column.js +5 -1
  54. package/dist/nodes/column-view.js +40 -43
  55. package/dist/nodes/dialog.d.ts +11 -0
  56. package/dist/nodes/dialog.js +20 -0
  57. package/dist/nodes/drawing-area.d.ts +1 -0
  58. package/dist/nodes/drawing-area.js +36 -0
  59. package/dist/nodes/event-controller.d.ts +1 -0
  60. package/dist/nodes/event-controller.js +96 -0
  61. package/dist/nodes/expander-row-child.d.ts +0 -14
  62. package/dist/nodes/expander-row-child.js +14 -12
  63. package/dist/nodes/expander-row.d.ts +6 -1
  64. package/dist/nodes/expander-row.js +11 -47
  65. package/dist/nodes/fixed-child.js +48 -36
  66. package/dist/nodes/font-dialog-button.d.ts +1 -0
  67. package/dist/nodes/font-dialog-button.js +90 -0
  68. package/dist/nodes/grid-child.js +39 -45
  69. package/dist/nodes/grid.d.ts +1 -0
  70. package/dist/nodes/grid.js +41 -0
  71. package/dist/nodes/index.d.ts +22 -6
  72. package/dist/nodes/index.js +22 -6
  73. package/dist/nodes/internal/base-item-renderer.d.ts +29 -0
  74. package/dist/nodes/internal/base-item-renderer.js +88 -0
  75. package/dist/nodes/internal/base-store.d.ts +9 -0
  76. package/dist/nodes/internal/base-store.js +20 -0
  77. package/dist/nodes/internal/child-attachment.d.ts +26 -0
  78. package/dist/nodes/internal/child-attachment.js +48 -0
  79. package/dist/nodes/internal/deferred-action.d.ts +8 -0
  80. package/dist/nodes/internal/deferred-action.js +19 -0
  81. package/dist/nodes/internal/list-item-renderer.d.ts +14 -14
  82. package/dist/nodes/internal/list-item-renderer.js +49 -70
  83. package/dist/nodes/internal/list-store.d.ts +7 -6
  84. package/dist/nodes/internal/list-store.js +20 -24
  85. package/dist/nodes/internal/predicates.d.ts +28 -1
  86. package/dist/nodes/internal/predicates.js +53 -38
  87. package/dist/nodes/internal/selection-model.d.ts +30 -0
  88. package/dist/nodes/internal/selection-model.js +91 -0
  89. package/dist/nodes/internal/signal-store.d.ts +5 -3
  90. package/dist/nodes/internal/signal-store.js +30 -21
  91. package/dist/nodes/internal/simple-list-store.js +6 -9
  92. package/dist/nodes/internal/text-buffer-controller.d.ts +43 -0
  93. package/dist/nodes/internal/text-buffer-controller.js +287 -0
  94. package/dist/nodes/internal/text-tag-styles.d.ts +43 -0
  95. package/dist/nodes/internal/text-tag-styles.js +52 -0
  96. package/dist/nodes/internal/tree-list-item-renderer.d.ts +16 -14
  97. package/dist/nodes/internal/tree-list-item-renderer.js +88 -91
  98. package/dist/nodes/internal/tree-store.d.ts +10 -9
  99. package/dist/nodes/internal/tree-store.js +31 -35
  100. package/dist/nodes/internal/utils.d.ts +7 -4
  101. package/dist/nodes/internal/utils.js +50 -5
  102. package/dist/nodes/level-bar.js +18 -66
  103. package/dist/nodes/list-item.d.ts +6 -3
  104. package/dist/nodes/list-item.js +7 -4
  105. package/dist/nodes/list-view.js +19 -11
  106. package/dist/nodes/menu.d.ts +3 -3
  107. package/dist/nodes/menu.js +3 -3
  108. package/dist/nodes/models/list.d.ts +11 -13
  109. package/dist/nodes/models/list.js +16 -73
  110. package/dist/nodes/models/menu.d.ts +8 -7
  111. package/dist/nodes/models/menu.js +43 -50
  112. package/dist/nodes/models/tree-list.d.ts +6 -12
  113. package/dist/nodes/models/tree-list.js +30 -93
  114. package/dist/nodes/navigation-page.d.ts +1 -0
  115. package/dist/nodes/navigation-page.js +7 -3
  116. package/dist/nodes/navigation-view.js +17 -28
  117. package/dist/nodes/notebook-page-tab.d.ts +4 -3
  118. package/dist/nodes/notebook-page-tab.js +5 -2
  119. package/dist/nodes/notebook-page.d.ts +7 -5
  120. package/dist/nodes/notebook-page.js +39 -16
  121. package/dist/nodes/notebook.js +2 -2
  122. package/dist/nodes/overlay-child.js +90 -30
  123. package/dist/nodes/pack-child.d.ts +0 -13
  124. package/dist/nodes/pack-child.js +14 -12
  125. package/dist/nodes/pack.d.ts +6 -1
  126. package/dist/nodes/pack.js +4 -37
  127. package/dist/nodes/popover-menu.js +2 -2
  128. package/dist/nodes/scale.js +15 -58
  129. package/dist/nodes/scrolled-window.js +7 -5
  130. package/dist/nodes/search-bar.d.ts +1 -0
  131. package/dist/nodes/search-bar.js +40 -0
  132. package/dist/nodes/shortcut-controller.d.ts +1 -0
  133. package/dist/nodes/shortcut-controller.js +90 -0
  134. package/dist/nodes/shortcut.d.ts +39 -0
  135. package/dist/nodes/shortcut.js +52 -0
  136. package/dist/nodes/simple-list-view.js +2 -3
  137. package/dist/nodes/slot.d.ts +6 -9
  138. package/dist/nodes/slot.js +27 -42
  139. package/dist/nodes/source-view.d.ts +1 -0
  140. package/dist/nodes/source-view.js +93 -0
  141. package/dist/nodes/stack-page.js +17 -13
  142. package/dist/nodes/stack.js +19 -5
  143. package/dist/nodes/text-anchor.d.ts +41 -0
  144. package/dist/nodes/text-anchor.js +59 -0
  145. package/dist/nodes/text-content.d.ts +10 -0
  146. package/dist/nodes/text-content.js +1 -0
  147. package/dist/nodes/text-paintable.d.ts +17 -0
  148. package/dist/nodes/text-paintable.js +34 -0
  149. package/dist/nodes/text-segment.d.ts +15 -0
  150. package/dist/nodes/text-segment.js +29 -0
  151. package/dist/nodes/text-tag.d.ts +136 -0
  152. package/dist/nodes/text-tag.js +202 -0
  153. package/dist/nodes/text-view.d.ts +31 -0
  154. package/dist/nodes/text-view.js +73 -0
  155. package/dist/nodes/toggle-group.js +24 -32
  156. package/dist/nodes/toggle.d.ts +1 -15
  157. package/dist/nodes/toggle.js +40 -32
  158. package/dist/nodes/toolbar-child.js +14 -16
  159. package/dist/nodes/tree-list-item.d.ts +7 -5
  160. package/dist/nodes/tree-list-item.js +24 -36
  161. package/dist/nodes/tree-list-view.js +9 -4
  162. package/dist/nodes/virtual.d.ts +1 -1
  163. package/dist/nodes/widget.d.ts +3 -13
  164. package/dist/nodes/widget.js +117 -231
  165. package/dist/nodes/window.d.ts +9 -3
  166. package/dist/nodes/window.js +35 -19
  167. package/dist/registry.d.ts +1 -1
  168. package/dist/render.js +8 -6
  169. package/dist/scheduler.d.ts +11 -1
  170. package/dist/scheduler.js +16 -4
  171. package/dist/types.d.ts +2 -110
  172. package/package.json +3 -3
  173. package/dist/nodes/calendar-mark.d.ts +0 -15
  174. package/dist/nodes/calendar-mark.js +0 -29
  175. package/dist/nodes/internal/constants.d.ts +0 -1
  176. package/dist/nodes/internal/constants.js +0 -21
  177. package/dist/nodes/level-bar-offset.d.ts +0 -13
  178. package/dist/nodes/level-bar-offset.js +0 -35
  179. package/dist/nodes/scale-mark.d.ts +0 -17
  180. package/dist/nodes/scale-mark.js +0 -38
  181. package/dist/nodes/virtual-child.d.ts +0 -18
  182. package/dist/nodes/virtual-child.js +0 -62
@@ -1,79 +1,81 @@
1
- import { batch, isObjectEqual, NativeObject } from "@gtkx/ffi";
2
- import * as Gdk from "@gtkx/ffi/gdk";
1
+ import { getNativeObject, isObjectEqual } from "@gtkx/ffi";
2
+ import { ObjectClass, ParamSpecString, Type, TypeInstance, typeClassRef, typeFromName, typeFundamental, typeNameFromInstance, } from "@gtkx/ffi/gobject";
3
3
  import * as Gtk from "@gtkx/ffi/gtk";
4
4
  import { CONSTRUCTOR_PROPS } from "../generated/internal.js";
5
5
  import { Node } from "../node.js";
6
6
  import { registerNodeClass } from "../registry.js";
7
- import { EVENT_CONTROLLER_PROPS } from "./internal/constants.js";
8
- import { hasSingleContent, isAddable, isAppendable, isEditable, isInsertable, isRemovable, isReorderable, isSingleChild, } from "./internal/predicates.js";
9
- import { signalStore } from "./internal/signal-store.js";
10
- import { filterProps, isContainerType, resolvePropMeta, resolveSignal } from "./internal/utils.js";
11
- import { SlotNode } from "./slot.js";
12
- const PROPS = ["children", "widthRequest", "heightRequest"];
7
+ import { getAttachmentStrategy, attachChild as performAttachment, detachChild as performDetachment, } from "./internal/child-attachment.js";
8
+ import { isAttachable, isEditable, isInsertable, isRemovable, isReorderable, } from "./internal/predicates.js";
9
+ import { filterProps, matchesAnyClass, propNameToSignalName, resolvePropMeta, resolveSignal, } from "./internal/utils.js";
10
+ const EXCLUDED_PROPS = ["children", "widthRequest", "heightRequest", "grabFocus"];
11
+ function findProperty(obj, key) {
12
+ if (!obj.handle)
13
+ return null;
14
+ const propertyName = key.replace(/([A-Z])/g, "-$1").toLowerCase();
15
+ const typeInstance = getNativeObject(obj.handle, TypeInstance);
16
+ const typeName = typeNameFromInstance(typeInstance);
17
+ const gtype = typeFromName(typeName);
18
+ const typeClass = typeClassRef(gtype);
19
+ const objectClass = getNativeObject(typeClass.handle, ObjectClass);
20
+ return objectClass.findProperty(propertyName) ?? null;
21
+ }
13
22
  export class WidgetNode extends Node {
14
23
  static priority = 3;
15
- motionController;
16
- clickController;
17
- keyController;
18
- scrollController;
19
- dragSourceController;
20
- dropTargetController;
21
24
  static matches(_type, containerOrClass) {
22
- return isContainerType(Gtk.Widget, containerOrClass);
25
+ return matchesAnyClass([Gtk.Widget], containerOrClass);
23
26
  }
24
- static createContainer(props, containerClass) {
27
+ static createContainer(props, containerClass, _rootContainer) {
25
28
  const WidgetClass = containerClass;
26
29
  const typeName = WidgetClass.glibTypeName;
27
30
  const args = (CONSTRUCTOR_PROPS[typeName] ?? []).map((name) => props[name]);
28
31
  return new WidgetClass(...args);
29
32
  }
30
33
  appendChild(child) {
31
- if (child instanceof SlotNode) {
32
- child.setParent(this.container);
34
+ if (isAttachable(child) && child.canBeChildOf(this)) {
35
+ child.attachTo(this);
33
36
  return;
34
37
  }
35
38
  if (!(child instanceof WidgetNode)) {
36
- throw new Error(`Cannot append '${child.typeName}' to 'Widget': expected WidgetNode child`);
39
+ throw new Error(`Cannot append '${child.typeName}' to 'Widget': expected Widget`);
37
40
  }
38
41
  if (child.container instanceof Gtk.Window) {
39
42
  throw new Error(`Cannot append 'Window' to '${this.typeName}': windows must be top-level containers`);
40
43
  }
41
- batch(() => this.attachChild(child));
44
+ this.attachChild(child);
42
45
  }
43
46
  removeChild(child) {
44
- if (child instanceof SlotNode) {
47
+ if (isAttachable(child) && child.canBeChildOf(this)) {
48
+ child.detachFrom(this);
45
49
  return;
46
50
  }
47
51
  if (!(child instanceof WidgetNode)) {
48
- throw new Error(`Cannot remove '${child.typeName}' from 'Widget': expected WidgetNode child`);
52
+ throw new Error(`Cannot remove '${child.typeName}' from 'Widget': expected Widget`);
49
53
  }
50
54
  if (child.container instanceof Gtk.Window) {
51
55
  throw new Error(`Cannot remove 'Window' from '${this.typeName}': windows must be top-level containers`);
52
56
  }
53
- batch(() => this.detachChild(child));
57
+ this.detachChild(child);
54
58
  }
55
59
  insertBefore(child, before) {
56
- if (child instanceof SlotNode) {
57
- child.setParent(this.container);
60
+ if (isAttachable(child) && child.canBeChildOf(this)) {
61
+ child.attachTo(this);
58
62
  return;
59
63
  }
60
64
  if (!(child instanceof WidgetNode) || !(before instanceof WidgetNode)) {
61
- throw new Error(`Cannot insert '${child.typeName}' before '${before.typeName}': expected WidgetNode children`);
65
+ throw new Error(`Cannot insert '${child.typeName}' into '${this.typeName}': expected Widget`);
62
66
  }
63
67
  if (child.container instanceof Gtk.Window) {
64
68
  throw new Error(`Cannot insert 'Window' into '${this.typeName}': windows must be top-level containers`);
65
69
  }
66
- batch(() => {
67
- if (isReorderable(this.container)) {
68
- this.insertBeforeReorderable(this.container, child, before);
69
- }
70
- else if (isInsertable(this.container)) {
71
- this.insertBeforeInsertable(this.container, child, before);
72
- }
73
- else {
74
- this.appendChild(child);
75
- }
76
- });
70
+ if (isReorderable(this.container)) {
71
+ this.insertBeforeReorderable(this.container, child, before);
72
+ }
73
+ else if (isInsertable(this.container)) {
74
+ this.insertBeforeInsertable(this.container, child, before);
75
+ }
76
+ else {
77
+ this.appendChild(child);
78
+ }
77
79
  }
78
80
  insertBeforeReorderable(container, child, before) {
79
81
  const previousSibling = this.findPreviousSibling(before);
@@ -93,52 +95,48 @@ export class WidgetNode extends Node {
93
95
  container.insert(child.container, position);
94
96
  }
95
97
  updateProps(oldProps, newProps) {
98
+ if (!this.container) {
99
+ throw new Error(`WidgetNode.updateProps: container is undefined for ${this.typeName}`);
100
+ }
96
101
  this.updateSizeRequest(oldProps, newProps);
102
+ this.updateGrabFocus(oldProps, newProps);
97
103
  const propNames = new Set([
98
- ...Object.keys(filterProps(oldProps ?? {}, PROPS)),
99
- ...Object.keys(filterProps(newProps ?? {}, PROPS)),
104
+ ...Object.keys(filterProps(oldProps ?? {}, EXCLUDED_PROPS)),
105
+ ...Object.keys(filterProps(newProps ?? {}, EXCLUDED_PROPS)),
100
106
  ]);
101
107
  const pendingSignals = [];
108
+ const pendingProperties = [];
102
109
  for (const name of propNames) {
103
110
  const oldValue = oldProps?.[name];
104
111
  const newValue = newProps[name];
105
112
  if (oldValue === newValue)
106
113
  continue;
107
- if (EVENT_CONTROLLER_PROPS.has(name)) {
108
- pendingSignals.push({ name, newValue });
109
- continue;
110
- }
111
- if (name === "onNotify") {
112
- pendingSignals.push({ name, newValue });
113
- continue;
114
- }
115
- const signalName = this.propNameToSignalName(name);
114
+ const signalName = propNameToSignalName(name);
116
115
  if (resolveSignal(this.container, signalName)) {
117
116
  pendingSignals.push({ name, newValue });
118
117
  }
119
118
  else if (newValue !== undefined) {
120
- const isEditableText = name === "text" && isEditable(this.container);
121
- if (isEditableText && oldValue !== undefined) {
122
- const currentValue = this.getProperty(name);
123
- if (oldValue !== currentValue) {
124
- continue;
125
- }
119
+ pendingProperties.push({ name, oldValue, newValue });
120
+ }
121
+ else if (oldValue !== undefined) {
122
+ const defaultValue = this.getPropertyDefaultValue(name);
123
+ if (defaultValue !== undefined) {
124
+ pendingProperties.push({ name, oldValue, newValue: defaultValue });
126
125
  }
127
- this.setProperty(name, newValue);
128
126
  }
129
127
  }
130
128
  for (const { name, newValue } of pendingSignals) {
131
- if (EVENT_CONTROLLER_PROPS.has(name)) {
132
- this.updateEventControllerProp(name, newValue ?? null);
133
- }
134
- else if (name === "onNotify") {
135
- this.updateNotifyHandler(newValue ?? null);
136
- }
137
- else {
138
- const signalName = this.propNameToSignalName(name);
139
- const handler = typeof newValue === "function" ? newValue : undefined;
140
- signalStore.set(this, this.container, signalName, handler);
129
+ const signalName = propNameToSignalName(name);
130
+ const handler = typeof newValue === "function" ? newValue : undefined;
131
+ this.signalStore.set(this, this.container, signalName, handler);
132
+ }
133
+ for (const { name, oldValue, newValue } of pendingProperties) {
134
+ if (name === "text" && oldValue !== undefined && isEditable(this.container)) {
135
+ if (oldValue !== this.container.getText()) {
136
+ continue;
137
+ }
141
138
  }
139
+ this.setProperty(name, newValue);
142
140
  }
143
141
  }
144
142
  updateSizeRequest(oldProps, newProps) {
@@ -150,138 +148,46 @@ export class WidgetNode extends Node {
150
148
  this.container.setSizeRequest(newWidth ?? -1, newHeight ?? -1);
151
149
  }
152
150
  }
153
- updateEventControllerProp(propName, handlerOrValue) {
154
- const wrappedHandler = typeof handlerOrValue === "function"
155
- ? (_self, ...args) => handlerOrValue(...args)
156
- : undefined;
157
- switch (propName) {
158
- case "onEnter":
159
- case "onLeave":
160
- case "onMotion": {
161
- if (!this.motionController) {
162
- this.motionController = new Gtk.EventControllerMotion();
163
- this.container.addController(this.motionController);
164
- }
165
- const signalName = propName === "onEnter" ? "enter" : propName === "onLeave" ? "leave" : "motion";
166
- signalStore.set(this, this.motionController, signalName, wrappedHandler);
167
- break;
168
- }
169
- case "onPressed":
170
- case "onReleased": {
171
- if (!this.clickController) {
172
- this.clickController = new Gtk.GestureClick();
173
- this.container.addController(this.clickController);
174
- }
175
- const signalName = propName === "onPressed" ? "pressed" : "released";
176
- signalStore.set(this, this.clickController, signalName, wrappedHandler);
177
- break;
178
- }
179
- case "onKeyPressed":
180
- case "onKeyReleased": {
181
- if (!this.keyController) {
182
- this.keyController = new Gtk.EventControllerKey();
183
- this.container.addController(this.keyController);
184
- }
185
- const signalName = propName === "onKeyPressed" ? "key-pressed" : "key-released";
186
- signalStore.set(this, this.keyController, signalName, wrappedHandler);
187
- break;
188
- }
189
- case "onScroll": {
190
- if (!this.scrollController) {
191
- this.scrollController = new Gtk.EventControllerScroll(Gtk.EventControllerScrollFlags.BOTH_AXES);
192
- this.container.addController(this.scrollController);
193
- }
194
- signalStore.set(this, this.scrollController, "scroll", wrappedHandler);
195
- break;
196
- }
197
- case "onDragPrepare":
198
- case "onDragBegin":
199
- case "onDragEnd":
200
- case "onDragCancel":
201
- case "dragActions": {
202
- const dragSource = this.ensureDragSource();
203
- if (propName === "dragActions") {
204
- dragSource.setActions(handlerOrValue ?? Gdk.DragAction.COPY);
205
- }
206
- else {
207
- const signalName = propName === "onDragPrepare"
208
- ? "prepare"
209
- : propName === "onDragBegin"
210
- ? "drag-begin"
211
- : propName === "onDragEnd"
212
- ? "drag-end"
213
- : "drag-cancel";
214
- signalStore.set(this, dragSource, signalName, wrappedHandler);
215
- }
216
- break;
217
- }
218
- case "onDrop":
219
- case "onDropEnter":
220
- case "onDropLeave":
221
- case "onDropMotion":
222
- case "dropActions":
223
- case "dropTypes": {
224
- const dropTarget = this.ensureDropTarget();
225
- if (propName === "dropActions") {
226
- dropTarget.setActions(handlerOrValue ?? Gdk.DragAction.COPY);
227
- }
228
- else if (propName === "dropTypes") {
229
- const types = handlerOrValue ?? [];
230
- dropTarget.setGtypes(types.length, types);
231
- }
232
- else {
233
- const signalName = propName === "onDrop"
234
- ? "drop"
235
- : propName === "onDropEnter"
236
- ? "enter"
237
- : propName === "onDropLeave"
238
- ? "leave"
239
- : "motion";
240
- signalStore.set(this, dropTarget, signalName, wrappedHandler);
241
- }
242
- break;
243
- }
244
- }
245
- }
246
- ensureDragSource() {
247
- if (!this.dragSourceController) {
248
- this.dragSourceController = new Gtk.DragSource();
249
- this.dragSourceController.setActions(Gdk.DragAction.COPY);
250
- this.container.addController(this.dragSourceController);
251
- }
252
- return this.dragSourceController;
253
- }
254
- ensureDropTarget() {
255
- if (!this.dropTargetController) {
256
- this.dropTargetController = new Gtk.DropTarget(0, Gdk.DragAction.COPY);
257
- this.container.addController(this.dropTargetController);
151
+ updateGrabFocus(oldProps, newProps) {
152
+ const oldGrabFocus = oldProps?.grabFocus;
153
+ const newGrabFocus = newProps.grabFocus;
154
+ if (!oldGrabFocus && newGrabFocus) {
155
+ this.container.grabFocus();
258
156
  }
259
- return this.dropTargetController;
260
157
  }
261
- updateNotifyHandler(handler) {
262
- const wrappedHandler = handler
263
- ? (obj, pspec) => {
264
- handler(obj, pspec.getName());
265
- }
266
- : undefined;
267
- signalStore.set(this, this.container, "notify", wrappedHandler);
268
- }
269
- propNameToSignalName(name) {
270
- return name
271
- .slice(2)
272
- .replace(/([A-Z])/g, "-$1")
273
- .toLowerCase()
274
- .replace(/^-/, "");
275
- }
276
- getProperty(key) {
277
- const propMeta = resolvePropMeta(this.container, key);
278
- if (!propMeta)
158
+ getPropertyDefaultValue(key) {
159
+ if (!resolvePropMeta(this.container, key))
279
160
  return undefined;
280
- const [getterName] = propMeta;
281
- const getter = getterName ? this.container[getterName] : undefined;
282
- if (getter && typeof getter === "function") {
283
- return getter.call(this.container);
284
- }
161
+ const pspec = findProperty(this.container, key);
162
+ if (!pspec)
163
+ return undefined;
164
+ const value = pspec.getDefaultValue();
165
+ const gtype = value.getType();
166
+ const fundamental = typeFundamental(gtype);
167
+ if (fundamental === Type.BOOLEAN)
168
+ return value.getBoolean();
169
+ if (fundamental === Type.INT)
170
+ return value.getInt();
171
+ if (fundamental === Type.UINT)
172
+ return value.getUint();
173
+ if (fundamental === Type.LONG)
174
+ return value.getLong();
175
+ if (fundamental === Type.ULONG)
176
+ return value.getUlong();
177
+ if (fundamental === Type.INT64)
178
+ return value.getInt64();
179
+ if (fundamental === Type.UINT64)
180
+ return value.getUint64();
181
+ if (fundamental === Type.FLOAT)
182
+ return value.getFloat();
183
+ if (fundamental === Type.DOUBLE)
184
+ return value.getDouble();
185
+ if (fundamental === Type.STRING)
186
+ return value.getString();
187
+ if (fundamental === Type.ENUM)
188
+ return value.getEnum();
189
+ if (fundamental === Type.FLAGS)
190
+ return value.getFlags();
285
191
  return undefined;
286
192
  }
287
193
  setProperty(key, value) {
@@ -290,21 +196,17 @@ export class WidgetNode extends Node {
290
196
  return;
291
197
  const [getterName, setterName] = propMeta;
292
198
  const setter = this.container[setterName];
293
- const getter = getterName ? this.container[getterName] : undefined;
294
- if (getter && typeof getter === "function") {
295
- const currentValue = getter.call(this.container);
296
- if (currentValue === value) {
297
- return;
298
- }
299
- if (currentValue instanceof NativeObject &&
300
- value instanceof NativeObject &&
301
- isObjectEqual(currentValue, value)) {
302
- return;
199
+ if (!setter || typeof setter !== "function")
200
+ return;
201
+ if (getterName && findProperty(this.container, key) instanceof ParamSpecString) {
202
+ const getter = this.container[getterName];
203
+ if (getter && typeof getter === "function") {
204
+ const currentValue = getter.call(this.container);
205
+ if (currentValue === value)
206
+ return;
303
207
  }
304
208
  }
305
- if (setter && typeof setter === "function") {
306
- setter.call(this.container, value);
307
- }
209
+ setter.call(this.container, value);
308
210
  }
309
211
  detachChildFromParent(child) {
310
212
  const currentParent = child.container.getParent();
@@ -313,37 +215,21 @@ export class WidgetNode extends Node {
313
215
  }
314
216
  }
315
217
  attachChild(child) {
316
- if (isAppendable(this.container)) {
317
- this.detachChildFromParent(child);
318
- this.container.append(child.container);
218
+ const strategy = getAttachmentStrategy(this.container);
219
+ if (!strategy) {
220
+ throw new Error(`Cannot append '${child.typeName}' to '${this.container.constructor.name}': container does not support children`);
319
221
  }
320
- else if (isAddable(this.container)) {
222
+ if (strategy.type === "appendable" || strategy.type === "addable") {
321
223
  this.detachChildFromParent(child);
322
- this.container.add(child.container);
323
- }
324
- else if (hasSingleContent(this.container)) {
325
- this.container.setContent(child.container);
326
- }
327
- else if (isSingleChild(this.container)) {
328
- this.container.setChild(child.container);
329
- }
330
- else {
331
- throw new Error(`Cannot append '${child.typeName}' to '${this.container.constructor.name}': container does not support children`);
332
224
  }
225
+ performAttachment(child.container, strategy);
333
226
  }
334
227
  detachChild(child) {
335
- if (isRemovable(this.container)) {
336
- this.container.remove(child.container);
337
- }
338
- else if (hasSingleContent(this.container)) {
339
- this.container.setContent(null);
340
- }
341
- else if (isSingleChild(this.container)) {
342
- this.container.setChild(null);
343
- }
344
- else {
228
+ const strategy = getAttachmentStrategy(this.container);
229
+ if (!strategy) {
345
230
  throw new Error(`Cannot remove '${child.typeName}' from '${this.container.constructor.name}': container does not support child removal`);
346
231
  }
232
+ performDetachment(child.container, strategy);
347
233
  }
348
234
  findPreviousSibling(before) {
349
235
  let beforeChild = this.container.getFirstChild();
@@ -2,22 +2,28 @@ import * as Gtk from "@gtkx/ffi/gtk";
2
2
  import type { Node } from "../node.js";
3
3
  import type { Container, ContainerClass, Props } from "../types.js";
4
4
  import { WidgetNode } from "./widget.js";
5
- type WindowProps = Props & {
5
+ type CreditSection = {
6
+ name: string;
7
+ people: string[];
8
+ };
9
+ export type WindowProps = Props & {
6
10
  defaultWidth?: number;
7
11
  defaultHeight?: number;
8
12
  onClose?: () => void;
13
+ creditSections?: CreditSection[];
9
14
  };
10
15
  export declare class WindowNode extends WidgetNode<Gtk.Window, WindowProps> {
11
16
  static priority: number;
12
17
  private menu;
13
18
  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);
19
+ static createContainer(props: Props, containerClass: typeof Gtk.Window, rootContainer: Container | undefined): Gtk.Window;
20
+ constructor(typeName: string, props: WindowProps, container: Gtk.Window, rootContainer: Container);
16
21
  appendChild(child: Node): void;
17
22
  removeChild(child: Node): void;
18
23
  insertBefore(child: Node, before: Node): void;
19
24
  mount(): void;
20
25
  unmount(): void;
21
26
  updateProps(oldProps: WindowProps | null, newProps: WindowProps): void;
27
+ protected applyOwnProps(oldProps: WindowProps | null, newProps: WindowProps): void;
22
28
  }
23
29
  export {};
@@ -1,25 +1,25 @@
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";
5
- import { filterProps, isContainerType } from "./internal/utils.js";
6
- import { Menu } from "./models/menu.js";
4
+ import { DialogNode } from "./dialog.js";
5
+ import { filterProps, hasChanged, matchesAnyClass } from "./internal/utils.js";
6
+ import { MenuModel } from "./models/menu.js";
7
7
  import { WidgetNode } from "./widget.js";
8
- const PROPS = ["defaultWidth", "defaultHeight", "onClose"];
8
+ const OWN_PROPS = ["defaultWidth", "defaultHeight", "onClose"];
9
9
  export class WindowNode extends WidgetNode {
10
10
  static priority = 1;
11
11
  menu;
12
12
  static matches(_type, containerOrClass) {
13
- return isContainerType(Gtk.Window, containerOrClass);
13
+ return matchesAnyClass([Gtk.Window], containerOrClass);
14
14
  }
15
15
  static createContainer(props, containerClass, rootContainer) {
16
16
  const WindowClass = containerClass;
17
- if (isContainerType(Gtk.ApplicationWindow, WindowClass) ||
18
- isContainerType(Adw.ApplicationWindow, WindowClass)) {
17
+ if (matchesAnyClass([Gtk.ApplicationWindow], WindowClass) ||
18
+ matchesAnyClass([Adw.ApplicationWindow], WindowClass)) {
19
19
  if (!(rootContainer instanceof Gtk.Application)) {
20
20
  throw new Error("Expected ApplicationWindow to be created within Application");
21
21
  }
22
- if (isContainerType(Adw.ApplicationWindow, WindowClass)) {
22
+ if (matchesAnyClass([Adw.ApplicationWindow], WindowClass)) {
23
23
  return new Adw.ApplicationWindow(rootContainer);
24
24
  }
25
25
  return new Gtk.ApplicationWindow(rootContainer);
@@ -30,13 +30,22 @@ export class WindowNode extends WidgetNode {
30
30
  super(typeName, props, container, rootContainer);
31
31
  const application = rootContainer instanceof Gtk.Application ? rootContainer : undefined;
32
32
  const actionMap = container instanceof Gtk.ApplicationWindow ? container : undefined;
33
- this.menu = new Menu("root", {}, actionMap, application);
33
+ this.menu = new MenuModel("root", {}, rootContainer, actionMap, application);
34
+ if (container instanceof Gtk.AboutDialog && props.creditSections) {
35
+ for (const section of props.creditSections) {
36
+ container.addCreditSection(section.name, section.people);
37
+ }
38
+ }
34
39
  }
35
40
  appendChild(child) {
36
41
  if (child.container instanceof Gtk.Window) {
37
42
  child.container.setTransientFor(this.container);
38
43
  return;
39
44
  }
45
+ if (child instanceof DialogNode) {
46
+ child.parent = this.container;
47
+ return;
48
+ }
40
49
  this.menu.appendChild(child);
41
50
  super.appendChild(child);
42
51
  }
@@ -45,6 +54,10 @@ export class WindowNode extends WidgetNode {
45
54
  child.container.setTransientFor(null);
46
55
  return;
47
56
  }
57
+ if (child instanceof DialogNode) {
58
+ child.parent = null;
59
+ return;
60
+ }
48
61
  this.menu.removeChild(child);
49
62
  super.removeChild(child);
50
63
  }
@@ -61,22 +74,25 @@ export class WindowNode extends WidgetNode {
61
74
  super.unmount();
62
75
  }
63
76
  updateProps(oldProps, newProps) {
64
- if (!oldProps ||
65
- oldProps.defaultWidth !== newProps.defaultWidth ||
66
- oldProps.defaultHeight !== newProps.defaultHeight) {
77
+ super.updateProps(oldProps ? filterProps(oldProps, OWN_PROPS) : null, filterProps(newProps, OWN_PROPS));
78
+ this.applyOwnProps(oldProps, newProps);
79
+ }
80
+ applyOwnProps(oldProps, newProps) {
81
+ if (hasChanged(oldProps, newProps, "defaultWidth") || hasChanged(oldProps, newProps, "defaultHeight")) {
67
82
  const width = newProps.defaultWidth ?? -1;
68
83
  const height = newProps.defaultHeight ?? -1;
69
84
  this.container.setDefaultSize(width, height);
70
85
  }
71
- if (oldProps?.onClose !== newProps.onClose) {
86
+ if (hasChanged(oldProps, newProps, "onClose")) {
72
87
  const userHandler = newProps.onClose;
73
- const wrappedHandler = () => {
74
- userHandler?.();
75
- return true;
76
- };
77
- signalStore.set(this, this.container, "close-request", wrappedHandler);
88
+ const wrappedHandler = userHandler
89
+ ? () => {
90
+ userHandler();
91
+ return true;
92
+ }
93
+ : undefined;
94
+ this.signalStore.set(this, this.container, "close-request", wrappedHandler);
78
95
  }
79
- super.updateProps(filterProps(oldProps ?? {}, PROPS), filterProps(newProps, PROPS));
80
96
  }
81
97
  }
82
98
  registerNodeClass(WindowNode);
@@ -1,7 +1,7 @@
1
1
  import type { Node } from "./node.js";
2
2
  import type { Container, Props } from "./types.js";
3
3
  type NodeClass<T = unknown, P = Props> = {
4
- new (typeName: string, props: P, container: T, rootContainer?: Container): Node<T, P>;
4
+ new (typeName: string, props: P, container: T, rootContainer: Container): Node<T, P>;
5
5
  } & Omit<typeof Node, "prototype">;
6
6
  export declare const NODE_CLASSES: NodeClass[];
7
7
  export declare const registerNodeClass: <T, P>(nodeClass: NodeClass<T, P>) => void;
package/dist/render.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { discardAllBatches, start, stop } from "@gtkx/ffi";
2
+ import { start, stop } from "@gtkx/ffi";
3
3
  import { createContext, useContext } from "react";
4
4
  import { formatBoundaryError, formatRenderError } from "./errors.js";
5
+ import { getSignalStore } from "./nodes/internal/signal-store.js";
5
6
  import { reconciler } from "./reconciler.js";
6
7
  /**
7
8
  * React Context providing access to the GTK Application instance.
@@ -96,17 +97,18 @@ export const setHotReloading = (value) => {
96
97
  * @see {@link update} for hot-reloading the rendered tree
97
98
  */
98
99
  export const render = (element, appId, flags) => {
99
- app = start(appId, flags);
100
+ const application = start(appId, flags);
101
+ app = application;
100
102
  const instance = reconciler.getInstance();
101
- container = instance.createContainer(app, 1, null, false, null, "", (error) => {
102
- discardAllBatches();
103
+ container = instance.createContainer(application, 1, null, false, null, "", (error) => {
104
+ getSignalStore(application).forceUnblockAll();
103
105
  throw formatRenderError(error);
104
106
  }, (error) => {
105
- discardAllBatches();
107
+ getSignalStore(application).forceUnblockAll();
106
108
  const formattedError = formatBoundaryError(error);
107
109
  console.error(formattedError.toString());
108
110
  }, () => { }, () => { }, null);
109
- instance.updateContainer(_jsx(ApplicationContext.Provider, { value: app, children: element }), container, null, () => { });
111
+ instance.updateContainer(_jsx(ApplicationContext.Provider, { value: application, children: element }), container, null, () => { });
110
112
  };
111
113
  /**
112
114
  * Updates the rendered React element tree.
@@ -1,10 +1,20 @@
1
1
  type Callback = () => void;
2
+ /**
3
+ * Priority levels for scheduling operations after React commit.
4
+ *
5
+ * Priority guidelines:
6
+ * - HIGH: Removals and detachments (must run before additions to avoid conflicts)
7
+ * - NORMAL: Additions and attachments (standard widget operations)
8
+ * - LOW: Sync operations that depend on all additions being complete (e.g., model updates)
9
+ *
10
+ * Within the same priority level, callbacks execute in FIFO order.
11
+ */
2
12
  export declare enum CommitPriority {
3
13
  /** Runs first. Used for widget removals to unparent before reparenting. */
4
14
  HIGH = 0,
5
15
  /** Runs after HIGH priority. Used for widget additions. */
6
16
  NORMAL = 1,
7
- /** Runs last. Used for model sync operations that need all data to be added first. */
17
+ /** Runs after NORMAL. Used for model sync operations that need all data to be added first. */
8
18
  LOW = 2
9
19
  }
10
20
  /**