@gtkx/react 0.18.9 → 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 (212) 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 +8 -3
  6. package/dist/generated/internal.d.ts.map +1 -1
  7. package/dist/generated/internal.js +3553 -53
  8. package/dist/generated/internal.js.map +1 -1
  9. package/dist/generated/jsx.d.ts +178 -326
  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 +48 -10
  15. package/dist/host-config.js.map +1 -1
  16. package/dist/jsx.d.ts +111 -54
  17. package/dist/jsx.d.ts.map +1 -1
  18. package/dist/jsx.js +3 -28
  19. package/dist/jsx.js.map +1 -1
  20. package/dist/metadata.d.ts +1 -0
  21. package/dist/metadata.d.ts.map +1 -1
  22. package/dist/metadata.js +7 -1
  23. package/dist/metadata.js.map +1 -1
  24. package/dist/node.d.ts +0 -2
  25. package/dist/node.d.ts.map +1 -1
  26. package/dist/node.js +19 -25
  27. package/dist/node.js.map +1 -1
  28. package/dist/nodes/application.d.ts.map +1 -1
  29. package/dist/nodes/application.js +4 -0
  30. package/dist/nodes/application.js.map +1 -1
  31. package/dist/nodes/column-view-column.d.ts +19 -16
  32. package/dist/nodes/column-view-column.d.ts.map +1 -1
  33. package/dist/nodes/column-view-column.js +129 -97
  34. package/dist/nodes/column-view-column.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 +9 -7
  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/internal/accessible.d.ts +5 -0
  48. package/dist/nodes/internal/accessible.d.ts.map +1 -0
  49. package/dist/nodes/internal/accessible.js +119 -0
  50. package/dist/nodes/internal/accessible.js.map +1 -0
  51. package/dist/nodes/internal/bound-item.d.ts +4 -0
  52. package/dist/nodes/internal/bound-item.d.ts.map +1 -0
  53. package/dist/nodes/internal/bound-item.js +2 -0
  54. package/dist/nodes/internal/bound-item.js.map +1 -0
  55. package/dist/nodes/internal/construct.d.ts +3 -0
  56. package/dist/nodes/internal/construct.d.ts.map +1 -0
  57. package/dist/nodes/internal/construct.js +44 -0
  58. package/dist/nodes/internal/construct.js.map +1 -0
  59. package/dist/nodes/internal/text-buffer-controller.d.ts +4 -0
  60. package/dist/nodes/internal/text-buffer-controller.d.ts.map +1 -1
  61. package/dist/nodes/internal/text-buffer-controller.js +49 -9
  62. package/dist/nodes/internal/text-buffer-controller.js.map +1 -1
  63. package/dist/nodes/internal/widget.d.ts.map +1 -1
  64. package/dist/nodes/internal/widget.js +4 -1
  65. package/dist/nodes/internal/widget.js.map +1 -1
  66. package/dist/nodes/list-item-node.d.ts +12 -0
  67. package/dist/nodes/list-item-node.d.ts.map +1 -0
  68. package/dist/nodes/list-item-node.js +23 -0
  69. package/dist/nodes/list-item-node.js.map +1 -0
  70. package/dist/nodes/list.d.ts +100 -0
  71. package/dist/nodes/list.d.ts.map +1 -0
  72. package/dist/nodes/list.js +950 -0
  73. package/dist/nodes/list.js.map +1 -0
  74. package/dist/nodes/notebook-page.d.ts.map +1 -1
  75. package/dist/nodes/notebook-page.js +4 -0
  76. package/dist/nodes/notebook-page.js.map +1 -1
  77. package/dist/nodes/shortcut.d.ts +3 -2
  78. package/dist/nodes/shortcut.d.ts.map +1 -1
  79. package/dist/nodes/shortcut.js +19 -4
  80. package/dist/nodes/shortcut.js.map +1 -1
  81. package/dist/nodes/text-anchor.d.ts.map +1 -1
  82. package/dist/nodes/text-anchor.js +7 -1
  83. package/dist/nodes/text-anchor.js.map +1 -1
  84. package/dist/nodes/text-tag.d.ts.map +1 -1
  85. package/dist/nodes/text-tag.js +5 -1
  86. package/dist/nodes/text-tag.js.map +1 -1
  87. package/dist/nodes/text-view.d.ts +1 -0
  88. package/dist/nodes/text-view.d.ts.map +1 -1
  89. package/dist/nodes/text-view.js +4 -0
  90. package/dist/nodes/text-view.js.map +1 -1
  91. package/dist/nodes/widget.d.ts +0 -2
  92. package/dist/nodes/widget.d.ts.map +1 -1
  93. package/dist/nodes/widget.js +51 -67
  94. package/dist/nodes/widget.js.map +1 -1
  95. package/dist/nodes/window.d.ts.map +1 -1
  96. package/dist/nodes/window.js +2 -2
  97. package/dist/nodes/window.js.map +1 -1
  98. package/dist/registry.d.ts +0 -2
  99. package/dist/registry.d.ts.map +1 -1
  100. package/dist/registry.js +4 -13
  101. package/dist/registry.js.map +1 -1
  102. package/dist/types.d.ts +2 -2
  103. package/dist/types.d.ts.map +1 -1
  104. package/package.json +5 -4
  105. package/src/components/list.tsx +83 -0
  106. package/src/generated/internal.ts +3559 -49
  107. package/src/generated/jsx.ts +178 -326
  108. package/src/host-config.ts +43 -10
  109. package/src/jsx.ts +121 -62
  110. package/src/metadata.ts +8 -1
  111. package/src/node.ts +23 -25
  112. package/src/nodes/application.ts +5 -0
  113. package/src/nodes/column-view-column.ts +125 -104
  114. package/src/nodes/event-controller.ts +8 -8
  115. package/src/nodes/fixed-child.ts +24 -23
  116. package/src/nodes/font-dialog-button.ts +10 -0
  117. package/src/nodes/internal/accessible.ts +155 -0
  118. package/src/nodes/internal/bound-item.ts +4 -0
  119. package/src/nodes/internal/construct.ts +60 -0
  120. package/src/nodes/internal/text-buffer-controller.ts +51 -8
  121. package/src/nodes/internal/widget.ts +3 -1
  122. package/src/nodes/list-item-node.ts +29 -0
  123. package/src/nodes/list.ts +1082 -0
  124. package/src/nodes/notebook-page.ts +4 -0
  125. package/src/nodes/shortcut.ts +22 -5
  126. package/src/nodes/text-anchor.ts +6 -1
  127. package/src/nodes/text-tag.ts +7 -1
  128. package/src/nodes/text-view.ts +5 -0
  129. package/src/nodes/widget.ts +47 -69
  130. package/src/nodes/window.ts +2 -2
  131. package/src/registry.ts +11 -17
  132. package/src/types.ts +7 -2
  133. package/dist/fiber-root.d.ts +0 -4
  134. package/dist/fiber-root.d.ts.map +0 -1
  135. package/dist/fiber-root.js +0 -6
  136. package/dist/fiber-root.js.map +0 -1
  137. package/dist/nodes/column-view.d.ts +0 -36
  138. package/dist/nodes/column-view.d.ts.map +0 -1
  139. package/dist/nodes/column-view.js +0 -175
  140. package/dist/nodes/column-view.js.map +0 -1
  141. package/dist/nodes/drop-down.d.ts +0 -27
  142. package/dist/nodes/drop-down.d.ts.map +0 -1
  143. package/dist/nodes/drop-down.js +0 -85
  144. package/dist/nodes/drop-down.js.map +0 -1
  145. package/dist/nodes/grid-view.d.ts +0 -29
  146. package/dist/nodes/grid-view.d.ts.map +0 -1
  147. package/dist/nodes/grid-view.js +0 -85
  148. package/dist/nodes/grid-view.js.map +0 -1
  149. package/dist/nodes/internal/base-item-renderer.d.ts +0 -28
  150. package/dist/nodes/internal/base-item-renderer.d.ts.map +0 -1
  151. package/dist/nodes/internal/base-item-renderer.js +0 -86
  152. package/dist/nodes/internal/base-item-renderer.js.map +0 -1
  153. package/dist/nodes/internal/grid-item-renderer.d.ts +0 -20
  154. package/dist/nodes/internal/grid-item-renderer.d.ts.map +0 -1
  155. package/dist/nodes/internal/grid-item-renderer.js +0 -66
  156. package/dist/nodes/internal/grid-item-renderer.js.map +0 -1
  157. package/dist/nodes/internal/list-item-renderer.d.ts +0 -27
  158. package/dist/nodes/internal/list-item-renderer.d.ts.map +0 -1
  159. package/dist/nodes/internal/list-item-renderer.js +0 -131
  160. package/dist/nodes/internal/list-item-renderer.js.map +0 -1
  161. package/dist/nodes/internal/list-store.d.ts +0 -22
  162. package/dist/nodes/internal/list-store.d.ts.map +0 -1
  163. package/dist/nodes/internal/list-store.js +0 -91
  164. package/dist/nodes/internal/list-store.js.map +0 -1
  165. package/dist/nodes/internal/selection-model-controller.d.ts +0 -26
  166. package/dist/nodes/internal/selection-model-controller.d.ts.map +0 -1
  167. package/dist/nodes/internal/selection-model-controller.js +0 -79
  168. package/dist/nodes/internal/selection-model-controller.js.map +0 -1
  169. package/dist/nodes/internal/simple-list-store.d.ts +0 -20
  170. package/dist/nodes/internal/simple-list-store.d.ts.map +0 -1
  171. package/dist/nodes/internal/simple-list-store.js +0 -87
  172. package/dist/nodes/internal/simple-list-store.js.map +0 -1
  173. package/dist/nodes/internal/tree-store.d.ts +0 -34
  174. package/dist/nodes/internal/tree-store.d.ts.map +0 -1
  175. package/dist/nodes/internal/tree-store.js +0 -208
  176. package/dist/nodes/internal/tree-store.js.map +0 -1
  177. package/dist/nodes/list-item.d.ts +0 -24
  178. package/dist/nodes/list-item.d.ts.map +0 -1
  179. package/dist/nodes/list-item.js +0 -83
  180. package/dist/nodes/list-item.js.map +0 -1
  181. package/dist/nodes/list-view.d.ts +0 -29
  182. package/dist/nodes/list-view.d.ts.map +0 -1
  183. package/dist/nodes/list-view.js +0 -83
  184. package/dist/nodes/list-view.js.map +0 -1
  185. package/dist/nodes/models/grid.d.ts +0 -28
  186. package/dist/nodes/models/grid.d.ts.map +0 -1
  187. package/dist/nodes/models/grid.js +0 -69
  188. package/dist/nodes/models/grid.js.map +0 -1
  189. package/dist/nodes/models/list.d.ts +0 -31
  190. package/dist/nodes/models/list.d.ts.map +0 -1
  191. package/dist/nodes/models/list.js +0 -93
  192. package/dist/nodes/models/list.js.map +0 -1
  193. package/dist/nodes/shortcut-controller.d.ts +0 -10
  194. package/dist/nodes/shortcut-controller.d.ts.map +0 -1
  195. package/dist/nodes/shortcut-controller.js +0 -23
  196. package/dist/nodes/shortcut-controller.js.map +0 -1
  197. package/src/fiber-root.ts +0 -20
  198. package/src/nodes/column-view.ts +0 -217
  199. package/src/nodes/drop-down.ts +0 -108
  200. package/src/nodes/grid-view.ts +0 -109
  201. package/src/nodes/internal/base-item-renderer.ts +0 -108
  202. package/src/nodes/internal/grid-item-renderer.ts +0 -78
  203. package/src/nodes/internal/list-item-renderer.ts +0 -162
  204. package/src/nodes/internal/list-store.ts +0 -105
  205. package/src/nodes/internal/selection-model-controller.ts +0 -115
  206. package/src/nodes/internal/simple-list-store.ts +0 -99
  207. package/src/nodes/internal/tree-store.ts +0 -237
  208. package/src/nodes/list-item.ts +0 -107
  209. package/src/nodes/list-view.ts +0 -113
  210. package/src/nodes/models/grid.ts +0 -105
  211. package/src/nodes/models/list.ts +0 -140
  212. package/src/nodes/shortcut-controller.ts +0 -27
