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

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 (210) hide show
  1. package/dist/adw/index.d.ts +3 -0
  2. package/dist/adw/index.d.ts.map +1 -0
  3. package/dist/adw/index.js +2 -0
  4. package/dist/adw/index.js.map +1 -0
  5. package/dist/adw/toast.d.ts +10 -0
  6. package/dist/adw/toast.d.ts.map +1 -0
  7. package/dist/adw/toast.js +51 -0
  8. package/dist/adw/toast.js.map +1 -0
  9. package/dist/adw/types.d.ts +28 -0
  10. package/dist/adw/types.d.ts.map +1 -0
  11. package/dist/adw/types.js +2 -0
  12. package/dist/adw/types.js.map +1 -0
  13. package/dist/column-view.d.ts +7 -29
  14. package/dist/column-view.d.ts.map +1 -1
  15. package/dist/column-view.js +98 -70
  16. package/dist/column-view.js.map +1 -1
  17. package/dist/drop-down.d.ts +8 -27
  18. package/dist/drop-down.d.ts.map +1 -1
  19. package/dist/drop-down.js +112 -81
  20. package/dist/drop-down.js.map +1 -1
  21. package/dist/grid-view.d.ts +6 -16
  22. package/dist/grid-view.d.ts.map +1 -1
  23. package/dist/grid-view.js +25 -33
  24. package/dist/grid-view.js.map +1 -1
  25. package/dist/index.d.ts +6 -12
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +1 -6
  28. package/dist/index.js.map +1 -1
  29. package/dist/internal/cells.d.ts +50 -0
  30. package/dist/internal/cells.d.ts.map +1 -0
  31. package/dist/internal/cells.js +185 -0
  32. package/dist/internal/cells.js.map +1 -0
  33. package/dist/internal/collection-index.d.ts +21 -0
  34. package/dist/internal/collection-index.d.ts.map +1 -0
  35. package/dist/internal/collection-index.js +106 -0
  36. package/dist/internal/collection-index.js.map +1 -0
  37. package/dist/internal/collection-model.d.ts +12 -0
  38. package/dist/internal/collection-model.d.ts.map +1 -0
  39. package/dist/internal/collection-model.js +164 -0
  40. package/dist/internal/collection-model.js.map +1 -0
  41. package/dist/internal/collection.d.ts +18 -0
  42. package/dist/internal/collection.d.ts.map +1 -0
  43. package/dist/internal/collection.js +64 -0
  44. package/dist/internal/collection.js.map +1 -0
  45. package/dist/internal/expansion.d.ts +9 -0
  46. package/dist/internal/expansion.d.ts.map +1 -0
  47. package/dist/internal/expansion.js +75 -0
  48. package/dist/internal/expansion.js.map +1 -0
  49. package/dist/internal/selection.d.ts +12 -0
  50. package/dist/internal/selection.d.ts.map +1 -0
  51. package/dist/internal/selection.js +99 -0
  52. package/dist/internal/selection.js.map +1 -0
  53. package/dist/internal/use-collection.d.ts +18 -0
  54. package/dist/internal/use-collection.d.ts.map +1 -0
  55. package/dist/internal/use-collection.js +42 -0
  56. package/dist/internal/use-collection.js.map +1 -0
  57. package/dist/internal/use-widget-ref.d.ts +4 -0
  58. package/dist/internal/use-widget-ref.d.ts.map +1 -0
  59. package/dist/internal/use-widget-ref.js +8 -0
  60. package/dist/internal/use-widget-ref.js.map +1 -0
  61. package/dist/list-view.d.ts +5 -20
  62. package/dist/list-view.d.ts.map +1 -1
  63. package/dist/list-view.js +32 -53
  64. package/dist/list-view.js.map +1 -1
  65. package/dist/size-group.d.ts +8 -16
  66. package/dist/size-group.d.ts.map +1 -1
  67. package/dist/size-group.js +35 -51
  68. package/dist/size-group.js.map +1 -1
  69. package/dist/types.d.ts +97 -24
  70. package/dist/types.d.ts.map +1 -1
  71. package/dist/types.js.map +1 -1
  72. package/package.json +17 -6
  73. package/src/adw/index.ts +2 -0
  74. package/src/adw/toast.tsx +74 -0
  75. package/src/adw/types.ts +32 -0
  76. package/src/column-view.tsx +176 -160
  77. package/src/drop-down.tsx +180 -167
  78. package/src/grid-view.tsx +29 -73
  79. package/src/index.ts +18 -25
  80. package/src/internal/cells.tsx +345 -0
  81. package/src/internal/collection-index.ts +168 -0
  82. package/src/internal/collection-model.ts +246 -0
  83. package/src/internal/collection.ts +104 -0
  84. package/src/internal/expansion.ts +123 -0
  85. package/src/internal/selection.tsx +159 -0
  86. package/src/internal/use-collection.tsx +70 -0
  87. package/src/internal/use-widget-ref.ts +11 -0
  88. package/src/list-view.tsx +43 -139
  89. package/src/size-group.tsx +63 -82
  90. package/src/types.ts +155 -24
  91. package/dist/cell.d.ts +0 -30
  92. package/dist/cell.d.ts.map +0 -1
  93. package/dist/cell.js +0 -45
  94. package/dist/cell.js.map +0 -1
  95. package/dist/column-view-column.d.ts +0 -27
  96. package/dist/column-view-column.d.ts.map +0 -1
  97. package/dist/column-view-column.js +0 -30
  98. package/dist/column-view-column.js.map +0 -1
  99. package/dist/constraint-layout-apply.d.ts +0 -55
  100. package/dist/constraint-layout-apply.d.ts.map +0 -1
  101. package/dist/constraint-layout-apply.js +0 -107
  102. package/dist/constraint-layout-apply.js.map +0 -1
  103. package/dist/constraint-layout.d.ts +0 -20
  104. package/dist/constraint-layout.d.ts.map +0 -1
  105. package/dist/constraint-layout.js +0 -88
  106. package/dist/constraint-layout.js.map +0 -1
  107. package/dist/fixed.d.ts +0 -26
  108. package/dist/fixed.d.ts.map +0 -1
  109. package/dist/fixed.js +0 -40
  110. package/dist/fixed.js.map +0 -1
  111. package/dist/grid.d.ts +0 -27
  112. package/dist/grid.d.ts.map +0 -1
  113. package/dist/grid.js +0 -48
  114. package/dist/grid.js.map +0 -1
  115. package/dist/hooks/use-cell-containers.d.ts +0 -18
  116. package/dist/hooks/use-cell-containers.d.ts.map +0 -1
  117. package/dist/hooks/use-cell-containers.js +0 -67
  118. package/dist/hooks/use-cell-containers.js.map +0 -1
  119. package/dist/hooks/use-collection-model.d.ts +0 -26
  120. package/dist/hooks/use-collection-model.d.ts.map +0 -1
  121. package/dist/hooks/use-collection-model.js +0 -35
  122. package/dist/hooks/use-collection-model.js.map +0 -1
  123. package/dist/hooks/use-collection-widget.d.ts +0 -28
  124. package/dist/hooks/use-collection-widget.d.ts.map +0 -1
  125. package/dist/hooks/use-collection-widget.js +0 -21
  126. package/dist/hooks/use-collection-widget.js.map +0 -1
  127. package/dist/hooks/use-drop-down-selection.d.ts +0 -16
  128. package/dist/hooks/use-drop-down-selection.d.ts.map +0 -1
  129. package/dist/hooks/use-drop-down-selection.js +0 -35
  130. package/dist/hooks/use-drop-down-selection.js.map +0 -1
  131. package/dist/hooks/use-expansion-model.d.ts +0 -12
  132. package/dist/hooks/use-expansion-model.d.ts.map +0 -1
  133. package/dist/hooks/use-expansion-model.js +0 -71
  134. package/dist/hooks/use-expansion-model.js.map +0 -1
  135. package/dist/hooks/use-header-menu.d.ts +0 -4
  136. package/dist/hooks/use-header-menu.d.ts.map +0 -1
  137. package/dist/hooks/use-header-menu.js +0 -20
  138. package/dist/hooks/use-header-menu.js.map +0 -1
  139. package/dist/hooks/use-installed-model.d.ts +0 -4
  140. package/dist/hooks/use-installed-model.d.ts.map +0 -1
  141. package/dist/hooks/use-installed-model.js +0 -15
  142. package/dist/hooks/use-installed-model.js.map +0 -1
  143. package/dist/hooks/use-list-model.d.ts +0 -19
  144. package/dist/hooks/use-list-model.d.ts.map +0 -1
  145. package/dist/hooks/use-list-model.js +0 -97
  146. package/dist/hooks/use-list-model.js.map +0 -1
  147. package/dist/hooks/use-placed-child.d.ts +0 -18
  148. package/dist/hooks/use-placed-child.d.ts.map +0 -1
  149. package/dist/hooks/use-placed-child.js +0 -47
  150. package/dist/hooks/use-placed-child.js.map +0 -1
  151. package/dist/hooks/use-selection-model.d.ts +0 -13
  152. package/dist/hooks/use-selection-model.d.ts.map +0 -1
  153. package/dist/hooks/use-selection-model.js +0 -103
  154. package/dist/hooks/use-selection-model.js.map +0 -1
  155. package/dist/hooks/use-sort-handler.d.ts +0 -17
  156. package/dist/hooks/use-sort-handler.d.ts.map +0 -1
  157. package/dist/hooks/use-sort-handler.js +0 -87
  158. package/dist/hooks/use-sort-handler.js.map +0 -1
  159. package/dist/menu-items.d.ts +0 -5
  160. package/dist/menu-items.d.ts.map +0 -1
  161. package/dist/menu-items.js +0 -48
  162. package/dist/menu-items.js.map +0 -1
  163. package/dist/menu.d.ts +0 -14
  164. package/dist/menu.d.ts.map +0 -1
  165. package/dist/menu.js +0 -24
  166. package/dist/menu.js.map +0 -1
  167. package/dist/overlay.d.ts +0 -24
  168. package/dist/overlay.d.ts.map +0 -1
  169. package/dist/overlay.js +0 -49
  170. package/dist/overlay.js.map +0 -1
  171. package/dist/utils/cell-container-store.d.ts +0 -28
  172. package/dist/utils/cell-container-store.d.ts.map +0 -1
  173. package/dist/utils/cell-container-store.js +0 -98
  174. package/dist/utils/cell-container-store.js.map +0 -1
  175. package/dist/utils/item-models.d.ts +0 -10
  176. package/dist/utils/item-models.d.ts.map +0 -1
  177. package/dist/utils/item-models.js +0 -82
  178. package/dist/utils/item-models.js.map +0 -1
  179. package/dist/utils/item-resolver.d.ts +0 -25
  180. package/dist/utils/item-resolver.d.ts.map +0 -1
  181. package/dist/utils/item-resolver.js +0 -102
  182. package/dist/utils/item-resolver.js.map +0 -1
  183. package/dist/utils/list-item-flatten.d.ts +0 -24
  184. package/dist/utils/list-item-flatten.d.ts.map +0 -1
  185. package/dist/utils/list-item-flatten.js +0 -68
  186. package/dist/utils/list-item-flatten.js.map +0 -1
  187. package/src/cell.tsx +0 -144
  188. package/src/column-view-column.tsx +0 -82
  189. package/src/constraint-layout-apply.ts +0 -173
  190. package/src/constraint-layout.tsx +0 -135
  191. package/src/fixed.tsx +0 -86
  192. package/src/grid.tsx +0 -88
  193. package/src/hooks/use-cell-containers.ts +0 -93
  194. package/src/hooks/use-collection-model.ts +0 -60
  195. package/src/hooks/use-collection-widget.ts +0 -47
  196. package/src/hooks/use-drop-down-selection.ts +0 -52
  197. package/src/hooks/use-expansion-model.ts +0 -82
  198. package/src/hooks/use-header-menu.ts +0 -34
  199. package/src/hooks/use-installed-model.ts +0 -20
  200. package/src/hooks/use-list-model.ts +0 -161
  201. package/src/hooks/use-placed-child.ts +0 -79
  202. package/src/hooks/use-selection-model.ts +0 -114
  203. package/src/hooks/use-sort-handler.ts +0 -107
  204. package/src/menu-items.ts +0 -48
  205. package/src/menu.tsx +0 -32
  206. package/src/overlay.tsx +0 -88
  207. package/src/utils/cell-container-store.ts +0 -114
  208. package/src/utils/item-models.ts +0 -113
  209. package/src/utils/item-resolver.ts +0 -138
  210. package/src/utils/list-item-flatten.ts +0 -90
