@gtkx/components 1.0.0-rc.2 → 1.0.0-rc.4

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 (79) hide show
  1. package/README.md +4 -5
  2. package/dist/adw/combo-row.d.ts +12 -0
  3. package/dist/adw/combo-row.d.ts.map +1 -0
  4. package/dist/adw/combo-row.js +14 -0
  5. package/dist/adw/combo-row.js.map +1 -0
  6. package/dist/adw/index.d.ts +2 -1
  7. package/dist/adw/index.d.ts.map +1 -1
  8. package/dist/adw/index.js +1 -0
  9. package/dist/adw/index.js.map +1 -1
  10. package/dist/adw/types.d.ts +11 -3
  11. package/dist/adw/types.d.ts.map +1 -1
  12. package/dist/adw/types.js.map +1 -1
  13. package/dist/column-view.d.ts.map +1 -1
  14. package/dist/column-view.js +4 -4
  15. package/dist/column-view.js.map +1 -1
  16. package/dist/drop-down.d.ts +4 -5
  17. package/dist/drop-down.d.ts.map +1 -1
  18. package/dist/drop-down.js +5 -112
  19. package/dist/drop-down.js.map +1 -1
  20. package/dist/grid-view.js +1 -1
  21. package/dist/grid-view.js.map +1 -1
  22. package/dist/index.d.ts +1 -2
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +0 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/internal/cells.d.ts +3 -3
  27. package/dist/internal/cells.d.ts.map +1 -1
  28. package/dist/internal/cells.js.map +1 -1
  29. package/dist/internal/collection-index.d.ts +5 -5
  30. package/dist/internal/collection-index.d.ts.map +1 -1
  31. package/dist/internal/collection-index.js +3 -3
  32. package/dist/internal/collection-index.js.map +1 -1
  33. package/dist/internal/collection-model.d.ts.map +1 -1
  34. package/dist/internal/collection-model.js +2 -5
  35. package/dist/internal/collection-model.js.map +1 -1
  36. package/dist/internal/collection.d.ts +2 -2
  37. package/dist/internal/collection.d.ts.map +1 -1
  38. package/dist/internal/collection.js.map +1 -1
  39. package/dist/internal/drop-down.d.ts +16 -0
  40. package/dist/internal/drop-down.d.ts.map +1 -0
  41. package/dist/internal/drop-down.js +110 -0
  42. package/dist/internal/drop-down.js.map +1 -0
  43. package/dist/internal/expansion.d.ts +1 -1
  44. package/dist/internal/expansion.d.ts.map +1 -1
  45. package/dist/internal/expansion.js +3 -4
  46. package/dist/internal/expansion.js.map +1 -1
  47. package/dist/internal/selection.d.ts +1 -0
  48. package/dist/internal/selection.d.ts.map +1 -1
  49. package/dist/internal/selection.js +1 -0
  50. package/dist/internal/selection.js.map +1 -1
  51. package/dist/internal/use-collection.d.ts +5 -6
  52. package/dist/internal/use-collection.d.ts.map +1 -1
  53. package/dist/internal/use-collection.js +10 -18
  54. package/dist/internal/use-collection.js.map +1 -1
  55. package/dist/types.d.ts +70 -48
  56. package/dist/types.d.ts.map +1 -1
  57. package/dist/types.js.map +1 -1
  58. package/package.json +7 -7
  59. package/src/adw/combo-row.tsx +21 -0
  60. package/src/adw/index.ts +8 -1
  61. package/src/adw/types.ts +17 -3
  62. package/src/column-view.tsx +13 -13
  63. package/src/drop-down.tsx +10 -195
  64. package/src/grid-view.tsx +1 -1
  65. package/src/index.ts +7 -12
  66. package/src/internal/cells.tsx +12 -12
  67. package/src/internal/collection-index.ts +16 -16
  68. package/src/internal/collection-model.ts +2 -5
  69. package/src/internal/collection.ts +2 -2
  70. package/src/internal/drop-down.tsx +186 -0
  71. package/src/internal/expansion.ts +5 -6
  72. package/src/internal/selection.tsx +3 -0
  73. package/src/internal/use-collection.tsx +16 -29
  74. package/src/types.ts +83 -66
  75. package/dist/size-group.d.ts +0 -12
  76. package/dist/size-group.d.ts.map +0 -1
  77. package/dist/size-group.js +0 -41
  78. package/dist/size-group.js.map +0 -1
  79. package/src/size-group.tsx +0 -77
