@gtkx/components 0.21.0 → 1.0.0-rc.1

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 (185) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +173 -0
  3. package/dist/cell.d.ts +30 -0
  4. package/dist/cell.d.ts.map +1 -0
  5. package/dist/cell.js +45 -0
  6. package/dist/cell.js.map +1 -0
  7. package/dist/column-view-column.d.ts +24 -14
  8. package/dist/column-view-column.d.ts.map +1 -1
  9. package/dist/column-view-column.js +19 -40
  10. package/dist/column-view-column.js.map +1 -1
  11. package/dist/column-view.d.ts +25 -11
  12. package/dist/column-view.d.ts.map +1 -1
  13. package/dist/column-view.js +53 -83
  14. package/dist/column-view.js.map +1 -1
  15. package/dist/constraint-layout-apply.d.ts +50 -1
  16. package/dist/constraint-layout-apply.d.ts.map +1 -1
  17. package/dist/constraint-layout-apply.js.map +1 -1
  18. package/dist/constraint-layout.d.ts +8 -1
  19. package/dist/constraint-layout.d.ts.map +1 -1
  20. package/dist/constraint-layout.js +12 -9
  21. package/dist/constraint-layout.js.map +1 -1
  22. package/dist/drop-down.d.ts +26 -36
  23. package/dist/drop-down.d.ts.map +1 -1
  24. package/dist/drop-down.js +47 -87
  25. package/dist/drop-down.js.map +1 -1
  26. package/dist/fixed.d.ts +26 -0
  27. package/dist/fixed.d.ts.map +1 -0
  28. package/dist/fixed.js +40 -0
  29. package/dist/fixed.js.map +1 -0
  30. package/dist/grid-view.d.ts +13 -10
  31. package/dist/grid-view.d.ts.map +1 -1
  32. package/dist/grid-view.js +30 -16
  33. package/dist/grid-view.js.map +1 -1
  34. package/dist/grid.d.ts +27 -0
  35. package/dist/grid.d.ts.map +1 -0
  36. package/dist/grid.js +48 -0
  37. package/dist/grid.js.map +1 -0
  38. package/dist/hooks/use-cell-containers.d.ts +18 -0
  39. package/dist/hooks/use-cell-containers.d.ts.map +1 -0
  40. package/dist/hooks/{use-realized-slots.js → use-cell-containers.js} +18 -17
  41. package/dist/hooks/use-cell-containers.js.map +1 -0
  42. package/dist/hooks/use-collection-model.d.ts +26 -0
  43. package/dist/hooks/use-collection-model.d.ts.map +1 -0
  44. package/dist/hooks/use-collection-model.js +35 -0
  45. package/dist/hooks/use-collection-model.js.map +1 -0
  46. package/dist/hooks/use-collection-widget.d.ts +28 -0
  47. package/dist/hooks/use-collection-widget.d.ts.map +1 -0
  48. package/dist/hooks/use-collection-widget.js +21 -0
  49. package/dist/hooks/use-collection-widget.js.map +1 -0
  50. package/dist/hooks/use-drop-down-selection.d.ts +7 -6
  51. package/dist/hooks/use-drop-down-selection.d.ts.map +1 -1
  52. package/dist/hooks/use-drop-down-selection.js +6 -6
  53. package/dist/hooks/use-drop-down-selection.js.map +1 -1
  54. package/dist/hooks/use-expansion-model.d.ts +12 -0
  55. package/dist/hooks/use-expansion-model.d.ts.map +1 -0
  56. package/dist/hooks/use-expansion-model.js +71 -0
  57. package/dist/hooks/use-expansion-model.js.map +1 -0
  58. package/dist/hooks/use-header-menu.d.ts.map +1 -1
  59. package/dist/hooks/use-header-menu.js +12 -24
  60. package/dist/hooks/use-header-menu.js.map +1 -1
  61. package/dist/hooks/use-installed-model.d.ts +4 -0
  62. package/dist/hooks/use-installed-model.d.ts.map +1 -0
  63. package/dist/hooks/use-installed-model.js +15 -0
  64. package/dist/hooks/use-installed-model.js.map +1 -0
  65. package/dist/hooks/use-list-model.d.ts +14 -15
  66. package/dist/hooks/use-list-model.d.ts.map +1 -1
  67. package/dist/hooks/use-list-model.js +61 -34
  68. package/dist/hooks/use-list-model.js.map +1 -1
  69. package/dist/hooks/use-placed-child.d.ts +18 -0
  70. package/dist/hooks/use-placed-child.d.ts.map +1 -0
  71. package/dist/hooks/use-placed-child.js +47 -0
  72. package/dist/hooks/use-placed-child.js.map +1 -0
  73. package/dist/hooks/use-selection-model.d.ts +4 -3
  74. package/dist/hooks/use-selection-model.d.ts.map +1 -1
  75. package/dist/hooks/use-selection-model.js +8 -16
  76. package/dist/hooks/use-selection-model.js.map +1 -1
  77. package/dist/hooks/use-sort-handler.d.ts +10 -5
  78. package/dist/hooks/use-sort-handler.d.ts.map +1 -1
  79. package/dist/hooks/use-sort-handler.js +2 -2
  80. package/dist/hooks/use-sort-handler.js.map +1 -1
  81. package/dist/index.d.ts +11 -17
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +6 -13
  84. package/dist/index.js.map +1 -1
  85. package/dist/list-view.d.ts +17 -9
  86. package/dist/list-view.d.ts.map +1 -1
  87. package/dist/list-view.js +49 -24
  88. package/dist/list-view.js.map +1 -1
  89. package/dist/menu-items.d.ts.map +1 -1
  90. package/dist/menu-items.js.map +1 -1
  91. package/dist/menu.d.ts +6 -10
  92. package/dist/menu.d.ts.map +1 -1
  93. package/dist/menu.js +7 -9
  94. package/dist/menu.js.map +1 -1
  95. package/dist/overlay.d.ts +24 -0
  96. package/dist/overlay.d.ts.map +1 -0
  97. package/dist/overlay.js +49 -0
  98. package/dist/overlay.js.map +1 -0
  99. package/dist/size-group.d.ts +20 -0
  100. package/dist/size-group.d.ts.map +1 -0
  101. package/dist/size-group.js +57 -0
  102. package/dist/size-group.js.map +1 -0
  103. package/dist/types.d.ts +43 -173
  104. package/dist/types.d.ts.map +1 -1
  105. package/dist/types.js.map +1 -1
  106. package/dist/utils/{realized-slot-store.d.ts → cell-container-store.d.ts} +7 -9
  107. package/dist/utils/cell-container-store.d.ts.map +1 -0
  108. package/dist/utils/{realized-slot-store.js → cell-container-store.js} +9 -14
  109. package/dist/utils/cell-container-store.js.map +1 -0
  110. package/dist/utils/item-models.d.ts +10 -0
  111. package/dist/utils/item-models.d.ts.map +1 -0
  112. package/dist/utils/{position-only-model.js → item-models.js} +13 -14
  113. package/dist/utils/item-models.js.map +1 -0
  114. package/dist/utils/item-resolver.d.ts +14 -17
  115. package/dist/utils/item-resolver.d.ts.map +1 -1
  116. package/dist/utils/item-resolver.js +34 -43
  117. package/dist/utils/item-resolver.js.map +1 -1
  118. package/dist/utils/list-item-flatten.d.ts +16 -24
  119. package/dist/utils/list-item-flatten.d.ts.map +1 -1
  120. package/dist/utils/list-item-flatten.js +16 -36
  121. package/dist/utils/list-item-flatten.js.map +1 -1
  122. package/package.json +52 -53
  123. package/src/cell.tsx +144 -0
  124. package/src/column-view-column.tsx +57 -48
  125. package/src/column-view.tsx +130 -146
  126. package/src/constraint-layout-apply.ts +53 -1
  127. package/src/constraint-layout.tsx +18 -13
  128. package/src/drop-down.tsx +129 -196
  129. package/src/fixed.tsx +86 -0
  130. package/src/grid-view.tsx +52 -44
  131. package/src/grid.tsx +88 -0
  132. package/src/hooks/{use-realized-slots.ts → use-cell-containers.ts} +30 -30
  133. package/src/hooks/use-collection-model.ts +60 -0
  134. package/src/hooks/use-collection-widget.ts +47 -0
  135. package/src/hooks/use-drop-down-selection.ts +12 -13
  136. package/src/hooks/use-expansion-model.ts +82 -0
  137. package/src/hooks/use-header-menu.ts +24 -23
  138. package/src/hooks/use-installed-model.ts +20 -0
  139. package/src/hooks/use-list-model.ts +103 -87
  140. package/src/hooks/use-placed-child.ts +79 -0
  141. package/src/hooks/use-selection-model.ts +11 -18
  142. package/src/hooks/use-sort-handler.ts +13 -8
  143. package/src/index.ts +25 -27
  144. package/src/list-view.tsx +120 -57
  145. package/src/menu.tsx +12 -14
  146. package/src/overlay.tsx +88 -0
  147. package/src/size-group.tsx +96 -0
  148. package/src/types.ts +55 -206
  149. package/src/utils/{realized-slot-store.ts → cell-container-store.ts} +13 -25
  150. package/src/utils/{position-only-model.ts → item-models.ts} +26 -28
  151. package/src/utils/item-resolver.ts +54 -59
  152. package/src/utils/list-item-flatten.ts +33 -56
  153. package/dist/collection-view.d.ts +0 -30
  154. package/dist/collection-view.d.ts.map +0 -1
  155. package/dist/collection-view.js +0 -52
  156. package/dist/collection-view.js.map +0 -1
  157. package/dist/contexts/column-view-context.d.ts +0 -17
  158. package/dist/contexts/column-view-context.d.ts.map +0 -1
  159. package/dist/contexts/column-view-context.js +0 -10
  160. package/dist/contexts/column-view-context.js.map +0 -1
  161. package/dist/hooks/use-model-installation.d.ts +0 -9
  162. package/dist/hooks/use-model-installation.d.ts.map +0 -1
  163. package/dist/hooks/use-model-installation.js +0 -20
  164. package/dist/hooks/use-model-installation.js.map +0 -1
  165. package/dist/hooks/use-realized-slots.d.ts +0 -20
  166. package/dist/hooks/use-realized-slots.d.ts.map +0 -1
  167. package/dist/hooks/use-realized-slots.js.map +0 -1
  168. package/dist/list-portal-host.d.ts +0 -11
  169. package/dist/list-portal-host.d.ts.map +0 -1
  170. package/dist/list-portal-host.js +0 -8
  171. package/dist/list-portal-host.js.map +0 -1
  172. package/dist/list-slot.d.ts +0 -14
  173. package/dist/list-slot.d.ts.map +0 -1
  174. package/dist/list-slot.js +0 -29
  175. package/dist/list-slot.js.map +0 -1
  176. package/dist/utils/position-only-model.d.ts +0 -10
  177. package/dist/utils/position-only-model.d.ts.map +0 -1
  178. package/dist/utils/position-only-model.js.map +0 -1
  179. package/dist/utils/realized-slot-store.d.ts.map +0 -1
  180. package/dist/utils/realized-slot-store.js.map +0 -1
  181. package/src/collection-view.tsx +0 -125
  182. package/src/contexts/column-view-context.ts +0 -28
  183. package/src/hooks/use-model-installation.ts +0 -25
  184. package/src/list-portal-host.tsx +0 -27
  185. package/src/list-slot.tsx +0 -56