package/src/drop-down.tsx CHANGED
@@ -1,192 +1,205 @@
1
- import type * as Gtk from "@gtkx/gi/gtk";
2
- import { GtkDropDown, GtkLabel } from "@gtkx/jsx/gtk";
3
- import { useMergeRefs } from "@gtkx/react/internal";
4
- import { type ElementType, type ReactNode, type Ref, useCallback, useRef, useState } from "react";
5
- import { type CellRenderer, CellRenderHost, HeaderRenderHost, itemRenderer } from "./cell.js";
6
- import { makeFactoryInstaller, useCellContainers } from "./hooks/use-cell-containers.js";
7
- import { useDropDownSelection } from "./hooks/use-drop-down-selection.js";
8
- import { useInstalledModel } from "./hooks/use-installed-model.js";
9
- import { useListModel } from "./hooks/use-list-model.js";
10
- import type { ItemNode, RenderItemProps, SectionNode, WidgetProps } from "./types.js";
11
- import type { CellContainerStore } from "./utils/cell-container-store.js";
12
- import type { ItemResolver } from "./utils/item-resolver.js";
13
-
14
- export type DropDownItemRenderer<T> = (props: RenderItemProps<T>) => ReactNode;
15
-
16
- const itemFactoryInstaller = makeFactoryInstaller<Gtk.DropDown>((widget, factory) => widget.setFactory(factory));
17
-
18
- const listFactoryInstaller = makeFactoryInstaller<Gtk.DropDown>((widget, factory) => widget.setListFactory(factory));
19
-
20
- const headerFactoryInstaller = makeFactoryInstaller<Gtk.DropDown>((widget, factory) =>
21
- widget.setHeaderFactory(factory),
22
- );
23
-
24
- const defaultRenderer: CellRenderer<unknown, unknown> = (value) => {
25
- if (value === undefined || value === null) return null;
26
- return <GtkLabel>{String(value)}</GtkLabel>;
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";
12
+
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;
27
25
  };
