@gtkx/components 2.0.0-beta.10 → 2.0.0-beta.11

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 (75) hide show
  1. package/README.md +8 -168
  2. package/dist/column-view.d.ts.map +1 -1
  3. package/dist/column-view.js +19 -9
  4. package/dist/column-view.js.map +1 -1
  5. package/dist/index.d.ts +3 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +2 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/internal/cells.d.ts +8 -1
  10. package/dist/internal/cells.d.ts.map +1 -1
  11. package/dist/internal/cells.js +84 -19
  12. package/dist/internal/cells.js.map +1 -1
  13. package/dist/internal/collection-index.d.ts +3 -2
  14. package/dist/internal/collection-index.d.ts.map +1 -1
  15. package/dist/internal/collection-index.js +3 -3
  16. package/dist/internal/collection-index.js.map +1 -1
  17. package/dist/internal/collection-model.d.ts +23 -2
  18. package/dist/internal/collection-model.d.ts.map +1 -1
  19. package/dist/internal/collection-model.js +118 -98
  20. package/dist/internal/collection-model.js.map +1 -1
  21. package/dist/internal/collection.d.ts +5 -4
  22. package/dist/internal/collection.d.ts.map +1 -1
  23. package/dist/internal/collection.js +20 -21
  24. package/dist/internal/collection.js.map +1 -1
  25. package/dist/internal/controlled-sync.d.ts +6 -8
  26. package/dist/internal/controlled-sync.d.ts.map +1 -1
  27. package/dist/internal/controlled-sync.js +3 -3
  28. package/dist/internal/controlled-sync.js.map +1 -1
  29. package/dist/internal/drop-down.d.ts.map +1 -1
  30. package/dist/internal/drop-down.js +43 -44
  31. package/dist/internal/drop-down.js.map +1 -1
  32. package/dist/internal/expansion.js +4 -4
  33. package/dist/internal/expansion.js.map +1 -1
  34. package/dist/internal/selection.d.ts +1 -0
  35. package/dist/internal/selection.d.ts.map +1 -1
  36. package/dist/internal/selection.js +6 -6
  37. package/dist/internal/selection.js.map +1 -1
  38. package/dist/internal/tree-expansion.d.ts.map +1 -1
  39. package/dist/internal/tree-expansion.js +2 -11
  40. package/dist/internal/tree-expansion.js.map +1 -1
  41. package/dist/internal/tree-order.d.ts.map +1 -1
  42. package/dist/internal/tree-order.js +32 -76
  43. package/dist/internal/tree-order.js.map +1 -1
  44. package/dist/internal/use-collection.d.ts +5 -1
  45. package/dist/internal/use-collection.d.ts.map +1 -1
  46. package/dist/internal/use-collection.js +21 -7
  47. package/dist/internal/use-collection.js.map +1 -1
  48. package/dist/list-item-factory.d.ts +10 -0
  49. package/dist/list-item-factory.d.ts.map +1 -0
  50. package/dist/list-item-factory.js +14 -0
  51. package/dist/list-item-factory.js.map +1 -0
  52. package/dist/toast.d.ts +3 -5
  53. package/dist/toast.d.ts.map +1 -1
  54. package/dist/toast.js +3 -12
  55. package/dist/toast.js.map +1 -1
  56. package/dist/types.d.ts +33 -50
  57. package/dist/types.d.ts.map +1 -1
  58. package/dist/types.js.map +1 -1
  59. package/package.json +12 -10
  60. package/src/column-view.tsx +23 -12
  61. package/src/index.ts +3 -2
  62. package/src/internal/cells.tsx +137 -20
  63. package/src/internal/collection-index.ts +8 -6
  64. package/src/internal/collection-model.ts +156 -134
  65. package/src/internal/collection.ts +28 -24
  66. package/src/internal/controlled-sync.ts +9 -12
  67. package/src/internal/drop-down.tsx +64 -60
  68. package/src/internal/expansion.ts +4 -4
  69. package/src/internal/selection.tsx +8 -8
  70. package/src/internal/tree-expansion.ts +2 -14
  71. package/src/internal/tree-order.ts +33 -121
  72. package/src/internal/use-collection.tsx +29 -8
  73. package/src/list-item-factory.tsx +27 -0
  74. package/src/toast.tsx +4 -18
  75. package/src/types.ts +35 -51
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gtkx/components",
3
- "version": "2.0.0-beta.10",
4
- "description": "Adwaita and GTK collection views, dropdowns, combo rows, and toasts.",
3
+ "version": "2.0.0-beta.11",
4
+ "description": "Collection views, selection controls, and toasts for GTKX.",
5
5
  "keywords": [
6
6
  "gtkx",
7
7
  "gtk",
@@ -53,18 +53,20 @@
53
53
  "node": ">=26.7.0"
54
54
  },
