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

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 (51) hide show
  1. package/dist/adw/combo-row.d.ts +11 -0
  2. package/dist/adw/combo-row.d.ts.map +1 -0
  3. package/dist/adw/combo-row.js +14 -0
  4. package/dist/adw/combo-row.js.map +1 -0
  5. package/dist/adw/index.d.ts +2 -1
  6. package/dist/adw/index.d.ts.map +1 -1
  7. package/dist/adw/index.js +1 -0
  8. package/dist/adw/index.js.map +1 -1
  9. package/dist/adw/types.d.ts +9 -2
  10. package/dist/adw/types.d.ts.map +1 -1
  11. package/dist/adw/types.js.map +1 -1
  12. package/dist/column-view.js +2 -2
  13. package/dist/column-view.js.map +1 -1
  14. package/dist/drop-down.d.ts +3 -5
  15. package/dist/drop-down.d.ts.map +1 -1
  16. package/dist/drop-down.js +5 -112
  17. package/dist/drop-down.js.map +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/internal/drop-down.d.ts +16 -0
  22. package/dist/internal/drop-down.d.ts.map +1 -0
  23. package/dist/internal/drop-down.js +110 -0
  24. package/dist/internal/drop-down.js.map +1 -0
  25. package/dist/internal/expansion.d.ts +1 -1
  26. package/dist/internal/expansion.d.ts.map +1 -1
  27. package/dist/internal/expansion.js +3 -4
  28. package/dist/internal/expansion.js.map +1 -1
  29. package/dist/internal/selection.d.ts +1 -0
  30. package/dist/internal/selection.d.ts.map +1 -1
  31. package/dist/internal/selection.js +1 -0
  32. package/dist/internal/selection.js.map +1 -1
  33. package/dist/internal/use-collection.d.ts +1 -2
  34. package/dist/internal/use-collection.d.ts.map +1 -1
  35. package/dist/internal/use-collection.js +9 -17
  36. package/dist/internal/use-collection.js.map +1 -1
  37. package/dist/types.d.ts +7 -9
  38. package/dist/types.d.ts.map +1 -1
  39. package/dist/types.js.map +1 -1
  40. package/package.json +7 -7
  41. package/src/adw/combo-row.tsx +20 -0
  42. package/src/adw/index.ts +8 -1
  43. package/src/adw/types.ts +15 -2
  44. package/src/column-view.tsx +2 -2
  45. package/src/drop-down.tsx +9 -195
  46. package/src/index.ts +0 -1
  47. package/src/internal/drop-down.tsx +186 -0
  48. package/src/internal/expansion.ts +5 -6
  49. package/src/internal/selection.tsx +3 -0
  50. package/src/internal/use-collection.tsx +11 -24
  51. package/src/types.ts +13 -14
package/src/drop-down.tsx CHANGED
@@ -1,205 +1,19 @@
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
+ type DropDownComponent = <T = unknown, S = unknown>(props: DropDownProps<T, S>) => ReactNode;
47
8
 
48
9
  /**
49
- * Renders a drop-down backed by a collection model, with customizable renderers for the
10
+ * Renders a `Gtk.DropDown` backed by a collection model, with customizable renderers for the
50
11
  * collapsed display, popup rows, and popup section headers, and controlled selection.
51
- * The backing widget defaults to GtkDropDown and is swappable through `component`.
52
12
  */
53
13
  const DropDown: DropDownComponent = DropDownImpl as DropDownComponent;
54
14
 
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
- );
15
+ function DropDownImpl(props: DropDownBaseProps): ReactNode {
16
+ return <DropDownBase {...props} component={GtkDropDown} />;
203
17
  }
204
18
 
205
19
  export { DropDown };
package/src/index.ts CHANGED
@@ -18,5 +18,4 @@ export type {
18
18
  Section,
19
19
  SizeGroupChildProps,
20
20
  SizeGroupProps,
21
- WidgetProps,
22
21
  } from "./types.js";