28
26
 
29
- const toItemRenderer = <T, S>(renderItem: DropDownItemRenderer<T> | null | undefined): CellRenderer<T, S> => {
30
- if (typeof renderItem !== "function") return defaultRenderer as CellRenderer<T, S>;
31
- return itemRenderer<T, S>(renderItem);
27
+ type KnownSelection = {
28
+ known: { current: string | null };
29
+ onSelectionChanged: ((id: string) => void) | null | undefined;
32
30
  };
33
31
 
34
- const toListRenderer = <T, S>(
35
- renderListItem: DropDownItemRenderer<T> | null | undefined,
36
- renderItem: DropDownItemRenderer<T> | null | undefined,
37
- ): CellRenderer<T, S> => {
38
- if (typeof renderListItem === "function") return itemRenderer<T, S>(renderListItem);
39
- return toItemRenderer<T, S>(renderItem);
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
+ ];
47
+
48
+ /**
49
+ * Renders a drop-down backed by a collection model, with customizable renderers for the
50
+ * collapsed display, popup rows, and popup section headers, and controlled selection.
51
+ * The backing widget defaults to GtkDropDown and is swappable through `component`.
52
+ */
53
+ const DropDown: DropDownComponent = DropDownImpl as DropDownComponent;
54
+
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
+ }
40
65
  };
