@gtkx/react 0.18.9 → 0.19.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 (174) hide show
  1. package/dist/generated/internal.d.ts +6 -0
  2. package/dist/generated/internal.d.ts.map +1 -1
  3. package/dist/generated/internal.js +331 -44
  4. package/dist/generated/internal.js.map +1 -1
  5. package/dist/generated/jsx.d.ts +178 -2
  6. package/dist/generated/jsx.d.ts.map +1 -1
  7. package/dist/generated/jsx.js.map +1 -1
  8. package/dist/host-config.d.ts.map +1 -1
  9. package/dist/host-config.js +46 -10
  10. package/dist/host-config.js.map +1 -1
  11. package/dist/jsx.d.ts +133 -13
  12. package/dist/jsx.d.ts.map +1 -1
  13. package/dist/jsx.js +41 -2
  14. package/dist/jsx.js.map +1 -1
  15. package/dist/metadata.d.ts +1 -0
  16. package/dist/metadata.d.ts.map +1 -1
  17. package/dist/metadata.js +3 -1
  18. package/dist/metadata.js.map +1 -1
  19. package/dist/node.d.ts +2 -0
  20. package/dist/node.d.ts.map +1 -1
  21. package/dist/node.js +22 -6
  22. package/dist/node.js.map +1 -1
  23. package/dist/nodes/column-view-column.d.ts +4 -1
  24. package/dist/nodes/column-view-column.d.ts.map +1 -1
  25. package/dist/nodes/column-view-column.js +29 -8
  26. package/dist/nodes/column-view-column.js.map +1 -1
  27. package/dist/nodes/column-view.d.ts +4 -3
  28. package/dist/nodes/column-view.d.ts.map +1 -1
  29. package/dist/nodes/column-view.js +44 -14
  30. package/dist/nodes/column-view.js.map +1 -1
  31. package/dist/nodes/drop-down.d.ts +12 -2
  32. package/dist/nodes/drop-down.d.ts.map +1 -1
  33. package/dist/nodes/drop-down.js +151 -5
  34. package/dist/nodes/drop-down.js.map +1 -1
  35. package/dist/nodes/event-controller.d.ts +1 -0
  36. package/dist/nodes/event-controller.d.ts.map +1 -1
  37. package/dist/nodes/event-controller.js +11 -3
  38. package/dist/nodes/event-controller.js.map +1 -1
  39. package/dist/nodes/fixed-child.d.ts +1 -2
  40. package/dist/nodes/fixed-child.d.ts.map +1 -1
  41. package/dist/nodes/fixed-child.js +21 -21
  42. package/dist/nodes/fixed-child.js.map +1 -1
  43. package/dist/nodes/font-dialog-button.d.ts +1 -1
  44. package/dist/nodes/font-dialog-button.d.ts.map +1 -1
  45. package/dist/nodes/font-dialog-button.js +8 -0
  46. package/dist/nodes/font-dialog-button.js.map +1 -1
  47. package/dist/nodes/grid-view.d.ts +6 -5
  48. package/dist/nodes/grid-view.d.ts.map +1 -1
  49. package/dist/nodes/grid-view.js +23 -18
  50. package/dist/nodes/grid-view.js.map +1 -1
  51. package/dist/nodes/internal/accessible.d.ts +5 -0
  52. package/dist/nodes/internal/accessible.d.ts.map +1 -0
  53. package/dist/nodes/internal/accessible.js +119 -0
  54. package/dist/nodes/internal/accessible.js.map +1 -0
  55. package/dist/nodes/internal/base-item-renderer.d.ts.map +1 -1
  56. package/dist/nodes/internal/base-item-renderer.js +0 -1
  57. package/dist/nodes/internal/base-item-renderer.js.map +1 -1
  58. package/dist/nodes/internal/construct.d.ts +10 -0
  59. package/dist/nodes/internal/construct.d.ts.map +1 -0
  60. package/dist/nodes/internal/construct.js +68 -0
  61. package/dist/nodes/internal/construct.js.map +1 -0
  62. package/dist/nodes/internal/header-item-renderer.d.ts +23 -0
  63. package/dist/nodes/internal/header-item-renderer.d.ts.map +1 -0
  64. package/dist/nodes/internal/header-item-renderer.js +87 -0
  65. package/dist/nodes/internal/header-item-renderer.js.map +1 -0
  66. package/dist/nodes/internal/header-renderer-manager.d.ts +13 -0
  67. package/dist/nodes/internal/header-renderer-manager.d.ts.map +1 -0
  68. package/dist/nodes/internal/header-renderer-manager.js +20 -0
  69. package/dist/nodes/internal/header-renderer-manager.js.map +1 -0
  70. package/dist/nodes/internal/list-store.d.ts +10 -11
  71. package/dist/nodes/internal/list-store.d.ts.map +1 -1
  72. package/dist/nodes/internal/list-store.js +28 -29
  73. package/dist/nodes/internal/list-store.js.map +1 -1
  74. package/dist/nodes/internal/sectioned-list-store.d.ts +50 -0
  75. package/dist/nodes/internal/sectioned-list-store.d.ts.map +1 -0
  76. package/dist/nodes/internal/sectioned-list-store.js +250 -0
  77. package/dist/nodes/internal/sectioned-list-store.js.map +1 -0
  78. package/dist/nodes/internal/selection-helpers.d.ts +12 -0
  79. package/dist/nodes/internal/selection-helpers.d.ts.map +1 -0
  80. package/dist/nodes/internal/selection-helpers.js +25 -0
  81. package/dist/nodes/internal/selection-helpers.js.map +1 -0
  82. package/dist/nodes/internal/selection-model-controller.d.ts.map +1 -1
  83. package/dist/nodes/internal/selection-model-controller.js +3 -0
  84. package/dist/nodes/internal/selection-model-controller.js.map +1 -1
  85. package/dist/nodes/internal/simple-list-store.d.ts +7 -12
  86. package/dist/nodes/internal/simple-list-store.d.ts.map +1 -1
  87. package/dist/nodes/internal/simple-list-store.js +58 -35
  88. package/dist/nodes/internal/simple-list-store.js.map +1 -1
  89. package/dist/nodes/internal/text-buffer-controller.d.ts +4 -0
  90. package/dist/nodes/internal/text-buffer-controller.d.ts.map +1 -1
  91. package/dist/nodes/internal/text-buffer-controller.js +49 -9
  92. package/dist/nodes/internal/text-buffer-controller.js.map +1 -1
  93. package/dist/nodes/internal/tree-store.d.ts +3 -0
  94. package/dist/nodes/internal/tree-store.d.ts.map +1 -1
  95. package/dist/nodes/internal/tree-store.js +55 -10
  96. package/dist/nodes/internal/tree-store.js.map +1 -1
  97. package/dist/nodes/list-section.d.ts +27 -0
  98. package/dist/nodes/list-section.d.ts.map +1 -0
  99. package/dist/nodes/list-section.js +43 -0
  100. package/dist/nodes/list-section.js.map +1 -0
  101. package/dist/nodes/list-view.d.ts +6 -3
  102. package/dist/nodes/list-view.d.ts.map +1 -1
  103. package/dist/nodes/list-view.js +54 -14
  104. package/dist/nodes/list-view.js.map +1 -1
  105. package/dist/nodes/models/list.d.ts +13 -5
  106. package/dist/nodes/models/list.d.ts.map +1 -1
  107. package/dist/nodes/models/list.js +135 -21
  108. package/dist/nodes/models/list.js.map +1 -1
  109. package/dist/nodes/shortcut.d.ts +3 -2
  110. package/dist/nodes/shortcut.d.ts.map +1 -1
  111. package/dist/nodes/shortcut.js +19 -4
  112. package/dist/nodes/shortcut.js.map +1 -1
  113. package/dist/nodes/text-anchor.d.ts.map +1 -1
  114. package/dist/nodes/text-anchor.js +7 -1
  115. package/dist/nodes/text-anchor.js.map +1 -1
  116. package/dist/nodes/text-tag.d.ts.map +1 -1
  117. package/dist/nodes/text-tag.js +5 -1
  118. package/dist/nodes/text-tag.js.map +1 -1
  119. package/dist/nodes/text-view.d.ts +1 -0
  120. package/dist/nodes/text-view.d.ts.map +1 -1
  121. package/dist/nodes/text-view.js +4 -0
  122. package/dist/nodes/text-view.js.map +1 -1
  123. package/dist/nodes/widget.d.ts +0 -2
  124. package/dist/nodes/widget.d.ts.map +1 -1
  125. package/dist/nodes/widget.js +44 -61
  126. package/dist/nodes/widget.js.map +1 -1
  127. package/dist/registry.d.ts.map +1 -1
  128. package/dist/registry.js +2 -2
  129. package/dist/registry.js.map +1 -1
  130. package/package.json +3 -3
  131. package/src/generated/internal.ts +333 -44
  132. package/src/generated/jsx.ts +178 -2
  133. package/src/host-config.ts +41 -10
  134. package/src/jsx.ts +166 -15
  135. package/src/metadata.ts +5 -1
  136. package/src/node.ts +20 -6
  137. package/src/nodes/column-view-column.ts +32 -8
  138. package/src/nodes/column-view.ts +59 -14
  139. package/src/nodes/drop-down.ts +182 -6
  140. package/src/nodes/event-controller.ts +11 -3
  141. package/src/nodes/fixed-child.ts +24 -23
  142. package/src/nodes/font-dialog-button.ts +10 -0
  143. package/src/nodes/grid-view.ts +29 -19
  144. package/src/nodes/internal/accessible.ts +156 -0
  145. package/src/nodes/internal/base-item-renderer.ts +0 -1
  146. package/src/nodes/internal/construct.ts +90 -0
  147. package/src/nodes/internal/header-item-renderer.ts +105 -0
  148. package/src/nodes/internal/header-renderer-manager.ts +33 -0
  149. package/src/nodes/internal/list-store.ts +32 -30
  150. package/src/nodes/internal/sectioned-list-store.ts +287 -0
  151. package/src/nodes/internal/selection-helpers.ts +35 -0
  152. package/src/nodes/internal/selection-model-controller.ts +4 -0
  153. package/src/nodes/internal/simple-list-store.ts +60 -43
  154. package/src/nodes/internal/text-buffer-controller.ts +51 -8
  155. package/src/nodes/internal/tree-store.ts +61 -9
  156. package/src/nodes/list-section.ts +64 -0
  157. package/src/nodes/list-view.ts +65 -14
  158. package/src/nodes/models/list.ts +147 -37
  159. package/src/nodes/shortcut.ts +22 -5
  160. package/src/nodes/text-anchor.ts +6 -1
  161. package/src/nodes/text-tag.ts +7 -1
  162. package/src/nodes/text-view.ts +5 -0
  163. package/src/nodes/widget.ts +45 -62
  164. package/src/registry.ts +4 -2
  165. package/dist/nodes/models/grid.d.ts +0 -28
  166. package/dist/nodes/models/grid.d.ts.map +0 -1
  167. package/dist/nodes/models/grid.js +0 -69
  168. package/dist/nodes/models/grid.js.map +0 -1
  169. package/dist/nodes/shortcut-controller.d.ts +0 -10
  170. package/dist/nodes/shortcut-controller.d.ts.map +0 -1
  171. package/dist/nodes/shortcut-controller.js +0 -23
  172. package/dist/nodes/shortcut-controller.js.map +0 -1
  173. package/src/nodes/models/grid.ts +0 -105
  174. package/src/nodes/shortcut-controller.ts +0 -27
