@gtkx/react 0.19.0 → 0.20.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 (189) hide show
  1. package/dist/components/list.d.ts +35 -0
  2. package/dist/components/list.d.ts.map +1 -0
  3. package/dist/components/list.js +40 -0
  4. package/dist/components/list.js.map +1 -0
  5. package/dist/generated/internal.d.ts +5 -6
  6. package/dist/generated/internal.d.ts.map +1 -1
  7. package/dist/generated/internal.js +3473 -260
  8. package/dist/generated/internal.js.map +1 -1
  9. package/dist/generated/jsx.d.ts +0 -324
  10. package/dist/generated/jsx.d.ts.map +1 -1
  11. package/dist/generated/jsx.js +0 -324
  12. package/dist/generated/jsx.js.map +1 -1
  13. package/dist/host-config.d.ts.map +1 -1
  14. package/dist/host-config.js +2 -0
  15. package/dist/host-config.js.map +1 -1
  16. package/dist/jsx.d.ts +42 -105
  17. package/dist/jsx.d.ts.map +1 -1
  18. package/dist/jsx.js +2 -66
  19. package/dist/jsx.js.map +1 -1
  20. package/dist/metadata.d.ts.map +1 -1
  21. package/dist/metadata.js +7 -3
  22. package/dist/metadata.js.map +1 -1
  23. package/dist/node.d.ts +0 -4
  24. package/dist/node.d.ts.map +1 -1
  25. package/dist/node.js +19 -41
  26. package/dist/node.js.map +1 -1
  27. package/dist/nodes/application.d.ts.map +1 -1
  28. package/dist/nodes/application.js +4 -0
  29. package/dist/nodes/application.js.map +1 -1
  30. package/dist/nodes/column-view-column.d.ts +19 -19
  31. package/dist/nodes/column-view-column.d.ts.map +1 -1
  32. package/dist/nodes/column-view-column.js +130 -119
  33. package/dist/nodes/column-view-column.js.map +1 -1
  34. package/dist/nodes/event-controller.d.ts.map +1 -1
  35. package/dist/nodes/event-controller.js +3 -9
  36. package/dist/nodes/event-controller.js.map +1 -1
  37. package/dist/nodes/internal/accessible.d.ts.map +1 -1
  38. package/dist/nodes/internal/accessible.js.map +1 -1
  39. package/dist/nodes/internal/bound-item.d.ts +4 -0
  40. package/dist/nodes/internal/bound-item.d.ts.map +1 -0
  41. package/dist/nodes/internal/bound-item.js +2 -0
  42. package/dist/nodes/internal/bound-item.js.map +1 -0
  43. package/dist/nodes/internal/construct.d.ts +1 -8
  44. package/dist/nodes/internal/construct.d.ts.map +1 -1
  45. package/dist/nodes/internal/construct.js +30 -54
  46. package/dist/nodes/internal/construct.js.map +1 -1
  47. package/dist/nodes/internal/widget.d.ts.map +1 -1
  48. package/dist/nodes/internal/widget.js +4 -1
  49. package/dist/nodes/internal/widget.js.map +1 -1
  50. package/dist/nodes/list-item-node.d.ts +12 -0
  51. package/dist/nodes/list-item-node.d.ts.map +1 -0
  52. package/dist/nodes/list-item-node.js +23 -0
  53. package/dist/nodes/list-item-node.js.map +1 -0
  54. package/dist/nodes/list.d.ts +100 -0
  55. package/dist/nodes/list.d.ts.map +1 -0
  56. package/dist/nodes/list.js +950 -0
  57. package/dist/nodes/list.js.map +1 -0
  58. package/dist/nodes/notebook-page.d.ts.map +1 -1
  59. package/dist/nodes/notebook-page.js +4 -0
  60. package/dist/nodes/notebook-page.js.map +1 -1
  61. package/dist/nodes/widget.d.ts.map +1 -1
  62. package/dist/nodes/widget.js +9 -8
  63. package/dist/nodes/widget.js.map +1 -1
  64. package/dist/nodes/window.d.ts.map +1 -1
  65. package/dist/nodes/window.js +2 -2
  66. package/dist/nodes/window.js.map +1 -1
  67. package/dist/registry.d.ts +0 -2
  68. package/dist/registry.d.ts.map +1 -1
  69. package/dist/registry.js +4 -13
  70. package/dist/registry.js.map +1 -1
  71. package/dist/types.d.ts +2 -2
  72. package/dist/types.d.ts.map +1 -1
  73. package/package.json +5 -4
  74. package/src/components/list.tsx +83 -0
  75. package/src/generated/internal.ts +3479 -258
  76. package/src/generated/jsx.ts +0 -324
  77. package/src/host-config.ts +2 -0
  78. package/src/jsx.ts +49 -141
  79. package/src/metadata.ts +6 -3
  80. package/src/node.ts +23 -39
  81. package/src/nodes/application.ts +5 -0
  82. package/src/nodes/column-view-column.ts +125 -128
  83. package/src/nodes/event-controller.ts +3 -11
  84. package/src/nodes/internal/accessible.ts +0 -1
  85. package/src/nodes/internal/bound-item.ts +4 -0
  86. package/src/nodes/internal/construct.ts +38 -68
  87. package/src/nodes/internal/widget.ts +3 -1
  88. package/src/nodes/list-item-node.ts +29 -0
  89. package/src/nodes/list.ts +1082 -0
  90. package/src/nodes/notebook-page.ts +4 -0
  91. package/src/nodes/widget.ts +8 -13
  92. package/src/nodes/window.ts +2 -2
  93. package/src/registry.ts +11 -19
  94. package/src/types.ts +7 -2
  95. package/dist/fiber-root.d.ts +0 -4
  96. package/dist/fiber-root.d.ts.map +0 -1
  97. package/dist/fiber-root.js +0 -6
  98. package/dist/fiber-root.js.map +0 -1
  99. package/dist/nodes/column-view.d.ts +0 -37
  100. package/dist/nodes/column-view.d.ts.map +0 -1
  101. package/dist/nodes/column-view.js +0 -205
  102. package/dist/nodes/column-view.js.map +0 -1
  103. package/dist/nodes/drop-down.d.ts +0 -37
  104. package/dist/nodes/drop-down.d.ts.map +0 -1
  105. package/dist/nodes/drop-down.js +0 -231
  106. package/dist/nodes/drop-down.js.map +0 -1
  107. package/dist/nodes/grid-view.d.ts +0 -30
  108. package/dist/nodes/grid-view.d.ts.map +0 -1
  109. package/dist/nodes/grid-view.js +0 -90
  110. package/dist/nodes/grid-view.js.map +0 -1
  111. package/dist/nodes/internal/base-item-renderer.d.ts +0 -28
  112. package/dist/nodes/internal/base-item-renderer.d.ts.map +0 -1
  113. package/dist/nodes/internal/base-item-renderer.js +0 -85
  114. package/dist/nodes/internal/base-item-renderer.js.map +0 -1
  115. package/dist/nodes/internal/grid-item-renderer.d.ts +0 -20
  116. package/dist/nodes/internal/grid-item-renderer.d.ts.map +0 -1
  117. package/dist/nodes/internal/grid-item-renderer.js +0 -66
  118. package/dist/nodes/internal/grid-item-renderer.js.map +0 -1
  119. package/dist/nodes/internal/header-item-renderer.d.ts +0 -23
  120. package/dist/nodes/internal/header-item-renderer.d.ts.map +0 -1
  121. package/dist/nodes/internal/header-item-renderer.js +0 -87
  122. package/dist/nodes/internal/header-item-renderer.js.map +0 -1
  123. package/dist/nodes/internal/header-renderer-manager.d.ts +0 -13
  124. package/dist/nodes/internal/header-renderer-manager.d.ts.map +0 -1
  125. package/dist/nodes/internal/header-renderer-manager.js +0 -20
  126. package/dist/nodes/internal/header-renderer-manager.js.map +0 -1
  127. package/dist/nodes/internal/list-item-renderer.d.ts +0 -27
  128. package/dist/nodes/internal/list-item-renderer.d.ts.map +0 -1
  129. package/dist/nodes/internal/list-item-renderer.js +0 -131
  130. package/dist/nodes/internal/list-item-renderer.js.map +0 -1
  131. package/dist/nodes/internal/list-store.d.ts +0 -21
  132. package/dist/nodes/internal/list-store.d.ts.map +0 -1
  133. package/dist/nodes/internal/list-store.js +0 -90
  134. package/dist/nodes/internal/list-store.js.map +0 -1
  135. package/dist/nodes/internal/sectioned-list-store.d.ts +0 -50
  136. package/dist/nodes/internal/sectioned-list-store.d.ts.map +0 -1
  137. package/dist/nodes/internal/sectioned-list-store.js +0 -250
  138. package/dist/nodes/internal/sectioned-list-store.js.map +0 -1
  139. package/dist/nodes/internal/selection-helpers.d.ts +0 -12
  140. package/dist/nodes/internal/selection-helpers.d.ts.map +0 -1
  141. package/dist/nodes/internal/selection-helpers.js +0 -25
  142. package/dist/nodes/internal/selection-helpers.js.map +0 -1
  143. package/dist/nodes/internal/selection-model-controller.d.ts +0 -26
  144. package/dist/nodes/internal/selection-model-controller.d.ts.map +0 -1
  145. package/dist/nodes/internal/selection-model-controller.js +0 -82
  146. package/dist/nodes/internal/selection-model-controller.js.map +0 -1
  147. package/dist/nodes/internal/simple-list-store.d.ts +0 -15
  148. package/dist/nodes/internal/simple-list-store.d.ts.map +0 -1
  149. package/dist/nodes/internal/simple-list-store.js +0 -110
  150. package/dist/nodes/internal/simple-list-store.js.map +0 -1
  151. package/dist/nodes/internal/tree-store.d.ts +0 -37
  152. package/dist/nodes/internal/tree-store.d.ts.map +0 -1
  153. package/dist/nodes/internal/tree-store.js +0 -253
  154. package/dist/nodes/internal/tree-store.js.map +0 -1
  155. package/dist/nodes/list-item.d.ts +0 -24
  156. package/dist/nodes/list-item.d.ts.map +0 -1
  157. package/dist/nodes/list-item.js +0 -83
  158. package/dist/nodes/list-item.js.map +0 -1
  159. package/dist/nodes/list-section.d.ts +0 -27
  160. package/dist/nodes/list-section.d.ts.map +0 -1
  161. package/dist/nodes/list-section.js +0 -43
  162. package/dist/nodes/list-section.js.map +0 -1
  163. package/dist/nodes/list-view.d.ts +0 -32
  164. package/dist/nodes/list-view.d.ts.map +0 -1
  165. package/dist/nodes/list-view.js +0 -123
  166. package/dist/nodes/list-view.js.map +0 -1
  167. package/dist/nodes/models/list.d.ts +0 -39
  168. package/dist/nodes/models/list.d.ts.map +0 -1
  169. package/dist/nodes/models/list.js +0 -207
  170. package/dist/nodes/models/list.js.map +0 -1
  171. package/src/fiber-root.ts +0 -20
  172. package/src/nodes/column-view.ts +0 -262
  173. package/src/nodes/drop-down.ts +0 -284
  174. package/src/nodes/grid-view.ts +0 -119
  175. package/src/nodes/internal/base-item-renderer.ts +0 -107
  176. package/src/nodes/internal/grid-item-renderer.ts +0 -78
  177. package/src/nodes/internal/header-item-renderer.ts +0 -105
  178. package/src/nodes/internal/header-renderer-manager.ts +0 -33
  179. package/src/nodes/internal/list-item-renderer.ts +0 -162
  180. package/src/nodes/internal/list-store.ts +0 -107
  181. package/src/nodes/internal/sectioned-list-store.ts +0 -287
  182. package/src/nodes/internal/selection-helpers.ts +0 -35
  183. package/src/nodes/internal/selection-model-controller.ts +0 -119
  184. package/src/nodes/internal/simple-list-store.ts +0 -116
  185. package/src/nodes/internal/tree-store.ts +0 -289
  186. package/src/nodes/list-item.ts +0 -107
  187. package/src/nodes/list-section.ts +0 -64
  188. package/src/nodes/list-view.ts +0 -164
  189. package/src/nodes/models/list.ts +0 -250