41
66
 
42
- const createSelectionResolver = <T, S>(resolver: ItemResolver<T, S>, selectedPosition: number): ItemResolver<T, S> => ({
43
- positionOfId: (id) => resolver.positionOfId(id),
44
- idOf: (position) => resolver.idOf(position),
45
- resolve: (_position, treeRow) => resolver.resolve(selectedPosition, treeRow),
46
- });
47
-
48
- /** Declarative props for {@link DropDown}'s backing collection and cell rendering. */
49
- export type DropDownDeclarativeProps<T = unknown, S = unknown> = {
50
- items?: ItemNode<T>[] | undefined;
51
- sections?: SectionNode<S, T>[] | undefined;
52
- /** Id of the currently selected item, making the selection controlled. */
53
- selectedId?: string | null | undefined;
54
- onSelectionChanged?: ((id: string) => void) | null | undefined;
55
- renderItem?: DropDownItemRenderer<T> | null | undefined;
56
- /** Renderer for items in the open popup list, falling back to renderItem when omitted. */
57
- renderListItem?: DropDownItemRenderer<T> | null | undefined;
58
- /** Renderer for section headers in the popup list. */
59
- renderHeader?: ((info: { section: S }) => ReactNode) | null | undefined;
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();
60
86
  };
61
87
 
