@gtkx/react 0.9.4 → 0.10.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 +55 -67
  2. package/dist/errors.d.ts +3 -3
  3. package/dist/errors.js +8 -8
  4. package/dist/factory.d.ts +3 -5
  5. package/dist/factory.js +18 -71
  6. package/dist/fiber-root.d.ts +1 -1
  7. package/dist/fiber-root.js +1 -2
  8. package/dist/generated/internal.d.ts +3 -6
  9. package/dist/generated/internal.js +10386 -13577
  10. package/dist/generated/jsx.d.ts +914 -808
  11. package/dist/generated/jsx.js +123 -358
  12. package/dist/generated/registry.d.ts +4 -0
  13. package/dist/generated/registry.js +13 -0
  14. package/dist/host-config.d.ts +7 -4
  15. package/dist/host-config.js +53 -18
  16. package/dist/index.d.ts +2 -22
  17. package/dist/index.js +2 -40
  18. package/dist/jsx.d.ts +719 -0
  19. package/dist/jsx.js +392 -0
  20. package/dist/node.d.ts +15 -32
  21. package/dist/node.js +20 -240
  22. package/dist/nodes/action-row-child.d.ts +21 -0
  23. package/dist/nodes/action-row-child.js +69 -0
  24. package/dist/nodes/action-row.js +33 -0
  25. package/dist/nodes/application.d.ts +1 -0
  26. package/dist/nodes/application.js +38 -0
  27. package/dist/nodes/autowrapped.d.ts +1 -0
  28. package/dist/nodes/autowrapped.js +109 -0
  29. package/dist/nodes/column-view-column.d.ts +16 -0
  30. package/dist/nodes/column-view-column.js +54 -0
  31. package/dist/nodes/column-view.d.ts +0 -59
  32. package/dist/nodes/column-view.js +107 -226
  33. package/dist/nodes/fixed-child.d.ts +1 -0
  34. package/dist/nodes/fixed-child.js +45 -0
  35. package/dist/nodes/grid-child.d.ts +1 -0
  36. package/dist/nodes/grid-child.js +54 -0
  37. package/dist/nodes/index.d.ts +34 -0
  38. package/dist/nodes/index.js +34 -0
  39. package/dist/nodes/internal/list-item-renderer.d.ts +18 -0
  40. package/dist/nodes/internal/list-item-renderer.js +67 -0
  41. package/dist/nodes/internal/list-store.d.ts +16 -0
  42. package/dist/nodes/internal/list-store.js +69 -0
  43. package/dist/nodes/internal/predicates.d.ts +26 -0
  44. package/dist/nodes/internal/predicates.js +36 -0
  45. package/dist/nodes/internal/signal-store.d.ts +9 -0
  46. package/dist/nodes/internal/signal-store.js +54 -0
  47. package/dist/nodes/internal/simple-list-store.d.ts +14 -0
  48. package/dist/nodes/internal/simple-list-store.js +60 -0
  49. package/dist/nodes/internal/tree-list-item-renderer.d.ts +18 -0
  50. package/dist/nodes/internal/tree-list-item-renderer.js +90 -0
  51. package/dist/nodes/internal/tree-store.d.ts +28 -0
  52. package/dist/nodes/internal/tree-store.js +153 -0
  53. package/dist/nodes/internal/utils.d.ts +3 -0
  54. package/dist/nodes/internal/utils.js +20 -0
  55. package/dist/nodes/list-item.d.ts +12 -0
  56. package/dist/nodes/list-item.js +24 -0
  57. package/dist/nodes/list-view.d.ts +0 -22
  58. package/dist/nodes/list-view.js +45 -38
  59. package/dist/nodes/menu.d.ts +6 -106
  60. package/dist/nodes/menu.js +16 -268
  61. package/dist/nodes/models/list.d.ts +24 -0
  62. package/dist/nodes/models/list.js +102 -0
  63. package/dist/nodes/models/menu.d.ts +45 -0
  64. package/dist/nodes/models/menu.js +265 -0
  65. package/dist/nodes/models/tree-list.d.ts +28 -0
  66. package/dist/nodes/models/tree-list.js +141 -0
  67. package/dist/nodes/navigation-page.d.ts +21 -0
  68. package/dist/nodes/navigation-page.js +95 -0
  69. package/dist/nodes/navigation-view.d.ts +1 -0
  70. package/dist/nodes/navigation-view.js +29 -0
  71. package/dist/nodes/notebook-page-tab.d.ts +15 -0
  72. package/dist/nodes/notebook-page-tab.js +42 -0
  73. package/dist/nodes/notebook-page.d.ts +23 -0
  74. package/dist/nodes/notebook-page.js +106 -0
  75. package/dist/nodes/notebook.d.ts +0 -32
  76. package/dist/nodes/notebook.js +20 -113
  77. package/dist/nodes/overlay-child.d.ts +1 -0
  78. package/dist/nodes/overlay-child.js +30 -0
  79. package/dist/nodes/pack-child.d.ts +21 -0
  80. package/dist/nodes/pack-child.js +68 -0
  81. package/dist/nodes/pack.d.ts +1 -0
  82. package/dist/nodes/pack.js +33 -0
  83. package/dist/nodes/popover-menu.d.ts +1 -0
  84. package/dist/nodes/popover-menu.js +58 -0
  85. package/dist/nodes/simple-list-item.d.ts +9 -0
  86. package/dist/nodes/simple-list-item.js +9 -0
  87. package/dist/nodes/simple-list-view.d.ts +1 -0
  88. package/dist/nodes/simple-list-view.js +75 -0
  89. package/dist/nodes/slot.d.ts +18 -10
  90. package/dist/nodes/slot.js +83 -51
  91. package/dist/nodes/stack-page.d.ts +1 -0
  92. package/dist/nodes/stack-page.js +80 -0
  93. package/dist/nodes/stack.d.ts +1 -22
  94. package/dist/nodes/stack.js +21 -60
  95. package/dist/nodes/toast-overlay.d.ts +1 -0
  96. package/dist/nodes/toast-overlay.js +35 -0
  97. package/dist/nodes/toast.d.ts +17 -0
  98. package/dist/nodes/toast.js +77 -0
  99. package/dist/nodes/toolbar-child.d.ts +9 -0
  100. package/dist/nodes/toolbar-child.js +33 -0
  101. package/dist/nodes/toolbar.d.ts +1 -0
  102. package/dist/nodes/toolbar.js +42 -0
  103. package/dist/nodes/tree-list-item.d.ts +20 -0
  104. package/dist/nodes/tree-list-item.js +102 -0
  105. package/dist/nodes/tree-list-view.d.ts +1 -0
  106. package/dist/nodes/tree-list-view.js +57 -0
  107. package/dist/nodes/virtual.d.ts +13 -0
  108. package/dist/nodes/virtual.js +21 -0
  109. package/dist/nodes/widget.d.ts +17 -3
  110. package/dist/nodes/widget.js +258 -2
  111. package/dist/nodes/window.d.ts +1 -12
  112. package/dist/nodes/window.js +66 -27
  113. package/dist/portal.d.ts +18 -13
  114. package/dist/portal.js +17 -14
  115. package/dist/reconciler.d.ts +0 -4
  116. package/dist/reconciler.js +1 -9
  117. package/dist/registry.d.ts +8 -0
  118. package/dist/registry.js +5 -0
  119. package/dist/render.d.ts +108 -12
  120. package/dist/render.js +140 -16
  121. package/dist/scheduler.d.ts +4 -0
  122. package/dist/scheduler.js +10 -0
  123. package/dist/types.d.ts +3 -136
  124. package/package.json +6 -6
  125. package/dist/batch.d.ts +0 -5
  126. package/dist/batch.js +0 -31
  127. package/dist/codegen/jsx-generator.d.ts +0 -56
  128. package/dist/codegen/jsx-generator.js +0 -959
  129. package/dist/containers.d.ts +0 -58
  130. package/dist/nodes/about-dialog.d.ts +0 -8
  131. package/dist/nodes/about-dialog.js +0 -16
  132. package/dist/nodes/action-bar.d.ts +0 -5
  133. package/dist/nodes/action-bar.js +0 -6
  134. package/dist/nodes/combo-row.d.ts +0 -5
  135. package/dist/nodes/combo-row.js +0 -6
  136. package/dist/nodes/drop-down.d.ts +0 -9
  137. package/dist/nodes/drop-down.js +0 -12
  138. package/dist/nodes/flow-box.d.ts +0 -10
  139. package/dist/nodes/flow-box.js +0 -41
  140. package/dist/nodes/grid.d.ts +0 -30
  141. package/dist/nodes/grid.js +0 -84
  142. package/dist/nodes/header-bar.d.ts +0 -43
  143. package/dist/nodes/header-bar.js +0 -116
  144. package/dist/nodes/indexed-child-container.d.ts +0 -16
  145. package/dist/nodes/indexed-child-container.js +0 -22
  146. package/dist/nodes/list-box.d.ts +0 -10
  147. package/dist/nodes/list-box.js +0 -48
  148. package/dist/nodes/list-item-factory.d.ts +0 -19
  149. package/dist/nodes/list-item-factory.js +0 -58
  150. package/dist/nodes/overlay.d.ts +0 -11
  151. package/dist/nodes/overlay.js +0 -50
  152. package/dist/nodes/paged-stack.d.ts +0 -31
  153. package/dist/nodes/paged-stack.js +0 -95
  154. package/dist/nodes/root.d.ts +0 -8
  155. package/dist/nodes/root.js +0 -13
  156. package/dist/nodes/selectable-list.d.ts +0 -45
  157. package/dist/nodes/selectable-list.js +0 -260
  158. package/dist/nodes/stack-page-props.d.ts +0 -11
  159. package/dist/nodes/stack-page-props.js +0 -23
  160. package/dist/nodes/string-list-container.d.ts +0 -34
  161. package/dist/nodes/string-list-container.js +0 -118
  162. package/dist/nodes/string-list-item.d.ts +0 -19
  163. package/dist/nodes/string-list-item.js +0 -50
  164. package/dist/nodes/string-list-store.d.ts +0 -13
  165. package/dist/nodes/string-list-store.js +0 -44
  166. package/dist/nodes/text-view.d.ts +0 -8
  167. package/dist/nodes/text-view.js +0 -16
  168. package/dist/nodes/toggle-button.d.ts +0 -14
  169. package/dist/nodes/toggle-button.js +0 -39
  170. package/dist/nodes/toolbar-view.d.ts +0 -14
  171. package/dist/nodes/toolbar-view.js +0 -78
  172. package/dist/nodes/view-stack.d.ts +0 -9
  173. package/dist/nodes/view-stack.js +0 -28
  174. package/dist/nodes/virtual-item.d.ts +0 -19
  175. package/dist/nodes/virtual-item.js +0 -48
  176. package/dist/nodes/virtual-slot.d.ts +0 -25
  177. package/dist/nodes/virtual-slot.js +0 -57
  178. package/dist/predicates.d.ts +0 -29
  179. package/dist/predicates.js +0 -37
  180. package/dist/props.d.ts +0 -7
  181. package/dist/props.js +0 -12
  182. /package/dist/{containers.js → nodes/action-row.d.ts} +0 -0