@@ -0,0 +1,186 @@
1
+ import type * as GObject from "@gtkx/gi/gobject";
2
+ import type { ElementType, ReactNode, Ref } from "react";
3
+ import { GtkLabel, GtkSignalListItemFactory } from "@gtkx/jsx/gtk";
4
+ import { applyWrite } from "@gtkx/react/internal";
5
+ import { omit } from "@gtkx/utils";
6
+ import { useEffectEvent, useLayoutEffect, useRef } from "react";
7
+ import type { DropDownOwnProps, ItemRenderer, RenderItemArgs } from "../types.js";
8
+ import type { Collection } from "./collection.js";
9
+ import { HeaderPortals, ItemPortals, useHeaderCells, useItemCells } from "./cells.js";
10
+ import { useCollectionData } from "./use-collection.js";
11
+ import { useWidgetRef } from "./use-widget-ref.js";
12
+
13
+ type SelectableWidget = GObject.Object & { getSelected: () => number; selected: number };
14
+
15
+ type DropDownBaseProps = DropDownOwnProps<unknown, unknown> & {
16
+ onNotifySelected?: ((value: number | null, self: SelectableWidget) => void) | null | undefined;
17
+ ref?: Ref<SelectableWidget | null> | undefined;
18
+ } & Record<string, unknown>;
19
+
20
+ type SelectionOptions = {
21
+ widget: SelectableWidget | null;
22
+ collection: Collection;
23
+ props: DropDownBaseProps;
24
+ };
25
+
26
+ type NotifySelectedHandler = NonNullable<DropDownBaseProps["onNotifySelected"]>;
27
+
28
+ type KnownSelection = {
29
+ known: { current: string | null };
30
+ onSelectionChanged: ((id: string) => void) | null | undefined;
31
+ };
32
+
33
+ const DROP_DOWN_PROPS: string[] = [
34
+ "component",
35
+ "items",
36
+ "sections",
37
+ "selectedId",
38
+ "onSelectionChanged",
39
+ "onNotifySelected",
40
+ "renderItem",
41
+ "renderListItem",
42
+ "renderHeader",
43
+ "ref",
44
+ ];
45
+
46
+ const describeValue = (value: unknown): string => {
47
+ if (typeof value === "string") {
48
+ return value;
49
+ }
50
+
51
+ try {
52
+ return JSON.stringify(value);
53
+ } catch {
54
+ return String(value);
55
+ }
56
+ };
57
+
58
+ const defaultItemContent = (value: unknown): ReactNode =>
59
+ value == null ? null : <GtkLabel>{describeValue(value)}</GtkLabel>;
60
+
61
+ const defaultRenderItem = ({ item }: RenderItemArgs<unknown>): ReactNode => defaultItemContent(item);
62
+ const faceRenderer = (props: DropDownBaseProps): ItemRenderer<never> => props.renderItem ?? defaultRenderItem;
63
+
64
+ const listRenderer = (props: DropDownBaseProps): ItemRenderer<never> =>
65
+ props.renderListItem ?? props.renderItem ?? defaultRenderItem;
66
+
67
+ const resolvePosition = (
68
+ widget: SelectableWidget,
69
+ collection: Collection,
70
+ selectedId: string | null | undefined,
71
+ ): number => {
72
+ const requested = selectedId == null ? -1 : collection.positionFor(selectedId);
73
+
74
+ return requested >= 0 ? requested : widget.getSelected();
75
+ };
76
+
77
+ const updateKnownSelection = (
78
+ tracker: KnownSelection,
79
+ effectiveId: string,
80
+ selectedId: string | null | undefined,
81
+ ): void => {
82
+ const { known, onSelectionChanged } = tracker;
83
+ const expectedId = selectedId ?? known.current;
84
+ const isNew = effectiveId !== known.current;
85
+ known.current = effectiveId;
86
+
87
+ if (expectedId !== null && effectiveId !== expectedId && isNew) {
88
+ onSelectionChanged?.(effectiveId);
89
+ }
90
+ };
91
+
92
+ const reportKnownSelection = (tracker: KnownSelection, id: string | null): void => {
93
+ const { known, onSelectionChanged } = tracker;
94
+
95
+ if (id === null || id === known.current) {
96
+ return;
97
+ }
98
+
99
+ known.current = id;
100
+ onSelectionChanged?.(id);
101
+ };
102
+
103
+ const reportSelectedNotify = (options: SelectionOptions, tracker: KnownSelection): void => {
104
+ const { widget, collection } = options;
105
+
106
+ if (widget === null) {
107
+ return;
108
+ }
109
+
110
+ reportKnownSelection(tracker, collection.idAt(widget.getSelected()));
111
+ };
112
+
113
+ const applySelectedPosition = (widget: SelectableWidget, position: number): void => {
114
+ applyWrite(() => {
115
+ widget.selected = position;
116
+ });
117
+ };
118
+
119
+ const useDropDownSelection = (options: SelectionOptions): NotifySelectedHandler => {
120
+ const { widget, collection } = options;
121
+ const { selectedId } = options.props;
122
+ const known = useRef<string | null>(null);
123
+
124
+ const syncKnownSelection = useEffectEvent((position: number): void => {
125
+ const effectiveId = collection.idAt(position);
126
+
127
+ if (effectiveId === null) {
128
+ known.current = null;
129
+
130
+ return;
131
+ }
132
+
133
+ const { onSelectionChanged } = options.props;
134
+ updateKnownSelection({ known, onSelectionChanged }, effectiveId, selectedId);
135
+ });
136
+
137
+ useLayoutEffect(() => {
138
+ if (widget === null) {
139
+ return;
140
+ }
141
+
142
+ const position = resolvePosition(widget, collection, selectedId);
143
+ applySelectedPosition(widget, position);
144
+ syncKnownSelection(position);
145
+ }, [widget, collection, selectedId]);
146
+
147
+ return (value, self) => {
148
+ reportSelectedNotify(options, { known, onSelectionChanged: options.props.onSelectionChanged });
149
+ options.props.onNotifySelected?.(value, self);
150
+ };
151
+ };
152
+
153
+ function DropDownBase(props: DropDownBaseProps & { component: ElementType }): ReactNode {
154
+ const { component: Component, items, sections, renderListItem, renderHeader, ref } = props;
155
+ const rest = omit(props, DROP_DOWN_PROPS);
156
+ const [widget, refCallback] = useWidgetRef<SelectableWidget>(ref);
157
+ const collection = useCollectionData({ items, sections });
158
+ const faceCells = useItemCells({ width: -1, height: -1 });
159
+ const listCells = useItemCells({ width: -1, height: -1 });
160
+ const headerCells = useHeaderCells({ width: -1, height: -1 });
161
+ const handleNotifySelected = useDropDownSelection({ widget, collection, props });
162
+ const hasHeader = typeof renderHeader === "function";
163
+
164
+ return (
165
+ <>
166
+ <Component
167
+ ref={refCallback}
168
+ model={collection.model}
169
+ factory={<GtkSignalListItemFactory {...faceCells.handlers} />}
170
+ {...(renderListItem != null && {
171
+ listFactory: <GtkSignalListItemFactory {...listCells.handlers} />,
172
+ })}
173
+ {...(hasHeader && { headerFactory: <GtkSignalListItemFactory {...headerCells.handlers} /> })}
174
+ {...rest}
175
+ onNotifySelected={handleNotifySelected}
176
+ />
177
+ <ItemPortals store={faceCells} render={faceRenderer(props)} collection={collection} />
178
+ {renderListItem != null && (
179
+ <ItemPortals store={listCells} render={listRenderer(props)} collection={collection} />
180
+ )}
181
+ {hasHeader && <HeaderPortals store={headerCells} render={renderHeader} collection={collection} />}
182
+ </>
183
+ );
184
+ }
185
+
186
+ export { DropDownBase, type DropDownBaseProps };
@@ -1,6 +1,5 @@
1
1
  import type * as Gtk from "@gtkx/gi/gtk";