62
- /**
63
- * Props for {@link DropDown}. The backing widget is chosen through the `component` prop, defaulting to
64
- * GtkDropDown, and its own props combine with {@link DropDownDeclarativeProps}.
65
- */
66
- export type DropDownProps<T = unknown, S = unknown, C extends ElementType = typeof GtkDropDown> = WidgetProps<
67
- C,
68
- DropDownDeclarativeProps<T, S>,
69
- "model" | "factory" | "listFactory" | "headerFactory"
70
- >;
71
-
72
- interface NormalizedDropDownProps<T, S> {
73
- ref: Ref<Gtk.DropDown | null> | undefined;
74
- items: ItemNode<T>[] | undefined;
75
- sections: SectionNode<S, T>[] | undefined;
76
- selectedId: string | null | undefined;
77
- onSelectionChanged: ((id: string) => void) | null | undefined;
78
- renderHeader: ((info: { section: S }) => ReactNode) | null | undefined;
79
- }
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
+ };
80
102
 
81
- interface DropDownWiring<T, S> {
82
- setRef: (value: Gtk.DropDown | null) => void;
83
- resolver: ItemResolver<T, S>;
84
- selectionResolver: ItemResolver<T, S>;
85
- headerResolver: ItemResolver<T, S>;
86
- selectionStore: CellContainerStore;
87
- listStore: CellContainerStore;
88
- headerStore: CellContainerStore;
89
- useHeader: boolean;
90
- }
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
+ }
91
109
 
92
- const useDropDownWiring = <T, S>(props: NormalizedDropDownProps<T, S>): DropDownWiring<T, S> => {
93
- const widgetRef = useRef<Gtk.DropDown | null>(null);
94
- const [widget, setWidget] = useState<Gtk.DropDown | null>(null);
95
- const captureWidget = useCallback((value: Gtk.DropDown | null) => {
96
- widgetRef.current = value;
97
- setWidget(value);
98
- }, []);
99
- const setRef = useMergeRefs<Gtk.DropDown>(props.ref, captureWidget);
110
+ known.current = id;
111
+ onSelectionChanged?.(id);
112
+ };
100
113
 