package/src/node.ts CHANGED
@@ -14,8 +14,6 @@ export class Node<TContainer = any, TProps = any, TParent extends Node = any, TC
14
14
  rootContainer: Container;
15
15
  parent: TParent | null = null;
16
16
  children: TChild[] = [];
17
- private childIndices = new Map<TChild, number>();
18
- private childrenDirty = false;
19
17
 
20
18
  constructor(typeName: string, props: TProps, container: TContainer, rootContainer: Container) {
21
19
  this.typeName = typeName;
@@ -37,6 +35,7 @@ export class Node<TContainer = any, TProps = any, TParent extends Node = any, TC
37
35
  if (parent !== null && !this.isValidParent(parent)) {
38
36
  throw new Error(`Cannot add '${this.typeName}' to '${parent.typeName}'`);
39
37
  }
38
+
40
39
  this.parent = parent;
41
40
  }
42
41
 
@@ -45,64 +44,49 @@ export class Node<TContainer = any, TProps = any, TParent extends Node = any, TC
45
44
  }
46
45
 
47
46
  public appendChild(child: TChild): void {
48
- if (this.childrenDirty) this.flushChildRemovals();
49
47
  if (!this.isValidChild(child)) {
50
48
  throw new Error(`Cannot append '${child.typeName}' to '${this.typeName}'`);
51
49
  }
52
- this.childIndices.set(child, this.children.length);
50
+
51
+ const existingIndex = this.children.indexOf(child);
52
+ if (existingIndex !== -1) {
53
+ this.children.splice(existingIndex, 1);
54
+ }
53
55
  this.children.push(child);
54
- child.setParent(this);
56
+
57
+ if (child.parent !== this) {
58
+ child.setParent(this);
59
+ }
55
60
  }