2
2
  import type { RefObject } from "react";
3
- import { useSignal } from "@gtkx/react";
4
3
  import { useEffectEvent, useLayoutEffect, useRef, useState } from "react";
5
4
  import type { Collection } from "./collection.js";
6
5
  import { eachRow } from "./collection.js";
@@ -102,7 +101,7 @@ function runControlledExpansion(
102
101
  report();
103
102
  }
104
103
 
105
- function useExpansion(options: ExpansionOptions): void {
104
+ function useExpansion(options: ExpansionOptions): () => void {
106
105
  const { collection, expandedIds, onExpandedChange } = options;
107
106
  const [last] = useState<LastExpansion>(newLastExpansion);
108
107
  const expanding = useRef(false);
@@ -111,13 +110,13 @@ function useExpansion(options: ExpansionOptions): void {
111
110
  reportWhenIdle(collection, last, expanding, onExpandedChange);
112
111
  });
113
112
 
114
- useSignal(collection.isTree ? collection.model : null, "items-changed", () => {
115
- reportWhenIdle(collection, last, expanding, onExpandedChange);
116
- });
117
-
118
113
  useLayoutEffect(() => {
119
114
  runControlledExpansion(collection, expandedIds, expanding, report);
120
115
  }, [collection, expandedIds]);
116
+
117
+ return () => {
118
+ reportWhenIdle(collection, last, expanding, onExpandedChange);
119
+ };
121
120
  }