101
- const listModel = useListModel<T, S>({ items: props.items, sections: props.sections });
114
+ const reportSelectedNotify = (
115
+ options: SelectionOptions,
116
+ tracker: KnownSelection,
117
+ applying: RefObject<boolean>,
118
+ ): void => {
119
+ const { widget, collection } = options;
102
120
 
103
- const useHeader = typeof props.renderHeader === "function";
121
+ if (widget === null || applying.current) {
122
+ return;
123
+ }
104
124
 
105
- const selectionStore = useCellContainers<Gtk.DropDown>({ object: widgetRef, installer: itemFactoryInstaller });
106
- const listStore = useCellContainers<Gtk.DropDown>({ object: widgetRef, installer: listFactoryInstaller });
107
- const headerStore = useCellContainers<Gtk.DropDown>({
108
- object: useHeader ? widgetRef : null,
109
- installer: headerFactoryInstaller,
110
- });
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);
111
142
 
112
- useInstalledModel(widgetRef, listModel.model, (widget, value) => widget.setModel(value));
143
+ const syncKnownSelection = useEffectEvent((position: number): void => {
144
+ const effectiveId = collection.idAt(position);
113
145
 
114
- const selectedPosition = useDropDownSelection<T, S>({
115
- widget,
116
- resolver: listModel.resolver,
117
- selectedId: props.selectedId,
118
- onSelectionChanged: props.onSelectionChanged,
146
+ if (effectiveId === null) {
147
+ known.current = null;
148
+
149
+ return;
150
+ }
151
+
152
+ const { onSelectionChanged } = options.props;
153
+ updateKnownSelection({ known, onSelectionChanged }, effectiveId, selectedId);
119
154
  });
120
155
 
121
- const controlledPosition =
122
- props.selectedId === undefined || props.selectedId === null
123
- ? -1
124
- : listModel.resolver.positionOfId(props.selectedId);
125
- const selectionPosition =
126
- controlledPosition >= 0 ? controlledPosition : selectedPosition < 0 ? 0 : selectedPosition;
127
-
128
- return {
129
- setRef,
130
- resolver: listModel.resolver,
131
- selectionResolver: createSelectionResolver(listModel.resolver, selectionPosition),
132
- headerResolver: listModel.headerResolver,
133
- selectionStore,
134
- listStore,
135
- headerStore,
136
- useHeader,
137
- };
138
- };
156
+ useLayoutEffect(() => {
157
+ if (widget === null) {
158
+ return;
159
+ }
139
160
 
140
- /**
141
- * Renders a drop-down widget (Gtk.DropDown by default, or the widget given as `component`) backed by a
142
- * collection model, with customizable rendering for the selected item, the popup list rows, and section
143
- * headers.
144
- */
145
- export const DropDown = <T = unknown, S = unknown, C extends ElementType = typeof GtkDropDown>(
146
- props: DropDownProps<T, S, C>,
147
- ): ReactNode => {
148
- const {
149
- component,
150
- ref,
151
- items,
152
- sections,
153
- renderItem,
154
- renderListItem,
155
- renderHeader,
156
- selectedId,
157
- onSelectionChanged,
158
- ...intrinsicProps
159
- } = props;
160
- const Component: ElementType = component ?? GtkDropDown;
161
-
162
- const wiring = useDropDownWiring<T, S>({
163
- ref,
164
- items,
165
- sections,
166
- selectedId,
167
- onSelectionChanged,
168
- renderHeader,
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);
169
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";
170
183
 
171
184
  return (
172
185
  <>
173
- <Component {...intrinsicProps} ref={wiring.setRef} />
174
- <CellRenderHost
175
- store={wiring.selectionStore}
176
- resolver={wiring.selectionResolver}
177
- render={toItemRenderer<T, S>(renderItem)}
178
- />
179
- <CellRenderHost
180
- store={wiring.listStore}
181
- resolver={wiring.resolver}
182
- render={toListRenderer<T, S>(renderListItem, renderItem)}
183
- />
184
- <HeaderRenderHost
185
- useHeader={wiring.useHeader}
186
- store={wiring.headerStore}
187
- resolver={wiring.headerResolver}
188
- renderHeader={renderHeader}
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}
189
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} />}
190
201
  </>
191
202
  );
192
- };
203
+ }
204
+
205
+ export { DropDown };
package/src/grid-view.tsx CHANGED
@@ -1,81 +1,37 @@
1
- import type * as Gtk from "@gtkx/gi/gtk";
2
- import { GtkGridView, type GtkGridViewProps } from "@gtkx/jsx/gtk";
3
- import { useMergeRefs } from "@gtkx/react/internal";
4
- import { type ReactNode, type Ref, useRef } from "react";
5
- import { type CellRenderer, CellRenderHost, itemRenderer } from "./cell.js";
6
- import { makeFactoryInstaller, useCellContainers } from "./hooks/use-cell-containers.js";
7
- import { useCollectionModel } from "./hooks/use-collection-model.js";
8
- import { useInstalledModel } from "./hooks/use-installed-model.js";
9
- import type { CollectionItemSizeProps, ControlledSelectionProps, ItemNode, RenderItemProps } from "./types.js";
10
-
11
- const factoryInstaller = makeFactoryInstaller<Gtk.GridView>((widget, factory) => widget.setFactory(factory));
12
-
13
- export type GridViewDeclarativeProps<T = unknown> = CollectionItemSizeProps &
14
- ControlledSelectionProps & {
15
- items?: ItemNode<T>[] | undefined;
16
- renderItem: (props: RenderItemProps<T>) => ReactNode;
17
- };
18
-
19
- /**
20
- * Props for {@link GridView}. Combines the underlying Gtk.GridView props with
21
- * declarative collection props: items, a per-cell renderItem, controlled selection,
22
- * and estimated item sizing.
23
- */
24
- export type GridViewProps<T = unknown> = Omit<
25
- GtkGridViewProps,
26
- "model" | "factory" | keyof GridViewDeclarativeProps<T>
27
- > &
28
- GridViewDeclarativeProps<T>;
1
+ import type { ReactNode } from "react";
2
+ import { GtkGridView, GtkSignalListItemFactory } from "@gtkx/jsx/gtk";
3
+ import { omit } from "@gtkx/utils";
4
+ import type { GridViewProps } from "./types.js";
5
+ import { ItemPortals, useItemCells } from "./internal/cells.js";
6
+ import { useCollection } from "./internal/use-collection.js";
7
+
8
+ const GRID_VIEW_PROPS = [
9
+ "items",
10
+ "renderItem",
11
+ "selectedIds",
12
+ "onSelectionChanged",
13
+ "selectionMode",
14
+ "estimatedItemHeight",
15
+ "estimatedItemWidth",
16
+ ] as const satisfies (keyof GridViewProps)[];
29
17
 
