@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
@@ -1,33 +0,0 @@
1
- import type * as Gtk from "@gtkx/ffi/gtk";
2
- import type { ReactNode } from "react";
3
- import { HeaderItemRenderer } from "./header-item-renderer.js";
4
- import type { SignalStore } from "./signal-store.js";
5
-
6
- type HeaderRendererConfig = {
7
- signalStore: SignalStore;
8
- isEnabled: () => boolean;
9
- resolveItem: (id: string) => unknown;
10
- setFactory: (factory: Gtk.SignalListItemFactory | null) => void;
11
- };
12
-
13
- export function updateHeaderRenderer(
14
- current: HeaderItemRenderer | null,
15
- config: HeaderRendererConfig,
16
- renderFn: ((item: unknown) => ReactNode) | null | undefined,
17
- ): HeaderItemRenderer | null {
18
- if (renderFn) {
19
- if (!current && config.isEnabled()) {
20
- current = new HeaderItemRenderer(config.signalStore);
21
- current.setResolveItem(config.resolveItem);
22
- config.setFactory(current.getFactory());
23
- }
24
- if (current) {
25
- current.setRenderFn(renderFn);
26
- }
27
- } else if (current) {
28
- config.setFactory(null);
29
- current.dispose();
30
- current = null;
31
- }
32
- return current;
33
- }
@@ -1,162 +0,0 @@
1
- import * as Gtk from "@gtkx/ffi/gtk";
2
- import type { ReactNode } from "react";
3
- import type Reconciler from "react-reconciler";
4
- import { reconciler } from "../../reconciler.js";
5
- import { BaseItemRenderer } from "./base-item-renderer.js";
6
- import type { TreeStore } from "./tree-store.js";
7
-
8
- type RenderItemFn<T> = (item: T | null, row: Gtk.TreeListRow | null) => ReactNode;
9
-
10
- type PendingBind = {
11
- treeListRow: Gtk.TreeListRow;
12
- expander: Gtk.TreeExpander;
13
- id: string;
14
- };
15
-
16
- export class ListItemRenderer extends BaseItemRenderer<TreeStore> {
17
- private expanders = new Map<Gtk.ListItem, Gtk.TreeExpander>();
18
- private setupComplete = new Set<Gtk.ListItem>();
19
- private pendingBinds = new Map<Gtk.ListItem, PendingBind>();
20
- private renderFn: RenderItemFn<unknown> | null = () => null;
21
- private boundItems = new Map<string, { listItem: Gtk.ListItem; treeListRow: Gtk.TreeListRow }>();
22
-
23
- public setRenderFn(renderFn: RenderItemFn<unknown> | null): void {
24
- this.renderFn = renderFn;
25
- this.rebindAllItems();
26
- }
27
-
28
- public rebindAllItems(): void {
29
- for (const id of this.boundItems.keys()) {
30
- this.rebindItem(id);
31
- }
32
- }
33
-
34
- public rebindItem(id: string): void {
35
- const binding = this.boundItems.get(id);
36
- if (!binding) return;
37
-
38
- const fiberRoot = this.fiberRoots.get(binding.listItem);
39
- if (!fiberRoot) return;
40
-
41
- const expander = this.expanders.get(binding.listItem);
42
- if (!expander) return;
43
-
44
- this.renderBind(binding.listItem, expander, binding.treeListRow, id, fiberRoot);
45
- }
46
-
47
- public override dispose(): void {
48
- super.dispose();
49
- this.expanders.clear();
50
- this.setupComplete.clear();
51
- this.pendingBinds.clear();
52
- this.boundItems.clear();
53
- }
54
-
55
- protected override renderItem(_listItem: Gtk.ListItem): ReactNode {
56
- return this.renderFn?.(null, null);
57
- }
58
-
59
- protected override onSetup(listItem: Gtk.ListItem): Gtk.Widget {
60
- const expander = new Gtk.TreeExpander();
61
- const box = this.createBox();
62
- expander.setChild(box);
63
- listItem.setChild(expander);
64
- this.expanders.set(listItem, expander);
65
- return box;
66
- }
67
-
68
- protected override onSetupComplete(listItem: Gtk.ListItem): void {
69
- this.setupComplete.add(listItem);
70
- this.processPendingBind(listItem);
71
- }
72
-
73
- protected override onBind(listItem: Gtk.ListItem, fiberRoot: Reconciler.FiberRoot): void {
74
- const expander = this.expanders.get(listItem);
75
- if (!expander) return;
76
-
77
- const treeListRow = listItem.getItem();
78
- if (!(treeListRow instanceof Gtk.TreeListRow)) return;
79
-
80
- expander.setListRow(treeListRow);
81
-
82
- const stringObject = treeListRow.getItem();
83
- if (!(stringObject instanceof Gtk.StringObject)) return;
84
-
85
- const id = stringObject.getString();
86
- this.boundItems.set(id, { listItem, treeListRow });
87
-
88
- if (!this.setupComplete.has(listItem)) {
89
- this.pendingBinds.set(listItem, { treeListRow, expander, id });
90
- return;
91
- }
92
-
93
- this.renderBind(listItem, expander, treeListRow, id, fiberRoot);
94
- }
95
-
96
- protected override onUnbind(listItem: Gtk.ListItem): void {
97
- const expander = listItem.getChild();
98
- if (expander instanceof Gtk.TreeExpander) {
99
- expander.setListRow(null);
100
- const treeListRow = listItem.getItem();
101
- if (treeListRow instanceof Gtk.TreeListRow) {
102
- const stringObject = treeListRow.getItem();
103
- if (stringObject instanceof Gtk.StringObject) {
104
- this.boundItems.delete(stringObject.getString());
105
- }
106
- }
107
- }
108
- }
109
-
110
- protected override onTeardown(listItem: Gtk.ListItem): void {
111
- this.expanders.delete(listItem);
112
- this.setupComplete.delete(listItem);
113
- this.pendingBinds.delete(listItem);
114
- }
115
-
116
- private processPendingBind(listItem: Gtk.ListItem): void {
117
- const pending = this.pendingBinds.get(listItem);
118
- if (!pending) return;
119
-
120
- this.pendingBinds.delete(listItem);
121
- const fiberRoot = this.fiberRoots.get(listItem);
122
- if (fiberRoot) {
123
- this.renderBind(listItem, pending.expander, pending.treeListRow, pending.id, fiberRoot);
124
- }
125
- }
126
-
127
- private renderBind(
128
- listItem: Gtk.ListItem,
129
- expander: Gtk.TreeExpander,
130
- treeListRow: Gtk.TreeListRow,
131
- id: string,
132
- fiberRoot: Reconciler.FiberRoot,
133
- ): void {
134
- const itemData = this.getStore().getItem(id);
135
-
136
- if (itemData) {
137
- expander.setIndentForDepth(itemData.indentForDepth ?? true);
138
- expander.setHideExpander(itemData.hideExpander ?? false);
139
-
140
- if (itemData.indentForIcon !== undefined) {
141
- expander.setIndentForIcon(itemData.indentForIcon);
142
- } else {
143
- expander.setIndentForIcon(treeListRow.isExpandable());
144
- }
145
- } else {
146
- expander.setIndentForIcon(treeListRow.isExpandable());
147
- }
148
-
149
- const element = this.renderFn?.(itemData?.value ?? null, treeListRow);
150
-
151
- reconciler.getInstance().updateContainer(element, fiberRoot, null, () => {
152
- if (this.tornDown.has(listItem)) return;
153
- if (this.estimatedItemHeight !== null) return;
154
- const currentExpander = this.expanders.get(listItem);
155
- if (!currentExpander) return;
156
- const box = currentExpander.getChild();
157
- if (box) {
158
- this.clearBoxSizeRequest(box);
159
- }
160
- });
161
- }
162
- }
@@ -1,107 +0,0 @@
1
- import { SectionedListStore } from "./sectioned-list-store.js";
2
-
3
- type ItemUpdatedCallback = (id: string) => void;
4
-
5
- export class ListStore extends SectionedListStore {
6
- private items = new Map<string, unknown>();
7
- private onItemUpdated: ItemUpdatedCallback | null = null;
8
-
9
- public setOnItemUpdated(callback: ItemUpdatedCallback | null): void {
10
- this.onItemUpdated = callback;
11
- }
12
-
13
- protected override getInitialPendingBatch(): string[] | null {
14
- return null;
15
- }
16
-
17
- protected override getModelString(itemId: string, _item: unknown): string {
18
- return itemId;
19
- }
20
-
21
- protected override onItemAdded(itemId: string, item: unknown): void {
22
- this.items.set(itemId, item);
23
- }
24
-
25
- protected override onItemRemoved(itemId: string): void {
26
- this.items.delete(itemId);
27
- }
28
-
29
- public addItem(id: string, item: unknown): void {
30
- this.flushRemovals();
31
- this.items.set(id, item);
32
-
33
- const existingIndex = this.idToIndex.get(id);
34
- if (existingIndex !== undefined) {
35
- this.model.remove(existingIndex);
36
- this.ids.splice(existingIndex, 1);
37
- this.rebuildIndices(existingIndex);
38
- }
39
-
40
- this.idToIndex.set(id, this.ids.length);
41
- this.ids.push(id);
42
-
43
- if (this.pendingBatch) {
44
- this.pendingBatch.push(id);
45
- } else {
46
- this.model.append(id);
47
- }
48
- }
49
-
50
- public insertItemBefore(id: string, beforeId: string, item: unknown): void {
51
- this.flushRemovals();
52
- this.items.set(id, item);
53
-
54
- const existingIndex = this.idToIndex.get(id);
55
- if (existingIndex !== undefined) {
56
- this.model.remove(existingIndex);
57
- this.ids.splice(existingIndex, 1);
58
- this.idToIndex.delete(id);
59
- this.rebuildIndices(existingIndex);
60
- }
61
-
62
- const beforeIndex = this.idToIndex.get(beforeId);
63
- if (beforeIndex === undefined) {
64
- this.idToIndex.set(id, this.ids.length);
65
- this.ids.push(id);
66
- this.model.append(id);
67
- } else {
68
- this.ids.splice(beforeIndex, 0, id);
69
- this.rebuildIndices(beforeIndex);
70
- this.model.splice(beforeIndex, 0, [id]);
71
- }
72
- }
73
-
74
- public updateItem(id: string, item: unknown): void {
75
- if (this.items.has(id)) {
76
- this.items.set(id, item);
77
- this.onItemUpdated?.(id);
78
- } else {
79
- this.addItem(id, item);
80
- }
81
- }
82
-
83
- public getItem(id: string): unknown {
84
- return this.items.get(id);
85
- }
86
-
87
- public getHeaderValue(itemId: string): unknown {
88
- const sectionId = this.itemToSection.get(itemId);
89
- if (sectionId) {
90
- return this.headerValues.get(sectionId);
91
- }
92
- return undefined;
93
- }
94
-
95
- public getHeaderValueById(sectionId: string): unknown {
96
- return this.headerValues.get(sectionId);
97
- }
98
-
99
- public getStringList(): import("@gtkx/ffi/gtk").StringList {
100
- return this.model;
101
- }
102
-
103
- public getNItems(): number {
104
- this.flushRemovals();
105
- return this.ids.length;
106
- }
107
- }
@@ -1,287 +0,0 @@
1
- import * as Gio from "@gtkx/ffi/gio";
2
- import * as GObject from "@gtkx/ffi/gobject";
3
- import * as Gtk from "@gtkx/ffi/gtk";
4
-
5
- interface SectionData {
6
- id: string;
7
- model: Gtk.StringList;
8
- itemIds: string[];
9
- pendingBatch: string[] | null;
10
- }
11
-
12
- export abstract class SectionedListStore {
13
- protected ids: string[] = [];
14
- protected idToIndex = new Map<string, number>();
15
- protected model = new Gtk.StringList();
16
- protected pendingBatch: string[] | null = null;
17
- private pendingRemovals: Set<string> | null = null;
18
- private flushScheduled = false;
19
-
20
- protected sectioned = false;
21
- protected sections: SectionData[] = [];
22
- protected sectionById = new Map<string, SectionData>();
23
- protected itemToSection = new Map<string, string>();
24
- protected headerValues = new Map<string, unknown>();
25
- protected sectionContainer: Gio.ListStore | null = null;
26
- protected flatModel: Gtk.FlattenListModel | null = null;
27
-
28
- public enableSections(): void {
29
- if (this.sectioned) return;
30
- this.sectioned = true;
31
- this.sectionContainer = new Gio.ListStore(GObject.typeFromName("GtkStringList"));
32
- this.flatModel = new Gtk.FlattenListModel(this.sectionContainer);
33
- }
34
-
35
- public beginBatch(): void {
36
- if (this.sectioned) {
37
- for (const section of this.sections) {
38
- section.pendingBatch = [];
39
- }
40
- } else {
41
- this.pendingBatch = [];
42
- }
43
- }
44
-
45
- public flushBatch(): void {
46
- if (this.sectioned) {
47
- for (const section of this.sections) {
48
- const batch = section.pendingBatch;
49
- section.pendingBatch = null;
50
- if (batch && batch.length > 0) {
51
- section.model.splice(0, 0, batch);
52
- }
53
- }
54
- } else {
55
- const batch = this.pendingBatch;
56
- this.pendingBatch = null;
57
- if (batch && batch.length > 0) {
58
- this.model.splice(0, 0, batch);
59
- }
60
- }
61
- }
62
-
63
- public addSection(id: string, value: unknown): void {
64
- if (!this.sectioned) this.enableSections();
65
-
66
- this.headerValues.set(id, value);
67
- if (this.sectionById.has(id)) return;
68
-
69
- const sectionModel = new Gtk.StringList();
70
- const section: SectionData = {
71
- id,
72
- model: sectionModel,
73
- itemIds: [],
74
- pendingBatch: this.getInitialPendingBatch(),
75
- };
76
- this.sections.push(section);
77
- this.sectionById.set(id, section);
78
- this.sectionContainer?.append(sectionModel);
79
- }
80
-
81
- public removeSection(id: string): void {
82
- const section = this.sectionById.get(id);
83
- if (!section) return;
84
-
85
- for (const itemId of [...section.itemIds]) {
86
- this.removeItemFromSection(itemId);
87
- }
88
-
89
- const sectionIndex = this.sections.indexOf(section);
90
- if (sectionIndex >= 0) {
91
- this.sections.splice(sectionIndex, 1);
92
- this.sectionContainer?.remove(sectionIndex);
93
- }
94
- this.sectionById.delete(id);
95
- this.headerValues.delete(id);
96
- }
97
-
98
- public removeItemFromSection(itemId: string): void {
99
- const sectionId = this.itemToSection.get(itemId);
100
- if (!sectionId) return;
101
-
102
- const section = this.sectionById.get(sectionId);
103
- if (!section) return;
104
-
105
- const indexInSection = section.itemIds.indexOf(itemId);
106
- if (indexInSection >= 0) {
107
- section.itemIds.splice(indexInSection, 1);
108
- section.model.remove(indexInSection);
109
- }
110
-
111
- this.itemToSection.delete(itemId);
112
- this.onItemRemoved(itemId);
113
- this.rebuildGlobalIndices();
114
- }
115
-
116
- public removeItem(id: string): void {
117
- if (this.sectioned) {
118
- this.removeItemFromSection(id);
119
- return;
120
- }
121
-
122
- if (!this.idToIndex.has(id)) return;
123
-
124
- if (!this.pendingRemovals) {
125
- this.pendingRemovals = new Set();
126
- if (!this.flushScheduled) {
127
- this.flushScheduled = true;
128
- queueMicrotask(() => this.flushRemovals());
129
- }
130
- }
131
- this.pendingRemovals.add(id);
132
- this.onItemRemoved(id);
133
- }
134
-
135
- public flushRemovals(): void {
136
- this.flushScheduled = false;
137
- const removals = this.pendingRemovals;
138
- if (!removals || removals.size === 0) {
139
- this.pendingRemovals = null;
140
- return;
141
- }
142
- this.pendingRemovals = null;
143
-
144
- const indices: number[] = [];
145
- for (const id of removals) {
146
- const index = this.idToIndex.get(id);
147
- if (index !== undefined) {
148
- indices.push(index);
149
- this.idToIndex.delete(id);
150
- }
151
- }
152
-
153
- if (indices.length === 0) return;
154
-
155
- indices.sort((a, b) => a - b);
156
-
157
- let i = indices.length - 1;
158
- while (i >= 0) {
159
- let rangeStart = indices[i] ?? 0;
160
- const rangeEnd = rangeStart;
161
-
162
- while (i > 0) {
163
- const prev = indices[i - 1];
164
- if (prev !== rangeStart - 1) break;
165
- i--;
166
- rangeStart = prev ?? 0;
167
- }
168
-
169
- const count = rangeEnd - rangeStart + 1;
170
- this.model.splice(rangeStart, count);
171
- this.ids.splice(rangeStart, count);
172
-
173
- i--;
174
- }
175
-
176
- this.rebuildIndices(0);
177
- }
178
-
179
- public addItemToSection(sectionId: string, itemId: string, item: unknown): void {
180
- const section = this.sectionById.get(sectionId);
181
- if (!section) return;
182
-
183
- this.onItemAdded(itemId, item);
184
- this.itemToSection.set(itemId, sectionId);
185
- section.itemIds.push(itemId);
186
- this.rebuildGlobalIndices();
187
-
188
- const modelString = this.getModelString(itemId, item);
189
- if (section.pendingBatch) {
190
- section.pendingBatch.push(modelString);
191
- } else {
192
- section.model.append(modelString);
193
- }
194
- }
195
-
196
- public addItemsToSection(sectionId: string, items: { itemId: string; item: unknown }[]): void {
197
- const section = this.sectionById.get(sectionId);
198
- if (!section) return;
199
- if (items.length === 0) return;
200
-
201
- const modelStrings: string[] = [];
202
- for (const { itemId, item } of items) {
203
- this.onItemAdded(itemId, item);
204
- this.itemToSection.set(itemId, sectionId);
205
- section.itemIds.push(itemId);
206
- modelStrings.push(this.getModelString(itemId, item));
207
- }
208
-
209
- this.rebuildGlobalIndices();
210
-
211
- if (section.pendingBatch) {
212
- for (const s of modelStrings) {
213
- section.pendingBatch.push(s);
214
- }
215
- } else {
216
- section.model.splice(section.model.getNItems(), 0, modelStrings);
217
- }
218
- }
219
-
220
- public insertItemToSectionBefore(sectionId: string, itemId: string, beforeId: string, item: unknown): void {
221
- const section = this.sectionById.get(sectionId);
222
- if (!section) return;
223
-
224
- this.onItemAdded(itemId, item);
225
- this.itemToSection.set(itemId, sectionId);
226
-
227
- const modelString = this.getModelString(itemId, item);
228
- const beforeIndex = section.itemIds.indexOf(beforeId);
229
- if (beforeIndex >= 0) {
230
- section.itemIds.splice(beforeIndex, 0, itemId);
231
- this.rebuildGlobalIndices();
232
- section.model.splice(beforeIndex, 0, [modelString]);
233
- } else {
234
- section.itemIds.push(itemId);
235
- this.rebuildGlobalIndices();
236
- section.model.append(modelString);
237
- }
238
- }
239
-
240
- public updateHeaderValue(sectionId: string, value: unknown): void {
241
- this.headerValues.set(sectionId, value);
242
- }
243
-
244
- public isSectioned(): boolean {
245
- return this.sectioned;
246
- }
247
-
248
- public getModel(): Gio.ListModel {
249
- if (this.sectioned && this.flatModel) return this.flatModel;
250
- return this.model;
251
- }
252
-
253
- public getIdAtIndex(index: number): string | null {
254
- this.flushRemovals();
255
- return this.ids[index] ?? null;
256
- }
257
-
258
- public getIndexById(id: string): number | null {
259
- this.flushRemovals();
260
- return this.idToIndex.get(id) ?? null;
261
- }
262
-
263
- protected rebuildGlobalIndices(): void {
264
- this.ids = [];
265
- this.idToIndex.clear();
266
- for (const section of this.sections) {
267
- for (const itemId of section.itemIds) {
268
- this.idToIndex.set(itemId, this.ids.length);
269
- this.ids.push(itemId);
270
- }
271
- }
272
- }
273
-
274
- protected rebuildIndices(fromIndex: number): void {
275
- for (let i = fromIndex; i < this.ids.length; i++) {
276
- this.idToIndex.set(this.ids[i] as string, i);
277
- }
278
- }
279
-
280
- protected abstract getInitialPendingBatch(): string[] | null;
281
-
282
- protected abstract getModelString(itemId: string, item: unknown): string;
283
-
284
- protected onItemAdded(_itemId: string, _item: unknown): void {}
285
-
286
- protected onItemRemoved(_itemId: string): void {}
287
- }
@@ -1,35 +0,0 @@
1
- import * as Gtk from "@gtkx/ffi/gtk";
2
-
3
- type StoreWithIdLookup = {
4
- getIdAtIndex(index: number): string | null;
5
- };
6
-
7
- type StoreWithIndexLookup = {
8
- getIndexById(id: string): number | null;
9
- getNItems(): number;
10
- };
11
-
12
- export function getSelectionFromStore(selection: Gtk.Bitset, store: StoreWithIdLookup): string[] {
13
- const size = selection.getSize();
14
- const ids: string[] = [];
15
- for (let i = 0; i < size; i++) {
16
- const index = selection.getNth(i);
17
- const id = store.getIdAtIndex(index);
18
- if (id !== null) {
19
- ids.push(id);
20
- }
21
- }
22
- return ids;
23
- }
24
-
25
- export function resolveSelectionIndices(ids: string[], store: StoreWithIndexLookup): Gtk.Bitset {
26
- const nItems = store.getNItems();
27
- const selected = new Gtk.Bitset();
28
- for (const id of ids) {
29
- const index = store.getIndexById(id);
30
- if (index !== null && index < nItems) {
31
- selected.add(index);
32
- }
33
- }
34
- return selected;
35
- }