package/src/types.ts CHANGED
@@ -1,227 +1,76 @@
1
- import type * as Gio from "@gtkx/gi/gio";
2
- import type * as GObject from "@gtkx/gi/gobject";
3
1
  import type * as Gtk from "@gtkx/gi/gtk";
4
- import type { ReactNode } from "react";
2
+ import type { ComponentPropsWithRef, ElementType } from "react";
3
+
4
+ /** Props of a container's Child component: the widget to render, its own props, and any placement props. */
5
+ export type ChildProps<C extends ElementType, Placement = unknown> = Placement & {
6
+ component: C;
7
+ } & Omit<ComponentPropsWithRef<C>, keyof Placement>;
8
+
9
+ /** Props of a component whose backing widget defaults to one type but can be swapped through `component`. */
10
+ export type WidgetProps<C extends ElementType, Own = unknown, ExtraOmit extends string = never> = Own & {
11
+ component?: C;
12
+ } & Omit<ComponentPropsWithRef<C>, ExtraOmit | keyof Own>;
5
13
 
6
14
  /**
7
- * A declarative item rendered by the {@link ListView}, {@link GridView},
8
- * {@link ColumnView}, and {@link DropDown} components.
9
- *
10
- * A non-section item carries an arbitrary value of type `T` and may declare
11
- * tree `children`. A section item carries a value of type `S` and groups a list
12
- * of child items underneath a header.
15
+ * A single item in a collection model, identified by a stable id and holding an
16
+ * arbitrary value. Nested items form a tree.
13
17
  */