@@ -0,0 +1,102 @@
1
+ import { registerNodeClass } from "../registry.js";
2
+ import { scheduleAfterCommit } from "../scheduler.js";
3
+ import { VirtualNode } from "./virtual.js";
4
+ export class TreeListItemNode extends VirtualNode {
5
+ static priority = 1;
6
+ store;
7
+ parentItemId;
8
+ childNodes = [];
9
+ static matches(type) {
10
+ return type === "TreeListItem";
11
+ }
12
+ setStore(store) {
13
+ this.store = store;
14
+ for (const child of this.childNodes) {
15
+ child.setStore(store);
16
+ }
17
+ }
18
+ setParentItemId(parentId) {
19
+ this.parentItemId = parentId;
20
+ }
21
+ getParentItemId() {
22
+ return this.parentItemId;
23
+ }
24
+ appendChild(child) {
25
+ if (!(child instanceof TreeListItemNode)) {
26
+ return;
27
+ }
28
+ child.setStore(this.store);
29
+ child.setParentItemId(this.props.id);
30
+ this.childNodes.push(child);
31
+ scheduleAfterCommit(() => {
32
+ if (this.store && child.props.id !== undefined) {
33
+ this.store.addItem(child.props.id, {
34
+ value: child.props.value,
35
+ indentForDepth: child.props.indentForDepth,
36
+ indentForIcon: child.props.indentForIcon,
37
+ hideExpander: child.props.hideExpander,
38
+ }, this.props.id);
39
+ }
40
+ });
41
+ }
42
+ insertBefore(child, before) {
43
+ if (!(child instanceof TreeListItemNode) || !(before instanceof TreeListItemNode)) {
44
+ return;
45
+ }
46
+ child.setStore(this.store);
47
+ child.setParentItemId(this.props.id);
48
+ const beforeIndex = this.childNodes.indexOf(before);
49
+ if (beforeIndex === -1) {
50
+ this.childNodes.push(child);
51
+ }
52
+ else {
53
+ this.childNodes.splice(beforeIndex, 0, child);
54
+ }
55
+ scheduleAfterCommit(() => {
56
+ if (this.store && child.props.id !== undefined && before.props.id !== undefined) {
57
+ this.store.insertItemBefore(child.props.id, before.props.id, {
58
+ value: child.props.value,
59
+ indentForDepth: child.props.indentForDepth,
60
+ indentForIcon: child.props.indentForIcon,
61
+ hideExpander: child.props.hideExpander,
62
+ }, this.props.id);
63
+ }
64
+ });
65
+ }
66
+ removeChild(child) {
67
+ if (!(child instanceof TreeListItemNode)) {
68
+ return;
69
+ }
70
+ const index = this.childNodes.indexOf(child);
71
+ if (index !== -1) {
72
+ this.childNodes.splice(index, 1);
73
+ }
74
+ if (this.store && child.props.id !== undefined) {
75
+ this.store.removeItem(child.props.id, this.props.id);
76
+ }
77
+ child.setStore(undefined);
78
+ child.setParentItemId(undefined);
79
+ }
80
+ updateProps(oldProps, newProps) {
81
+ super.updateProps(oldProps, newProps);
82
+ if (!this.store) {
83
+ return;
84
+ }
85
+ if (!oldProps ||
86
+ oldProps.id !== newProps.id ||
87
+ oldProps.value !== newProps.value ||
88
+ oldProps.indentForDepth !== newProps.indentForDepth ||
89
+ oldProps.indentForIcon !== newProps.indentForIcon ||
90
+ oldProps.hideExpander !== newProps.hideExpander) {
91
+ if (newProps.id !== undefined) {
92
+ this.store.updateItem(newProps.id, {
93
+ value: newProps.value,
94
+ indentForDepth: newProps.indentForDepth,
95
+ indentForIcon: newProps.indentForIcon,
96
+ hideExpander: newProps.hideExpander,
97
+ });
98
+ }
99
+ }
100
+ }
101
+ }
102
+ registerNodeClass(TreeListItemNode);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ import * as Gtk from "@gtkx/ffi/gtk";
2
+ import { registerNodeClass } from "../registry.js";
3
+ import { TreeListItemRenderer } from "./internal/tree-list-item-renderer.js";
4
+ import { filterProps } from "./internal/utils.js";
5
+ import { TreeList } from "./models/tree-list.js";
6
+ import { TreeListItemNode } from "./tree-list-item.js";
7
+ import { WidgetNode } from "./widget.js";
8
+ const PROP_NAMES = ["renderItem", "autoexpand", "selectionMode", "selected", "onSelectionChanged"];
9
+ class TreeListViewNode extends WidgetNode {
10
+ static priority = 1;
11
+ itemRenderer;
12
+ treeList;
13
+ static matches(type) {
14
+ return type === "TreeListView";
15
+ }
16
+ static createContainer() {
17
+ return new Gtk.ListView();
18
+ }
19
+ constructor(typeName, props, container, rootContainer) {
20
+ const listView = container ?? new Gtk.ListView();
21
+ super(typeName, props, listView, rootContainer);
22
+ this.treeList = new TreeList(props.autoexpand, props.selectionMode);
23
+ this.itemRenderer = new TreeListItemRenderer(this.signalStore);
24
+ this.itemRenderer.setStore(this.treeList.getStore());
25
+ this.container.setFactory(this.itemRenderer.getFactory());
26
+ }
27
+ mount() {
28
+ super.mount();
29
+ this.container.setModel(this.treeList.getSelectionModel());
30
+ }
31
+ appendChild(child) {
32
+ if (!(child instanceof TreeListItemNode)) {
33
+ throw new Error(`Cannot append '${child.typeName}' to 'TreeListView': expected TreeListItem`);
34
+ }
35
+ this.treeList.appendChild(child);
36
+ }
37
+ insertBefore(child, before) {
38
+ if (!(child instanceof TreeListItemNode) || !(before instanceof TreeListItemNode)) {
39
+ throw new Error(`Cannot insert '${child.typeName}' to 'TreeListView': expected TreeListItem`);
40
+ }
41
+ this.treeList.insertBefore(child, before);
42
+ }
43
+ removeChild(child) {
44
+ if (!(child instanceof TreeListItemNode)) {
45
+ throw new Error(`Cannot remove '${child.typeName}' from 'TreeListView': expected TreeListItem`);
46
+ }
47
+ this.treeList.removeChild(child);
48
+ }
49
+ updateProps(oldProps, newProps) {
50
+ if (!oldProps || oldProps.renderItem !== newProps.renderItem) {
51
+ this.itemRenderer.setRenderFn(newProps.renderItem);
52
+ }
53
+ this.treeList.updateProps(filterProps(oldProps ?? {}, ["renderItem"]), filterProps(newProps, ["renderItem"]));
54
+ super.updateProps(filterProps(oldProps ?? {}, PROP_NAMES), filterProps(newProps, PROP_NAMES));
55
+ }
56
+ }
57
+ registerNodeClass(TreeListViewNode);
@@ -0,0 +1,13 @@
1
+ import { Node } from "../node.js";
2
+ import type { Container, Props } from "../types.js";
3
+ export declare class VirtualNode<P = Props> extends Node<undefined, P> {
4
+ static priority: number;
5
+ static matches(_type: string): boolean;
6
+ static createContainer(): void;
7
+ props: P;
8
+ constructor(typeName: string, props: P | undefined, container: undefined, rootContainer?: Container);
9
+ appendChild(_child: Node): void;
10
+ removeChild(_child: Node): void;
11
+ insertBefore(_child: Node, _before: Node): void;
12
+ updateProps(_oldProps: P | null, newProps: P): void;
13
+ }
@@ -0,0 +1,21 @@
1
+ import { Node } from "../node.js";
2
+ import { registerNodeClass } from "../registry.js";
3
+ export class VirtualNode extends Node {
4
+ static priority = 1;
5
+ static matches(_type) {
6
+ return false;
7
+ }
8
+ static createContainer() { }
9
+ props;
10
+ constructor(typeName, props = {}, container, rootContainer) {
11
+ super(typeName, props, container, rootContainer);
12
+ this.props = props;
13
+ }
14
+ appendChild(_child) { }
15
+ removeChild(_child) { }
16
+ insertBefore(_child, _before) { }
17
+ updateProps(_oldProps, newProps) {
18
+ this.props = newProps;
19
+ }
20
+ }
21
+ registerNodeClass(VirtualNode);
@@ -1,5 +1,19 @@
1
- import type * as Gtk from "@gtkx/ffi/gtk";
1
+ import * as Gtk from "@gtkx/ffi/gtk";
2
2
  import { Node } from "../node.js";