122
121
 
123
122
  export { useExpansion, type ExpansionOptions };
@@ -10,6 +10,7 @@ type SelectionOptions = {
10
10
  selectedIds?: string[] | null | undefined;
11
11
  onSelectionChanged?: ((ids: string[]) => void) | null | undefined;
12
12
  selectionMode?: Gtk.SelectionMode | null | undefined;
13
+ onItemsChanged: () => void;
13
14
  };
14
15
 
15
16
  type LastSelection = {
@@ -21,6 +22,7 @@ type SelectionElementProps = {
21
22
  ref: (value: Gtk.SelectionModel | null) => void;
22
23
  model: Gio.ListModel;
23
24
  onSelectionChanged: () => void;
25
+ onItemsChanged: () => void;
24
26
  };
25
27
 
26
28
  function getSelectedIds(selection: Gtk.SelectionModel, collection: Collection): string[] {
@@ -153,6 +155,7 @@ function useSelection(options: SelectionOptions): ReactElement {
153
155
  onSelectionChanged: () => {
154
156
  reportSelection(selection, collection, last, onSelectionChanged);
155
157
  },
158
+ onItemsChanged: options.onItemsChanged,
156
159
  });
157
160
  }
158
161
 
@@ -1,8 +1,6 @@
1
- import type { ReactElement, RefObject } from "react";
1
+ import type { ReactElement } from "react";
2
2
  import { useLayoutEffect, useMemo, useState } from "react";
3
3
  import type { ExpansionProps, Item, Section, SelectionProps } from "../types.js";
4
- import type { CollectionIndex } from "./collection-index.js";
5
- import type { CollectionModel } from "./collection-model.js";
6
4
  import type { Collection } from "./collection.js";
7
5
  import { createCollectionIndex } from "./collection-index.js";
8
6
  import { createCollectionModel } from "./collection-model.js";
@@ -14,7 +12,6 @@ type CollectionDataOptions = {
14
12
  items?: Item[] | undefined;
15
13
  sections?: Section[] | undefined;
16
14
  flat?: boolean | undefined;
17
- applying?: RefObject<boolean> | undefined;
18
15
  };
19
16
 
20
17
  type CollectionOptions = CollectionDataOptions & SelectionProps & ExpansionProps;
@@ -24,44 +21,34 @@ type CollectionResult = {
24
21
  selection: ReactElement;
25
22
  };
26
23
 
27
- function runSync(gtk: CollectionModel, index: CollectionIndex, applying: RefObject<boolean> | undefined): void {
28
- if (applying === undefined) {
29
- gtk.sync(index);
30
-
31
- return;
32
- }
33
-
34
- applying.current = true;
35
-
36
- try {
37
- gtk.sync(index);
38
- } finally {
39
- applying.current = false;
40
- }
41
- }
42
-
43
24
  function useCollectionData(options: CollectionDataOptions): Collection {
44
- const { items, sections, flat, applying } = options;
25
+ const { items, sections, flat } = options;
45
26
  const [gtk] = useState(createCollectionModel);
46
27
  const index = useMemo(() => createCollectionIndex(items, sections, flat === true), [items, sections, flat]);
47
28
  const collection = useMemo(() => createCollection(gtk, index), [gtk, index]);
48
29
 
49
30
  useLayoutEffect(() => {
50
- runSync(gtk, index, applying);
51
- }, [gtk, index, applying]);
31
+ gtk.sync(index);
32
+ }, [gtk, index]);
52
33
 