30
18
  /**
31
- * Renders a Gtk.GridView: a scrollable grid of uniformly sized cells backed by a
32
- * collection model, with each cell drawn by renderItem.
19
+ * Renders a Gtk.GridView of uniform cells from declarative items, with per-cell
20
+ * rendering, controlled selection, and estimated item sizing.
33
21
  */
34
- export const GridView = <T = unknown>(props: GridViewProps<T>): ReactNode => {
35
- const {
36
- ref,
37
- items,
38
- renderItem,
39
- selectedIds,
40
- selectionMode,
41
- onSelectionChanged,
42
- estimatedItemHeight,
43
- estimatedItemWidth,
44
- ...intrinsicProps
45
- } = props as GridViewDeclarativeProps<T> & {
46
- ref?: Ref<Gtk.GridView | null>;
47
- estimatedItemHeight?: number;
48
- estimatedItemWidth?: number;
49
- [key: string]: unknown;
50
- };
51
-
52
- const cellRenderer: CellRenderer<T, unknown> = itemRenderer<T, unknown>(renderItem);
53
-
54
- const widgetRef = useRef<Gtk.GridView | null>(null);
55
- const setRef = useMergeRefs<Gtk.GridView>(ref, widgetRef);
56
-
57
- const collection = useCollectionModel<T, unknown>({
58
- items,
59
- sections: undefined,
60
- selectionMode,
61
- selectedIds,
62
- onSelectionChanged,
63
- renderHeader: undefined,
64
- });
65
-
66
- const itemStore = useCellContainers<Gtk.GridView>({
67
- object: widgetRef,
68
- installer: factoryInstaller,
69
- estimatedHeight: estimatedItemHeight,
70
- estimatedWidth: estimatedItemWidth,
71
- });
72
-
73
- useInstalledModel(widgetRef, collection.installedModel, (widget, value) => widget.setModel(value));
22
+ function GridView<T = unknown>(props: GridViewProps<T>): ReactNode {
23
+ const { renderItem, estimatedItemHeight, estimatedItemWidth } = props;
24
+ const rest = omit(props, GRID_VIEW_PROPS);
25
+ const size = { width: estimatedItemWidth ?? -1, height: estimatedItemHeight ?? -1 };
26
+ const { collection, selection } = useCollection({ ...props, flat: true });
27
+ const itemCells = useItemCells(size);
74
28
 
75
29
  return (
76
30
  <>
77
- <GtkGridView {...intrinsicProps} ref={setRef} />
78
- <CellRenderHost store={itemStore} resolver={collection.resolver} render={cellRenderer} />
31
+ <GtkGridView model={selection} factory={<GtkSignalListItemFactory {...itemCells.handlers} />} {...rest} />
32
+ <ItemPortals store={itemCells} render={renderItem} collection={collection} />
79
33
  </>
80
34
  );
81
- };
35
+ }
36
+
37
+ export { GridView };
package/src/index.ts CHANGED
@@ -1,29 +1,22 @@
1
- export {
2
- type ColumnDef,
3
- type ColumnDefDeclarativeProps,
4
- ColumnView,
5
- type ColumnViewDeclarativeProps,
6
- type ColumnViewProps,
7
- type ColumnViewSortProps,
8
- } from "./column-view.js";
9
- export { ConstraintLayout, type ConstraintLayoutProps } from "./constraint-layout.js";
10
- export type { ConstraintGuideProps, ConstraintProps, ConstraintVflProps } from "./constraint-layout-apply.js";
11
- export { DropDown, type DropDownDeclarativeProps, type DropDownItemRenderer, type DropDownProps } from "./drop-down.js";
12
- export { Fixed, type FixedChildProps, type FixedPlacementProps, type FixedProps } from "./fixed.js";
13
- export { Grid, type GridChildProps, type GridPlacement, type GridProps } from "./grid.js";
14
- export { GridView, type GridViewDeclarativeProps, type GridViewProps } from "./grid-view.js";
15
- export { ListView, type ListViewDeclarativeProps, type ListViewProps } from "./list-view.js";
16
- export { Menu, type MenuItemsProps, type MenuProps } from "./menu.js";
17
- export { Overlay, type OverlayChildProps, type OverlayPlacementProps, type OverlayProps } from "./overlay.js";
18
- export { SizeGroup, type SizeGroupChildProps, type SizeGroupProps } from "./size-group.js";
1
+ export { ColumnView } from "./column-view.js";
2
+ export { DropDown } from "./drop-down.js";
3
+ export { GridView } from "./grid-view.js";
4
+ export { ListView } from "./list-view.js";
5
+ export { SizeGroup } from "./size-group.js";
19
6
  export type {
20
7
  ChildProps,
21
- CollectionItemSizeProps,
22
- ControlledExpansionProps,
23
- ControlledSelectionProps,
24
- ItemNode,
25
- MenuEntry,
26
- RenderItemProps,
27
- SectionNode,
8
+ Column,
9
+ ColumnViewProps,
10
+ DropDownProps,
11
+ GridViewProps,
12
+ HeaderRenderer,
13
+ Item,
14
+ ItemRenderer,
15
+ ListViewProps,
16
+ RenderHeaderArgs,
17
+ RenderItemArgs,
18
+ Section,
19
+ SizeGroupChildProps,
20
+ SizeGroupProps,
28
21
  WidgetProps,
29
22
  } from "./types.js";