@@ -1,12 +1,12 @@
1
- import type { ReactNode } from "react";
1
+ import type { ReactNode, RefObject } from "react";
2
2
  import * as Gtk from "@gtkx/gi/gtk";
3
3
  import { GtkColumnView, GtkColumnViewColumn, GtkCustomSorter, GtkSignalListItemFactory } from "@gtkx/jsx/gtk";
4
- import { useProperty, useSignal } from "@gtkx/react";
4
+ import { useSignal } from "@gtkx/react";
5
5
  import { omit } from "@gtkx/utils";
6
6
  import { useLayoutEffect, useRef } from "react";
7
7
  import type { CellSize } from "./internal/cells.js";
8
8
  import type { Collection } from "./internal/collection.js";
9
- import type { Column, ColumnViewProps } from "./types.js";
9
+ import type { ColumnViewColumn, ColumnViewProps } from "./types.js";
10
10
  import { HeaderPortals, ItemPortals, useHeaderCells, useItemCells } from "./internal/cells.js";
11
11
  import { useCollection } from "./internal/use-collection.js";
12
12
  import { useWidgetRef } from "./internal/use-widget-ref.js";
@@ -20,14 +20,14 @@ type SortProps = {
20
20
  type SortTarget = { view: Gtk.ColumnView; column: Gtk.ColumnViewColumn | null };
21
21
 
22
22
  type ColumnCellsProps = {
23
- column: Column<never>;
23
+ column: ColumnViewColumn<never>;
24
24
  collection: Collection;
25
25
  expandedIds: string[] | null | undefined;
26
26
  size: CellSize;
27
27
  };
28
28
 
29
29
  type ColumnListProps = Omit<ColumnCellsProps, "column"> & {
30
- columns: Column<never>[];
30
+ columns: ColumnViewColumn<never>[];
31
31
  };
32
32
 
33
33
  const COLUMN_VIEW_PROPS = [
@@ -77,7 +77,7 @@ const getSortTarget = (view: Gtk.ColumnView | null, sortColumn: string | null |
77
77
  };
78
78
 
79
79
  const applySort = (
80
- sorting: { current: boolean },
80
+ sorting: RefObject<boolean>,
81
81
  view: Gtk.ColumnView,
82
82
  column: Gtk.ColumnViewColumn | null,
83
83
  order: Gtk.SortType | null | undefined,
@@ -93,7 +93,7 @@ const applySort = (
93
93
 
94
94
  const emitSortChanged = (
95
95
  sorter: Gtk.ColumnViewSorter | null,
96
- sorting: { current: boolean },
96
+ sorting: RefObject<boolean>,
97
97
  sort: SortProps,
98
98
  ): void => {
99
99
  if (sorter === null || sorting.current) {
@@ -104,7 +104,7 @@ const emitSortChanged = (
104
104
  };
105
105
 
106
106
  const syncSort = (
107
- sorting: { current: boolean },
107
+ sorting: RefObject<boolean>,
108
108
  view: Gtk.ColumnView | null,
109
109
  sortColumn: string | null | undefined,
110
110
  sortOrder: Gtk.SortType | null | undefined,
@@ -118,9 +118,9 @@ const syncSort = (
118
118
  applySort(sorting, target.view, target.column, sortOrder);
119
119
  };
120
120
 
121
- const useColumnSorting = (view: Gtk.ColumnView | null, sort: SortProps, columns: Column<never>[]): void => {
121
+ const useColumnSorting = (view: Gtk.ColumnView | null, sort: SortProps, columns: ColumnViewColumn<never>[]): void => {
122
122
  const sorting = useRef(false);
123
- const sorter = useProperty(view, "sorter");
123
+ const sorter = view?.getSorter() ?? null;
124
124
  const columnSorter = sorter instanceof Gtk.ColumnViewSorter ? sorter : null;
125
125
 
126
126
  useSignal(columnSorter, "changed", (): void => {
@@ -136,14 +136,14 @@ const useColumnSorting = (view: Gtk.ColumnView | null, sort: SortProps, columns:
136
136
 
137
137
  const ColumnCells = ({ column, collection, expandedIds, size }: ColumnCellsProps): ReactNode => {
138
138
  const cells = useItemCells(size);
139
- const rest = omit(column, ["id", "renderCell", "sortable"]);
139
+ const rest = omit(column, ["id", "renderCell", "isSortable"]);
140
140
 
141
141
  return (
142
142
  <>
143
143
  <GtkColumnViewColumn
144
144
  id={column.id}
145
145
  factory={<GtkSignalListItemFactory {...cells.handlers} />}
146
- sorter={column.sortable === true ? <GtkCustomSorter /> : null}
146
+ sorter={column.isSortable === true ? <GtkCustomSorter /> : null}
147
147
  {...rest}
148
148
  />
149
149
  <ItemPortals
@@ -181,7 +181,7 @@ function ColumnView<T = unknown, S = unknown>(props: ColumnViewProps<T, S>): Rea
181
181
  const size = { width: -1, height: estimatedItemHeight ?? -1 };
182
182
  const { collection, selection } = useCollection(props);
183
183
  const headerCells = useHeaderCells(size);
184
- const columnList = columns as Column<never>[];
184
+ const columnList = columns as ColumnViewColumn<never>[];
185
185
  useColumnSorting(view, { sortColumn, sortOrder, onSortChanged }, columnList);
186
186
 
187
187
  return (
package/src/drop-down.tsx CHANGED
@@ -1,205 +1,20 @@
1
- import type * as GObject from "@gtkx/gi/gobject";
2
- import type { ElementType, ReactNode, Ref, RefObject } from "react";
3
- import { GtkDropDown, GtkLabel, GtkSignalListItemFactory } from "@gtkx/jsx/gtk";
4
- import { useSignal } from "@gtkx/react";
5
- import { omit } from "@gtkx/utils";
6
- import { useEffectEvent, useLayoutEffect, useRef } from "react";
7
- import type { Collection } from "./internal/collection.js";
8
- import type { DropDownProps, ItemRenderer, RenderItemArgs } from "./types.js";
9
- import { HeaderPortals, ItemPortals, useHeaderCells, useItemCells } from "./internal/cells.js";
10
- import { useCollectionData } from "./internal/use-collection.js";
11
- import { useWidgetRef } from "./internal/use-widget-ref.js";
1
+ import type { ReactNode } from "react";
2
+ import { GtkDropDown } from "@gtkx/jsx/gtk";
3
+ import type { DropDownBaseProps } from "./internal/drop-down.js";
4
+ import type { DropDownProps } from "./types.js";
5
+ import { DropDownBase } from "./internal/drop-down.js";
12
6
 
13
- type SelectableWidget = GObject.Object & { getSelected: () => number; selected: number };
14
-
15
- type DropDownRuntimeProps = DropDownProps & {
16
- component?: ElementType | undefined;
17
- ref?: Ref<SelectableWidget | null> | undefined;
18
- } & Record<string, unknown>;
19
-
20
- type SelectionOptions = {
21
- widget: SelectableWidget | null;
22
- collection: Collection;
23
- applying: RefObject<boolean>;
24
- props: DropDownRuntimeProps;
25
- };
26
-
27
- type KnownSelection = {
28
- known: { current: string | null };
29
- onSelectionChanged: ((id: string) => void) | null | undefined;
30
- };
31
-
32
- type DropDownComponent = <T = unknown, S = unknown, C extends ElementType = typeof GtkDropDown>(
33
- props: DropDownProps<T, S, C>,
34
- ) => ReactNode;
35
-
36
- const DROP_DOWN_PROPS: string[] = [
37
- "component",
38
- "items",
39
- "sections",
40
- "selectedId",
41
- "onSelectionChanged",
42
- "renderItem",
43
- "renderListItem",
44
- "renderHeader",
45
- "ref",
46
- ];
7
+ /** Call signature of {@link DropDown}, generic in the item and section value types. */
8
+ type DropDownComponent = <T = unknown, S = unknown>(props: DropDownProps<T, S>) => ReactNode;
47
9
 
48
10
  /**
49
- * Renders a drop-down backed by a collection model, with customizable renderers for the
11
+ * Renders a `Gtk.DropDown` backed by a collection model, with customizable renderers for the
50
12
  * collapsed display, popup rows, and popup section headers, and controlled selection.
51
- * The backing widget defaults to GtkDropDown and is swappable through `component`.
52
13
  */
53
14
  const DropDown: DropDownComponent = DropDownImpl as DropDownComponent;
54
15
 
55
- const describeValue = (value: unknown): string => {
56
- if (typeof value === "string") {
57
- return value;
58
- }
59
-
60
- try {
61
- return JSON.stringify(value);
62
- } catch {
63
- return String(value);
64
- }
65
- };
66
-
67
- const defaultItemContent = (value: unknown): ReactNode =>
68
- value == null ? null : <GtkLabel>{describeValue(value)}</GtkLabel>;
69
-
70
- const defaultRenderItem = ({ item }: RenderItemArgs<unknown>): ReactNode => defaultItemContent(item);
71
-
72
- const faceRenderer = (props: DropDownRuntimeProps): ItemRenderer<never> =>
73
- props.renderItem ?? defaultRenderItem;
74
-
75
- const listRenderer = (props: DropDownRuntimeProps): ItemRenderer<never> =>
76
- props.renderListItem ?? props.renderItem ?? defaultRenderItem;
77
-
78
- const resolvePosition = (
79
- widget: SelectableWidget,
80
- collection: Collection,
81
- selectedId: string | null | undefined,
82
- ): number => {
83
- const requested = selectedId == null ? -1 : collection.positionFor(selectedId);
84
-
85
- return requested >= 0 ? requested : widget.getSelected();
86
- };
87
-
88
- const updateKnownSelection = (
89
- tracker: KnownSelection,
90
- effectiveId: string,
91
- selectedId: string | null | undefined,
92
- ): void => {
93
- const { known, onSelectionChanged } = tracker;
94
- const expectedId = selectedId ?? known.current;
95
- const isNew = effectiveId !== known.current;
96
- known.current = effectiveId;
97
-
98
- if (expectedId !== null && effectiveId !== expectedId && isNew) {
99
- onSelectionChanged?.(effectiveId);
100
- }
101
- };
102
-
103
- const reportKnownSelection = (tracker: KnownSelection, id: string | null): void => {
104
- const { known, onSelectionChanged } = tracker;
105
-
106
- if (id === null || id === known.current) {
107
- return;
108
- }
109
-
110
- known.current = id;
111
- onSelectionChanged?.(id);
112
- };
113
-
114
- const reportSelectedNotify = (
115
- options: SelectionOptions,
116
- tracker: KnownSelection,
117
- applying: RefObject<boolean>,
118
- ): void => {
119
- const { widget, collection } = options;
120
-
121
- if (widget === null || applying.current) {
122
- return;
123
- }
124
-
125
- reportKnownSelection(tracker, collection.idAt(widget.getSelected()));
126
- };
127
-
128
- const applySelectedPosition = (widget: SelectableWidget, position: number, applying: RefObject<boolean>): void => {
129
- applying.current = true;
130
-
131
- try {
132
- widget.selected = position;
133
- } finally {
134
- applying.current = false;
135
- }
136
- };
137
-
138
- const useDropDownSelection = (options: SelectionOptions): void => {
139
- const { widget, collection, applying } = options;
140
- const { selectedId } = options.props;
141
- const known = useRef<string | null>(null);
142
-
143
- const syncKnownSelection = useEffectEvent((position: number): void => {
144
- const effectiveId = collection.idAt(position);
145
-
146
- if (effectiveId === null) {
147
- known.current = null;
148
-
149
- return;
150
- }
151
-
152
- const { onSelectionChanged } = options.props;
153
- updateKnownSelection({ known, onSelectionChanged }, effectiveId, selectedId);
154
- });
155
-
156
- useLayoutEffect(() => {
157
- if (widget === null) {
158
- return;
159
- }
160
-
161
- const position = resolvePosition(widget, collection, selectedId);
162
- applySelectedPosition(widget, position, applying);
163
- syncKnownSelection(position);
164
- }, [widget, collection, selectedId, applying]);
165
-
166
- useSignal(widget, "notify::selected", (): void => {
167
- reportSelectedNotify(options, { known, onSelectionChanged: options.props.onSelectionChanged }, applying);
168
- });
169
- };
170
-
171
- function DropDownImpl(props: DropDownRuntimeProps): ReactNode {
172
- const { component, items, sections, renderListItem, renderHeader, ref } = props;
173
- const rest = omit(props, DROP_DOWN_PROPS);
174
- const [widget, refCallback] = useWidgetRef<SelectableWidget>(ref);
175
- const applying = useRef(false);
176
- const collection = useCollectionData({ items, sections, applying });
177
- const faceCells = useItemCells({ width: -1, height: -1 });
178
- const listCells = useItemCells({ width: -1, height: -1 });
179
- const headerCells = useHeaderCells({ width: -1, height: -1 });
180
- useDropDownSelection({ widget, collection, applying, props });
181
- const Component = component ?? GtkDropDown;
182
- const hasHeader = typeof renderHeader === "function";
183
-
184
- return (
185
- <>
186
- <Component
187
- ref={refCallback}
188
- model={collection.model}
189
- factory={<GtkSignalListItemFactory {...faceCells.handlers} />}
190
- {...(renderListItem != null && {
191
- listFactory: <GtkSignalListItemFactory {...listCells.handlers} />,
192
- })}
193
- {...(hasHeader && { headerFactory: <GtkSignalListItemFactory {...headerCells.handlers} /> })}
194
- {...rest}
195
- />
196
- <ItemPortals store={faceCells} render={faceRenderer(props)} collection={collection} />
197
- {renderListItem != null && (
198
- <ItemPortals store={listCells} render={listRenderer(props)} collection={collection} />
199
- )}
200
- {hasHeader && <HeaderPortals store={headerCells} render={renderHeader} collection={collection} />}
201
- </>
202
- );
16
+ function DropDownImpl(props: DropDownBaseProps): ReactNode {
17
+ return <DropDownBase {...props} component={GtkDropDown} />;
203
18
  }
204
19
 
205
20
  export { DropDown };
package/src/grid-view.tsx CHANGED
@@ -23,7 +23,7 @@ function GridView<T = unknown>(props: GridViewProps<T>): ReactNode {
23
23
  const { renderItem, estimatedItemHeight, estimatedItemWidth } = props;
24
24
  const rest = omit(props, GRID_VIEW_PROPS);
25
25
  const size = { width: estimatedItemWidth ?? -1, height: estimatedItemHeight ?? -1 };
26
- const { collection, selection } = useCollection({ ...props, flat: true });
26
+ const { collection, selection } = useCollection({ ...props, isFlat: true });
27
27
  const itemCells = useItemCells(size);
28
28
 
29
29
  return (
package/src/index.ts CHANGED
@@ -2,21 +2,16 @@ export { ColumnView } from "./column-view.js";
2
2
  export { DropDown } from "./drop-down.js";
3
3
  export { GridView } from "./grid-view.js";
4
4
  export { ListView } from "./list-view.js";
5
- export { SizeGroup } from "./size-group.js";
6
5
  export type {
7
- ChildProps,
8
- Column,
6
+ ColumnViewColumn,
9
7
  ColumnViewProps,
10
8
  DropDownProps,
11
9
  GridViewProps,
12
- HeaderRenderer,
13
- Item,
14
- ItemRenderer,
10
+ ListItem,
11
+ ListItemRenderArgs,
12
+ ListItemRenderer,
13
+ ListSection,
14
+ ListSectionRenderArgs,
15
+ ListSectionRenderer,
15
16
  ListViewProps,
16
- RenderHeaderArgs,
17
- RenderItemArgs,
18
- Section,
19
- SizeGroupChildProps,
20
- SizeGroupProps,
21
- WidgetProps,
22
17
  } from "./types.js";
@@ -4,7 +4,7 @@ import * as Gtk from "@gtkx/gi/gtk";
4
4
  import { GtkTreeExpander } from "@gtkx/jsx/gtk";
5
5
  import { createPortal, useProperty } from "@gtkx/react";
6
6
  import { memo, useLayoutEffect, useState, useSyncExternalStore } from "react";
7
- import type { HeaderRenderer, Item, ItemRenderer, RenderItemArgs } from "../types.js";
7
+ import type { ListItem, ListItemRenderArgs, ListItemRenderer, ListSectionRenderer } from "../types.js";
8
8
  import type { Collection } from "./collection.js";
9
9
  import { getId } from "./collection-model.js";
10
10
 
@@ -56,34 +56,34 @@ type ItemBodyOptions = {
56
56
  position: number | undefined;
57
57
  row: Gtk.TreeListRow | null;
58
58
  isRowExpanded: boolean | undefined;
59
- render: ItemRenderer<never>;
59
+ render: ListItemRenderer<never>;
60
60
  collection: Collection;
61
61
  expandedIds: string[] | null | undefined;
62
62
  };
63
63
 
64
64
  type ItemCellProps = {
65
65
  entry: CellEntry<Gtk.ListItem>;
66
- render: ItemRenderer<never>;
66
+ render: ListItemRenderer<never>;
67
67
  collection: Collection;
68
68
  expandedIds: string[] | null | undefined;
69
69
  };
70
70
 
71
71
  type ItemPortalsProps = {
72
72
  store: CellStore<Gtk.ListItem>;
73
- render: ItemRenderer<never>;
73
+ render: ListItemRenderer<never>;
74
74
  collection: Collection;
75
75
  expandedIds?: string[] | null | undefined;
76
76
  };
77
77
 
78
78
  type HeaderCellProps = {
79
79
  entry: CellEntry<Gtk.ListHeader>;
80
- render: HeaderRenderer<never>;
80
+ render: ListSectionRenderer<never>;
81
81
  collection: Collection;
82
82
  };
83
83
 
84
84
  type HeaderPortalsProps = {
85
85
  store: CellStore<Gtk.ListHeader>;
86
- render: HeaderRenderer<never>;
86
+ render: ListSectionRenderer<never>;
87
87
  collection: Collection;
88
88
  };
89
89
 
@@ -246,14 +246,14 @@ function useHeaderCells(size: CellSize): CellStore<Gtk.ListHeader> {
246
246
  return useCellStore(isListHeader, size);
247
247
  }
248
248
 
249
- function isRowWanted(options: ItemBodyOptions, item: Item): boolean {
249
+ function isRowWanted(options: ItemBodyOptions, item: ListItem): boolean {
250
250
  const { expandedIds } = options;
251
251
 
252
252
  return expandedIds == null ? options.isRowExpanded === true : expandedIds.includes(item.id);
253
253
  }
254
254
 
255
- function itemArgs(item: Item, options: ItemBodyOptions): RenderItemArgs<unknown> {
256
- const args: RenderItemArgs<unknown> = { item: item.value, index: options.position ?? 0 };
255
+ function itemArgs(item: ListItem, options: ItemBodyOptions): ListItemRenderArgs<unknown> {
256
+ const args: ListItemRenderArgs<unknown> = { item: item.value, index: options.position ?? 0 };
257
257
  const row = options.row;
258
258
 
259
259
  if (row === null) {
@@ -269,7 +269,7 @@ function itemArgs(item: Item, options: ItemBodyOptions): RenderItemArgs<unknown>
269
269
  return args;
270
270
  }
271
271
 
272
- function wrapExpander(item: Item, row: Gtk.TreeListRow | null, content: ReactNode): ReactNode {
272
+ function wrapExpander(item: ListItem, row: Gtk.TreeListRow | null, content: ReactNode): ReactNode {
273
273
  const expander: ReactNode =
274
274
  row === null
275
275
  ? (
@@ -297,7 +297,7 @@ function itemBody(options: ItemBodyOptions): ReactNode {
297
297
  return null;
298
298
  }
299
299
 
300
- const render = options.render as ItemRenderer<unknown>;
300
+ const render = options.render as ListItemRenderer<unknown>;
301
301
 
302
302
  return wrapExpander(item, options.row, render(itemArgs(item, options)));
303
303
  }
@@ -315,7 +315,7 @@ function ItemCellImpl({ entry, render, collection, expandedIds }: ItemCellProps)
315
315
  function HeaderCellImpl({ entry, render, collection }: HeaderCellProps): ReactNode {
316
316
  const item = useSyncExternalStore(entry.subscribe, entry.getItem);
317
317
  const id = getId(item);
318
- const renderHeader = render as HeaderRenderer<unknown>;
318
+ const renderHeader = render as ListSectionRenderer<unknown>;
319
319
  const body = id === null ? null : renderHeader({ section: collection.sectionFor(id) });
320
320
 
321
321
  return createPortal(body, entry.cell, entry.key);
@@ -1,9 +1,9 @@
1
- import type { Item, Section } from "../types.js";
1
+ import type { ListItem, ListSection } from "../types.js";
2
2
 
3
3
  type Level = {
4
4
  key: string;
5
5
  ids: string[];
6
- isExpandable: boolean[];
6
+ expandableFlags: boolean[];
7
7
  };
8
8
 
9
9
  type CollectionIndex = {
@@ -11,8 +11,8 @@ type CollectionIndex = {
11
11
  size: number;
12
12
  groups: Level[];
13
13
  children: Map<string, Level>;
14
- has: (id: string) => boolean;
15
- itemFor: (id: string) => Item | undefined;
14
+ hasId: (id: string) => boolean;
15
+ itemFor: (id: string) => ListItem | undefined;
16
16
  sectionFor: (id: string) => unknown;
17
17
  positionFor: (id: string) => number;
18
18
  };
@@ -21,24 +21,24 @@ type IndexState = {
21
21
  isTree: boolean;
22
22
  groups: Level[];
23
23
  children: Map<string, Level>;
24
- itemsById: Map<string, Item>;
25
- sections: Section[] | undefined;
24
+ itemsById: Map<string, ListItem>;
25
+ sections: ListSection[] | undefined;
26
26
  positions: Map<string, number> | null;
27
27
  starts: number[] | null;
28
28
  };
29
29
 
30
30
  const ROOT_LEVEL_KEY = "";
31
31
 
32
- const hasChildren = (item: Item): boolean => item.children !== undefined && item.children.length > 0;
32
+ const hasChildren = (item: ListItem): boolean => item.children !== undefined && item.children.length > 0;
33
33
  const childLevelKey = (id: string): string => `child:${id}`;
34
34
 
35
- function collectChildren(state: IndexState, level: Level, item: Item): void {
35
+ function collectChildren(state: IndexState, level: Level, item: ListItem): void {
36
36
  if (!state.isTree) {
37
37
  return;
38
38
  }
39
39
 
40
40
  const canExpand = hasChildren(item);
41
- level.isExpandable.push(canExpand);
41
+ level.expandableFlags.push(canExpand);
42
42
 
43
43
  if (!canExpand) {
44
44
  return;
@@ -48,8 +48,8 @@ function collectChildren(state: IndexState, level: Level, item: Item): void {
48
48
  state.children.set(key, collectLevel(state, key, item.children ?? []));
49
49
  }
50
50
 
51
- function collectLevel(state: IndexState, key: string, items: Item[]): Level {
52
- const level: Level = { key, ids: [], isExpandable: [] };
51
+ function collectLevel(state: IndexState, key: string, items: ListItem[]): Level {
52
+ const level: Level = { key, ids: [], expandableFlags: [] };
53
53
 
54
54
  for (const item of items) {
55
55
  level.ids.push(item.id);
@@ -118,7 +118,7 @@ function sectionFor(state: IndexState, id: string): unknown {
118
118
  return sections[sectionAt(state.starts, position)]?.value;
119
119
  }
120
120
 
121
- function buildGroups(state: IndexState, source: Item[], sections: Section[] | undefined): Level[] {
121
+ function buildGroups(state: IndexState, source: ListItem[], sections: ListSection[] | undefined): Level[] {
122
122
  if (sections === undefined) {
123
123
  return [collectLevel(state, ROOT_LEVEL_KEY, source)];
124
124
  }
@@ -126,7 +126,7 @@ function buildGroups(state: IndexState, source: Item[], sections: Section[] | un
126
126
  return sections.map((section) => collectLevel(state, section.id, section.data));
127
127
  }
128
128
 
129
- function isTreeSource(source: Item[], sections: Section[] | undefined, isFlat: boolean): boolean {
129
+ function isTreeSource(source: ListItem[], sections: ListSection[] | undefined, isFlat: boolean): boolean {
130
130
  if (sections !== undefined || isFlat) {
131
131
  return false;
132
132
  }
@@ -135,8 +135,8 @@ function isTreeSource(source: Item[], sections: Section[] | undefined, isFlat: b
135
135
  }
136
136
 
137
137
  function createCollectionIndex(
138
- items: Item[] | undefined,
139
- sections: Section[] | undefined,
138
+ items: ListItem[] | undefined,
139
+ sections: ListSection[] | undefined,
140
140
  isFlat: boolean,
141
141
  ): CollectionIndex {
142
142
  const source = items ?? [];
@@ -158,7 +158,7 @@ function createCollectionIndex(
158
158
  size: state.itemsById.size,
159
159
  groups: state.groups,
160
160
  children: state.children,
161
- has: (id) => state.itemsById.has(id),
161
+ hasId: (id) => state.itemsById.has(id),
162
162
  itemFor: (id) => state.itemsById.get(id),
163
163
  sectionFor: (id) => sectionFor(state, id),
164
164
  positionFor: (id) => positionFor(state, id),
@@ -104,7 +104,7 @@ function refreshExpandable(current: LevelState, level: Level): void {
104
104
  const next: Map<string, boolean> = new Map();
105
105
 
106
106
  for (const [index, id] of level.ids.entries()) {
107
- const isWanted = level.isExpandable[index] ?? false;
107
+ const isWanted = level.expandableFlags[index] ?? false;
108
108
  const previous = current.canExpand.get(id);
109
109
  next.set(id, isWanted);
110
110
 
@@ -138,16 +138,13 @@ function dropUnvisited(state: ModelState, visited: Set<string>): void {
138
138
  }
139
139
  }
140
140
 
141
- // Items keep their identity by id, so a collection that shrinks keeps its objects cached: filtering a long
142
- // list down and back up reuses them instead of rebuilding one GObject per row. Incremental filters shrink and
143
- // grow the list many times per keystroke, so the cache is only swept once it passes an absolute bound.
144
141
  function pruneObjects(state: ModelState, index: CollectionIndex): void {
145
142
  if (state.objects.size <= OBJECT_CACHE_MAX) {
146
143
  return;
147
144
  }
148
145
 
149
146
  for (const id of state.objects.keys()) {
150
- if (!index.has(id)) {
147
+ if (!index.hasId(id)) {
151
148
  state.objects.delete(id);
152
149
  }
153
150
  }
@@ -1,6 +1,6 @@
1
1
  import type * as GObject from "@gtkx/gi/gobject";
2
2
  import type * as Gtk from "@gtkx/gi/gtk";
3
- import type { Item } from "../types.js";
3
+ import type { ListItem } from "../types.js";
4
4
  import type { CollectionIndex } from "./collection-index.js";
5
5
  import type { CollectionModel } from "./collection-model.js";
6
6
  import { getId } from "./collection-model.js";
@@ -9,7 +9,7 @@ type Collection = {
9
9
  isTree: boolean;
10
10
  model: Gtk.FlattenListModel;
11
11
  treeModel: () => Gtk.TreeListModel | null;
12
- itemFor: (id: string) => Item | undefined;
12
+ itemFor: (id: string) => ListItem | undefined;
13
13
  sectionFor: (id: string) => unknown;
14
14
  idAt: (position: number) => string | null;
15
15
  positionFor: (id: string) => number;