14
- export type ListItem<T = unknown, S = unknown> =
15
- | {
16
- id: string;
17
- value: T;
18
- section?: false | undefined;
19
- children?: ListItem<T, S>[] | undefined;
20
- hideExpander?: boolean | undefined;
21
- indentForDepth?: boolean | undefined;
22
- indentForIcon?: boolean | undefined;
23
- }
24
- | {
25
- id: string;
26
- value: S;
27
- section: true;
28
- children: ListItem<T, S>[];
29
- };
18
+ export type ItemNode<T = unknown> = {
19
+ /** Stable identifier used to track the item across updates and selection. */
20
+ id: string;
21
+ value: T;
22
+ children?: ItemNode<T>[] | undefined;
23
+ /** Hides the tree expander arrow even when the item has children. */
24
+ hideExpander?: boolean | undefined;
25
+ /** Adds indentation matching the item's depth in the tree. */
26
+ indentForDepth?: boolean | undefined;
27
+ /** Reserves indentation space for an expander icon. */
28
+ indentForIcon?: boolean | undefined;
29
+ };
30
+
31
+ /** A group of items rendered under a shared section header. */
32
+ export type SectionNode<S = unknown, T = unknown> = {
33
+ /** Stable identifier used to track the section across updates. */
34
+ id: string;
35
+ value: S;
36
+ /** Items belonging to this section. */
37
+ data: ItemNode<T>[];
38
+ };
39
+
40
+ /** Props passed to a renderItem callback when rendering one cell. */
41
+ export type RenderItemProps<T> = {
42
+ item: T;
43
+ index: number;
44
+ /** Depth of the item within a tree, starting at zero for top-level items. */
45
+ depth?: number | undefined;
46
+ /** Whether the item is currently expanded in a tree view. */
47
+ isExpanded?: boolean | undefined;
48
+ };
30
49
 