56
61
 
57
62
  public removeChild(child: TChild): void {
58
- if (!this.childIndices.has(child)) return;
59
63
  child.setParent(null);
60
- this.childIndices.delete(child);
61
- if (!this.childrenDirty) {
62
- this.childrenDirty = true;
63
- queueMicrotask(() => this.flushChildRemovals());
64
+ const index = this.children.indexOf(child);
65
+
66
+ if (index !== -1) {
67
+ this.children.splice(index, 1);
64
68
  }
65
69
  }
66
70
 
67
71
  public insertBefore(child: TChild, before: TChild): void {
68
- if (this.childrenDirty) this.flushChildRemovals();
69
-
70
- const beforeIndex = this.childIndices.get(before);
71
- if (beforeIndex === undefined) {
72
- throw new Error(`Cannot find 'before' child '${before.typeName}' in '${this.typeName}'`);
72
+ if (!this.isValidChild(child)) {
73
+ throw new Error(`Cannot insert '${child.typeName}' into '${this.typeName}'`);
73
74
  }
74
75
 
75
- const existingIndex = this.childIndices.get(child);
76
- if (existingIndex !== undefined) {
76
+ const existingIndex = this.children.indexOf(child);
77
+ if (existingIndex !== -1) {
77
78
  this.children.splice(existingIndex, 1);
78
- const adjustedIndex = existingIndex < beforeIndex ? beforeIndex - 1 : beforeIndex;
79
- this.children.splice(adjustedIndex, 0, child);
80
- this.rebuildChildIndices(Math.min(existingIndex, adjustedIndex));
81
- return;
82
79
  }
83
80
 
84
- if (!this.isValidChild(child)) {
85
- throw new Error(`Cannot insert '${child.typeName}' into '${this.typeName}'`);
81
+ const beforeIndex = this.children.indexOf(before);
82
+ if (beforeIndex === -1) {
83
+ throw new Error(`Cannot find 'before' child '${before.typeName}' in '${this.typeName}'`);
86
84
  }
87
85
 
88
86
  this.children.splice(beforeIndex, 0, child);
89
- this.rebuildChildIndices(beforeIndex);
90
- child.setParent(this);
91
- }
92
-
93
- private flushChildRemovals(): void {
94
- if (!this.childrenDirty) return;
95
- this.childrenDirty = false;
96
- this.children = this.children.filter((c) => this.childIndices.has(c));
97
- this.childIndices.clear();
98
- for (let i = 0; i < this.children.length; i++) {
99
- this.childIndices.set(this.children[i] as TChild, i);
100
- }
101
- }
102
87
 
103
- private rebuildChildIndices(fromIndex: number): void {
104
- for (let i = fromIndex; i < this.children.length; i++) {
105
- this.childIndices.set(this.children[i] as TChild, i);
88
+ if (child.parent !== this) {
89
+ child.setParent(this);
106
90
  }
107
91
  }
108
92
 
@@ -3,6 +3,7 @@ import { Node } from "../node.js";
3
3
  import type { Container, Props } from "../types.js";
4
4
  import { MenuNode } from "./menu.js";
5
5
  import { MenuModel } from "./models/menu.js";
6
+ import { WindowNode } from "./window.js";
6
7
 
7
8
  export class ApplicationNode extends Node<Gtk.Application, Props, Node, Node> {
8
9
  private menu: MenuModel;
@@ -51,6 +52,10 @@ export class ApplicationNode extends Node<Gtk.Application, Props, Node, Node> {
51
52
  return;
52
53
  }
53
54
 
55
+ if (child instanceof WindowNode) {
56
+ child.container.setVisible(false);
57
+ }
58
+
54
59
  super.removeChild(child);
55
60
  }
56
61
  }
@@ -1,19 +1,34 @@
1
+ import { getNativeId } from "@gtkx/ffi";
1
2
  import * as Gio from "@gtkx/ffi/gio";
3
+ import type * as GObject from "@gtkx/ffi/gobject";
2
4
  import * as Gtk from "@gtkx/ffi/gtk";
3
- import type { ColumnViewColumnProps } from "../jsx.js";
5
+ import type { ColumnViewColumnProps, ListItem } from "../jsx.js";
4
6
  import type { Node } from "../node.js";
5
7
  import type { Container } from "../types.js";
6
- import { GridItemRenderer } from "./internal/grid-item-renderer.js";
7
- import { ListItemRenderer } from "./internal/list-item-renderer.js";
8
- import type { ListStore } from "./internal/list-store.js";
8
+ import type { BoundItem } from "./internal/bound-item.js";
9
9
  import { hasChanged } from "./internal/props.js";
10
- import type { TreeStore } from "./internal/tree-store.js";
11
10
  import { MenuNode } from "./menu.js";
12
11
  import { MenuModel } from "./models/menu.js";
13
12
  import { VirtualNode } from "./virtual.js";
14
13
  import { WidgetNode } from "./widget.js";
15
14
 
16
- export class ColumnViewColumnNode extends VirtualNode<ColumnViewColumnProps, WidgetNode<Gtk.ColumnView>, MenuNode> {
15
+ const UNBOUND_POSITION = -1;
16
+
17
+ export class ColumnViewColumnNode extends VirtualNode<ColumnViewColumnProps, WidgetNode, MenuNode> {
18
+ private column: Gtk.ColumnViewColumn | null = null;
19
+ private columnFactory: Gtk.SignalListItemFactory | null = null;
20
+ private containers = new Map<Gtk.ListItem, number>();
21
+ private containerKeys = new Map<Gtk.ListItem, string>();
22
+ private menu: MenuModel;
23
+ private actionGroup: Gio.SimpleActionGroup;
24
+
25
+ constructor(typeName: string, props: ColumnViewColumnProps, container: undefined, rootContainer: Container) {
26
+ super(typeName, props, container, rootContainer);
27
+ this.actionGroup = new Gio.SimpleActionGroup();
28
+ this.menu = new MenuModel("root", {}, rootContainer, this.actionGroup);
29
+ this.menu.setActionMap(this.actionGroup, props.id);
30
+ }
31
+
17
32
  public override isValidChild(child: Node): boolean {
18
33
  return child instanceof MenuNode;
19
34
  }
@@ -21,172 +36,154 @@ export class ColumnViewColumnNode extends VirtualNode<ColumnViewColumnProps, Wid
21
36
  public override isValidParent(parent: Node): boolean {
22
37
  return parent instanceof WidgetNode && parent.container instanceof Gtk.ColumnView;
23
38
  }
24
- private column: Gtk.ColumnViewColumn;
25
- private treeRenderer: ListItemRenderer | null;
26
- private flatRenderer: GridItemRenderer | null = null;
27
- private menu: MenuModel | null = null;
28
- private actionGroup: Gio.SimpleActionGroup | null = null;
29
- private columnView: Gtk.ColumnView | null = null;
30
39
 
31
- constructor(typeName: string, props: ColumnViewColumnProps, container: undefined, rootContainer: Container) {
32
- super(typeName, props, container, rootContainer);
33
- this.treeRenderer = new ListItemRenderer(this.signalStore);
34
- this.column = new Gtk.ColumnViewColumn();
35
- this.column.setFactory(this.treeRenderer.getFactory());
40
+ public override finalizeInitialChildren(props: ColumnViewColumnProps): boolean {
41
+ this.setupFactory();
42
+ this.setupColumn(props);
43
+ this.updateHeaderMenu();
44
+ return false;
45
+ }
46
+
47
+ public override commitUpdate(oldProps: ColumnViewColumnProps | null, newProps: ColumnViewColumnProps): void {
48
+ super.commitUpdate(oldProps, newProps);
49
+ if (oldProps === null) return;
50
+ this.applyColumnProps(oldProps, newProps);
36
51
  }
37
52
 
38
53
  public override appendChild(child: MenuNode): void {
39
- this.initMenu();
40
- this.menu?.appendChild(child);
54
+ this.menu.appendChild(child);
55
+ this.updateHeaderMenu();
41
56
  }
42
57
 
43
58
  public override insertBefore(child: MenuNode, before: MenuNode): void {
44
- this.initMenu();
45
- if (before instanceof MenuNode) {
46
- this.menu?.insertBefore(child, before);
47
- } else {
48
- this.menu?.appendChild(child);
49
- }
59
+ this.menu.insertBefore(child, before);
60
+ this.updateHeaderMenu();
50
61
  }
51
62
 
52
63
  public override removeChild(child: MenuNode): void {
53
- this.menu?.removeChild(child);
54
-
55
- if (this.menu && this.menu.getMenu().getNItems() === 0) {
56
- this.cleanupMenu();
57
- }
58
- }
59
-
60
- public override commitUpdate(oldProps: ColumnViewColumnProps | null, newProps: ColumnViewColumnProps): void {
61
- super.commitUpdate(oldProps, newProps);
62
- this.applyOwnProps(oldProps, newProps);
64
+ this.menu.removeChild(child);
65
+ this.updateHeaderMenu();
63
66
  }
64
67
 
65
68
  public override detachDeletedInstance(): void {
66
- this.cleanupMenu();
67
- this.treeRenderer?.dispose();
68
- this.flatRenderer?.dispose();
69
69
  super.detachDeletedInstance();
70
70
  }
71
71
 
72
72
  public getColumn(): Gtk.ColumnViewColumn {
73
+ if (!this.column) throw new Error("ColumnViewColumn not initialized");
73
74
  return this.column;
74
75
  }
75
76
 
76
- public rebindItem(id: string): void {
77
- this.treeRenderer?.rebindItem(id);
78
- this.flatRenderer?.rebindItem(id);
79
- }
80
-
81
- public setStore(model: TreeStore | null): void {
82
- this.treeRenderer?.setStore(model);
83
- }
84
-
85
- public setFlatStore(store: ListStore): void {
86
- if (this.treeRenderer) {
87
- this.treeRenderer.dispose();
88
- this.treeRenderer = null;
89
- }
90
- if (!this.flatRenderer) {
91
- this.flatRenderer = new GridItemRenderer(this.signalStore);
92
- this.flatRenderer.setRenderFn(this.props.renderCell);
93
- this.column.setFactory(this.flatRenderer.getFactory());
94
- }
95
- this.flatRenderer.setStore(store);
96
- }
77
+ public collectBoundItems(flatItems: ListItem[]): BoundItem[] {
78
+ const { renderCell } = this.props;
79
+ if (!renderCell) return [];
97
80
 
98
- public setEstimatedRowHeight(height: number | null): void {
99
- this.treeRenderer?.setEstimatedItemHeight(height);
100
- this.flatRenderer?.setEstimatedItemHeight(height);
101
- }
81
+ const items: BoundItem[] = [];
102
82
 
103
- public attachToColumnView(columnView: Gtk.ColumnView): void {
104
- this.columnView = columnView;
83
+ for (const [container, position] of this.containers) {
84
+ if (position === UNBOUND_POSITION) continue;
105
85
 
106
- if (this.actionGroup) {
107
- this.columnView.insertActionGroup(this.props.id, this.actionGroup);
108
- }
109
- }
86
+ const key = this.containerKeys.get(container);
87
+ if (!key) continue;
110
88
 
111
- public detachFromColumnView(): void {
112
- if (this.columnView && this.actionGroup) {
113
- this.columnView.insertActionGroup(this.props.id, null);
89
+ const item = flatItems[position];
90
+ if (!item) continue;
91
+ const content = renderCell(item.value);
92
+ items.push([content, container, key]);
114
93
  }
115
94
 
116
- this.columnView = null;
95
+ return items;
117
96
  }
118
97
 
119
- private initMenu(): void {
120
- if (this.menu) return;
121
-
122
- this.actionGroup = new Gio.SimpleActionGroup();
123
- this.menu = new MenuModel("root", {}, this.rootContainer);
124
- this.menu.setActionMap(this.actionGroup, this.props.id);
125
- this.column.setHeaderMenu(this.menu.getMenu());
98
+ public installActionGroup(widget: Gtk.Widget): void {
99
+ widget.insertActionGroup(this.props.id, this.actionGroup);
100
+ }
126
101
 
127
- if (this.columnView) {
128
- this.columnView.insertActionGroup(this.props.id, this.actionGroup);
129
- }
102
+ public uninstallActionGroup(widget: Gtk.Widget): void {
103
+ widget.insertActionGroup(this.props.id, null);
130
104
  }
131
105
 
132
- private cleanupMenu(): void {
133
- if (!this.menu) return;
106
+ private setupFactory(): void {
107
+ this.columnFactory = new Gtk.SignalListItemFactory();
134
108
 
135
- this.column.setHeaderMenu(null);
109
+ this.columnFactory.connect("setup", (_self: GObject.Object, obj: GObject.Object) => {
110
+ const listItem = obj as unknown as Gtk.ListItem;
111
+ const key = String(getNativeId(listItem.handle));
112
+ const placeholder = new Gtk.Box();
113
+ const { width, height } = this.getParentEstimatedItemSize();
114
+ placeholder.setSizeRequest(width, height);
115
+ listItem.setChild(placeholder);
116
+ this.containers.set(listItem, UNBOUND_POSITION);
117
+ this.containerKeys.set(listItem, key);
118
+ });
136
119
 
137
- if (this.columnView && this.actionGroup) {
138
- this.columnView.insertActionGroup(this.props.id, null);
139
- }
120
+ this.columnFactory.connect("bind", (_self: GObject.Object, obj: GObject.Object) => {
121
+ const listItem = obj as unknown as Gtk.ListItem;
122
+ this.containers.set(listItem, listItem.getPosition());
123
+ this.scheduleParentUpdate();
124
+ });
140
125
 
141
- this.menu = null;
142
- this.actionGroup = null;
126
+ this.columnFactory.connect("unbind", (_self: GObject.Object, obj: GObject.Object) => {
127
+ const listItem = obj as unknown as Gtk.ListItem;
128
+ this.containers.set(listItem, UNBOUND_POSITION);
129
+ this.scheduleParentUpdate();
130
+ });
131
+
132
+ this.columnFactory.connect("teardown", (_self: GObject.Object, obj: GObject.Object) => {
133
+ const listItem = obj as unknown as Gtk.ListItem;
134
+ this.containers.delete(listItem);
135
+ this.containerKeys.delete(listItem);
136
+ listItem.setChild(null);
137
+ });
143
138
  }
144
139
 
145
- private applyOwnProps(oldProps: ColumnViewColumnProps | null, newProps: ColumnViewColumnProps): void {
146
- if (hasChanged(oldProps, newProps, "renderCell")) {
147
- this.treeRenderer?.setRenderFn(newProps.renderCell);
148
- this.flatRenderer?.setRenderFn(newProps.renderCell);
149
- }
140
+ private setupColumn(props: ColumnViewColumnProps): void {
141
+ this.column = new Gtk.ColumnViewColumn(props.title, this.columnFactory);
142
+ this.column.setId(props.id);
150
143
 
151
- if (hasChanged(oldProps, newProps, "title")) {
152
- this.column.setTitle(newProps.title);
153
- }
154
-
155
- if (hasChanged(oldProps, newProps, "expand")) {
156
- this.column.setExpand(newProps.expand ?? false);
157
- }
144
+ if (props.expand !== undefined) this.column.setExpand(props.expand);
145
+ if (props.resizable !== undefined) this.column.setResizable(props.resizable);
146
+ if (props.fixedWidth !== undefined) this.column.setFixedWidth(props.fixedWidth);
147
+ if (props.visible !== undefined) this.column.setVisible(props.visible);
148
+ if (props.sortable) this.column.setSorter(new Gtk.Sorter());
149
+ }
158
150
 
159
- if (hasChanged(oldProps, newProps, "resizable")) {
160
- this.column.setResizable(newProps.resizable ?? false);
161
- }
151
+ private applyColumnProps(oldProps: ColumnViewColumnProps, newProps: ColumnViewColumnProps): void {
152
+ if (!this.column) return;
162
153
 
163
- if (hasChanged(oldProps, newProps, "fixedWidth")) {
164
- this.column.setFixedWidth(newProps.fixedWidth ?? -1);
154
+ if (hasChanged(oldProps, newProps, "title")) this.column.setTitle(newProps.title);
155
+ if (hasChanged(oldProps, newProps, "expand")) this.column.setExpand(newProps.expand ?? false);
156
+ if (hasChanged(oldProps, newProps, "resizable")) this.column.setResizable(newProps.resizable ?? false);
157
+ if (hasChanged(oldProps, newProps, "fixedWidth")) this.column.setFixedWidth(newProps.fixedWidth ?? -1);
158
+ if (hasChanged(oldProps, newProps, "visible")) this.column.setVisible(newProps.visible ?? true);
159
+ if (hasChanged(oldProps, newProps, "sortable")) {
160
+ this.column.setSorter(newProps.sortable ? new Gtk.Sorter() : null);
165
161
  }
162
+ if (hasChanged(oldProps, newProps, "renderCell")) this.scheduleParentUpdate();
163
+ }
166
164
 
167
- if (hasChanged(oldProps, newProps, "id")) {
168
- if (this.columnView && this.actionGroup && oldProps?.id) {
169
- this.columnView.insertActionGroup(oldProps.id, null);
170
- this.columnView.insertActionGroup(newProps.id, this.actionGroup);
171
- }
172
-
173
- this.column.setId(newProps.id);
174
-
175
- if (oldProps && this.menu && this.actionGroup) {
176
- this.menu.setActionMap(this.actionGroup, newProps.id);
177
- }
178
- }
165
+ private updateHeaderMenu(): void {
166
+ if (!this.column) return;
167
+ const menu = this.menu.getMenu();
168
+ this.column.setHeaderMenu(menu.getNItems() > 0 ? menu : null);
169
+ }
179
170
 
180
- if (hasChanged(oldProps, newProps, "visible")) {
181
- this.column.setVisible(newProps.visible ?? true);
171
+ private getParentEstimatedItemSize(): { width: number; height: number } {
172
+ if (this.parent && "getEstimatedItemSize" in this.parent) {
173
+ return (
174
+ this.parent as { getEstimatedItemSize(): { width: number; height: number } }
175
+ ).getEstimatedItemSize();
182
176
  }
177
+ return { width: -1, height: -1 };
178
+ }
183
179
 
184
- if (hasChanged(oldProps, newProps, "sortable")) {
185
- if (newProps.sortable) {
186
- this.column.setSorter(new Gtk.StringSorter());
187
- } else {
188
- this.column.setSorter(null);
189
- }
180
+ private scheduleParentUpdate(): void {
181
+ if (
182
+ this.parent &&
183
+ "scheduleBoundItemsUpdate" in this.parent &&
184
+ typeof this.parent.scheduleBoundItemsUpdate === "function"
185
+ ) {
186
+ (this.parent as { scheduleBoundItemsUpdate(): void }).scheduleBoundItemsUpdate();
190
187
  }
191
188
  }
192
189
  }
@@ -1,9 +1,8 @@
1
1
  import * as Gtk from "@gtkx/ffi/gtk";
2
- import { CONSTRUCTOR_PROPS } from "../generated/internal.js";
3
2
  import { isConstructOnlyProp, resolvePropMeta, resolveSignal } from "../metadata.js";
4
3
  import { Node } from "../node.js";
5
4
  import type { Props } from "../types.js";
6
- import { createContainerWithConstructOnly } from "./internal/construct.js";
5
+ import { createContainerWithProperties } from "./internal/construct.js";
7
6
  import type { SignalHandler } from "./internal/signal-store.js";
8
7
  import { WidgetNode } from "./widget.js";
9
8
 
@@ -18,19 +17,12 @@ export class EventControllerNode<
18
17
  props: Props,
19
18
  containerClass: typeof Gtk.EventController,
20
19
  ): Gtk.EventController {
21
- const typeName = containerClass.glibTypeName;
22
-
23
- if (typeName === "GtkDropTarget") {
20
+ if (containerClass.glibTypeName === "GtkDropTarget") {
24
21
  const actions = (props.actions as number | undefined) ?? 0;
25
22
  return new Gtk.DropTarget(G_TYPE_INVALID, actions);
26
23
  }
27
24
 
28
- const args = (CONSTRUCTOR_PROPS[typeName] ?? []).map((name) => props[name]);
29
-
30
- return createContainerWithConstructOnly(containerClass, props, () => {
31
- // biome-ignore lint/suspicious/noExplicitAny: Dynamic constructor invocation
32
- return new (containerClass as any)(...args);
33
- }) as Gtk.EventController;
25
+ return createContainerWithProperties(containerClass, props) as Gtk.EventController;
34
26
  }
35
27
 
36
28
  public override isValidChild(child: Node): boolean {
@@ -29,7 +29,6 @@ const fromString: CreateValue = (val) => Value.newFromString(val as string);
29
29
  const fromBoolean: CreateValue = (val) => Value.newFromBoolean(val as boolean);
30
30
  const fromInt: CreateValue = (val) => Value.newFromInt(val as number);
31
31
  const fromDouble: CreateValue = (val) => Value.newFromDouble(val as number);
32
-
33
32
  const fromObject: CreateValue = (val) => Value.newFromObject((val as GObject.Object) ?? null);
34
33
 
35
34
  const fromRefList: CreateValue = (val) => {
@@ -0,0 +1,4 @@
1
+ import type { ReactNode } from "react";
2
+ import type { Container } from "../../types.js";
3
+
4
+ export type BoundItem = [ReactNode, Container, string];
@@ -1,90 +1,60 @@
1
- import { type Arg, call, type NativeHandle, registerNativeObject, setInstantiating, type Type } from "@gtkx/ffi";
2
- import { typeFromName } from "@gtkx/ffi/gobject";
3
- import { CONSTRUCT_ONLY_PROPS, CONSTRUCTOR_PROPS } from "../../generated/internal.js";
1
+ import type { NativeClass, Type } from "@gtkx/ffi";
2
+ import { Object as GObject, toValue, type Value } from "@gtkx/ffi/gobject";
3
+ import { CONSTRUCTION_META } from "../../generated/internal.js";
4
4
  import type { Container, ContainerClass, Props } from "../../types.js";
5
5
 
6
- type ConstructOnlyPropInfo = { girName: string; ffiType: Type };
6
+ type ConstructionPropMeta = {
7
+ girName: string;
8
+ ffiType: Type;
9
+ constructOnly?: true;
10
+ };
7
11
 
8
- /**
9
- * Collects construct-only property metadata by walking the class hierarchy.
10
- * Returns all active construct-only props (those with values set in the initial props)
11
- * that are NOT already handled by the designated constructor parameters.
12
- */
13
- function collectActiveConstructOnlyProps(
12
+ function collectConstructionProps(
14
13
  containerClass: ContainerClass,
15
14
  props: Props,
16
15
  ): Array<{ girName: string; ffiType: Type; value: unknown }> {
17
16
  const result: Array<{ girName: string; ffiType: Type; value: unknown }> = [];
18
- const constructorParams = new Set(CONSTRUCTOR_PROPS[containerClass.glibTypeName] ?? []);
19
-
20
- // biome-ignore lint/suspicious/noExplicitAny: Walking static prototype chain
21
- let current: any = containerClass;
22
- while (current?.glibTypeName) {
23
- const propsForType: Record<string, ConstructOnlyPropInfo> | undefined =
24
- CONSTRUCT_ONLY_PROPS[current.glibTypeName as string];
25
- if (propsForType) {
26
- for (const [camelName, meta] of Object.entries(propsForType)) {
27
- if (constructorParams.has(camelName)) continue;
17
+ const seen = new Set<string>();
18
+
19
+ // biome-ignore lint/complexity/noBannedTypes: Walking prototype chain requires Function type
20
+ let current: Function | null = containerClass;
21
+ while (current) {
22
+ const typeName = (current as NativeClass).glibTypeName;
23
+ if (!typeName) break;
24
+
25
+ const meta: Record<string, ConstructionPropMeta> | undefined = CONSTRUCTION_META[typeName];
26
+ if (meta) {
27
+ for (const [camelName, propMeta] of Object.entries(meta)) {
28
+ if (seen.has(camelName)) continue;
29
+ seen.add(camelName);
28
30
  if (props[camelName] !== undefined) {
29
- const rawValue = props[camelName];
30
- const value =
31
- meta.ffiType.type === "gobject" &&
32
- rawValue &&
33
- typeof rawValue === "object" &&
34
- "handle" in rawValue
35
- ? (rawValue as { handle: NativeHandle }).handle
36
- : rawValue;
37
- result.push({ girName: meta.girName, ffiType: meta.ffiType, value });
31
+ result.push({
32
+ girName: propMeta.girName,
33
+ ffiType: propMeta.ffiType,
34
+ value: props[camelName],
35
+ });
38
36
  }
39
37
  }
40
38
  }
39
+
41
40
  current = Object.getPrototypeOf(current);
41
+ if (current === Object || current === Function) break;
42
42
  }
43
43
 
44
44
  return result;
45
45
  }
46
46
 
47
- /**
48
- * Creates a container (widget or controller) with construct-only properties.
49
- *
50
- * When construct-only props are present that aren't handled by the designated
51
- * constructor, uses `g_object_new` to set them during object construction.
52
- * Otherwise falls back to the normal constructor.
53
- */
54
- export function createContainerWithConstructOnly(
55
- containerClass: ContainerClass,
56
- props: Props,
57
- normalConstructor: () => Container,
58
- ): Container {
59
- const constructOnlyArgs = collectActiveConstructOnlyProps(containerClass, props);
60
-
61
- if (constructOnlyArgs.length === 0) {
62
- return normalConstructor();
63
- }
64
-
65
- const typeName = containerClass.glibTypeName;
66
- const gtype = typeFromName(typeName);
47
+ export function createContainerWithProperties(containerClass: ContainerClass, props: Props): Container {
48
+ const gtype = containerClass.getGType();
49
+ const constructionProps = collectConstructionProps(containerClass, props);
67
50
 
68
- const args: Arg[] = [{ type: { type: "int", size: 64, unsigned: true }, value: gtype, optional: false }];
51
+ const names: string[] = [];
52
+ const values: Value[] = [];
69
53
 
70
- for (const { girName, ffiType, value } of constructOnlyArgs) {
71
- args.push({ type: { type: "string", ownership: "borrowed" }, value: girName, optional: false });
72
- args.push({ type: ffiType, value, optional: false });
54
+ for (const { girName, ffiType, value } of constructionProps) {
55
+ names.push(girName);
56
+ values.push(toValue(ffiType, value));
73
57
  }
74
58
 
75
- args.push({ type: { type: "null" }, value: null, optional: false });
76
-
77
- const handle = call("libgobject-2.0.so.0", "g_object_new", args, {
78
- type: "gobject",
79
- ownership: "full",
80
- }) as NativeHandle;
81
-
82
- setInstantiating(true);
83
- // biome-ignore lint/suspicious/noExplicitAny: Dynamic instantiation with isInstantiating flag
84
- const instance = new (containerClass as any)() as Container & { handle: NativeHandle };
85
- setInstantiating(false);
86
- instance.handle = handle;
87
- registerNativeObject(instance);
88
-
89
- return instance;
59
+ return GObject.newWithProperties(gtype, names, values) as unknown as Container;
90
60
  }
@@ -14,6 +14,8 @@ export function detachChild(child: Gtk.Widget, container: Gtk.Widget): void {
14
14
  container.setChild(null);
15
15
  } else if (isRemovable(container)) {
16
16
  container.remove(child);
17
+ } else {
18
+ child.unparent();
17
19
  }
18
20
  }
19
21
 
@@ -27,7 +29,7 @@ export function attachChild(child: Gtk.Widget, container: Gtk.Widget): void {
27
29
  } else if (isSingleChild(container)) {
28
30
  container.setChild(child);
29
31
  } else {
30
- throw new Error(`Cannot attach child to '${container.constructor.name}': container does not support children`);
32
+ child.setParent(container);
31
33
  }
32
34
  }
33
35