3
- export declare class WidgetNode extends Node<Gtk.Widget> {
4
- static matches(_type: string): boolean;
3
+ import type { Container, ContainerClass, Props } from "../types.js";
4
+ export declare class WidgetNode<T extends Gtk.Widget = Gtk.Widget, P extends Props = Props> extends Node<T, P> {
5
+ static priority: number;
6
+ private motionController?;
7
+ private clickController?;
8
+ private keyController?;
9
+ private scrollController?;
10
+ static matches(_type: string, containerOrClass?: Container | ContainerClass): boolean;
11
+ static createContainer(props: Props, containerClass: typeof Gtk.Widget): Container | undefined;
12
+ appendChild(child: Node): void;
13
+ removeChild(child: Node): void;
14
+ insertBefore(child: Node, before: Node): void;
15
+ updateProps(oldProps: P | null, newProps: P): void;
16
+ private updateEventControllerProp;
17
+ private propNameToSignalName;
18
+ private setProperty;
5
19
  }
@@ -1,6 +1,262 @@
1
+ import { batch, NativeObject } from "@gtkx/ffi";
2
+ import * as Gtk from "@gtkx/ffi/gtk";
3
+ import { CONSTRUCTOR_PROPS, PROPS, SIGNALS } from "../generated/internal.js";
1
4
  import { Node } from "../node.js";
5
+ import { registerNodeClass } from "../registry.js";
6
+ import { isAddable, isAppendable, isEditable, isInsertable, isRemovable, isReorderable, isSingleChild, } from "./internal/predicates.js";
7
+ import { filterProps, isContainerType } from "./internal/utils.js";
8
+ import { SlotNode } from "./slot.js";
9
+ const EVENT_CONTROLLER_PROPS = new Set([
10
+ "onEnter",
11
+ "onLeave",
12
+ "onMotion",
13
+ "onPressed",
14
+ "onReleased",
15
+ "onKeyPressed",
16
+ "onKeyReleased",
17
+ "onScroll",
18
+ "onNotify",
19
+ ]);
2
20
  export class WidgetNode extends Node {
3
- static matches(_type) {
4
- return true;
21
+ static priority = 3;
22
+ motionController;
23
+ clickController;
24
+ keyController;
25
+ scrollController;
26
+ static matches(_type, containerOrClass) {
27
+ return isContainerType(Gtk.Widget, containerOrClass);
28
+ }
29
+ static createContainer(props, containerClass) {
30
+ const WidgetClass = containerClass;
31
+ const typeName = WidgetClass.glibTypeName;
32
+ const args = (CONSTRUCTOR_PROPS[typeName] ?? []).map((name) => props[name]);
33
+ return new WidgetClass(...args);
34
+ }
35
+ appendChild(child) {
36
+ if (child instanceof SlotNode) {
37
+ child.setParent(this.container);
38
+ return;
39
+ }
40
+ if (!(child instanceof WidgetNode)) {
41
+ throw new Error(`Cannot append '${child.typeName}' to 'Widget': expected WidgetNode child`);
42
+ }
43
+ if (child.container instanceof Gtk.Window) {
44
+ throw new Error(`Cannot append 'Window' to '${this.typeName}': windows must be top-level containers`);
45
+ }
46
+ batch(() => {
47
+ if (isAppendable(this.container)) {
48
+ const currentParent = child.container.getParent();
49
+ if (currentParent !== null && isRemovable(currentParent)) {
50
+ currentParent.remove(child.container);
51
+ }
52
+ this.container.append(child.container);
53
+ }
54
+ else if (isAddable(this.container)) {
55
+ const currentParent = child.container.getParent();
56
+ if (currentParent !== null && isRemovable(currentParent)) {
57
+ currentParent.remove(child.container);
58
+ }
59
+ this.container.add(child.container);
60
+ }
61
+ else if (isSingleChild(this.container)) {
62
+ this.container.setChild(child.container);
63
+ }
64
+ else {
65
+ throw new Error(`Cannot append '${child.typeName}' to '${this.container.constructor.name}': container does not support children`);
66
+ }
67
+ });
68
+ }
69
+ removeChild(child) {
70
+ if (child instanceof SlotNode) {
71
+ return;
72
+ }
73
+ if (!(child instanceof WidgetNode)) {
74
+ throw new Error(`Cannot remove '${child.typeName}' from 'Widget': expected WidgetNode child`);
75
+ }
76
+ if (child.container instanceof Gtk.Window) {
77
+ throw new Error(`Cannot remove 'Window' from '${this.typeName}': windows must be top-level containers`);
78
+ }
79
+ batch(() => {
80
+ if (isRemovable(this.container)) {
81
+ this.container.remove(child.container);
82
+ }
83
+ else if (isSingleChild(this.container)) {
84
+ this.container.setChild(null);
85
+ }
86
+ else {
87
+ throw new Error(`Cannot remove '${child.typeName}' from '${this.container.constructor.name}': container does not support child removal`);
88
+ }
89
+ });
90
+ }
91
+ insertBefore(child, before) {
92
+ if (child instanceof SlotNode) {
93
+ child.setParent(this.container);
94
+ return;
95
+ }
96
+ if (!(child instanceof WidgetNode) || !(before instanceof WidgetNode)) {
97
+ throw new Error(`Cannot insert '${child.typeName}' before '${before.typeName}': expected WidgetNode children`);
98
+ }
99
+ if (child.container instanceof Gtk.Window) {
100
+ throw new Error(`Cannot insert 'Window' into '${this.typeName}': windows must be top-level containers`);
101
+ }
102
+ batch(() => {
103
+ if (isReorderable(this.container)) {
104
+ let beforeChild = this.container.getFirstChild();
105
+ while (beforeChild) {
106
+ if (beforeChild.equals(before.container)) {
107
+ break;
108
+ }
109
+ beforeChild = beforeChild.getNextSibling();
110
+ }
111
+ if (!beforeChild) {
112
+ throw new Error(`Cannot insert '${child.typeName}': 'before' child not found in container`);
113
+ }
114
+ const previousSibling = beforeChild.getPrevSibling() ?? undefined;
115
+ const currentParent = child.container.getParent();
116
+ const isChildOfThisContainer = currentParent?.equals(this.container);
117
+ if (isChildOfThisContainer) {
118
+ this.container.reorderChildAfter(child.container, previousSibling);
119
+ }
120
+ else {
121
+ if (currentParent !== null && isRemovable(currentParent)) {
122
+ currentParent.remove(child.container);
123
+ }
124
+ this.container.insertChildAfter(child.container, previousSibling);
125
+ }
126
+ }
127
+ else if (isInsertable(this.container)) {
128
+ const currentParent = child.container.getParent();
129
+ if (currentParent !== null && isRemovable(currentParent)) {
130
+ currentParent.remove(child.container);
131
+ }
132
+ let position = 0;
133
+ let currentChild = this.container.getFirstChild();
134
+ while (currentChild) {
135
+ if (currentChild.equals(before.container)) {
136
+ break;
137
+ }
138
+ position++;
139
+ currentChild = currentChild.getNextSibling();
140
+ }
141
+ if (!currentChild) {
142
+ throw new Error(`Cannot insert '${child.typeName}': 'before' child not found in container`);
143
+ }
144
+ this.container.insert(child.container, position);
145
+ }
146
+ else {
147
+ this.appendChild(child);
148
+ }
149
+ });
150
+ }
151
+ updateProps(oldProps, newProps) {
152
+ const propNames = new Set([
153
+ ...Object.keys(filterProps(oldProps ?? {}, ["children"])),
154
+ ...Object.keys(filterProps(newProps ?? {}, ["children"])),
155
+ ]);
156
+ const WidgetClass = this.container.constructor;
157
+ const signals = SIGNALS[WidgetClass.glibTypeName] || new Set();
158
+ for (const name of propNames) {
159
+ const oldValue = oldProps?.[name];
160
+ const newValue = newProps[name];
161
+ if (oldValue === newValue)
162
+ continue;
163
+ if (EVENT_CONTROLLER_PROPS.has(name)) {
164
+ this.updateEventControllerProp(name, newValue);
165
+ continue;
166
+ }
167
+ const signalName = this.propNameToSignalName(name);
168
+ if (signals.has(signalName)) {
169
+ const handler = typeof newValue === "function" ? newValue : undefined;
170
+ this.signalStore.set(this.container, signalName, handler);
171
+ }
172
+ else if (newValue !== undefined) {
173
+ this.setProperty(name, newValue);
174
+ }
175
+ }
176
+ }
177
+ updateEventControllerProp(propName, handler) {
178
+ switch (propName) {
179
+ case "onEnter":
180
+ case "onLeave":
181
+ case "onMotion": {
182
+ if (!this.motionController) {
183
+ this.motionController = new Gtk.EventControllerMotion();
184
+ this.container.addController(this.motionController);
185
+ }
186
+ const signalName = propName === "onEnter" ? "enter" : propName === "onLeave" ? "leave" : "motion";
187
+ this.signalStore.set(this.motionController, signalName, handler);
188
+ break;
189
+ }
190
+ case "onPressed":
191
+ case "onReleased": {
192
+ if (!this.clickController) {
193
+ this.clickController = new Gtk.GestureClick();
194
+ this.container.addController(this.clickController);
195
+ }
196
+ const signalName = propName === "onPressed" ? "pressed" : "released";
197
+ this.signalStore.set(this.clickController, signalName, handler);
198
+ break;
199
+ }
200
+ case "onKeyPressed":
201
+ case "onKeyReleased": {
202
+ if (!this.keyController) {
203
+ this.keyController = new Gtk.EventControllerKey();
204
+ this.container.addController(this.keyController);
205
+ }
206
+ const signalName = propName === "onKeyPressed" ? "key-pressed" : "key-released";
207
+ this.signalStore.set(this.keyController, signalName, handler);
208
+ break;
209
+ }
210
+ case "onScroll": {
211
+ if (!this.scrollController) {
212
+ this.scrollController = new Gtk.EventControllerScroll(Gtk.EventControllerScrollFlags.BOTH_AXES);
213
+ this.container.addController(this.scrollController);
214
+ }
215
+ this.signalStore.set(this.scrollController, "scroll", handler);
216
+ break;
217
+ }
218
+ case "onNotify": {
219
+ const wrappedHandler = handler
220
+ ? (_obj, pspec) => {
221
+ handler(pspec.getName());
222
+ }
223
+ : undefined;
224
+ this.signalStore.set(this.container, "notify", wrappedHandler);
225
+ break;
226
+ }
227
+ }
228
+ }
229
+ propNameToSignalName(name) {
230
+ return name
231
+ .slice(2)
232
+ .replace(/([A-Z])/g, "-$1")
233
+ .toLowerCase()
234
+ .replace(/^-/, "");
235
+ }
236
+ setProperty(key, value) {
237
+ const WidgetClass = this.container.constructor;
238
+ const [getterName, setterName] = PROPS[WidgetClass.glibTypeName]?.[key] || [];
239
+ const setter = this.container[setterName];
240
+ const getter = this.container[getterName];
241
+ if (getter && typeof getter === "function") {
242
+ const currentValue = getter.call(this.container);
243
+ if (currentValue === value) {
244
+ return;
245
+ }
246
+ if (currentValue instanceof NativeObject && currentValue.equals(value)) {
247
+ return;
248
+ }
249
+ }
250
+ if (setter && typeof setter === "function") {
251
+ const editable = isEditable(this.container) ? this.container : null;
252
+ const shouldPreserveCursor = key === "text" && editable !== null;
253
+ const cursorPosition = shouldPreserveCursor ? editable.getPosition() : 0;
254
+ setter.call(this.container, value);
255
+ if (shouldPreserveCursor && editable !== null) {
256
+ const textLength = editable.getText().length;
257
+ editable.setPosition(Math.min(cursorPosition, textLength));
258
+ }
259
+ }
5
260
  }
6
261
  }
262
+ registerNodeClass(WidgetNode);
@@ -1,12 +1 @@
1
- import * as Gtk from "@gtkx/ffi/gtk";
2
- import type { Props } from "../factory.js";
3
- import { Node } from "../node.js";
4
- export declare class WindowNode extends Node<Gtk.Window> {
5
- static consumedPropNames: string[];
6
- static matches(type: string): boolean;
7
- protected isStandalone(): boolean;
8
- protected createWidget(type: string, _props: Props): Gtk.Window;
9
- mount(): void;
10
- unmount(): void;
11
- updateProps(oldProps: Props, newProps: Props): void;
12
- }
1
+ export {};
@@ -1,44 +1,83 @@
1
- import { getApplication } from "@gtkx/ffi";
2
1
  import * as Adw from "@gtkx/ffi/adw";
3
2
  import * as Gtk from "@gtkx/ffi/gtk";
4
- import { Node, normalizeWidgetType } from "../node.js";
5
- const WINDOW_TYPES = new Set(["Window", "ApplicationWindow", "AdwWindow", "AdwApplicationWindow"]);
6
- export class WindowNode extends Node {
7
- static consumedPropNames = ["defaultWidth", "defaultHeight"];
8
- static matches(type) {
9
- return WINDOW_TYPES.has(normalizeWidgetType(type));
10
- }
11
- isStandalone() {
12
- return true;
13
- }
14
- createWidget(type, _props) {
15
- const widgetType = normalizeWidgetType(type);
16
- if (widgetType === "ApplicationWindow") {
17
- return new Gtk.ApplicationWindow(getApplication());
3
+ import { registerNodeClass } from "../registry.js";
4
+ import { filterProps, isContainerType } from "./internal/utils.js";
5
+ import { Menu } from "./models/menu.js";
6
+ import { WidgetNode } from "./widget.js";
7
+ const PROPS = ["defaultWidth", "defaultHeight"];
8
+ class WindowNode extends WidgetNode {
9
+ static priority = 1;
10
+ menu;
11
+ static matches(_type, containerOrClass) {
12
+ return isContainerType(Gtk.Window, containerOrClass);
13
+ }
14
+ static createContainer(props, containerClass, rootContainer) {
15
+ const WindowClass = containerClass;
16
+ if (isContainerType(Gtk.ApplicationWindow, WindowClass) ||
17
+ isContainerType(Adw.ApplicationWindow, WindowClass)) {
18
+ if (!(rootContainer instanceof Gtk.Application)) {
19
+ throw new Error("Expected ApplicationWindow to be created within Application");
20
+ }
21
+ if (isContainerType(Adw.ApplicationWindow, WindowClass)) {
22
+ return new Adw.ApplicationWindow(rootContainer);
23
+ }
24
+ return new Gtk.ApplicationWindow(rootContainer);
18
25
  }
19
- if (widgetType === "AdwApplicationWindow") {
20
- return new Adw.ApplicationWindow(getApplication());
26
+ return WidgetNode.createContainer(props, containerClass);
27
+ }
28
+ constructor(typeName, props, container, rootContainer) {
29
+ super(typeName, props, container, rootContainer);
30
+ const application = rootContainer instanceof Gtk.Application ? rootContainer : undefined;
31
+ const actionMap = container instanceof Gtk.ApplicationWindow ? container : undefined;
32
+ this.menu = new Menu("root", {}, actionMap, application);
33
+ }
34
+ appendChild(child) {
35
+ if (child.container instanceof Gtk.Window) {
36
+ child.container.setTransientFor(this.container);
37
+ return;
21
38
  }
22
- if (widgetType === "AdwWindow") {
23
- return new Adw.Window();
39
+ this.menu.appendChild(child);
40
+ if (child instanceof WidgetNode &&
41
+ (this.container instanceof Adw.ApplicationWindow || this.container instanceof Adw.Window)) {
42
+ this.container.setContent(child.container);
43
+ return;
24
44
  }
25
- return new Gtk.Window();
45
+ super.appendChild(child);
46
+ }
47
+ removeChild(child) {
48
+ if (child.container instanceof Gtk.Window) {
49
+ child.container.setTransientFor(undefined);
50
+ return;
51
+ }
52
+ this.menu.removeChild(child);
53
+ if (child instanceof WidgetNode &&
54
+ (this.container instanceof Adw.ApplicationWindow || this.container instanceof Adw.Window)) {
55
+ this.container.setContent(undefined);
56
+ return;
57
+ }
58
+ super.removeChild(child);
59
+ }
60
+ insertBefore(child, before) {
61
+ this.menu.insertBefore(child, before);
62
+ this.appendChild(child);
26
63
  }
27
64
  mount() {
28
- this.widget.present();
65
+ this.container.present();
66
+ super.mount();
29
67
  }
30
68
  unmount() {
31
- this.widget.destroy();
69
+ this.container.destroy();
32
70
  super.unmount();
33
71
  }
34
72
  updateProps(oldProps, newProps) {
35
- const widthChanged = oldProps.defaultWidth !== newProps.defaultWidth;
36
- const heightChanged = oldProps.defaultHeight !== newProps.defaultHeight;
37
- if (widthChanged || heightChanged) {
73
+ if (!oldProps ||
74
+ oldProps.defaultWidth !== newProps.defaultWidth ||
75
+ oldProps.defaultHeight !== newProps.defaultHeight) {
38
76
  const width = newProps.defaultWidth ?? -1;
39
77
  const height = newProps.defaultHeight ?? -1;
40
- this.widget.setDefaultSize(width, height);
78
+ this.container.setDefaultSize(width, height);
41
79
  }
42
- super.updateProps(oldProps, newProps);
80
+ super.updateProps(filterProps(oldProps ?? {}, PROPS), filterProps(newProps, PROPS));
43
81
  }
44
82
  }
83
+ registerNodeClass(WindowNode);
package/dist/portal.d.ts CHANGED
@@ -1,23 +1,28 @@
1
1
  import type { ReactNode, ReactPortal } from "react";
2
+ import type { Container } from "./types.js";
2
3
  /**
3
- * Creates a portal that renders children into a different GTK widget container.
4
+ * Creates a React portal for rendering children into a different part of the widget tree.
4
5
  *
5
- * Similar to ReactDOM.createPortal, this allows you to render a subtree into
6
- * a different part of the widget tree.
6
+ * Portals are useful for rendering dialogs, tooltips, or other floating content
7
+ * that should visually appear outside its parent component's boundaries.
7
8
  *
8
- * When called without a container argument, the portal renders at the root level.
9
- * This is useful for dialogs which don't need a parent container.
10
- *
11
- * Implementation note: ReactPortal is an opaque type, so we manually construct
12
- * the internal representation required by custom reconcilers.
9
+ * @param children - The React elements to render in the portal
10
+ * @param container - The target container widget to render into
11
+ * @param key - Optional key for the portal element
12
+ * @returns A ReactPortal element
13
13
  *
14
14
  * @example
15
15
  * ```tsx
16
- * // Render dialog at root level (no container needed)
17
- * {createPortal(<AboutDialog programName="My App" />)}
16
+ * import { createPortal } from "@gtkx/react";
18
17
  *
19
- * // Render into a specific container
20
- * {createPortal(<Label label="This is in the Box" />, boxRef.current)}
18
+ * const Modal = ({ container, children }) => {
19
+ * return createPortal(
20
+ * <GtkWindow modal>
21
+ * {children}
22
+ * </GtkWindow>,
23
+ * container
24
+ * );
25
+ * };
21
26
  * ```
22
27
  */
23
- export declare const createPortal: (children: ReactNode, container?: unknown, key?: string | null) => ReactPortal;
28
+ export declare const createPortal: (children: ReactNode, container: Container, key?: string | null) => ReactPortal;