@@ -1,28 +1,54 @@
1
+ import * as Gtk from "@gtkx/ffi/gtk";
2
+ import type { ReactNode } from "react";
3
+ import type Reconciler from "react-reconciler";
4
+ import { createFiberRoot } from "../fiber-root.js";
1
5
  import type { AdwComboRowProps, GtkDropDownProps } from "../jsx.js";
2
6
  import type { Node } from "../node.js";
7
+ import { reconciler } from "../reconciler.js";
3
8
  import type { DropDownWidget } from "../registry.js";
4
9
  import type { Container } from "../types.js";
5
10
  import { ContainerSlotNode } from "./container-slot.js";
6
11
  import { EventControllerNode } from "./event-controller.js";
12
+ import type { HeaderItemRenderer } from "./internal/header-item-renderer.js";
13
+ import { updateHeaderRenderer } from "./internal/header-renderer-manager.js";
7
14
  import { filterProps, hasChanged } from "./internal/props.js";
8
15
  import { SimpleListStore } from "./internal/simple-list-store.js";
9
16
  import { ListItemNode } from "./list-item.js";
17
+ import { ListSectionNode } from "./list-section.js";
10
18
  import { SlotNode } from "./slot.js";
11
19
  import { WidgetNode } from "./widget.js";
12
20
 