53
34
  return collection;
54
35
  }
55
36
 
56
37
  function useCollection(options: CollectionOptions): CollectionResult {
57
38
  const collection = useCollectionData(options);
58
- useExpansion({ collection, expandedIds: options.expandedIds, onExpandedChange: options.onExpandedChange });
39
+
40
+ const onItemsChanged = useExpansion({
41
+ collection,
42
+ expandedIds: options.expandedIds,
43
+ onExpandedChange: options.onExpandedChange,
44
+ });
59
45
 
60
46
  const selection = useSelection({
61
47
  collection,
62
48
  selectedIds: options.selectedIds,
63
49
  onSelectionChanged: options.onSelectionChanged,
64
50
  selectionMode: options.selectionMode,
51
+ onItemsChanged,
65
52
  });
66
53
 
67
54
  return { collection, selection };
package/src/types.ts CHANGED
@@ -2,7 +2,7 @@ import type * as Gtk from "@gtkx/gi/gtk";
2
2
  import type {
3
3
  GtkColumnViewColumnProps,
4
4
  GtkColumnViewProps,
5
- GtkDropDown,
5
+ GtkDropDownProps,
6
6
  GtkGridViewProps,
7
7
  GtkListViewProps,
8
8
  } from "@gtkx/jsx/gtk";
@@ -13,11 +13,6 @@ type ChildProps<C extends ElementType> = {
13
13
  component: C;
14
14
  } & ComponentPropsWithRef<C>;
15
15
 
16
- /** Props of a component whose backing widget defaults to one type but can be swapped through `component`. */
17
- type WidgetProps<C extends ElementType, Own = unknown, ExtraOmit extends string = never> = Own & {
18
- component?: C;
19
- } & Omit<ComponentPropsWithRef<C>, ExtraOmit | keyof Own>;
20
-
21
16
  /**
22
17
  * A single item in a collection model, identified by a stable id and holding an
23
18
  * arbitrary value. Nested items form a tree.
@@ -131,15 +126,18 @@ type DropDownOwnProps<T, S> = SourceProps<T, S> & {
131
126
  renderHeader?: HeaderRenderer<S> | null | undefined;
132
127
  };
133
128
 
129
+ type DropDownWidgetProps<Widget, T, S> = Omit<
130
+ Widget,
131
+ "model" | "factory" | "listFactory" | "headerFactory" | keyof DropDownOwnProps<T, S>
132
+ > &
133
+ DropDownOwnProps<T, S>;
134
+
134
135
  /**
135
- * Props for {@link DropDown}. The backing widget is chosen through the `component` prop, defaulting to
136
- * GtkDropDown, and its own props combine with the declarative collection props.
136
+ * Props for {@link DropDown}. Combines the underlying Gtk.DropDown props with the declarative
137
+ * collection props: flat items or grouped sections, controlled single selection, and renderers
138
+ * for the collapsed display, popup rows, and popup section headers.
137
139
  */
138
- type DropDownProps<T = unknown, S = unknown, C extends ElementType = typeof GtkDropDown> = WidgetProps<
139
- C,
140
- DropDownOwnProps<T, S>,
141
- "model" | "factory" | "listFactory" | "headerFactory"
142
- >;
140
+ type DropDownProps<T = unknown, S = unknown> = DropDownWidgetProps<GtkDropDownProps, T, S>;
143
141
 
144
142
  type GridViewOwnProps<T> = ItemSizeProps &
145
143
  SelectionProps & {
@@ -190,7 +188,8 @@ export {
190
188
  type SelectionProps,
191
189
  type ExpansionProps,
192
190
  type ChildProps,
193
- type WidgetProps,
191
+ type DropDownOwnProps,
192
+ type DropDownWidgetProps,
194
193
  type Item,
195
194
  type Section,
196
195
  type RenderItemArgs,