31
- type ListViewSharedProps = {
50
+ export type CollectionItemSizeProps = {
32
51
  estimatedItemHeight?: number | undefined;
33
52
  estimatedItemWidth?: number | undefined;
34
53
  };
35
54
 
36
- type ListViewControlledSelectionProps = {
37
- selected?: string[] | null | undefined;
55
+ export type ControlledSelectionProps = {
56
+ selectedIds?: string[] | null | undefined;
38
57
  onSelectionChanged?: ((ids: string[]) => void) | null | undefined;
39
58
  selectionMode?: Gtk.SelectionMode | null | undefined;
40
59
  };
41
60
 
42
- type UncontrolledItemType<T> = [T] extends [GObject.Object] ? T : GObject.Object;
43
-
44
- /**
45
- * Props for the {@link ListView} component, replacing the raw `GtkListView`
46
- * factory/model surface with a declarative `items`/`renderItem` API, optional
47
- * controlled selection, section headers, and tree autoexpansion. Supplying an
48
- * external `model` switches to the uncontrolled form.
49
- */
50
- export type ListViewProps<T = unknown, S = unknown> = ListViewSharedProps &
51
- (
52
- | (ListViewControlledSelectionProps & {
53
- items?: ListItem<T, S>[] | undefined;
54
- renderItem: (item: T, row?: Gtk.TreeListRow | null) => ReactNode;
55
- autoexpand?: boolean | undefined;
56
- renderHeader?: ((item: S) => ReactNode) | null | undefined;
57
- model?: never;
58
- })
59
- | {
60
- model: Gio.ListModel;
61
- renderItem: (item: UncontrolledItemType<T>) => ReactNode;
62
- items?: never;
63
- autoexpand?: never;
64
- renderHeader?: never;
65
- selected?: never;
66
- onSelectionChanged?: never;
67
- selectionMode?: never;
68
- }
69
- );
70
-
71
- /**
72
- * Props for the {@link GridView} component, replacing the raw `GtkGridView`
73
- * factory/model surface with a declarative `items`/`renderItem` API and
74
- * optional controlled selection. Supplying an external `model` switches to the
75
- * uncontrolled form.
76
- */
77
- export type GridViewProps<T = unknown> = ListViewSharedProps &
78
- (
79
- | (ListViewControlledSelectionProps & {
80
- items?: ListItem<T>[] | undefined;
81
- renderItem: (item: T) => ReactNode;
82
- model?: never;
83
- })
84
- | {
85
- model: Gio.ListModel;
86
- renderItem: (item: UncontrolledItemType<T>) => ReactNode;
87
- items?: never;
88
- selected?: never;
89
- onSelectionChanged?: never;
90
- selectionMode?: never;
91
- }
92
- );
93
-
94
- type ColumnViewSortProps = {
95
- sortColumn?: string | null | undefined;
96
- sortOrder?: Gtk.SortType | null | undefined;
97
- onSortChanged?: ((column: string | null, order: Gtk.SortType) => void) | null | undefined;
98
- estimatedRowHeight?: number | null | undefined;
99
- };
100
-
101
- /**
102
- * Props for the {@link ColumnView} component, replacing the raw `GtkColumnView`
103
- * surface with a declarative `items` model, optional controlled selection,
104
- * controlled sorting, and section headers. Columns are declared as
105
- * {@link ColumnViewColumn} children. Supplying an external `model` switches to
106
- * the uncontrolled form.
107
- */
108
- export type ColumnViewProps<T = unknown, S = unknown> = ColumnViewSortProps &
109
- (
110
- | (ListViewControlledSelectionProps & {
111
- items?: ListItem<T, S>[] | undefined;
112
- renderHeader?: ((item: S) => ReactNode) | null | undefined;
113
- model?: never;
114
- })
115
- | {
116
- model: Gio.ListModel;
117
- items?: never;
118
- renderHeader?: never;
119
- selected?: never;
120
- onSelectionChanged?: never;
121
- selectionMode?: never;
122
- }
123
- );
124
-
125
- /**
126
- * Props for a single {@link ColumnViewColumn} of a {@link ColumnView},
127
- * replacing the raw `GtkColumnViewColumn` factory/sorter surface with a
128
- * declarative `renderCell` callback and an optional header context menu.
129
- */
130
- export type ColumnViewColumnProps<T = unknown> = {
131
- title: string;
132
- expand?: boolean | undefined;
133
- resizable?: boolean | undefined;
134
- fixedWidth?: number | undefined;
135
- id: string;
136
- sortable?: boolean | undefined;
137
- visible?: boolean | undefined;
138
- renderCell: (item: T) => ReactNode;
139
- headerMenu?: ReactNode;
61
+ export type ControlledExpansionProps = {
62
+ expandedIds?: string[] | null | undefined;
63
+ onExpandedChange?: ((ids: string[]) => void) | null | undefined;
140
64
  };
141
65
 
142
- /**
143
- * Props shared by the {@link DropDown} and {@link ComboRow} components,
144
- * replacing the raw factory/model surface with declarative `items`, controlled
145
- * `selectedId`, and per-slot renderers for the selected face, the popup list,
146
- * and section headers. Supplying an external `model` switches to the
147
- * uncontrolled form.
148
- */
149
- export type DropDownProps<T = unknown, S = unknown> =
150
- | {
151
- items?: ListItem<T, S>[] | undefined;
152
- selectedId?: string | null | undefined;
153
- onSelectionChanged?: ((id: string) => void) | null | undefined;
154
- renderItem?: ((item: T) => ReactNode) | null | undefined;
155
- renderListItem?: ((item: T) => ReactNode) | null | undefined;
156
- renderHeader?: ((item: S) => ReactNode) | null | undefined;
157
- model?: never;
158
- }
159
- | {
160
- model: Gio.ListModel;
161
- renderItem?: ((item: UncontrolledItemType<T>) => ReactNode) | null | undefined;
162
- renderListItem?: ((item: UncontrolledItemType<T>) => ReactNode) | null | undefined;
163
- items?: never;
164
- selectedId?: never;
165
- onSelectionChanged?: never;
166
- renderHeader?: never;
167
- };
168
-
169
- /**
170
- * A single declarative entry in a {@link Menu} model. An entry is a plain item
171
- * (`label` plus `action`), a `submenu`, or a `section` of nested entries.
172
- */
66
+ /** Declarative description of a single menu item, optionally nesting a submenu or section. */
173
67
  export type MenuEntry = {
68
+ /** Text shown for the item. */
174
69
  label?: string | undefined;
70
+ /** Action name activated when the item is chosen, for example "app.quit". */
175
71
  action?: string | undefined;
72
+ /** Nested entries shown as a submenu opened from this item. */
176
73
  submenu?: MenuEntry[] | undefined;
74
+ /** Nested entries grouped as a visually separated section. */
177
75
  section?: MenuEntry[] | undefined;
178
76
  };
179
-
180
- /**
181
- * The declarative menu-model surface added by the {@link Menu} component on top
182
- * of the raw `GMenu` element.
183
- */
184
- export type MenuItemsProps = {
185
- items?: MenuEntry[] | null | undefined;
186
- };
187
-
188
- /**
189
- * Props for a `<ConstraintLayout.Constraint>` declaring a single relation
190
- * between a target attribute and a source attribute within a
191
- * {@link ConstraintLayout}.
192
- */
193
- export type ConstraintProps = {
194
- target?: string;
195
- targetAttribute: Gtk.ConstraintAttribute;
196
- relation?: Gtk.ConstraintRelation;
197
- source?: string;
198
- sourceAttribute?: Gtk.ConstraintAttribute;
199
- multiplier?: number;
200
- constant?: number;
201
- strength?: number;
202
- };
203
-
204
- /**
205
- * Props for a `<ConstraintLayout.Guide>` declaring an invisible layout guide
206
- * that other constraints can reference by `id` within a {@link ConstraintLayout}.
207
- */
208
- export type ConstraintGuideProps = {
209
- id: string;
210
- minWidth?: number;
211
- minHeight?: number;
212
- natWidth?: number;
213
- natHeight?: number;
214
- maxWidth?: number;
215
- maxHeight?: number;
216
- strength?: Gtk.ConstraintStrength;
217
- };
218
-
219
- /**
220
- * Props for a `<ConstraintLayout.Vfl>` declaring constraints through the GTK
221
- * Visual Format Language within a {@link ConstraintLayout}.
222
- */
223
- export type ConstraintVflProps = {
224
- lines: string[];
225
- hspacing?: number;
226
- vspacing?: number;
227
- };
@@ -1,19 +1,17 @@
1
1
  import type * as GObject from "@gtkx/gi/gobject";
2
2
  import type * as Gtk from "@gtkx/gi/gtk";
3
3
 
4
- export interface SlotEntry {
5
- container: GObject.Object;
4
+ export type CellEntry = {
6
5
  position: number;
7
6
  treeRow: Gtk.TreeListRow | null;
8
- item: GObject.Object | null;
9
- }
7
+ };
10
8
 
11
9
  let nextKey = 0;
12
10
 
13
- export class RealizedSlotStore {
14
- private entries = new Map<GObject.Object, SlotEntry>();
11
+ export class CellContainerStore {
12
+ private entries = new Map<GObject.Object, CellEntry>();
15
13
 
16
- private published = new Map<GObject.Object, SlotEntry>();
14
+ private published = new Map<GObject.Object, CellEntry>();
17
15
 
18
16
  private setListeners = new Set<() => void>();
19
17
 
@@ -32,7 +30,7 @@ export class RealizedSlotStore {
32
30
  };
33
31
  };
34
32
 
35
- getSlotsSnapshot = (): GObject.Object[] => this.snapshot;
33
+ getContainersSnapshot = (): GObject.Object[] => this.snapshot;
36
34
 
37
35
  subscribePosition = (container: GObject.Object, onChange: () => void): (() => void) => {
38
36
  let listeners = this.positionListeners.get(container);
@@ -49,10 +47,10 @@ export class RealizedSlotStore {
49
47
  };
50
48
  };
51
49
 
52
- getPosition = (container: GObject.Object): SlotEntry => {
50
+ getPosition = (container: GObject.Object): CellEntry => {
53
51
  let entry = this.published.get(container);
54
52
  if (entry === undefined) {
55
- entry = { container, position: -1, treeRow: null, item: null };
53
+ entry = { position: -1, treeRow: null };
56
54
  this.published.set(container, entry);
57
55
  }
58
56
  return entry;
@@ -61,35 +59,25 @@ export class RealizedSlotStore {
61
59
  keyFor = (container: GObject.Object): string => {
62
60
  const existing = this.keys.get(container);
63
61
  if (existing !== undefined) return existing;
64
- const key = `slot-${nextKey++}`;
62
+ const key = `cell-${nextKey++}`;
65
63
  this.keys.set(container, key);
66
64
  return key;
67
65
  };
68
66
 
69
67
  addContainer = (container: GObject.Object): void => {
70
68
  if (this.entries.has(container)) return;
71
- this.entries.set(container, { container, position: -1, treeRow: null, item: null });
69
+ this.entries.set(container, { position: -1, treeRow: null });
72
70
  this.notifySet();
73
71
  };
74
72
 
75
- bind = (
76
- container: GObject.Object,
77
- position: number,
78
- treeRow: Gtk.TreeListRow | null,
79
- item: GObject.Object | null,
80
- ): void => {
81
- if (!this.entries.has(container)) {
82
- this.entries.set(container, { container, position, treeRow, item });
83
- this.notifySet();
84
- return;
85
- }
86
- this.entries.set(container, { container, position, treeRow, item });
73
+ bind = (container: GObject.Object, position: number, treeRow: Gtk.TreeListRow | null): void => {
74
+ this.entries.set(container, { position, treeRow });
87
75
  this.notifyPosition(container);
88
76
  };
89
77
 
90
78
  unbind = (container: GObject.Object): void => {
91
79
  if (!this.entries.has(container)) return;
92
- this.entries.set(container, { container, position: -1, treeRow: null, item: null });
80
+ this.entries.set(container, { position: -1, treeRow: null });
93
81
  this.notifyPosition(container);
94
82
  };
95
83
 
@@ -1,25 +1,24 @@
1
1
  import * as Gio from "@gtkx/gi/gio";
2
2
  import type * as GObject from "@gtkx/gi/gobject";
3
3
  import * as Gtk from "@gtkx/gi/gtk";
4
- import type { ListItem } from "../types.js";
4
+ import type { ItemNode, SectionNode } from "../types.js";
5
5
  import type { RowValue } from "./item-resolver.js";
6
- import { treeItemMetadata } from "./list-item-flatten.js";
6
+ import { countDescendants, treeItemMetadata } from "./list-item-flatten.js";
7
7
 
8
8
  const emptyStrings = (count: number): string[] => new Array<string>(Math.max(0, count)).fill("");
9
9
 
10
- export const retagRows = <T, S>(
11
- items: ListItem<T, S>[],
12
- rowValues: WeakMap<GObject.Object, RowValue<T, S>>,
10
+ export const retagRows = <T>(
11
+ items: ItemNode<T>[],
12
+ rowValues: WeakMap<GObject.Object, RowValue<T>>,
13
13
  placeholdersById: Map<string, GObject.Object>,
14
14
  ): void => {
15
- const walk = (list: ListItem<T, S>[]): void => {
15
+ const walk = (list: ItemNode<T>[]): void => {
16
16
  for (const item of list) {
17
17
  const placeholder = placeholdersById.get(item.id);
18
18
  if (placeholder !== undefined) {
19
19
  rowValues.set(placeholder, {
20
20
  id: item.id,
21
21
  value: item.value,
22
- isHeader: item.section === true,
23
22
  metadata: treeItemMetadata(item),
24
23
  });
25
24
  }
@@ -42,9 +41,9 @@ export const resizeFlatModel = (model: Gtk.StringList, count: number): void => {
42
41
  model.splice(target, current - target, null);
43
42
  };
44
43
 
45
- const createTaggedRow = <T, S>(
46
- row: RowValue<T, S>,
47
- rowValues: WeakMap<GObject.Object, RowValue<T, S>>,
44
+ const createTaggedRow = <T>(
45
+ row: RowValue<T>,
46
+ rowValues: WeakMap<GObject.Object, RowValue<T>>,
48
47
  placeholdersById?: Map<string, GObject.Object>,
49
48
  ): GObject.Object => {
50
49
  const placeholder = Gtk.StringObject.new("");
@@ -53,16 +52,16 @@ const createTaggedRow = <T, S>(
53
52
  return placeholder;
54
53
  };
55
54
 
56
- const childModelOf = <T, S>(
57
- children: ListItem<T, S>[],
58
- rowValues: WeakMap<GObject.Object, RowValue<T, S>>,
55
+ const childModelOf = <T>(
56
+ children: ItemNode<T>[],
57
+ rowValues: WeakMap<GObject.Object, RowValue<T>>,
59
58
  placeholdersById: Map<string, GObject.Object>,
60
59
  ): Gio.ListStore => {
61
- const store = Gio.ListStore.new(Gtk.StringObject.prototype.__gtype__);
60
+ const store = Gio.ListStore.new(Gtk.StringObject.prototype.__type__);
62
61
  for (const child of children) {
63
62
  store.append(
64
63
  createTaggedRow(
65
- { id: child.id, value: child.value, isHeader: false, metadata: treeItemMetadata(child) },
64
+ { id: child.id, value: child.value, metadata: treeItemMetadata(child) },
66
65
  rowValues,
67
66
  placeholdersById,
68
67
  ),
@@ -71,17 +70,16 @@ const childModelOf = <T, S>(
71
70
  return store;
72
71
  };
73
72
 
74
- export const createTreeModel = <T, S>(
75
- items: ListItem<T, S>[],
76
- autoexpand: boolean,
77
- rowValues: WeakMap<GObject.Object, RowValue<T, S>>,
73
+ export const createTreeModel = <T>(
74
+ items: ItemNode<T>[],
75
+ rowValues: WeakMap<GObject.Object, RowValue<T>>,
78
76
  placeholdersById: Map<string, GObject.Object>,
79
77
  ): Gtk.TreeListModel => {
80
- const childrenByRow = new WeakMap<GObject.Object, ListItem<T, S>[]>();
81
- const root = Gio.ListStore.new(Gtk.StringObject.prototype.__gtype__);
78
+ const childrenByRow = new WeakMap<GObject.Object, ItemNode<T>[]>();
79
+ const root = Gio.ListStore.new(Gtk.StringObject.prototype.__type__);
82
80
  for (const item of items) {
83
81
  const placeholder = createTaggedRow(
84
- { id: item.id, value: item.value, isHeader: item.section === true, metadata: treeItemMetadata(item) },
82
+ { id: item.id, value: item.value, metadata: treeItemMetadata(item) },
85
83
  rowValues,
86
84
  placeholdersById,
87
85
  );
@@ -103,13 +101,13 @@ export const createTreeModel = <T, S>(
103
101
  }
104
102
  return store;
105
103
  };
106
- return Gtk.TreeListModel.new(root, false, autoexpand, createFunc);
104
+ return Gtk.TreeListModel.new(root, false, false, createFunc);
107
105
  };
108
106
 
109
- export const createSectionModel = <T, S>(items: ListItem<T, S>[]): Gtk.FlattenListModel => {
110
- const sections = Gio.ListStore.new(Gtk.StringList.prototype.__gtype__);
111
- for (const section of items) {
112
- sections.append(Gtk.StringList.new(emptyStrings(section.children?.length ?? 0)));
107
+ export const createSectionModel = <S, T>(sections: SectionNode<S, T>[]): Gtk.FlattenListModel => {
108
+ const store = Gio.ListStore.new(Gtk.StringList.prototype.__type__);
109
+ for (const section of sections) {
110
+ store.append(Gtk.StringList.new(emptyStrings(countDescendants(section.data))));
113
111
  }
114
- return Gtk.FlattenListModel.new(sections);
112
+ return Gtk.FlattenListModel.new(store);
115
113
  };
@@ -1,8 +1,7 @@
1
- import type * as Gio from "@gtkx/gi/gio";
2
1
  import type * as GObject from "@gtkx/gi/gobject";
3
2
  import type * as Gtk from "@gtkx/gi/gtk";
4
- import type { ListItem } from "../types.js";
5
- import { flattenListItems, type TreeItemMetadata } from "./list-item-flatten.js";
3
+ import type { ItemNode, SectionNode } from "../types.js";
4
+ import { countDescendants, flattenListItems, type TreeItemMetadata } from "./list-item-flatten.js";
6
5
 
7
6
  const NO_TREE_METADATA: TreeItemMetadata = { hideExpander: false, indentForDepth: true, indentForIcon: true };
8
7
 
@@ -16,44 +15,48 @@ const treeRowItem = (treeRow: Gtk.TreeListRow): GObject.Object | null => {
16
15
  return item;
17
16
  };
18
17
 
19
- export interface Resolved<T = unknown, S = unknown> {
18
+ export type Resolved<T = unknown, S = unknown> = {
20
19
  value: T | S | undefined;
21
20
  present: boolean;
22
21
  isHeader: boolean;
23
- treeRow: Gtk.TreeListRow | null;
24
22
  metadata: TreeItemMetadata;
25
- }
23
+ };
26
24
 
27
- export interface ItemResolver<T = unknown, S = unknown> {
28
- resolve(position: number, treeRow: Gtk.TreeListRow | null, boundItem: GObject.Object | null): Resolved<T, S>;
29
- positionOf(id: string): number;
25
+ export type ItemResolver<T = unknown, S = unknown> = {
26
+ resolve(position: number, treeRow: Gtk.TreeListRow | null): Resolved<T, S>;
27
+ positionOfId(id: string): number;
30
28
  idOf(position: number): string | undefined;
31
- count: number;
32
- }
29
+ };
33
30
 
34
- export interface RowValue<T = unknown, S = unknown> {
31
+ export type RowValue<T = unknown> = {
35
32
  id: string;
36
- value: T | S;
37
- isHeader: boolean;
33
+ value: T;
38
34
  metadata: TreeItemMetadata;
39
- }
35
+ };
40
36
 
41
- export const createControlledResolver = <T, S>(
42
- items: ListItem<T, S>[] | undefined,
37
+ export const rowIdOf = <T>(
38
+ rowValues: WeakMap<GObject.Object, RowValue<T>>,
39
+ row: Gtk.TreeListRow,
40
+ ): string | undefined => {
41
+ const item = row.getItem();
42
+ return item === null ? undefined : rowValues.get(item)?.id;
43
+ };
44
+
45
+ export const createItemResolver = <T, S>(
46
+ items: ItemNode<T>[] | undefined,
43
47
  flattenTreeChildren: boolean,
44
- rowValues: WeakMap<GObject.Object, RowValue<T, S>>,
48
+ rowValues: WeakMap<GObject.Object, RowValue<T>>,
45
49
  ): ItemResolver<T, S> => {
46
50
  const flattened = flattenListItems(items, flattenTreeChildren);
47
51
  return {
48
- count: flattened.records.length,
49
- positionOf(id: string): number {
52
+ positionOfId(id: string): number {
50
53
  const position = flattened.idToPosition.get(id);
51
54
  return position === undefined ? -1 : position;
52
55
  },
53
56
  idOf(position: number): string | undefined {
54
57
  return flattened.positionToId.get(position);
55
58
  },
56
- resolve(position: number, treeRow: Gtk.TreeListRow | null, _boundItem: GObject.Object | null): Resolved<T, S> {
59
+ resolve(position: number, treeRow: Gtk.TreeListRow | null): Resolved<T, S> {
57
60
  if (treeRow !== null && !flattenTreeChildren) {
58
61
  const rowItem = treeRowItem(treeRow);
59
62
  if (rowItem !== null) {
@@ -62,8 +65,7 @@ export const createControlledResolver = <T, S>(
62
65
  return {
63
66
  value: tagged.value,
64
67
  present: true,
65
- isHeader: tagged.isHeader,
66
- treeRow,
68
+ isHeader: false,
67
69
  metadata: tagged.metadata,
68
70
  };
69
71
  }
@@ -75,69 +77,62 @@ export const createControlledResolver = <T, S>(
75
77
  value: undefined,
76
78
  present: false,
77
79
  isHeader: false,
78
- treeRow,
79
80
  metadata: NO_TREE_METADATA,
80
81
  };
81
82
  }
82
83
  return {
83
84
  value: record.value,
84
85
  present: true,
85
- isHeader: record.isHeader,
86
- treeRow,
87
- metadata: record.metadata,
88
- };
89
- },
90
- };
91
- };
92
-
93
- export const createModelResolver = <T, S>(model: Gio.ListModel): ItemResolver<T, S> => {
94
- return {
95
- get count(): number {
96
- return model.getNItems();
97
- },
98
- positionOf(id: string): number {
99
- const numeric = Number(id);
100
- return Number.isInteger(numeric) ? numeric : -1;
101
- },
102
- idOf(position: number): string {
103
- return String(position);
104
- },
105
- resolve(position: number, treeRow: Gtk.TreeListRow | null, boundItem: GObject.Object | null): Resolved<T, S> {
106
- const item = boundItem ?? model.getItem(position);
107
- return {
108
- value: (item ?? undefined) as T | undefined,
109
- present: item !== null,
110
86
  isHeader: false,
111
- treeRow,
112
- metadata: NO_TREE_METADATA,
87
+ metadata: record.metadata,
113
88
  };
114
89
  },
115
90
  };
116
91
  };
117
92
 
118
- export const createSectionHeaderResolver = <T, S>(items: ListItem<T, S>[] | undefined): ItemResolver<T, S> => {
93
+ export const createSectionHeaderResolver = <T, S>(sections: SectionNode<S, T>[] | undefined): ItemResolver<T, S> => {
119
94
  const valueByStart = new Map<number, S>();
120
95
  let start = 0;
121
- for (const section of items ?? []) {
122
- if (section.section !== true) continue;
96
+ for (const section of sections ?? []) {
123
97
  valueByStart.set(start, section.value);
124
- start += section.children?.length ?? 0;
98
+ start += countDescendants(section.data);
125
99
  }
126
100
  return {
127
- count: valueByStart.size,
128
- positionOf: () => -1,
101
+ positionOfId: () => -1,
129
102
  idOf: () => undefined,
130
103
  resolve(position: number): Resolved<T, S> {
131
104
  if (!valueByStart.has(position)) {
132
- return { value: undefined, present: false, isHeader: true, treeRow: null, metadata: NO_TREE_METADATA };
105
+ return { value: undefined, present: false, isHeader: true, metadata: NO_TREE_METADATA };
133
106
  }
134
107
  return {
135
108
  value: valueByStart.get(position),
136
109
  present: true,
137
110
  isHeader: true,
138
- treeRow: null,
139
111
  metadata: NO_TREE_METADATA,
140
112
  };
141
113
  },
142
114
  };
143
115
  };
116
+
117
+ export const createTreeResolver = <T, S>(
118
+ items: ItemNode<T>[] | undefined,
119
+ rowValues: WeakMap<GObject.Object, RowValue<T>>,
120
+ model: Gtk.TreeListModel,
121
+ ): ItemResolver<T, S> => {
122
+ const base = createItemResolver<T, S>(items, false, rowValues);
123
+ return {
124
+ resolve: base.resolve,
125
+ idOf: (position: number): string | undefined => {
126
+ const row = model.getRow(position);
127
+ return row === null ? undefined : rowIdOf(rowValues, row);
128
+ },
129
+ positionOfId: (id: string): number => {
130
+ const count = model.getNItems();
131
+ for (let position = 0; position < count; position++) {
132
+ const row = model.getRow(position);
133
+ if (row !== null && rowIdOf(rowValues, row) === id) return position;
134
+ }
135
+ return -1;
136
+ },
137
+ };
138
+ };