13
- const OWN_PROPS = ["selectedId", "onSelectionChanged"] as const;
21
+ const OWN_PROPS = ["selectedId", "onSelectionChanged", "renderItem", "renderListItem", "renderHeader"] as const;
14
22
 
15
23
  type DropDownProps = Pick<GtkDropDownProps | AdwComboRowProps, (typeof OWN_PROPS)[number]>;
16
24
 
17
- type DropDownChild = ListItemNode | EventControllerNode | SlotNode | ContainerSlotNode;
25
+ type DropDownChild = ListItemNode | ListSectionNode | EventControllerNode | SlotNode | ContainerSlotNode;
26
+
27
+ type RenderItemFn = (item: string | null) => ReactNode;
28
+
29
+ interface FactoryState {
30
+ factory: Gtk.SignalListItemFactory;
31
+ fiberRoots: Map<object, Reconciler.FiberRoot>;
32
+ tornDown: Set<object>;
33
+ boundLabels: Map<string, object>;
34
+ }
18
35
 
19
36
  export class DropDownNode extends WidgetNode<DropDownWidget, DropDownProps, DropDownChild> {
20
37
  private store = new SimpleListStore();
21
38
  private initialSelectedId: string | null | undefined;
22
39
 
40
+ private itemState: FactoryState | null = null;
41
+ private renderItemFn: RenderItemFn | null = null;
42
+
43
+ private listItemState: FactoryState | null = null;
44
+ private renderListItemFn: RenderItemFn | null = null;
45
+
46
+ private headerRenderer: HeaderItemRenderer | null = null;
47
+
23
48
  public override isValidChild(child: Node): boolean {
24
49
  return (
25
50
  child instanceof ListItemNode ||
51
+ child instanceof ListSectionNode ||
26
52
  child instanceof EventControllerNode ||
27
53
  child instanceof SlotNode ||
28
54
  child instanceof ContainerSlotNode
@@ -33,12 +59,12 @@ export class DropDownNode extends WidgetNode<DropDownWidget, DropDownProps, Drop
33
59
  super(typeName, props, container, rootContainer);
34
60
  this.store.beginBatch();
35
61
  this.initialSelectedId = props.selectedId;
36
- this.container.setModel(this.store.getModel());
37
62
  }
38
63
 
39
64
  public override finalizeInitialChildren(props: DropDownProps): boolean {
40
65
  super.finalizeInitialChildren(props);
41
66
  this.store.flushBatch();
67
+ this.container.setModel(this.store.getModel());
42
68
  this.reapplyInitialSelectedId();
43
69
  return false;
44
70
  }
@@ -54,7 +80,10 @@ export class DropDownNode extends WidgetNode<DropDownWidget, DropDownProps, Drop
54
80
 
55
81
  public override appendChild(child: DropDownChild): void {
56
82
  super.appendChild(child);
57
- if (child instanceof ListItemNode) {
83
+ if (child instanceof ListSectionNode) {
84
+ this.store.addSection(child.props.id, child.props.value);
85
+ child.setStore(this.store);
86
+ } else if (child instanceof ListItemNode) {
58
87
  child.setStore(this.store);
59
88
  this.store.addItem(child.props.id, child.props.value as string);
60
89
  }
@@ -62,15 +91,25 @@ export class DropDownNode extends WidgetNode<DropDownWidget, DropDownProps, Drop
62
91
 
63
92
  public override insertBefore(child: DropDownChild, before: DropDownChild): void {
64
93
  super.insertBefore(child, before);
65
- if (child instanceof ListItemNode && before instanceof ListItemNode) {
94
+ if (child instanceof ListSectionNode) {
95
+ this.store.addSection(child.props.id, child.props.value);
96
+ child.setStore(this.store);
97
+ } else if (child instanceof ListItemNode && before instanceof ListItemNode) {
66
98
  child.setStore(this.store);
67
99
  this.store.insertItemBefore(child.props.id, before.props.id, child.props.value as string);
100
+ } else if (child instanceof ListItemNode) {
101
+ child.setStore(this.store);
102
+ this.store.addItem(child.props.id, child.props.value as string);
68
103
  }
69
104
  }
70
105
 
71
106
  public override removeChild(child: DropDownChild): void {
72
- if (child instanceof ListItemNode) {
107
+ if (child instanceof ListSectionNode) {
108
+ this.store.removeSection(child.props.id);
109
+ child.setStore(null);
110
+ } else if (child instanceof ListItemNode) {
73
111
  this.store.removeItem(child.props.id);
112
+ child.setStore(null);
74
113
  }
75
114
  super.removeChild(child);
76
115
  }
@@ -80,6 +119,15 @@ export class DropDownNode extends WidgetNode<DropDownWidget, DropDownProps, Drop
80
119
  this.applyOwnProps(oldProps, newProps);
81
120
  }
82
121
 
122
+ public override detachDeletedInstance(): void {
123
+ this.disposeFactory(this.itemState);
124
+ this.itemState = null;
125
+ this.disposeFactory(this.listItemState);
126
+ this.listItemState = null;
127
+ this.headerRenderer?.dispose();
128
+ super.detachDeletedInstance();
129
+ }
130
+
83
131
  private applyOwnProps(oldProps: DropDownProps | null, newProps: DropDownProps): void {
84
132
  if (hasChanged(oldProps, newProps, "onSelectionChanged")) {
85
133
  const onSelectionChanged = newProps.onSelectionChanged;
@@ -104,5 +152,133 @@ export class DropDownNode extends WidgetNode<DropDownWidget, DropDownProps, Drop
104
152
  this.container.setSelected(index);
105
153
  }
106
154
  }
155
+
156
+ if (hasChanged(oldProps, newProps, "renderItem")) {
157
+ if (newProps.renderItem) {
158
+ if (!this.itemState) {
159
+ this.itemState = this.createFactory(
160
+ () => this.renderItemFn,
161
+ (f) => this.container.setFactory(f),
162
+ );
163
+ }
164
+ this.renderItemFn = newProps.renderItem;
165
+ this.rebindAll(this.itemState, this.renderItemFn);
166
+ } else if (this.itemState) {
167
+ this.disposeFactory(this.itemState);
168
+ this.itemState = null;
169
+ this.container.setFactory(null);
170
+ this.renderItemFn = null;
171
+ }
172
+ }
173
+
174
+ if (hasChanged(oldProps, newProps, "renderListItem")) {
175
+ if (newProps.renderListItem) {
176
+ if (!this.listItemState) {
177
+ this.listItemState = this.createFactory(
178
+ () => this.renderListItemFn,
179
+ (f) => this.container.setListFactory(f),
180
+ );
181
+ }
182
+ this.renderListItemFn = newProps.renderListItem;
183
+ this.rebindAll(this.listItemState, this.renderListItemFn);
184
+ } else if (this.listItemState) {
185
+ this.disposeFactory(this.listItemState);
186
+ this.listItemState = null;
187
+ this.container.setListFactory(null);
188
+ this.renderListItemFn = null;
189
+ }
190
+ }
191
+
192
+ if (hasChanged(oldProps, newProps, "renderHeader")) {
193
+ this.headerRenderer = updateHeaderRenderer(
194
+ this.headerRenderer,
195
+ {
196
+ signalStore: this.signalStore,
197
+ isEnabled: () => this.store.isSectioned(),
198
+ resolveItem: (label) => this.store.getHeaderValueByLabel(label),
199
+ setFactory: (factory) => this.container.setHeaderFactory(factory),
200
+ },
201
+ newProps.renderHeader,
202
+ );
203
+ }
204
+ }
205
+
206
+ private createFactory(
207
+ getRenderFn: () => RenderItemFn | null,
208
+ applyFactory: (factory: Gtk.SignalListItemFactory) => void,
209
+ ): FactoryState {
210
+ const state: FactoryState = {
211
+ factory: new Gtk.SignalListItemFactory(),
212
+ fiberRoots: new Map(),
213
+ tornDown: new Set(),
214
+ boundLabels: new Map(),
215
+ };
216
+
217
+ this.signalStore.set(this, state.factory, "setup", (listItem: Gtk.ListItem) => {
218
+ const box = new Gtk.Box(Gtk.Orientation.HORIZONTAL);
219
+ box.setValign(Gtk.Align.CENTER);
220
+ listItem.setChild(box);
221
+ const fiberRoot = createFiberRoot(box);
222
+ state.fiberRoots.set(listItem, fiberRoot);
223
+ const element = getRenderFn()?.(null);
224
+ reconciler.getInstance().updateContainer(element, fiberRoot, null, () => {});
225
+ });
226
+
227
+ this.signalStore.set(this, state.factory, "bind", (listItem: Gtk.ListItem) => {
228
+ const fiberRoot = state.fiberRoots.get(listItem);
229
+ if (!fiberRoot) return;
230
+ const stringObject = listItem.getItem();
231
+ const label = stringObject instanceof Gtk.StringObject ? stringObject.getString() : null;
232
+ if (label !== null) state.boundLabels.set(label, listItem);
233
+ const element = getRenderFn()?.(label);
234
+ reconciler.getInstance().updateContainer(element, fiberRoot, null, () => {});
235
+ });
236
+
237
+ this.signalStore.set(this, state.factory, "unbind", (listItem: Gtk.ListItem) => {
238
+ const stringObject = listItem.getItem();
239
+ if (stringObject instanceof Gtk.StringObject) {
240
+ state.boundLabels.delete(stringObject.getString());
241
+ }
242
+ });
243
+
244
+ this.signalStore.set(this, state.factory, "teardown", (listItem: Gtk.ListItem) => {
245
+ const fiberRoot = state.fiberRoots.get(listItem);
246
+ if (fiberRoot) {
247
+ state.tornDown.add(listItem);
248
+ reconciler.getInstance().updateContainer(null, fiberRoot, null, () => {});
249
+ queueMicrotask(() => {
250
+ state.fiberRoots.delete(listItem);
251
+ state.tornDown.delete(listItem);
252
+ });
253
+ }
254
+ });
255
+
256
+ applyFactory(state.factory);
257
+ return state;
258
+ }
259
+
260
+ private rebindAll(state: FactoryState, renderFn: RenderItemFn): void {
261
+ for (const [label, listItem] of state.boundLabels) {
262
+ const fiberRoot = state.fiberRoots.get(listItem);
263
+ if (!fiberRoot) continue;
264
+ const element = renderFn(label);
265
+ reconciler.getInstance().updateContainer(element, fiberRoot, null, () => {});
266
+ }
267
+ }
268
+
269
+ private disposeFactory(state: FactoryState | null): void {
270
+ if (!state) return;
271
+ this.signalStore.set(this, state.factory, "setup", undefined);
272
+ this.signalStore.set(this, state.factory, "bind", undefined);
273
+ this.signalStore.set(this, state.factory, "unbind", undefined);
274
+ this.signalStore.set(this, state.factory, "teardown", undefined);
275
+ for (const [listItem, fiberRoot] of state.fiberRoots) {
276
+ if (!state.tornDown.has(listItem)) {
277
+ reconciler.getInstance().updateContainer(null, fiberRoot, null, () => {});
278
+ }
279
+ }
280
+ state.fiberRoots.clear();
281
+ state.tornDown.clear();
282
+ state.boundLabels.clear();
107
283
  }
108
284
  }
@@ -1,8 +1,9 @@
1
1
  import * as Gtk from "@gtkx/ffi/gtk";
2
2
  import { CONSTRUCTOR_PROPS } from "../generated/internal.js";
3
- import { resolvePropMeta, resolveSignal } from "../metadata.js";
3
+ import { isConstructOnlyProp, resolvePropMeta, resolveSignal } from "../metadata.js";
4
4
  import { Node } from "../node.js";
5
5
  import type { Props } from "../types.js";
6
+ import { createContainerWithConstructOnly } from "./internal/construct.js";
6
7
  import type { SignalHandler } from "./internal/signal-store.js";
7
8
  import { WidgetNode } from "./widget.js";
8
9
 
@@ -26,8 +27,14 @@ export class EventControllerNode<
26
27
 
27
28
  const args = (CONSTRUCTOR_PROPS[typeName] ?? []).map((name) => props[name]);
28
29
 
29
- // biome-ignore lint/suspicious/noExplicitAny: Dynamic constructor invocation
30
- return new (containerClass as any)(...args);
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;
34
+ }
35
+
36
+ public override isValidChild(child: Node): boolean {
37
+ return this.container instanceof Gtk.ShortcutController && child.typeName === "Shortcut";
31
38
  }
32
39
 
33
40
  public override isValidParent(parent: Node): boolean {
@@ -63,6 +70,7 @@ export class EventControllerNode<
63
70
 
64
71
  for (const name of propNames) {
65
72
  if (name === "children") continue;
73
+ if (isConstructOnlyProp(this.container, name)) continue;
66
74
 
67
75
  const oldValue = oldProps?.[name];
68
76
  const newValue = newProps[name];
@@ -1,3 +1,5 @@
1
+ import * as Graphene from "@gtkx/ffi/graphene";
2
+ import * as Gsk from "@gtkx/ffi/gsk";
1
3
  import * as Gtk from "@gtkx/ffi/gtk";
2
4
  import type { FixedChildProps } from "../jsx.js";
3
5
  import type { Node } from "../node.js";
@@ -23,7 +25,7 @@ export class FixedChildNode extends VirtualNode<FixedChildProps, WidgetNode<Gtk.
23
25
 
24
26
  if (parent && this.children[0]) {
25
27
  this.attachToParent(parent.container, this.children[0].container);
26
- this.applyTransform();
28
+ this.applyLayoutTransform();
27
29
  }
28
30
  }
29
31
 
@@ -32,7 +34,7 @@ export class FixedChildNode extends VirtualNode<FixedChildProps, WidgetNode<Gtk.
32
34
 
33
35
  if (this.parent) {
34
36
  this.attachToParent(this.parent.container, child.container);
35
- this.applyTransform();
37
+ this.applyLayoutTransform();
36
38
  }
37
39
  }
38
40
 
@@ -51,12 +53,12 @@ export class FixedChildNode extends VirtualNode<FixedChildProps, WidgetNode<Gtk.
51
53
  return;
52
54
  }
53
55
 
54
- const positionChanged = hasChanged(oldProps, newProps, "x") || hasChanged(oldProps, newProps, "y");
55
-
56
- if (positionChanged) {
57
- this.repositionChild();
58
- } else if (hasChanged(oldProps, newProps, "transform")) {
59
- this.applyTransform();
56
+ if (
57
+ hasChanged(oldProps, newProps, "x") ||
58
+ hasChanged(oldProps, newProps, "y") ||
59
+ hasChanged(oldProps, newProps, "transform")
60
+ ) {
61
+ this.applyLayoutTransform();
60
62
  }
61
63
  }
62
64
 
@@ -80,29 +82,28 @@ export class FixedChildNode extends VirtualNode<FixedChildProps, WidgetNode<Gtk.
80
82
  }