@@ -0,0 +1,1082 @@
1
+ import { getNativeId } from "@gtkx/ffi";
2
+ import * as Adw from "@gtkx/ffi/adw";
3
+ import * as Gio from "@gtkx/ffi/gio";
4
+ import type * as GObject from "@gtkx/ffi/gobject";
5
+ import * as Gtk from "@gtkx/ffi/gtk";
6
+ import type { ReactNode } from "react";
7
+ import type { DropDownProps, GridViewProps, ListItem, ListViewProps } from "../jsx.js";
8
+ import type { Node } from "../node.js";
9
+ import type { Container } from "../types.js";
10
+ import { ColumnViewColumnNode } from "./column-view-column.js";
11
+ import { ContainerSlotNode } from "./container-slot.js";
12
+ import { EventControllerNode } from "./event-controller.js";
13
+ import type { BoundItem } from "./internal/bound-item.js";
14
+ import { filterProps, hasChanged } from "./internal/props.js";
15
+ import { SlotNode } from "./slot.js";
16
+ import { WidgetNode } from "./widget.js";
17
+
18
+ type ListProps = ListViewProps &
19
+ GridViewProps &
20
+ DropDownProps & {
21
+ __boundItemsRef?: { current: BoundItem[] };
22
+ __rerender?: () => void;
23
+ __headerBoundItemsRef?: { current: BoundItem[] };
24
+ estimatedRowHeight?: number | null;
25
+ sortColumn?: string | null;
26
+ sortOrder?: Gtk.SortType | null;
27
+ onSortChanged?: ((column: string | null, order: Gtk.SortType) => void) | null;
28
+ };
29
+
30
+ const OWN_PROPS = [
31
+ "items",
32
+ "renderItem",
33
+ "renderListItem",
34
+ "renderHeader",
35
+ "renderCell",
36
+ "autoexpand",
37
+ "selected",
38
+ "onSelectionChanged",
39
+ "selectionMode",
40
+ "selectedId",
41
+ "sortColumn",
42
+ "sortOrder",
43
+ "onSortChanged",
44
+ "estimatedItemHeight",
45
+ "estimatedItemWidth",
46
+ "estimatedRowHeight",
47
+ "__boundItemsRef",
48
+ "__rerender",
49
+ "__headerBoundItemsRef",
50
+ ] as const;
51
+
52
+ const UNBOUND_POSITION = -1;
53
+
54
+ type ListChild = ColumnViewColumnNode | EventControllerNode | SlotNode | ContainerSlotNode;
55
+
56
+ function resizeStringList(model: Gtk.StringList, newSize: number): void {
57
+ const oldSize = model.getNItems();
58
+ if (newSize > oldSize) {
59
+ model.splice(oldSize, 0, new Array(newSize - oldSize).fill(""));
60
+ } else if (newSize < oldSize) {
61
+ model.splice(newSize, oldSize - newSize, []);
62
+ }
63
+ }
64
+
65
+ export class ListNode extends WidgetNode<Gtk.Widget, ListProps, ListChild> {
66
+ private model: Gtk.StringList | null = null;
67
+ private selectionModel: Gtk.SingleSelection | Gtk.MultiSelection | Gtk.NoSelection | null = null;
68
+ private treeModel: Gtk.TreeListModel | null = null;
69
+ private factory: Gtk.SignalListItemFactory | null = null;
70
+ private headerFactory: Gtk.SignalListItemFactory | null = null;
71
+ private listFactory: Gtk.SignalListItemFactory | null = null;
72
+ private containers = new Map<Gtk.Widget | Gtk.ListItem, number>();
73
+ private containerKeys = new Map<Gtk.Widget | Gtk.ListItem, string>();
74
+ private headerContainers = new Map<Gtk.ListHeader, number>();
75
+ private headerContainerKeys = new Map<Gtk.ListHeader, string>();
76
+ private listContainers = new Map<Gtk.ListItem, number>();
77
+ private listContainerKeys = new Map<Gtk.ListItem, string>();
78
+ private treeExpanders = new Map<Gtk.ListItem, Gtk.TreeExpander>();
79
+ private disposed = false;
80
+ private boundItemsUpdateScheduled = false;
81
+ private syncScheduled = false;
82
+ private sectionModels: Gtk.StringList[] = [];
83
+ private sectionStore: Gio.ListStore | null = null;
84
+ private flattenModel: Gtk.FlattenListModel | null = null;
85
+ private treeChildModels = new Map<GObject.Object["handle"], Gtk.StringList>();
86
+ private queriedLeaves = new Set<GObject.Object["handle"]>();
87
+ private rootItemIds: string[] = [];
88
+
89
+ public override isValidChild(child: Node): boolean {
90
+ return (
91
+ child instanceof ColumnViewColumnNode ||
92
+ child instanceof EventControllerNode ||
93
+ child instanceof SlotNode ||
94
+ child instanceof ContainerSlotNode
95
+ );
96
+ }
97
+
98
+ public override appendChild(child: ListChild): void {
99
+ const isMove = child instanceof ColumnViewColumnNode && this.children.includes(child);
100
+ super.appendChild(child);
101
+ if (child instanceof ColumnViewColumnNode) {
102
+ const columnView = this.container as Gtk.ColumnView;
103
+ if (isMove) {
104
+ columnView.removeColumn(child.getColumn());
105
+ } else {
106
+ child.installActionGroup(columnView);
107
+ }
108
+ columnView.appendColumn(child.getColumn());
109
+ }
110
+ }
111
+
112
+ public override removeChild(child: ListChild): void {
113
+ if (child instanceof ColumnViewColumnNode) {
114
+ const columnView = this.container as Gtk.ColumnView;
115
+ columnView.removeColumn(child.getColumn());
116
+ child.uninstallActionGroup(columnView);
117
+ }
118
+ super.removeChild(child);
119
+ }
120
+
121
+ public override insertBefore(child: ListChild, before: ListChild): void {
122
+ const isMove = child instanceof ColumnViewColumnNode && this.children.includes(child);
123
+ super.insertBefore(child, before);
124
+ if (child instanceof ColumnViewColumnNode) {
125
+ const columnView = this.container as Gtk.ColumnView;
126
+ if (isMove) {
127
+ columnView.removeColumn(child.getColumn());
128
+ } else {
129
+ child.installActionGroup(columnView);
130
+ }
131
+ const position = this.getColumnPosition(child);
132
+ columnView.insertColumn(position, child.getColumn());
133
+ }
134
+ }
135
+
136
+ public override finalizeInitialChildren(props: ListProps): boolean {
137
+ this.commitUpdate(null, props);
138
+ this.setupModel();
139
+ this.setupFactory();
140
+ if (this.props.renderHeader && !this.isDropDown()) {
141
+ this.setupHeaderFactory();
142
+ }
143
+ this.setupSelectionModel(props);
144
+ this.assignModelToWidget();
145
+ this.assignFactoryToWidget();
146
+ this.syncModel();
147
+ return true;
148
+ }
149
+
150
+ public override commitUpdate(oldProps: ListProps | null, newProps: ListProps): void {
151
+ super.commitUpdate(oldProps ? filterProps(oldProps, OWN_PROPS) : null, filterProps(newProps, OWN_PROPS));
152
+ this.props = newProps;
153
+ if (oldProps === null) return;
154
+ this.applyOwnProps(oldProps, newProps);
155
+ }
156
+
157
+ public override commitMount(): void {
158
+ this.connectSelectionSignal();
159
+ this.connectSortSignal();
160
+ this.applySelection(this.props.selected ?? null);
161
+ this.applySelectedId(this.props.selectedId ?? null);
162
+ }
163
+
164
+ public override detachDeletedInstance(): void {
165
+ this.disposed = true;
166
+ this.treeChildModels.clear();
167
+ this.queriedLeaves.clear();
168
+ this.treeExpanders.clear();
169
+ this.rootItemIds = [];
170
+ super.detachDeletedInstance();
171
+ }
172
+
173
+ private getItems(): ListItem[] {
174
+ return this.props.items ?? [];
175
+ }
176
+
177
+ private collectFlatItems(): ListItem[] {
178
+ const items = this.getItems();
179
+ const flat: ListItem[] = [];
180
+ for (const item of items) {
181
+ if (item.section && item.children) {
182
+ for (const child of item.children) {
183
+ flat.push(child);
184
+ }
185
+ } else {
186
+ flat.push(item);
187
+ }
188
+ }
189
+ return flat;
190
+ }
191
+
192
+ private collectSections(): ListItem[] {
193
+ const items = this.getItems();
194
+ const sections: ListItem[] = [];
195
+ for (const item of items) {
196
+ if (item.section) {
197
+ sections.push(item);
198
+ }
199
+ }
200
+ return sections;
201
+ }
202
+
203
+ private collectRootItems(): ListItem[] {
204
+ return this.getItems().filter((item) => !item.section);
205
+ }
206
+
207
+ private isDropDown(): boolean {
208
+ return this.container instanceof Gtk.DropDown || this.container instanceof Adw.ComboRow;
209
+ }
210
+
211
+ private isColumnView(): boolean {
212
+ return this.container instanceof Gtk.ColumnView;
213
+ }
214
+
215
+ private detectMode(): "sections" | "tree" | "flat" {
216
+ const items = this.getItems();
217
+ for (const item of items) {
218
+ if (item.section) return "sections";
219
+ if (item.children && item.children.length > 0) return "tree";
220
+ }
221
+ return "flat";
222
+ }
223
+
224
+ private hasSections(): boolean {
225
+ return this.getItems().some((item) => item.section);
226
+ }
227
+
228
+ private isTreeMode(): boolean {
229
+ const items = this.getItems();
230
+ for (const item of items) {
231
+ if (!item.section && item.children && item.children.length > 0) return true;
232
+ }
233
+ return false;
234
+ }
235
+
236
+ private setupModel(): void {
237
+ this.model = new Gtk.StringList();
238
+ }
239
+
240
+ private setupFactory(): void {
241
+ if (this.isColumnView()) return;
242
+
243
+ this.factory = new Gtk.SignalListItemFactory();
244
+ const isTree = this.isTreeMode();
245
+
246
+ this.factory.connect("setup", (_self: GObject.Object, obj: GObject.Object) => {
247
+ const listItem = obj as unknown as Gtk.ListItem;
248
+
249
+ if (isTree) {
250
+ const expander = new Gtk.TreeExpander();
251
+ listItem.setChild(expander);
252
+ this.containers.set(expander, UNBOUND_POSITION);
253
+ this.containerKeys.set(expander, String(getNativeId(expander.handle)));
254
+ this.treeExpanders.set(listItem, expander);
255
+ } else {
256
+ const { width, height } = this.getEstimatedItemSize();
257
+ if (width !== -1 || height !== -1) {
258
+ const placeholder = new Gtk.Box();
259
+ placeholder.setSizeRequest(width, height);
260
+ listItem.setChild(placeholder);
261
+ }
262
+ this.containers.set(listItem, UNBOUND_POSITION);
263
+ this.containerKeys.set(listItem, String(getNativeId(listItem.handle)));
264
+ }
265
+ });
266
+
267
+ this.factory.connect("bind", (_self: GObject.Object, obj: GObject.Object) => {
268
+ if (this.disposed) return;
269
+ const listItem = obj as unknown as Gtk.ListItem;
270
+ const position = listItem.getPosition();
271
+
272
+ if (isTree) {
273
+ const expander = this.treeExpanders.get(listItem) as Gtk.TreeExpander;
274
+ const row = (listItem as unknown as { getItem(): GObject.Object | null }).getItem() as Gtk.TreeListRow;
275
+ expander.setListRow(row);
276
+ this.applyEstimatedItemSize(expander);
277
+
278
+ const treeItem = this.resolveTreeItem(row);
279
+ if (treeItem) {
280
+ this.applyTreeExpanderProps(expander, treeItem);
281
+ }
282
+
283
+ this.containers.set(expander, position);
284
+ } else {
285
+ this.containers.set(listItem, position);
286
+ }
287
+
288
+ this.scheduleBoundItemsUpdate();
289
+ });
290
+
291
+ this.factory.connect("unbind", (_self: GObject.Object, obj: GObject.Object) => {
292
+ if (this.disposed) return;
293
+ const listItem = obj as unknown as Gtk.ListItem;
294
+
295
+ if (isTree) {
296
+ const expander = this.treeExpanders.get(listItem) as Gtk.TreeExpander;
297
+ this.containers.set(expander, UNBOUND_POSITION);
298
+ expander.setListRow(null);
299
+ } else {
300
+ this.containers.set(listItem, UNBOUND_POSITION);
301
+ }
302
+
303
+ this.scheduleBoundItemsUpdate();
304
+ });
305
+
306
+ this.factory.connect("teardown", (_self: GObject.Object, obj: GObject.Object) => {
307
+ if (this.disposed) return;
308
+ const listItem = obj as unknown as Gtk.ListItem;
309
+
310
+ if (isTree) {
311
+ const expander = this.treeExpanders.get(listItem);
312
+ if (expander) {
313
+ this.containers.delete(expander);
314
+ this.containerKeys.delete(expander);
315
+ }
316
+ this.treeExpanders.delete(listItem);
317
+ } else {
318
+ this.containers.delete(listItem);
319
+ this.containerKeys.delete(listItem);
320
+ }
321
+
322
+ listItem.setChild(null);
323
+ });
324
+
325
+ if (this.props.renderListItem && this.isDropDown()) {
326
+ this.setupListFactory();
327
+ }
328
+ }
329
+
330
+ private setupListFactory(): void {
331
+ this.listFactory = new Gtk.SignalListItemFactory();
332
+
333
+ this.listFactory.connect("setup", (_self: GObject.Object, obj: GObject.Object) => {
334
+ const listItem = obj as unknown as Gtk.ListItem;
335
+ this.listContainers.set(listItem, UNBOUND_POSITION);
336
+ this.listContainerKeys.set(listItem, String(getNativeId(listItem.handle)));
337
+ });
338
+
339
+ this.listFactory.connect("bind", (_self: GObject.Object, obj: GObject.Object) => {
340
+ const listItem = obj as unknown as Gtk.ListItem;
341
+ this.listContainers.set(listItem, listItem.getPosition());
342
+ this.scheduleBoundItemsUpdate();
343
+ });
344
+
345
+ this.listFactory.connect("unbind", (_self: GObject.Object, obj: GObject.Object) => {
346
+ const listItem = obj as unknown as Gtk.ListItem;
347
+ this.listContainers.set(listItem, UNBOUND_POSITION);
348
+ this.scheduleBoundItemsUpdate();
349
+ });
350
+
351
+ this.listFactory.connect("teardown", (_self: GObject.Object, obj: GObject.Object) => {
352
+ const listItem = obj as unknown as Gtk.ListItem;
353
+ this.listContainers.delete(listItem);
354
+ this.listContainerKeys.delete(listItem);
355
+ listItem.setChild(null);
356
+ });
357
+ }
358
+
359
+ private setupHeaderFactory(): void {
360
+ this.headerFactory = new Gtk.SignalListItemFactory();
361
+
362
+ this.headerFactory.connect("setup", (_self: GObject.Object, obj: GObject.Object) => {
363
+ const listHeader = obj as unknown as Gtk.ListHeader;
364
+ this.headerContainers.set(listHeader, UNBOUND_POSITION);
365
+ this.headerContainerKeys.set(listHeader, String(getNativeId(listHeader.handle)));
366
+ });
367
+
368
+ this.headerFactory.connect("bind", (_self: GObject.Object, obj: GObject.Object) => {
369
+ const listHeader = obj as unknown as Gtk.ListHeader;
370
+ this.headerContainers.set(listHeader, listHeader.getStart());
371
+ this.scheduleBoundItemsUpdate();
372
+ });
373
+
374
+ this.headerFactory.connect("unbind", (_self: GObject.Object, obj: GObject.Object) => {
375
+ const listHeader = obj as unknown as Gtk.ListHeader;
376
+ this.headerContainers.set(listHeader, UNBOUND_POSITION);
377
+ this.scheduleBoundItemsUpdate();
378
+ });
379
+
380
+ this.headerFactory.connect("teardown", (_self: GObject.Object, obj: GObject.Object) => {
381
+ const listHeader = obj as unknown as Gtk.ListHeader;
382
+ this.headerContainers.delete(listHeader);
383
+ this.headerContainerKeys.delete(listHeader);
384
+ listHeader.setChild(null);
385
+ });
386
+ }
387
+
388
+ private setupSelectionModel(props: ListProps): void {
389
+ const baseModel = this.getBaseModel();
390
+ const selectionMode = props.selectionMode ?? (this.isDropDown() ? null : Gtk.SelectionMode.SINGLE);
391
+
392
+ if (this.isDropDown()) {
393
+ this.selectionModel = null;
394
+ return;
395
+ }
396
+
397
+ if (selectionMode === Gtk.SelectionMode.MULTIPLE) {
398
+ this.selectionModel = new Gtk.MultiSelection(baseModel);
399
+ } else if (selectionMode === Gtk.SelectionMode.NONE) {
400
+ this.selectionModel = new Gtk.NoSelection(baseModel);
401
+ } else {
402
+ const sel = new Gtk.SingleSelection(baseModel);
403
+ sel.setAutoselect(false);
404
+ sel.setCanUnselect(true);
405
+ this.selectionModel = sel;
406
+ }
407
+ }
408
+
409
+ private getBaseModel(): Gio.ListModel {
410
+ if (this.treeModel) return this.treeModel;
411
+ if (this.flattenModel) return this.flattenModel;
412
+ return this.model as Gtk.StringList;
413
+ }
414
+
415
+ private assignModelToWidget(): void {
416
+ const widget = this.container;
417
+
418
+ if (this.isDropDown()) {
419
+ const dropDownModel = this.hasSections()
420
+ ? (this.flattenModel as Gio.ListModel)
421
+ : (this.model as Gio.ListModel);
422
+ if (widget instanceof Gtk.DropDown) {
423
+ widget.setModel(dropDownModel);
424
+ } else if (widget instanceof Adw.ComboRow) {
425
+ widget.setModel(dropDownModel);
426
+ }
427
+ return;
428
+ }
429
+
430
+ if (!this.selectionModel) return;
431
+
432
+ if (widget instanceof Gtk.ListView) {
433
+ widget.setModel(this.selectionModel);
434
+ } else if (widget instanceof Gtk.GridView) {
435
+ widget.setModel(this.selectionModel);
436
+ } else if (widget instanceof Gtk.ColumnView) {
437
+ widget.setModel(this.selectionModel);
438
+ }
439
+ }
440
+
441
+ private assignFactoryToWidget(): void {
442
+ const widget = this.container;
443
+
444
+ if (widget instanceof Gtk.ListView) {
445
+ widget.setFactory(this.factory);
446
+ if (this.headerFactory) {
447
+ widget.setHeaderFactory(this.headerFactory);
448
+ }
449
+ } else if (widget instanceof Gtk.ColumnView) {
450
+ if (this.headerFactory) {
451
+ widget.setHeaderFactory(this.headerFactory);
452
+ }
453
+ } else if (widget instanceof Gtk.GridView) {
454
+ widget.setFactory(this.factory);
455
+ } else if (widget instanceof Gtk.DropDown) {
456
+ widget.setFactory(this.factory);
457
+ if (this.listFactory) {
458
+ widget.setListFactory(this.listFactory);
459
+ }
460
+ if (this.headerFactory) {
461
+ widget.setHeaderFactory(this.headerFactory);
462
+ }
463
+ } else if (widget instanceof Adw.ComboRow) {
464
+ widget.setFactory(this.factory);
465
+ if (this.listFactory) {
466
+ widget.setListFactory(this.listFactory);
467
+ }
468
+ if (this.headerFactory) {
469
+ widget.setHeaderFactory(this.headerFactory);
470
+ }
471
+ }
472
+ }
473
+
474
+ private syncModel(): void {
475
+ if (!this.model) return;
476
+
477
+ const mode = this.detectMode();
478
+
479
+ if (mode === "sections") {
480
+ this.syncSectionModel();
481
+ return;
482
+ }
483
+
484
+ if (mode === "tree") {
485
+ this.syncTreeModel();
486
+ return;
487
+ }
488
+
489
+ const flatItems = this.collectFlatItems();
490
+ resizeStringList(this.model, flatItems.length);
491
+
492
+ this.scheduleBoundItemsUpdate();
493
+ }
494
+
495
+ private syncTreeModel(): void {
496
+ if (!this.model) return;
497
+
498
+ const rootItems = this.collectRootItems();
499
+ const newSize = rootItems.length;
500
+
501
+ if (!this.treeModel) {
502
+ this.model.splice(0, this.model.getNItems(), new Array(newSize).fill(""));
503
+ this.rootItemIds = rootItems.map((item) => item.id);
504
+
505
+ this.treeModel = new Gtk.TreeListModel(
506
+ this.model as Gio.ListModel,
507
+ false,
508
+ this.props.autoexpand ?? false,
509
+ (_item: GObject.Object) => this.createChildModel(_item),
510
+ );
511
+
512
+ if (this.selectionModel) {
513
+ if (this.selectionModel instanceof Gtk.SingleSelection) {
514
+ this.selectionModel.setModel(this.treeModel);
515
+ } else if (this.selectionModel instanceof Gtk.MultiSelection) {
516
+ this.selectionModel.setModel(this.treeModel);
517
+ } else if (this.selectionModel instanceof Gtk.NoSelection) {
518
+ this.selectionModel.setModel(this.treeModel);
519
+ }
520
+ }
521
+
522
+ this.scheduleBoundItemsUpdate();
523
+ return;
524
+ }
525
+
526
+ const oldSize = this.model.getNItems();
527
+ const overlap = Math.min(oldSize, newSize);
528
+ const transitionPositions: number[] = [];
529
+
530
+ for (let i = 0; i < overlap; i++) {
531
+ const obj = this.model.getObject(i);
532
+ if (!obj) continue;
533
+
534
+ if (this.rootItemIds[i] !== rootItems[i]?.id) {
535
+ this.treeChildModels.delete(obj.handle);
536
+ this.queriedLeaves.delete(obj.handle);
537
+ transitionPositions.push(i);
538
+ continue;
539
+ }
540
+
541
+ const cachedChildModel = this.treeChildModels.get(obj.handle);
542
+ const newChildCount = rootItems[i]?.children?.length ?? 0;
543
+
544
+ if (cachedChildModel && newChildCount > 0) {
545
+ resizeStringList(cachedChildModel, newChildCount);
546
+ } else if (cachedChildModel && newChildCount === 0) {
547
+ this.treeChildModels.delete(obj.handle);
548
+ transitionPositions.push(i);
549
+ } else if (!cachedChildModel && newChildCount > 0) {
550
+ transitionPositions.push(i);
551
+ }
552
+ }
553
+
554
+ for (let i = overlap; i < oldSize; i++) {
555
+ const obj = this.model.getObject(i);
556
+ if (obj) {
557
+ this.treeChildModels.delete(obj.handle);
558
+ this.queriedLeaves.delete(obj.handle);
559
+ }
560
+ }
561
+
562
+ resizeStringList(this.model, newSize);
563
+
564
+ for (const pos of transitionPositions) {
565
+ if (pos < newSize) {
566
+ const oldObj = this.model.getObject(pos);
567
+ if (oldObj) {
568
+ this.queriedLeaves.delete(oldObj.handle);
569
+ this.treeChildModels.delete(oldObj.handle);
570
+ }
571
+ this.model.splice(pos, 1, [""]);
572
+ }
573
+ }
574
+
575
+ this.rootItemIds = rootItems.map((item) => item.id);
576
+ this.scheduleBoundItemsUpdate();
577
+ }
578
+
579
+ private createChildModel(_item: GObject.Object): Gio.ListModel | null {
580
+ const rootItems = this.collectRootItems();
581
+ const position = this.findStringObjectPosition(_item);
582
+
583
+ if (position === null || position >= rootItems.length) {
584
+ this.queriedLeaves.add(_item.handle);
585
+ return null;
586
+ }
587
+
588
+ const item = rootItems[position];
589
+ if (!item?.children || item.children.length === 0) {
590
+ this.queriedLeaves.add(_item.handle);
591
+ return null;
592
+ }
593
+
594
+ const childModel = new Gtk.StringList();
595
+ resizeStringList(childModel, item.children.length);
596
+ this.treeChildModels.set(_item.handle, childModel);
597
+ this.queriedLeaves.delete(_item.handle);
598
+ return childModel;
599
+ }
600
+
601
+ private findStringObjectPosition(item: GObject.Object): number | null {
602
+ if (!this.model) return null;
603
+ const nItems = this.model.getNItems();
604
+ for (let i = 0; i < nItems; i++) {
605
+ const obj = this.model.getObject(i);
606
+ if (obj && obj.handle === item.handle) {
607
+ return i;
608
+ }
609
+ }
610
+ return null;
611
+ }
612
+
613
+ private syncSectionModel(): void {
614
+ if (!this.model) return;
615
+
616
+ const sections = this.collectSections();
617
+
618
+ if (!this.sectionStore) {
619
+ this.sectionStore = new Gio.ListStore(Gtk.StringList.getGType());
620
+ this.flattenModel = new Gtk.FlattenListModel(this.sectionStore as unknown as Gio.ListModel);
621
+
622
+ if (this.selectionModel) {
623
+ if (this.selectionModel instanceof Gtk.SingleSelection) {
624
+ this.selectionModel.setModel(this.flattenModel);
625
+ } else if (this.selectionModel instanceof Gtk.MultiSelection) {
626
+ this.selectionModel.setModel(this.flattenModel);
627
+ } else if (this.selectionModel instanceof Gtk.NoSelection) {
628
+ this.selectionModel.setModel(this.flattenModel);
629
+ }
630
+ }
631
+
632
+ if (this.isDropDown()) {
633
+ this.assignModelToWidget();
634
+ }
635
+ }
636
+
637
+ while (this.sectionModels.length > sections.length) {
638
+ this.sectionModels.pop();
639
+ this.sectionStore.remove(this.sectionStore.getNItems() - 1);
640
+ }
641
+
642
+ for (let i = 0; i < sections.length; i++) {
643
+ const section = sections[i] as ListItem;
644
+ const itemCount = section.children?.length ?? 0;
645
+
646
+ if (i >= this.sectionModels.length) {
647
+ const sectionModel = new Gtk.StringList();
648
+ resizeStringList(sectionModel, itemCount);
649
+ this.sectionModels.push(sectionModel);
650
+ this.sectionStore.append(sectionModel as unknown as GObject.Object);
651
+ } else {
652
+ resizeStringList(this.sectionModels[i] as Gtk.StringList, itemCount);
653
+ }
654
+ }
655
+
656
+ this.scheduleBoundItemsUpdate();
657
+ }
658
+
659
+ private resolveTreeItem(row: Gtk.TreeListRow): ListItem | null {
660
+ const rootItems = this.collectRootItems();
661
+ const depth = row.getDepth();
662
+
663
+ if (depth === 0) {
664
+ const rootItem = row.getItem();
665
+ if (!rootItem) return null;
666
+ const pos = this.findStringObjectPosition(rootItem);
667
+ if (pos === null) return null;
668
+ return rootItems[pos] ?? null;
669
+ }
670
+
671
+ const parentRow = row.getParent();
672
+ if (!parentRow) return null;
673
+
674
+ const parentItem = this.resolveTreeItem(parentRow);
675
+ if (!parentItem?.children) return null;
676
+
677
+ const childItem = row.getItem();
678
+ if (!childItem) return null;
679
+
680
+ const childModel = parentRow.getChildren();
681
+ if (childModel) {
682
+ for (let j = 0; j < childModel.getNItems(); j++) {
683
+ const obj = childModel.getObject(j);
684
+ if (obj && obj.handle === childItem.handle) {
685
+ return parentItem.children[j] ?? null;
686
+ }
687
+ }
688
+ }
689
+
690
+ return null;
691
+ }
692
+
693
+ private applyTreeExpanderProps(expander: Gtk.TreeExpander, item: ListItem): void {
694
+ if (item.section) return;
695
+ expander.setIndentForDepth(item.indentForDepth ?? true);
696
+ expander.setIndentForIcon(item.indentForIcon ?? true);
697
+ expander.setHideExpander(item.hideExpander ?? false);
698
+ }
699
+
700
+ private resolveItemIdAtPosition(position: number): string | null {
701
+ if (this.treeModel) {
702
+ const row = this.treeModel.getRow(position);
703
+ const item = row ? this.resolveTreeItem(row) : null;
704
+ return item?.id ?? null;
705
+ }
706
+ const flatItems = this.collectFlatItems();
707
+ return flatItems[position]?.id ?? null;
708
+ }
709
+
710
+ private applyOwnProps(oldProps: ListProps, newProps: ListProps): void {
711
+ if (hasChanged(oldProps, newProps, "items")) {
712
+ this.scheduleSync();
713
+ }
714
+
715
+ if (hasChanged(oldProps, newProps, "selected")) {
716
+ this.applySelection(newProps.selected ?? null);
717
+ }
718
+
719
+ if (hasChanged(oldProps, newProps, "selectedId")) {
720
+ this.applySelectedId(newProps.selectedId ?? null);
721
+ }
722
+
723
+ if (hasChanged(oldProps, newProps, "onSelectionChanged")) {
724
+ this.connectSelectionSignal();
725
+ }
726
+
727
+ if (hasChanged(oldProps, newProps, "selectionMode")) {
728
+ this.rebuildSelectionModel(newProps);
729
+ }
730
+
731
+ if (
732
+ hasChanged(oldProps, newProps, "renderItem") ||
733
+ hasChanged(oldProps, newProps, "renderListItem") ||
734
+ hasChanged(oldProps, newProps, "renderHeader")
735
+ ) {
736
+ this.scheduleBoundItemsUpdate();
737
+ }
738
+
739
+ if (hasChanged(oldProps, newProps, "autoexpand") && this.treeModel) {
740
+ this.treeModel.setAutoexpand(newProps.autoexpand ?? false);
741
+ }
742
+
743
+ if (hasChanged(oldProps, newProps, "onSortChanged")) {
744
+ this.connectSortSignal();
745
+ }
746
+
747
+ if (hasChanged(oldProps, newProps, "sortColumn") || hasChanged(oldProps, newProps, "sortOrder")) {
748
+ this.applySortColumn(newProps);
749
+ }
750
+ }
751
+
752
+ private rebuildSelectionModel(props: ListProps): void {
753
+ const baseModel = this.getBaseModel();
754
+ const selectionMode = props.selectionMode ?? Gtk.SelectionMode.SINGLE;
755
+
756
+ if (selectionMode === Gtk.SelectionMode.MULTIPLE) {
757
+ this.selectionModel = new Gtk.MultiSelection(baseModel);
758
+ } else if (selectionMode === Gtk.SelectionMode.NONE) {
759
+ this.selectionModel = new Gtk.NoSelection(baseModel);
760
+ } else {
761
+ const sel = new Gtk.SingleSelection(baseModel);
762
+ sel.setAutoselect(false);
763
+ sel.setCanUnselect(true);
764
+ this.selectionModel = sel;
765
+ }
766
+
767
+ this.assignModelToWidget();
768
+ this.connectSelectionSignal();
769
+ }
770
+
771
+ private applySelection(ids: string[] | null): void {
772
+ if (!this.selectionModel || this.isDropDown()) return;
773
+
774
+ if (!ids || ids.length === 0) {
775
+ if (this.selectionModel instanceof Gtk.SingleSelection) {
776
+ this.selectionModel.setSelected(Gtk.INVALID_LIST_POSITION);
777
+ } else if (this.selectionModel instanceof Gtk.MultiSelection) {
778
+ this.selectionModel.unselectAll();
779
+ }
780
+ return;
781
+ }
782
+
783
+ const idSet = new Set(ids);
784
+ const nItems = this.selectionModel.getNItems();
785
+
786
+ if (this.selectionModel instanceof Gtk.SingleSelection) {
787
+ for (let i = 0; i < nItems; i++) {
788
+ const id = this.resolveItemIdAtPosition(i);
789
+ if (id && idSet.has(id)) {
790
+ this.selectionModel.setSelected(i);
791
+ return;
792
+ }
793
+ }
794
+ } else if (this.selectionModel instanceof Gtk.MultiSelection) {
795
+ this.selectionModel.unselectAll();
796
+ for (let i = 0; i < nItems; i++) {
797
+ const id = this.resolveItemIdAtPosition(i);
798
+ if (id && idSet.has(id)) {
799
+ this.selectionModel.selectItem(i, false);
800
+ }
801
+ }
802
+ }
803
+ }
804
+
805
+ private applySelectedId(id: string | null): void {
806
+ if (!this.isDropDown()) return;
807
+ if (id === null || id === undefined) return;
808
+
809
+ const flatItems = this.collectFlatItems();
810
+ for (let i = 0; i < flatItems.length; i++) {
811
+ if (flatItems[i]?.id === id) {
812
+ if (this.container instanceof Gtk.DropDown) {
813
+ this.container.setSelected(i);
814
+ } else if (this.container instanceof Adw.ComboRow) {
815
+ this.container.setSelected(i);
816
+ }
817
+ return;
818
+ }
819
+ }
820
+ }
821
+
822
+ private connectSelectionSignal(): void {
823
+ const { onSelectionChanged } = this.props;
824
+
825
+ if (this.isDropDown()) {
826
+ const handler = onSelectionChanged
827
+ ? () => {
828
+ const position =
829
+ this.container instanceof Gtk.DropDown
830
+ ? this.container.getSelected()
831
+ : this.container instanceof Adw.ComboRow
832
+ ? this.container.getSelected()
833
+ : -1;
834
+ const flatItems = this.collectFlatItems();
835
+ const item = flatItems[position];
836
+ if (item) {
837
+ (onSelectionChanged as (id: string) => void)(item.id);
838
+ }
839
+ }
840
+ : undefined;
841
+
842
+ this.signalStore.set(this, this.container as GObject.Object, "notify::selected", handler);
843
+ return;
844
+ }
845
+
846
+ if (!this.selectionModel) return;
847
+
848
+ const handler = onSelectionChanged
849
+ ? () => {
850
+ const selection = this.selectionModel?.getSelection();
851
+ if (!selection) return;
852
+
853
+ const ids: string[] = [];
854
+ const nItems = this.selectionModel?.getNItems() ?? 0;
855
+
856
+ if (this.treeModel) {
857
+ for (let i = 0; i < nItems; i++) {
858
+ if (selection.contains(i)) {
859
+ const row = this.treeModel.getRow(i);
860
+ const item = row ? this.resolveTreeItem(row) : null;
861
+ if (item) ids.push(item.id);
862
+ }
863
+ }
864
+ } else {
865
+ const flatItems = this.collectFlatItems();
866
+ for (let i = 0; i < nItems; i++) {
867
+ if (selection.contains(i)) {
868
+ const item = flatItems[i];
869
+ if (item) ids.push(item.id);
870
+ }
871
+ }
872
+ }
873
+
874
+ (onSelectionChanged as (ids: string[]) => void)(ids);
875
+ }
876
+ : undefined;
877
+
878
+ this.signalStore.set(this, this.selectionModel as GObject.Object, "selection-changed", handler, {
879
+ blockable: false,
880
+ });
881
+ }
882
+
883
+ private connectSortSignal(): void {
884
+ if (!this.isColumnView()) return;
885
+
886
+ const columnView = this.container as Gtk.ColumnView;
887
+ const sorter = columnView.getSorter();
888
+ if (!sorter) return;
889
+
890
+ const { onSortChanged } = this.props;
891
+ const handler = onSortChanged
892
+ ? () => {
893
+ const cvSorter = columnView.getSorter() as Gtk.ColumnViewSorter | null;
894
+ if (!cvSorter) {
895
+ onSortChanged(null, Gtk.SortType.ASCENDING);
896
+ return;
897
+ }
898
+ const primaryColumn = cvSorter.getPrimarySortColumn();
899
+ const primaryOrder = cvSorter.getPrimarySortOrder();
900
+ const columnId = primaryColumn?.getId() ?? null;
901
+ onSortChanged(columnId, primaryOrder);
902
+ }
903
+ : undefined;
904
+
905
+ this.signalStore.set(this, sorter as GObject.Object, "changed", handler, { blockable: false });
906
+ }
907
+
908
+ private applySortColumn(props: ListProps): void {
909
+ if (!this.isColumnView()) return;
910
+
911
+ const columnView = this.container as Gtk.ColumnView;
912
+ const { sortColumn, sortOrder } = props;
913
+
914
+ if (sortColumn === null || sortColumn === undefined) {
915
+ columnView.sortByColumn(Gtk.SortType.ASCENDING, null);
916
+ return;
917
+ }
918
+
919
+ const column = this.findColumnById(sortColumn);
920
+ if (column) {
921
+ columnView.sortByColumn(sortOrder ?? Gtk.SortType.ASCENDING, column);
922
+ }
923
+ }
924
+
925
+ public findColumnById(id: string): Gtk.ColumnViewColumn | null {
926
+ if (!this.isColumnView()) return null;
927
+ const columnView = this.container as Gtk.ColumnView;
928
+ const columns = columnView.getColumns();
929
+ const nItems = columns.getNItems();
930
+
931
+ for (let i = 0; i < nItems; i++) {
932
+ const obj = columns.getObject(i) as Gtk.ColumnViewColumn | null;
933
+ if (obj && obj.getId() === id) {
934
+ return obj;
935
+ }
936
+ }
937
+ return null;
938
+ }
939
+
940
+ private scheduleSync(): void {
941
+ if (this.syncScheduled) return;
942
+ this.syncScheduled = true;
943
+
944
+ queueMicrotask(() => {
945
+ this.syncScheduled = false;
946
+ if (this.disposed) return;
947
+ this.syncModel();
948
+ });
949
+ }
950
+
951
+ public scheduleBoundItemsUpdate(): void {
952
+ if (this.boundItemsUpdateScheduled) return;
953
+ this.boundItemsUpdateScheduled = true;
954
+
955
+ queueMicrotask(() => {
956
+ this.boundItemsUpdateScheduled = false;
957
+ if (this.disposed) return;
958
+ this.rebuildBoundItems();
959
+ });
960
+ }
961
+
962
+ private rebuildBoundItems(): void {
963
+ const { __boundItemsRef, __rerender, __headerBoundItemsRef, renderItem, renderListItem, renderHeader } =
964
+ this.props;
965
+ if (!__boundItemsRef || !__rerender) return;
966
+
967
+ const flatItems = this.collectFlatItems();
968
+ const newBoundItems: BoundItem[] = [];
969
+
970
+ if (this.isColumnView()) {
971
+ for (const child of this.children) {
972
+ if (child instanceof ColumnViewColumnNode) {
973
+ newBoundItems.push(...child.collectBoundItems(flatItems));
974
+ }
975
+ }
976
+ } else {
977
+ const renderFn = renderItem ?? (this.isDropDown() ? (item: unknown) => String(item ?? "") : null);
978
+
979
+ if (renderFn) {
980
+ this.collectContainerBoundItems(
981
+ this.containers,
982
+ this.containerKeys,
983
+ flatItems,
984
+ renderFn,
985
+ newBoundItems,
986
+ );
987
+ }
988
+
989
+ if (renderListItem && this.isDropDown()) {
990
+ this.collectContainerBoundItems(
991
+ this.listContainers,
992
+ this.listContainerKeys,
993
+ flatItems,
994
+ renderListItem,
995
+ newBoundItems,
996
+ );
997
+ }
998
+ }
999
+
1000
+ __boundItemsRef.current = newBoundItems;
1001
+
1002
+ if (__headerBoundItemsRef && renderHeader && this.sectionStore !== null) {
1003
+ const sections = this.collectSections();
1004
+ const headerBoundItems: BoundItem[] = [];
1005
+ let sectionStart = 0;
1006
+
1007
+ for (let i = 0; i < sections.length; i++) {
1008
+ const section = sections[i] as ListItem;
1009
+
1010
+ for (const [container, position] of this.headerContainers) {
1011
+ if (position === UNBOUND_POSITION) continue;
1012
+ if (position === sectionStart) {
1013
+ const key = this.headerContainerKeys.get(container);
1014
+ if (key) {
1015
+ const content = renderHeader(section.value);
1016
+ headerBoundItems.push([content, container, key]);
1017
+ }
1018
+ }
1019
+ }
1020
+
1021
+ sectionStart += section.children?.length ?? 0;
1022
+ }
1023
+
1024
+ __headerBoundItemsRef.current = headerBoundItems;
1025
+ }
1026
+
1027
+ __rerender();
1028
+ }
1029
+
1030
+ private collectContainerBoundItems(
1031
+ containers: Map<Container, number>,
1032
+ containerKeys: Map<Container, string>,
1033
+ flatItems: ListItem[],
1034
+ renderFn: (item: unknown, row?: Gtk.TreeListRow | null) => ReactNode,
1035
+ out: BoundItem[],
1036
+ ): void {
1037
+ const isTree = this.treeModel !== null;
1038
+
1039
+ for (const [container, position] of containers) {
1040
+ if (position === UNBOUND_POSITION) continue;
1041
+
1042
+ const key = containerKeys.get(container);
1043
+ if (!key) continue;
1044
+
1045
+ if (isTree) {
1046
+ const expander = container as Gtk.TreeExpander;
1047
+ const row = expander.getListRow() ?? null;
1048
+ if (!row) continue;
1049
+ const item = this.resolveTreeItem(row);
1050
+ if (!item) continue;
1051
+ const content = renderFn(item.value, row);
1052
+ out.push([content, container, key]);
1053
+ } else {
1054
+ const item = flatItems[position];
1055
+ if (!item) continue;
1056
+ const content = renderFn(item.value);
1057
+ out.push([content, container, key]);
1058
+ }
1059
+ }
1060
+ }
1061
+
1062
+ public getEstimatedItemSize(): { width: number; height: number } {
1063
+ return {
1064
+ width: this.props.estimatedItemWidth ?? -1,
1065
+ height: this.props.estimatedItemHeight ?? -1,
1066
+ };
1067
+ }
1068
+
1069
+ private applyEstimatedItemSize(widget: Gtk.Widget): void {
1070
+ const { width, height } = this.getEstimatedItemSize();
1071
+ widget.setSizeRequest(width, height);
1072
+ }
1073
+
1074
+ private getColumnPosition(columnNode: ColumnViewColumnNode): number {
1075
+ let columnIndex = 0;
1076
+ for (const child of this.children) {
1077
+ if (child === columnNode) return columnIndex;
1078
+ if (child instanceof ColumnViewColumnNode) columnIndex++;
1079
+ }
1080
+ return columnIndex;
1081
+ }
1082
+ }