55
55
  "dependencies": {
56
- "@gtkx/react": "2.0.0-beta.10",
57
- "@gtkx/runtime": "2.0.0-beta.10",
58
- "@gtkx/utils": "2.0.0-beta.10"
56
+ "@gtkx/react": "2.0.0-beta.11",
57
+ "@gtkx/runtime": "2.0.0-beta.11",
58
+ "@gtkx/utils": "2.0.0-beta.11"
59
59
  },
60
60
  "peerDependencies": {
61
- "react": "^19.2"
61
+ "@types/react": "^19.3",
62
+ "react": "^19.3"
62
63
  },
63
64
  "devDependencies": {
64
- "@gtkx/config": "2.0.0-beta.10",
65
- "@gtkx/testing": "2.0.0-beta.10",
66
- "@gtkx/vitest": "2.0.0-beta.10",
67
- "vitest": "^5.0.0"
65
+ "@gtkx/config": "2.0.0-beta.11",
66
+ "@gtkx/testing": "2.0.0-beta.11",
67
+ "@gtkx/vitest": "2.0.0-beta.11",
68
+ "@types/react": "^19.3.0",
69
+ "vitest": "^5.0.1"
68
70
  },
69
71
  "scripts": {
70
72
  "release": "tsx ../../scripts/release-package.ts"
@@ -3,15 +3,17 @@ import * as Gtk from "@gtkx/gi/gtk";
3
3
  import { GtkColumnView, GtkColumnViewColumn, GtkCustomSorter, GtkSignalListItemFactory } from "@gtkx/jsx/gtk";
4
4
  import { useSignal } from "@gtkx/react";
5
5
  import { omit } from "@gtkx/utils";
6
- import { useLayoutEffect, useRef } from "react";
6
+ import { useMemo, useRef } from "react";
7
7
  import type { CellSize } from "./internal/cells.js";
8
8
  import type { Collection } from "./internal/collection.js";
9
9
  import type { ColumnViewColumn, ColumnViewProps, ExpanderDescriptions, SortProps } from "./types.js";
10
10
  import { ItemPortals, useItemCells, useRowProps, useSectionHeader } from "./internal/cells.js";
11
+ import { useControlledSync } from "./internal/controlled-sync.js";
11
12
  import { useCollection } from "./internal/use-collection.js";
12
13
  import { useWidgetRef } from "./internal/use-widget-ref.js";
13
14
 
14
15
  type SortTarget = { view: Gtk.ColumnView; column: Gtk.ColumnViewColumn | null };
16
+ type SortRequest = Pick<SortProps, "sortColumn" | "sortOrder">;
15
17
 
16
18
  type ColumnCellsProps = {
17
19
  column: ColumnViewColumn<never>;
@@ -43,7 +45,6 @@ const COLUMN_VIEW_PROPS = [
43
45
  "sortOrder",
44
46
  "onSortChanged",
45
47
  "estimatedItemHeight",
46
- "children",
47
48
  "ref",
48
49
  ] as const satisfies (keyof ColumnViewProps)[];
49
50
 
@@ -93,16 +94,18 @@ const applySort = (
93
94
  }
94
95
  };
95
96
 
96
- const emitSortChanged = (
97
+ const didEmitSortChanged = (
97
98
  sorter: Gtk.ColumnViewSorter | null,
98
99
  sorting: RefObject<boolean>,
99
100
  sort: SortProps,
100
- ): void => {
101
+ ): boolean => {
101
102
  if (sorter === null || sorting.current) {
102
- return;
103
+ return false;
103
104
  }
104
105
 
105
106
  sort.onSortChanged?.(sorter.getPrimarySortColumn()?.getId() ?? null, sorter.getPrimarySortOrder());
107
+
108
+ return true;
106
109
  };
107
110
 
108
111
  const syncSort = (
@@ -124,16 +127,24 @@ const useColumnSorting = (view: Gtk.ColumnView | null, sort: SortProps, columns:
124
127
  const sorting = useRef(false);
125
128
  const sorter = view?.getSorter() ?? null;
126
129
  const columnSorter = sorter instanceof Gtk.ColumnViewSorter ? sorter : null;
127
-
128
- useSignal(columnSorter, "changed", (): void => {
129
- emitSortChanged(columnSorter, sorting, sort);
130
+ const { sortColumn, sortOrder } = sort;
131
+ const request = useMemo<SortRequest>(() => ({ sortColumn, sortOrder }), [sortColumn, sortOrder]);
132
+ const markDrift = useControlledSync({
133
+ value: request,
134
+ source: columns,
135
+ target: view,
136
+ apply: (value) => {
137
+ syncSort(sorting, view, value.sortColumn, value.sortOrder);
138
+ },
130
139
  });
131
140
 
132
- const { sortColumn, sortOrder } = sort;
141
+ useSignal(columnSorter, "changed", (): void => {
142
+ const didEmit = didEmitSortChanged(columnSorter, sorting, sort);
133
143
 
134
- useLayoutEffect(() => {
135
- syncSort(sorting, view, sortColumn, sortOrder);
136
- }, [view, sortColumn, sortOrder, columns]);
144
+ if (sortColumn !== undefined && didEmit) {
145
+ markDrift();
146
+ }
147
+ });
137
148
  };
138
149
 
139
150
  const ColumnCells = ({
package/src/index.ts CHANGED
@@ -2,8 +2,9 @@ export { ColumnView } from "./column-view.js";
2
2
  export { ComboRow } from "./combo-row.js";
3
3
  export { DropDown } from "./drop-down.js";
4
4
  export { GridView } from "./grid-view.js";
5
+ export { ListItemFactory } from "./list-item-factory.js";
5
6
  export { ListView } from "./list-view.js";
6
- export { ToastProvider, useToast, useToastOverlay } from "./toast.js";
7
+ export { ToastProvider, useToast } from "./toast.js";
7
8
  export type {
8
9
  ColumnViewColumn,
9
10
  ColumnViewProps,
@@ -12,6 +13,7 @@ export type {
12
13
  ExpanderDescriptions,
13
14
  GridViewProps,
14
15
  ListItem,
16
+ ListItemFactoryProps,
15
17
  ListItemRenderArgs,
16
18
  ListItemRenderer,
17
19
  ListRowProps,
@@ -22,6 +24,5 @@ export type {
22
24
  ListViewProps,
23
25
  ToastController,
24
26
  ToastOptions,
25
- ToastOverlayController,
26
27
  ToastProviderProps,
27
28
  } from "./types.js";
@@ -1,10 +1,9 @@
1
1
  import type * as GObject from "@gtkx/gi/gobject";
2
2
  import type { ReactElement, ReactNode } from "react";
3
3
  import * as Gtk from "@gtkx/gi/gtk";
4
- import { GtkSignalListItemFactory, GtkTreeExpander } from "@gtkx/jsx/gtk";
4
+ import { GtkBox, GtkSignalListItemFactory, GtkTreeExpander } from "@gtkx/jsx/gtk";
5
5
  import { createPortal, useProperty } from "@gtkx/react";
6
- import { setProperty, t } from "@gtkx/runtime";
7
- import { memo, useInsertionEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
6
+ import { Fragment, memo, useInsertionEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
8
7
  import type {
9
8
  ExpanderDescriptions,
10
9
  ListItem,
@@ -15,7 +14,8 @@ import type {
15
14
  ListSectionRenderer,
16
15
  } from "../types.js";
17
16
  import type { Collection } from "./collection.js";
18
- import { slotRefFor } from "./collection-model.js";
17
+ import { slotPathAt, type SlotRef, slotRefFor } from "./collection-model.js";
18
+ import { joinParts } from "./keys.js";
19
19
 
20
20
  type CellSize = {
21
21
  width: number;
@@ -84,6 +84,7 @@ type ItemSlotOptions = {
84
84
  type PositionedHost = Gtk.ListItem | Gtk.ColumnViewRow;
85
85
 
86
86
  type ItemSlot = {
87
+ itemKey: string;
87
88
  item: ListItem;
88
89
  row: Gtk.TreeListRow | null;
89
90
  args: ListItemRenderArgs<unknown>;
@@ -100,6 +101,7 @@ type CollectionStateStore = {
100
101
  };
101
102
 
102
103
  type ItemIdentity = {
104
+ itemKey: string;
103
105
  item: ListItem | undefined;
104
106
  position: number;
105
107
  isExpanded: boolean;
@@ -152,6 +154,23 @@ type HeaderPortalsProps = {
152
154
  collection: Collection;
153
155
  };
154
156
 
157
+ type NativeItemKeyStore = {
158
+ keyFor: (item: GObject.Object) => string;
159
+ };
160
+
161
+ type NativeItemCellProps<T extends GObject.Object> = {
162
+ entry: CellEntry<Gtk.ListItem>;
163
+ render: ListItemRenderer<T>;
164
+ keys: NativeItemKeyStore;
165
+ size: CellSize;
166
+ };
167
+
168
+ type NativeItemPortalsProps<T extends GObject.Object> = {
169
+ registry: CellRegistry<Gtk.ListItem>;
170
+ render: ListItemRenderer<T>;
171
+ size: CellSize;
172
+ };
173
+
155
174
  type SectionHeaderSlot = {
156
175
  factoryProps: { headerFactory?: ReactElement };
157
176
  portals: ReactNode;
@@ -170,10 +189,14 @@ const HEADER_OPTIONS: CellRegistryOptions<Gtk.ListHeader> = { isHost: isListHead
170
189
  const ROW_OPTIONS: CellRegistryOptions<Gtk.ColumnViewRow> = { isHost: isColumnViewRow };
171
190
  const NO_SIZE: CellSize = { width: -1, height: -1 };
172
191
  const NO_ROW_PROPS: ListRowProps = {};
173
- const ROW_TEXT_DESCRIPTOR = t.string("borrowed");
192
+ const placeholders: WeakSet<Gtk.Widget> = new WeakSet();
174
193
 
175
- const placeholder = (size: CellSize): Gtk.Widget =>
176
- new Gtk.Box({ widthRequest: size.width, heightRequest: size.height });
194
+ const placeholder = (size: CellSize): Gtk.Widget => {
195
+ const widget = new Gtk.Box({ widthRequest: size.width, heightRequest: size.height });
196
+ placeholders.add(widget);
197
+
198
+ return widget;
199
+ };
177
200
 
178
201
  function isListItem(value: GObject.Object): value is Gtk.ListItem {
179
202
  return value instanceof Gtk.ListItem;
@@ -188,8 +211,12 @@ function isColumnViewRow(value: GObject.Object): value is Gtk.ColumnViewRow {
188
211
  }
189
212
 
190
213
  function prepareCell(host: CellHost, size: CellSize): void {
191
- if (host.getChild() === null) {
214
+ const child = host.getChild();
215
+
216
+ if (child === null) {
192
217
  host.setChild(placeholder(size));
218
+ } else if (placeholders.has(child)) {
219
+ child.setSizeRequest(size.width, size.height);
193
220
  }
194
221
  }
195
222
 
@@ -274,7 +301,15 @@ function createCellRegistry<H extends FactoryHost>(options: CellRegistryOptions<
274
301
  return {
275
302
  handlers: createHandlers(state),
276
303
  setSize: (next) => {
304
+ if (state.size.width === next.width && state.size.height === next.height) {
305
+ return;
306
+ }
307
+
277
308
  state.size = next;
309
+
310
+ for (const host of state.entries.keys()) {
311
+ state.prepare?.(host, next);
312
+ }
278
313
  },
279
314
  subscribe: (onChange) => subscribeRegistry(state, onChange),
280
315
  getEntries: () => getRegistryEntries(state),
@@ -324,15 +359,25 @@ function itemArgs(
324
359
  return args;
325
360
  }
326
361
 
362
+ function keyForItem(ref: SlotRef, item: ListItem): string {
363
+ return joinParts([slotPathAt(ref.store, ref.slot), item.id]);
364
+ }
365
+
327
366
  function slotFor(options: ItemSlotOptions): ItemSlot | null {
328
367
  const ref = slotRefFor(options.item);
329
- const item = ref === null ? undefined : options.collection.itemAt(ref);
368
+
369
+ if (ref === null) {
370
+ return null;
371
+ }
372
+
373
+ const item = options.collection.itemAt(ref);
330
374
 
331
375
  if (item === undefined) {
332
376
  return null;
333
377
  }
334
378
 
335
379
  return {
380
+ itemKey: keyForItem(ref, item),
336
381
  item,
337
382
  row: options.row,
338
383
  args: itemArgs(item, options.position, options.row, isRowWanted(options, item)),
@@ -352,6 +397,7 @@ function useItemSlot(
352
397
  return identity.item === undefined
353
398
  ? null
354
399
  : {
400
+ itemKey: identity.itemKey,
355
401
  item: identity.item,
356
402
  row,
357
403
  args: itemArgs(identity.item, position, row, identity.isExpanded),
@@ -372,15 +418,17 @@ function itemIdentity(
372
418
  const item = ref === null ? undefined : collection.itemAt(ref);
373
419
 
374
420
  return {
421
+ itemKey: ref === null || item === undefined ? "" : keyForItem(ref, item),
375
422
  item,
376
423
  position: entry.host.getPosition(),
377
- isExpanded: item === undefined ? false : (expandedIds?.includes(item.id) ?? false),
424
+ isExpanded: item !== undefined && (expandedIds?.includes(item.id) ?? false),
378
425
  };
379
426
  }
380
427
 
381
428
  function isSameItemIdentity(previous: ItemIdentity, next: ItemIdentity): boolean {
382
429
  return (
383
430
  previous.item === next.item &&
431
+ previous.itemKey === next.itemKey &&
384
432
  previous.position === next.position &&
385
433
  previous.isExpanded === next.isExpanded
386
434
  );
@@ -415,8 +463,30 @@ function createItemIdentityStore(): ItemIdentityStore {
415
463
  };
416
464
  }
417
465
 
466
+ function createNativeItemKeyStore(): NativeItemKeyStore {
467
+ const keys: WeakMap<GObject.Object, string> = new WeakMap();
468
+ let serial = 0;
469
+
470
+ return {
471
+ keyFor: (item) => {
472
+ const current = keys.get(item);
473
+
474
+ if (current !== undefined) {
475
+ return current;
476
+ }
477
+
478
+ serial += 1;
479
+ const key = `gtkx-native-item-${String(serial)}`;
480
+ keys.set(item, key);
481
+
482
+ return key;
483
+ },
484
+ };
485
+ }
486
+
418
487
  function renderedIdentity(entry: CellEntry<PositionedHost>, slot: ItemSlot | null): ItemIdentity {
419
488
  return {
489
+ itemKey: slot?.itemKey ?? "",
420
490
  item: slot?.item,
421
491
  position: slot?.args.index ?? entry.host.getPosition(),
422
492
  isExpanded: slot?.args.isExpanded ?? false,
@@ -506,7 +576,9 @@ function itemBody(
506
576
  const renderItem = render as ListItemRenderer<unknown>;
507
577
  const content = renderItem(slot.args);
508
578
 
509
- return hasExpander ? wrapExpander(slot, content, descriptions) : content;
579
+ return (
580
+ <Fragment key={slot.itemKey}>{hasExpander ? wrapExpander(slot, content, descriptions) : content}</Fragment>
581
+ );
510
582
  }
511
583
 
512
584
  function rowText(value: string | undefined): string | null {
@@ -531,21 +603,17 @@ function rowPropsFor(slot: ItemSlot | null, rowProps: ListRowPropsResolver<never
531
603
  };
532
604
  }
533
605
 
534
- function setRowText(host: Gtk.ColumnViewRow, name: string, value: string | null): void {
535
- setProperty(host, name, ROW_TEXT_DESCRIPTOR, value);
536
- }
537
-
538
606
  function applyRowProps(
539
607
  host: Gtk.ColumnViewRow,
540
608
  props: ResolvedRowProps,
541
609
  previous: ResolvedRowProps | null,
542
610
  ): void {
543
611
  if (previous?.accessibleLabel !== props.accessibleLabel) {
544
- setRowText(host, "accessible-label", props.accessibleLabel);
612
+ host.accessibleLabel = props.accessibleLabel;
545
613
  }
546
614
 
547
615
  if (previous?.accessibleDescription !== props.accessibleDescription) {
548
- setRowText(host, "accessible-description", props.accessibleDescription);
616
+ host.accessibleDescription = props.accessibleDescription;
549
617
  }
550
618
 
551
619
  if (previous?.isActivatable !== props.isActivatable) {
@@ -592,13 +660,52 @@ function ItemRowImpl({ entry, rowProps, state, ...identity }: ItemRowProps): Rea
592
660
 
593
661
  function HeaderCellImpl({ entry, render, collection }: HeaderCellProps): ReactNode {
594
662
  const item = useProperty(entry.host, "item") ?? null;
595
- const ref = slotRefFor(item);
596
- const renderHeader = render as ListSectionRenderer<unknown>;
597
- const body = ref === null ? null : renderHeader({ section: collection.sectionFor(ref.store.level.path) });
663
+
664
+ return createPortal(headerBody(slotRefFor(item), render, collection), entry.host, entry.key);
665
+ }
666
+
667
+ function NativeItemCell<T extends GObject.Object>({
668
+ entry,
669
+ render,
670
+ keys,
671
+ size,
672
+ }: NativeItemCellProps<T>): ReactNode {
673
+ const position = useProperty(entry.host, "position");
674
+ const item = useProperty(entry.host, "item") ?? null;
675
+ let body: ReactNode = null;
676
+
677
+ if (item !== null && position !== undefined) {
678
+ const content = render({ item: item as T, index: position });
679
+ const child = content == null || typeof content === "boolean"
680
+ ? <GtkBox widthRequest={size.width} heightRequest={size.height} />
681
+ : content;
682
+ body = <Fragment key={keys.keyFor(item)}>{child}</Fragment>;
683
+ }
598
684
 
599
685
  return createPortal(body, entry.host, entry.key);
600
686
  }
601
687
 
688
+ function headerBody(
689
+ ref: SlotRef | null,
690
+ render: ListSectionRenderer<never>,
691
+ collection: Collection,
692
+ ): ReactNode {
693
+ if (ref === null) {
694
+ return null;
695
+ }
696
+
697
+ const section = collection.sectionFor(ref.store.level.path);
698
+
699
+ if (section === undefined) {
700
+ return null;
701
+ }
702
+
703
+ const renderHeader = render as ListSectionRenderer<unknown>;
704
+ const key = joinParts([ref.store.level.path, section.id]);
705
+
706
+ return <Fragment key={key}>{renderHeader({ section: section.value })}</Fragment>;
707
+ }
708
+
602
709
  function usePortalEntries<H extends FactoryHost>(registry: CellRegistry<H>): CellEntry<H>[] {
603
710
  return useSyncExternalStore(registry.subscribe, registry.getEntries);
604
711
  }
@@ -647,6 +754,15 @@ const HeaderPortals = ({ registry, render, collection }: HeaderPortalsProps): Re
647
754
  <HeaderCell key={entry.key} entry={entry} render={render} collection={collection} />
648
755
  ));
649
756
 
757
+ function NativeItemPortals<T extends GObject.Object>({ registry, render, size }: NativeItemPortalsProps<T>): ReactNode {
758
+ const entries = usePortalEntries(registry);
759
+ const [keys] = useState(createNativeItemKeyStore);
760
+
761
+ return entries.map((entry) => (
762
+ <NativeItemCell key={entry.key} entry={entry} render={render} keys={keys} size={size} />
763
+ ));
764
+ }
765
+
650
766
  const useSectionHeader = (
651
767
  render: ListSectionRenderer<never> | null | undefined,
652
768
  collection: Collection,
@@ -688,5 +804,6 @@ export {
688
804
  useRowProps,
689
805
  useSectionHeader,
690
806
  ItemPortals,
807
+ NativeItemPortals,
691
808
  type CellSize,
692
809
  };
@@ -7,20 +7,22 @@ type Level = {
7
7
  expandableFlags: boolean[];
8
8
  };
9
9
 
10
+ type SectionIdentity = Pick<ListSection, "id" | "value">;
11
+
10
12
  type CollectionIndex = {
11
13
  isTree: boolean;
12
14
  groups: Level[];
13
15
  childLevel: (level: Level, slot: number) => Level | undefined;
14
16
  levelFor: (levelPath: string) => Level | undefined;
15
17
  itemAt: (levelPath: string, slot: number) => ListItem | undefined;
16
- sectionFor: (levelPath: string) => unknown;
18
+ sectionFor: (levelPath: string) => SectionIdentity | undefined;
17
19
  };
18
20
 
19
21
  type IndexState = {
20
22
  isTree: boolean;
21
23
  groups: Level[];
22
24
  levels: Map<string, Level>;
23
- sectionValues: Map<string, unknown>;
25
+ sections: Map<string, SectionIdentity>;
24
26
  };
25
27
 
26
28
  type ParentItem = ListItem & { children: ListItem[] };
@@ -96,7 +98,7 @@ function buildGroups(state: IndexState, source: ListItem[], sections: ListSectio
96
98
 
97
99
  return sections.map((section, group) => {
98
100
  const path = encodePart(String(group));
99
- state.sectionValues.set(path, section.value);
101
+ state.sections.set(path, { id: section.id, value: section.value });
100
102
 
101
103
  return newLevel(state, path, section.data);
102
104
  });
@@ -125,7 +127,7 @@ function createCollectionIndex(
125
127
  isTree: isTreeSource(source, sections, isFlat),
126
128
  groups: [],
127
129
  levels: new Map(),
128
- sectionValues: new Map(),
130
+ sections: new Map(),
129
131
  };
130
132
 
131
133
  state.groups = buildGroups(state, source, sections);
@@ -136,8 +138,8 @@ function createCollectionIndex(
136
138
  childLevel: (level, slot) => childLevel(state, level, slot),
137
139
  levelFor: (levelPath) => levelFor(state, levelPath),
138
140
  itemAt: (levelPath, slot) => levelFor(state, levelPath)?.items[slot],
139
- sectionFor: (levelPath) => state.sectionValues.get(levelPath),
141
+ sectionFor: (levelPath) => state.sections.get(levelPath),
140
142
  };
141
143
  }
142
144
 
143
- export { createCollectionIndex, type CollectionIndex, type Level };
145
+ export { createCollectionIndex, type CollectionIndex, type Level, type SectionIdentity };