81
83
  }
82
84
 
83
- private repositionChild(): void {
85
+ private applyLayoutTransform(): void {
84
86
  if (!this.parent || !this.children[0]) return;
85
87
 
88
+ const layoutManager = this.parent.container.getLayoutManager();
89
+ if (!layoutManager) return;
90
+
91
+ const layoutChild = layoutManager.getLayoutChild(this.children[0].container) as Gtk.FixedLayoutChild;
92
+
86
93
  const x = this.props.x ?? 0;
87
94
  const y = this.props.y ?? 0;
95
+ const position = new Graphene.Point();
96
+ position.init(x, y);
88
97
 
89
- this.parent.container.remove(this.children[0].container);
90
- this.parent.container.put(this.children[0].container, x, y);
91
- this.applyTransform();
92
- }
98
+ let transform: Gsk.Transform | null = new Gsk.Transform();
99
+ transform = transform.translate(position);
93
100
 
94
- private applyTransform(): void {
95
- if (!this.parent || !this.children[0] || !this.props.transform) {
96
- return;
101
+ if (this.props.transform && transform) {
102
+ transform = transform.transform(this.props.transform);
97
103
  }
98
104
 
99
- const layoutManager = this.parent.container.getLayoutManager();
100
-
101
- if (!layoutManager) {
102
- return;
105
+ if (transform) {
106
+ layoutChild.setTransform(transform);
103
107
  }
104
-
105
- const layoutChild = layoutManager.getLayoutChild(this.children[0].container) as Gtk.FixedLayoutChild;
106
- layoutChild.setTransform(this.props.transform);
107
108
  }
108
109
  }
@@ -10,6 +10,8 @@ const OWN_PROPS = [
10
10
  "title",
11
11
  "modal",
12
12
  "language",
13
+ "filter",
14
+ "fontMap",
13
15
  "useFont",
14
16
  "useSize",
15
17
  "level",
@@ -61,6 +63,14 @@ export class FontDialogButtonNode extends WidgetNode<Gtk.FontDialogButton, FontD
61
63
  this.dialog.setLanguage(newProps.language);
62
64
  }
63
65
 
66
+ if (hasChanged(oldProps, newProps, "filter")) {
67
+ this.dialog.setFilter(newProps.filter);
68
+ }
69
+
70
+ if (hasChanged(oldProps, newProps, "fontMap")) {
71
+ this.dialog.setFontMap(newProps.fontMap);
72
+ }
73
+
64
74
  if (hasChanged(oldProps, newProps, "useFont")) {
65
75
  this.container.setUseFont(newProps.useFont ?? false);
66
76
  }
@@ -7,39 +7,43 @@ import { EventControllerNode } from "./event-controller.js";
7
7
  import { GridItemRenderer } from "./internal/grid-item-renderer.js";
8
8
  import { filterProps, hasChanged } from "./internal/props.js";
9
9
  import { ListItemNode } from "./list-item.js";
10
- import { GridModel, type GridModelProps } from "./models/grid.js";
10
+ import { ListSectionNode } from "./list-section.js";
11
+ import { ListModel, type ListModelProps } from "./models/list.js";
11
12
  import { SlotNode } from "./slot.js";
12
13
  import { WidgetNode } from "./widget.js";
13
14
 
14
- const OWN_PROPS = ["renderItem", "estimatedItemHeight"] as const;
15
+ const RENDERER_PROPS = ["renderItem", "estimatedItemHeight"] as const;
16
+ const OWN_PROPS = [...RENDERER_PROPS, "selectionMode", "selected", "onSelectionChanged"] as const;
15
17
 
16
- type GridViewProps = Pick<GtkGridViewProps, (typeof OWN_PROPS)[number]> & GridModelProps;
18
+ type GridViewProps = Pick<GtkGridViewProps, (typeof RENDERER_PROPS)[number]> & ListModelProps;
17
19
 
18
- type GridViewChild = ListItemNode | EventControllerNode | SlotNode | ContainerSlotNode;
20
+ type GridViewChild = ListItemNode | ListSectionNode | EventControllerNode | SlotNode | ContainerSlotNode;
19
21
 
20
22
  export class GridViewNode extends WidgetNode<Gtk.GridView, GridViewProps, GridViewChild> {
21
23
  private itemRenderer: GridItemRenderer;
22
- private grid: GridModel;
24
+ private list: ListModel;
23
25
 
24
26
  constructor(typeName: string, props: GridViewProps, container: Gtk.GridView, rootContainer: Container) {
25
27
  super(typeName, props, container, rootContainer);
26
- this.grid = new GridModel(
28
+ this.list = new ListModel(
27
29
  { owner: this, signalStore: this.signalStore },
28
30
  {
29
31
  selectionMode: props.selectionMode,
30
32
  selected: props.selected,
31
33
  onSelectionChanged: props.onSelectionChanged,
32
34
  },
35
+ true,
33
36
  );
34
37
  this.itemRenderer = new GridItemRenderer(this.signalStore);
35
- this.itemRenderer.setStore(this.grid.getStore());
36
- this.grid.getStore().setOnItemUpdated((id) => this.itemRenderer.rebindItem(id));
38
+ this.itemRenderer.setStore(this.list.getFlatStore());
39
+ this.list.setOnItemUpdated((id) => this.itemRenderer.rebindItem(id));
37
40
  this.container.setFactory(this.itemRenderer.getFactory());
38
41
  }
39
42
 
40
43
  public override isValidChild(child: Node): boolean {
41
44
  if (child instanceof EventControllerNode || child instanceof SlotNode || child instanceof ContainerSlotNode)
42
45
  return true;
46
+ if (child instanceof ListSectionNode) return true;
43
47
  if (!(child instanceof ListItemNode)) return false;
44
48
  if (child.getChildNodes().length > 0) {
45
49
  throw new Error("GtkGridView does not support nested ListItems. Use GtkListView for tree lists.");
@@ -49,21 +53,24 @@ export class GridViewNode extends WidgetNode<Gtk.GridView, GridViewProps, GridVi
49
53
 
50
54
  public override appendChild(child: GridViewChild): void {
51
55
  super.appendChild(child);
52
- if (child instanceof ListItemNode) {
53
- this.grid.appendChild(child);
56
+ if (child instanceof ListItemNode || child instanceof ListSectionNode) {
57
+ this.list.appendChild(child);
54
58
  }
55
59
  }
56
60
 
57
61
  public override insertBefore(child: GridViewChild, before: GridViewChild): void {
58
62
  super.insertBefore(child, before);
59
- if (child instanceof ListItemNode && before instanceof ListItemNode) {
60
- this.grid.insertBefore(child, before);
63
+ if (
64
+ (child instanceof ListItemNode || child instanceof ListSectionNode) &&
65
+ (before instanceof ListItemNode || before instanceof ListSectionNode)
66
+ ) {
67
+ this.list.insertBefore(child, before);
61
68
  }
62
69
  }
63
70
 
64
71
  public override removeChild(child: GridViewChild): void {
65
- if (child instanceof ListItemNode) {
66
- this.grid.removeChild(child);
72
+ if (child instanceof ListItemNode || child instanceof ListSectionNode) {
73
+ this.list.removeChild(child);
67
74
  }
68
75
  super.removeChild(child);
69
76
  }
@@ -80,8 +87,8 @@ export class GridViewNode extends WidgetNode<Gtk.GridView, GridViewProps, GridVi
80
87
 
81
88
  public override commitMount(): void {
82
89
  super.commitMount();
83
- this.grid.flushBatch();
84
- this.container.setModel(this.grid.getSelectionModel());
90
+ this.list.flushBatch();
91
+ this.container.setModel(this.list.getSelectionModel());
85
92
  }
86
93
 
87
94
  public override detachDeletedInstance(): void {
@@ -98,9 +105,12 @@ export class GridViewNode extends WidgetNode<Gtk.GridView, GridViewProps, GridVi
98
105
  this.itemRenderer.setEstimatedItemHeight(newProps.estimatedItemHeight ?? null);
99
106
  }
100
107
 
101
- const previousModel = this.grid.getSelectionModel();
102
- this.grid.updateProps(oldProps ? filterProps(oldProps, OWN_PROPS) : null, filterProps(newProps, OWN_PROPS));
103
- const currentModel = this.grid.getSelectionModel();
108
+ const previousModel = this.list.getSelectionModel();
109
+ this.list.updateProps(
110
+ oldProps ? filterProps(oldProps, RENDERER_PROPS) : null,
111
+ filterProps(newProps, RENDERER_PROPS),
112
+ );
113
+ const currentModel = this.list.getSelectionModel();
104
114
 
105
115
  if (previousModel !== currentModel) {
106
116
  this.container.setModel(currentModel);
@@ -0,0 +1,156 @@
1
+ import type * as GObject from "@gtkx/ffi/gobject";
2
+ import { Value } from "@gtkx/ffi/gobject";
3
+ import * as Gtk from "@gtkx/ffi/gtk";
4
+ import type { Props } from "../../types.js";
5
+
6
+ type CreateValue = (jsValue: unknown) => Value;
7
+
8
+ type PropertyDef = {
9
+ kind: "property";
10
+ enumValue: Gtk.AccessibleProperty;
11
+ createValue: CreateValue;
12
+ };
13
+
14
+ type StateDef = {
15
+ kind: "state";
16
+ enumValue: Gtk.AccessibleState;
17
+ createValue: CreateValue;
18
+ };
19
+
20
+ type RelationDef = {
21
+ kind: "relation";
22
+ enumValue: Gtk.AccessibleRelation;
23
+ createValue: CreateValue;
24
+ };
25
+
26
+ type AccessiblePropDef = PropertyDef | StateDef | RelationDef;
27
+
28
+ const fromString: CreateValue = (val) => Value.newFromString(val as string);
29
+ const fromBoolean: CreateValue = (val) => Value.newFromBoolean(val as boolean);
30
+ const fromInt: CreateValue = (val) => Value.newFromInt(val as number);
31
+ const fromDouble: CreateValue = (val) => Value.newFromDouble(val as number);
32
+
33
+ const fromObject: CreateValue = (val) => Value.newFromObject((val as GObject.Object) ?? null);
34
+
35
+ const fromRefList: CreateValue = (val) => {
36
+ const widgets = val as Gtk.Accessible[];
37
+ const list = Gtk.AccessibleList.newFromList(widgets);
38
+ return Value.newFromBoxed(list);
39
+ };
40
+
41
+ const prop = (enumValue: Gtk.AccessibleProperty, createValue: CreateValue): PropertyDef => ({
42
+ kind: "property",
43
+ enumValue,
44
+ createValue,
45
+ });
46
+
47
+ const state = (enumValue: Gtk.AccessibleState, createValue: CreateValue): StateDef => ({
48
+ kind: "state",
49
+ enumValue,
50
+ createValue,
51
+ });
52
+
53
+ const relation = (enumValue: Gtk.AccessibleRelation, createValue: CreateValue): RelationDef => ({
54
+ kind: "relation",
55
+ enumValue,
56
+ createValue,
57
+ });
58
+
59
+ const ACCESSIBLE_PROP_MAP: Record<string, AccessiblePropDef> = {
60
+ accessibleAutocomplete: prop(Gtk.AccessibleProperty.AUTOCOMPLETE, fromInt),
61
+ accessibleDescription: prop(Gtk.AccessibleProperty.DESCRIPTION, fromString),
62
+ accessibleHasPopup: prop(Gtk.AccessibleProperty.HAS_POPUP, fromBoolean),
63
+ accessibleKeyShortcuts: prop(Gtk.AccessibleProperty.KEY_SHORTCUTS, fromString),
64
+ accessibleLabel: prop(Gtk.AccessibleProperty.LABEL, fromString),
65
+ accessibleLevel: prop(Gtk.AccessibleProperty.LEVEL, fromInt),
66
+ accessibleModal: prop(Gtk.AccessibleProperty.MODAL, fromBoolean),
67
+ accessibleMultiLine: prop(Gtk.AccessibleProperty.MULTI_LINE, fromBoolean),
68
+ accessibleMultiSelectable: prop(Gtk.AccessibleProperty.MULTI_SELECTABLE, fromBoolean),
69
+ accessibleOrientation: prop(Gtk.AccessibleProperty.ORIENTATION, fromInt),
70
+ accessiblePlaceholder: prop(Gtk.AccessibleProperty.PLACEHOLDER, fromString),
71
+ accessibleReadOnly: prop(Gtk.AccessibleProperty.READ_ONLY, fromBoolean),
72
+ accessibleRequired: prop(Gtk.AccessibleProperty.REQUIRED, fromBoolean),
73
+ accessibleRoleDescription: prop(Gtk.AccessibleProperty.ROLE_DESCRIPTION, fromString),
74
+ accessibleSort: prop(Gtk.AccessibleProperty.SORT, fromInt),
75
+ accessibleValueMax: prop(Gtk.AccessibleProperty.VALUE_MAX, fromDouble),
76
+ accessibleValueMin: prop(Gtk.AccessibleProperty.VALUE_MIN, fromDouble),
77
+ accessibleValueNow: prop(Gtk.AccessibleProperty.VALUE_NOW, fromDouble),
78
+ accessibleValueText: prop(Gtk.AccessibleProperty.VALUE_TEXT, fromString),
79
+ accessibleHelpText: prop(Gtk.AccessibleProperty.HELP_TEXT, fromString),
80
+
81
+ accessibleBusy: state(Gtk.AccessibleState.BUSY, fromBoolean),
82
+ accessibleChecked: state(Gtk.AccessibleState.CHECKED, fromInt),
83
+ accessibleDisabled: state(Gtk.AccessibleState.DISABLED, fromBoolean),
84
+ accessibleExpanded: state(Gtk.AccessibleState.EXPANDED, fromInt),
85
+ accessibleHidden: state(Gtk.AccessibleState.HIDDEN, fromBoolean),
86
+ accessibleInvalid: state(Gtk.AccessibleState.INVALID, fromInt),
87
+ accessiblePressed: state(Gtk.AccessibleState.PRESSED, fromInt),
88
+ accessibleSelected: state(Gtk.AccessibleState.SELECTED, fromInt),
89
+ accessibleVisited: state(Gtk.AccessibleState.VISITED, fromInt),
90
+
91
+ accessibleActiveDescendant: relation(Gtk.AccessibleRelation.ACTIVE_DESCENDANT, fromObject),
92
+ accessibleColCount: relation(Gtk.AccessibleRelation.COL_COUNT, fromInt),
93
+ accessibleColIndex: relation(Gtk.AccessibleRelation.COL_INDEX, fromInt),
94
+ accessibleColIndexText: relation(Gtk.AccessibleRelation.COL_INDEX_TEXT, fromString),
95
+ accessibleColSpan: relation(Gtk.AccessibleRelation.COL_SPAN, fromInt),
96
+ accessibleControls: relation(Gtk.AccessibleRelation.CONTROLS, fromRefList),
97
+ accessibleDescribedBy: relation(Gtk.AccessibleRelation.DESCRIBED_BY, fromRefList),
98
+ accessibleDetails: relation(Gtk.AccessibleRelation.DETAILS, fromRefList),
99
+ accessibleErrorMessage: relation(Gtk.AccessibleRelation.ERROR_MESSAGE, fromRefList),
100
+ accessibleFlowTo: relation(Gtk.AccessibleRelation.FLOW_TO, fromRefList),
101
+ accessibleLabelledBy: relation(Gtk.AccessibleRelation.LABELLED_BY, fromRefList),
102
+ accessibleOwns: relation(Gtk.AccessibleRelation.OWNS, fromRefList),
103
+ accessiblePosInSet: relation(Gtk.AccessibleRelation.POS_IN_SET, fromInt),
104
+ accessibleRowCount: relation(Gtk.AccessibleRelation.ROW_COUNT, fromInt),
105
+ accessibleRowIndex: relation(Gtk.AccessibleRelation.ROW_INDEX, fromInt),
106
+ accessibleRowIndexText: relation(Gtk.AccessibleRelation.ROW_INDEX_TEXT, fromString),
107
+ accessibleRowSpan: relation(Gtk.AccessibleRelation.ROW_SPAN, fromInt),
108
+ accessibleSetSize: relation(Gtk.AccessibleRelation.SET_SIZE, fromInt),
109
+ };
110
+
111
+ export const isAccessibleProp = (name: string): boolean => name in ACCESSIBLE_PROP_MAP;
112
+
113
+ function applyDef(widget: Gtk.Widget, def: AccessiblePropDef, newValue: unknown): void {
114
+ const gvalue = def.createValue(newValue);
115
+
116
+ switch (def.kind) {
117
+ case "property":
118
+ widget.updatePropertyValue(1, [def.enumValue], [gvalue]);
119
+ break;
120
+ case "state":
121
+ widget.updateStateValue(1, [def.enumValue], [gvalue]);
122
+ break;
123
+ case "relation":
124
+ widget.updateRelationValue(1, [def.enumValue], [gvalue]);
125
+ break;
126
+ }
127
+ }
128
+
129
+ function resetDef(widget: Gtk.Widget, def: AccessiblePropDef): void {
130
+ switch (def.kind) {
131
+ case "property":
132
+ widget.resetProperty(def.enumValue);
133
+ break;
134
+ case "state":
135
+ widget.resetState(def.enumValue);
136
+ break;
137
+ case "relation":
138
+ widget.resetRelation(def.enumValue);
139
+ break;
140
+ }
141
+ }
142
+
143
+ export const applyAccessibleProps = (widget: Gtk.Widget, oldProps: Props | null, newProps: Props): void => {
144
+ for (const [name, def] of Object.entries(ACCESSIBLE_PROP_MAP)) {
145
+ const oldValue = oldProps?.[name];
146
+ const newValue = newProps[name];
147
+
148
+ if (oldValue === newValue) continue;
149
+
150
+ if (newValue === undefined) {
151
+ resetDef(widget, def);
152
+ } else {
153
+ applyDef(widget, def, newValue);
154
+ }
155
+ }
156
+ };