@gtkx/components 1.0.0-rc.1 → 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 (220) 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 +4 -0
  6. package/dist/adw/index.d.ts.map +1 -0
  7. package/dist/adw/index.js +3 -0
  8. package/dist/adw/index.js.map +1 -0
  9. package/dist/adw/toast.d.ts +10 -0
  10. package/dist/adw/toast.d.ts.map +1 -0
  11. package/dist/adw/toast.js +51 -0
  12. package/dist/adw/toast.js.map +1 -0
  13. package/dist/adw/types.d.ts +35 -0
  14. package/dist/adw/types.d.ts.map +1 -0
  15. package/dist/adw/types.js +2 -0
  16. package/dist/adw/types.js.map +1 -0
  17. package/dist/column-view.d.ts +7 -29
  18. package/dist/column-view.d.ts.map +1 -1
  19. package/dist/column-view.js +98 -70
  20. package/dist/column-view.js.map +1 -1
  21. package/dist/drop-down.d.ts +7 -28
  22. package/dist/drop-down.d.ts.map +1 -1
  23. package/dist/drop-down.js +10 -86
  24. package/dist/drop-down.js.map +1 -1
  25. package/dist/grid-view.d.ts +6 -16
  26. package/dist/grid-view.d.ts.map +1 -1
  27. package/dist/grid-view.js +25 -33
  28. package/dist/grid-view.js.map +1 -1
  29. package/dist/index.d.ts +6 -12
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +1 -6
  32. package/dist/index.js.map +1 -1
  33. package/dist/internal/cells.d.ts +50 -0
  34. package/dist/internal/cells.d.ts.map +1 -0
  35. package/dist/internal/cells.js +185 -0
  36. package/dist/internal/cells.js.map +1 -0
  37. package/dist/internal/collection-index.d.ts +21 -0
  38. package/dist/internal/collection-index.d.ts.map +1 -0
  39. package/dist/internal/collection-index.js +106 -0
  40. package/dist/internal/collection-index.js.map +1 -0
  41. package/dist/internal/collection-model.d.ts +12 -0
  42. package/dist/internal/collection-model.d.ts.map +1 -0
  43. package/dist/internal/collection-model.js +164 -0
  44. package/dist/internal/collection-model.js.map +1 -0
  45. package/dist/internal/collection.d.ts +18 -0
  46. package/dist/internal/collection.d.ts.map +1 -0
  47. package/dist/internal/collection.js +64 -0
  48. package/dist/internal/collection.js.map +1 -0
  49. package/dist/internal/drop-down.d.ts +16 -0
  50. package/dist/internal/drop-down.d.ts.map +1 -0
  51. package/dist/internal/drop-down.js +110 -0
  52. package/dist/internal/drop-down.js.map +1 -0
  53. package/dist/internal/expansion.d.ts +9 -0
  54. package/dist/internal/expansion.d.ts.map +1 -0
  55. package/dist/internal/expansion.js +74 -0
  56. package/dist/internal/expansion.js.map +1 -0
  57. package/dist/internal/selection.d.ts +13 -0
  58. package/dist/internal/selection.d.ts.map +1 -0
  59. package/dist/internal/selection.js +100 -0
  60. package/dist/internal/selection.js.map +1 -0
  61. package/dist/internal/use-collection.d.ts +17 -0
  62. package/dist/internal/use-collection.d.ts.map +1 -0
  63. package/dist/internal/use-collection.js +34 -0
  64. package/dist/internal/use-collection.js.map +1 -0
  65. package/dist/internal/use-widget-ref.d.ts +4 -0
  66. package/dist/internal/use-widget-ref.d.ts.map +1 -0
  67. package/dist/internal/use-widget-ref.js +8 -0
  68. package/dist/internal/use-widget-ref.js.map +1 -0
  69. package/dist/list-view.d.ts +5 -20
  70. package/dist/list-view.d.ts.map +1 -1
  71. package/dist/list-view.js +32 -53
  72. package/dist/list-view.js.map +1 -1
  73. package/dist/size-group.d.ts +8 -16
  74. package/dist/size-group.d.ts.map +1 -1
  75. package/dist/size-group.js +35 -51
  76. package/dist/size-group.js.map +1 -1
  77. package/dist/types.d.ts +98 -27
  78. package/dist/types.d.ts.map +1 -1
  79. package/dist/types.js.map +1 -1
  80. package/package.json +17 -6
  81. package/src/adw/combo-row.tsx +20 -0
  82. package/src/adw/index.ts +9 -0
  83. package/src/adw/toast.tsx +74 -0
  84. package/src/adw/types.ts +45 -0
  85. package/src/column-view.tsx +176 -160
  86. package/src/drop-down.tsx +12 -185
  87. package/src/grid-view.tsx +29 -73
  88. package/src/index.ts +18 -26
  89. package/src/internal/cells.tsx +345 -0
  90. package/src/internal/collection-index.ts +168 -0
  91. package/src/internal/collection-model.ts +246 -0
  92. package/src/internal/collection.ts +104 -0
  93. package/src/internal/drop-down.tsx +186 -0
  94. package/src/internal/expansion.ts +122 -0
  95. package/src/internal/selection.tsx +162 -0
  96. package/src/internal/use-collection.tsx +57 -0
  97. package/src/internal/use-widget-ref.ts +11 -0
  98. package/src/list-view.tsx +43 -139
  99. package/src/size-group.tsx +63 -82
  100. package/src/types.ts +158 -28
  101. package/dist/cell.d.ts +0 -30
  102. package/dist/cell.d.ts.map +0 -1
  103. package/dist/cell.js +0 -45
  104. package/dist/cell.js.map +0 -1
  105. package/dist/column-view-column.d.ts +0 -27
  106. package/dist/column-view-column.d.ts.map +0 -1
  107. package/dist/column-view-column.js +0 -30
  108. package/dist/column-view-column.js.map +0 -1
  109. package/dist/constraint-layout-apply.d.ts +0 -55
  110. package/dist/constraint-layout-apply.d.ts.map +0 -1
  111. package/dist/constraint-layout-apply.js +0 -107
  112. package/dist/constraint-layout-apply.js.map +0 -1
  113. package/dist/constraint-layout.d.ts +0 -20
  114. package/dist/constraint-layout.d.ts.map +0 -1
  115. package/dist/constraint-layout.js +0 -88
  116. package/dist/constraint-layout.js.map +0 -1
  117. package/dist/fixed.d.ts +0 -26
  118. package/dist/fixed.d.ts.map +0 -1
  119. package/dist/fixed.js +0 -40
  120. package/dist/fixed.js.map +0 -1
  121. package/dist/grid.d.ts +0 -27
  122. package/dist/grid.d.ts.map +0 -1
  123. package/dist/grid.js +0 -48
  124. package/dist/grid.js.map +0 -1
  125. package/dist/hooks/use-cell-containers.d.ts +0 -18
  126. package/dist/hooks/use-cell-containers.d.ts.map +0 -1
  127. package/dist/hooks/use-cell-containers.js +0 -67
  128. package/dist/hooks/use-cell-containers.js.map +0 -1
  129. package/dist/hooks/use-collection-model.d.ts +0 -26
  130. package/dist/hooks/use-collection-model.d.ts.map +0 -1
  131. package/dist/hooks/use-collection-model.js +0 -35
  132. package/dist/hooks/use-collection-model.js.map +0 -1
  133. package/dist/hooks/use-collection-widget.d.ts +0 -28
  134. package/dist/hooks/use-collection-widget.d.ts.map +0 -1
  135. package/dist/hooks/use-collection-widget.js +0 -21
  136. package/dist/hooks/use-collection-widget.js.map +0 -1
  137. package/dist/hooks/use-drop-down-selection.d.ts +0 -16
  138. package/dist/hooks/use-drop-down-selection.d.ts.map +0 -1
  139. package/dist/hooks/use-drop-down-selection.js +0 -35
  140. package/dist/hooks/use-drop-down-selection.js.map +0 -1
  141. package/dist/hooks/use-expansion-model.d.ts +0 -12
  142. package/dist/hooks/use-expansion-model.d.ts.map +0 -1
  143. package/dist/hooks/use-expansion-model.js +0 -71
  144. package/dist/hooks/use-expansion-model.js.map +0 -1
  145. package/dist/hooks/use-header-menu.d.ts +0 -4
  146. package/dist/hooks/use-header-menu.d.ts.map +0 -1
  147. package/dist/hooks/use-header-menu.js +0 -20
  148. package/dist/hooks/use-header-menu.js.map +0 -1
  149. package/dist/hooks/use-installed-model.d.ts +0 -4
  150. package/dist/hooks/use-installed-model.d.ts.map +0 -1
  151. package/dist/hooks/use-installed-model.js +0 -15
  152. package/dist/hooks/use-installed-model.js.map +0 -1
  153. package/dist/hooks/use-list-model.d.ts +0 -19
  154. package/dist/hooks/use-list-model.d.ts.map +0 -1
  155. package/dist/hooks/use-list-model.js +0 -97
  156. package/dist/hooks/use-list-model.js.map +0 -1
  157. package/dist/hooks/use-placed-child.d.ts +0 -18
  158. package/dist/hooks/use-placed-child.d.ts.map +0 -1
  159. package/dist/hooks/use-placed-child.js +0 -47
  160. package/dist/hooks/use-placed-child.js.map +0 -1
  161. package/dist/hooks/use-selection-model.d.ts +0 -13
  162. package/dist/hooks/use-selection-model.d.ts.map +0 -1
  163. package/dist/hooks/use-selection-model.js +0 -103
  164. package/dist/hooks/use-selection-model.js.map +0 -1
  165. package/dist/hooks/use-sort-handler.d.ts +0 -17
  166. package/dist/hooks/use-sort-handler.d.ts.map +0 -1
  167. package/dist/hooks/use-sort-handler.js +0 -87
  168. package/dist/hooks/use-sort-handler.js.map +0 -1
  169. package/dist/menu-items.d.ts +0 -5
  170. package/dist/menu-items.d.ts.map +0 -1
  171. package/dist/menu-items.js +0 -48
  172. package/dist/menu-items.js.map +0 -1
  173. package/dist/menu.d.ts +0 -14
  174. package/dist/menu.d.ts.map +0 -1
  175. package/dist/menu.js +0 -24
  176. package/dist/menu.js.map +0 -1
  177. package/dist/overlay.d.ts +0 -24
  178. package/dist/overlay.d.ts.map +0 -1
  179. package/dist/overlay.js +0 -49
  180. package/dist/overlay.js.map +0 -1
  181. package/dist/utils/cell-container-store.d.ts +0 -28
  182. package/dist/utils/cell-container-store.d.ts.map +0 -1
  183. package/dist/utils/cell-container-store.js +0 -98
  184. package/dist/utils/cell-container-store.js.map +0 -1
  185. package/dist/utils/item-models.d.ts +0 -10
  186. package/dist/utils/item-models.d.ts.map +0 -1
  187. package/dist/utils/item-models.js +0 -82
  188. package/dist/utils/item-models.js.map +0 -1
  189. package/dist/utils/item-resolver.d.ts +0 -25
  190. package/dist/utils/item-resolver.d.ts.map +0 -1
  191. package/dist/utils/item-resolver.js +0 -102
  192. package/dist/utils/item-resolver.js.map +0 -1
  193. package/dist/utils/list-item-flatten.d.ts +0 -24
  194. package/dist/utils/list-item-flatten.d.ts.map +0 -1
  195. package/dist/utils/list-item-flatten.js +0 -68
  196. package/dist/utils/list-item-flatten.js.map +0 -1
  197. package/src/cell.tsx +0 -144
  198. package/src/column-view-column.tsx +0 -82
  199. package/src/constraint-layout-apply.ts +0 -173
  200. package/src/constraint-layout.tsx +0 -135
  201. package/src/fixed.tsx +0 -86
  202. package/src/grid.tsx +0 -88
  203. package/src/hooks/use-cell-containers.ts +0 -93
  204. package/src/hooks/use-collection-model.ts +0 -60
  205. package/src/hooks/use-collection-widget.ts +0 -47
  206. package/src/hooks/use-drop-down-selection.ts +0 -52
  207. package/src/hooks/use-expansion-model.ts +0 -82
  208. package/src/hooks/use-header-menu.ts +0 -34
  209. package/src/hooks/use-installed-model.ts +0 -20
  210. package/src/hooks/use-list-model.ts +0 -161
  211. package/src/hooks/use-placed-child.ts +0 -79
  212. package/src/hooks/use-selection-model.ts +0 -114
  213. package/src/hooks/use-sort-handler.ts +0 -107
  214. package/src/menu-items.ts +0 -48
  215. package/src/menu.tsx +0 -32
  216. package/src/overlay.tsx +0 -88
  217. package/src/utils/cell-container-store.ts +0 -114
  218. package/src/utils/item-models.ts +0 -113
  219. package/src/utils/item-resolver.ts +0 -138
  220. package/src/utils/list-item-flatten.ts +0 -90
@@ -1,190 +1,206 @@
1
- import type * as Gtk from "@gtkx/gi/gtk";
2
- import { GtkColumnView, type GtkColumnViewProps } from "@gtkx/jsx/gtk";
3
- import { type ReactNode, type Ref, useCallback, useMemo, useState } from "react";
4
- import { createTreeRenderContext, HeaderRenderHost, type TreeRenderContext } from "./cell.js";
5
- import { type ColumnDef, ColumnViewColumn } from "./column-view-column.js";
6
- import { makeFactoryInstaller, useCellContainers } from "./hooks/use-cell-containers.js";
7
- import { useCollectionWidget } from "./hooks/use-collection-widget.js";
8
- import { type ColumnRegistration, useSortHandler } from "./hooks/use-sort-handler.js";
9
- import type {
10
- CollectionItemSizeProps,
11
- ControlledExpansionProps,
12
- ControlledSelectionProps,
13
- ItemNode,
14
- SectionNode,
15
- } from "./types.js";
16
- import type { CellContainerStore } from "./utils/cell-container-store.js";
17
- import type { ItemResolver } from "./utils/item-resolver.js";
18
-
19
- export type { ColumnDef, ColumnDefDeclarativeProps } from "./column-view-column.js";
20
-
21
- const headerFactoryInstaller = makeFactoryInstaller<Gtk.ColumnView>((widget, factory) =>
22
- widget.setHeaderFactory(factory),
23
- );
24
-
25
- export type ColumnViewSortProps = {
1
+ import type { ReactNode } from "react";
2
+ import * as Gtk from "@gtkx/gi/gtk";
3
+ import { GtkColumnView, GtkColumnViewColumn, GtkCustomSorter, GtkSignalListItemFactory } from "@gtkx/jsx/gtk";
4
+ import { useSignal } from "@gtkx/react";
5
+ import { omit } from "@gtkx/utils";
6
+ import { useLayoutEffect, useRef } from "react";
7
+ import type { CellSize } from "./internal/cells.js";
8
+ import type { Collection } from "./internal/collection.js";
9
+ import type { Column, ColumnViewProps } from "./types.js";
10
+ import { HeaderPortals, ItemPortals, useHeaderCells, useItemCells } from "./internal/cells.js";
11
+ import { useCollection } from "./internal/use-collection.js";
12
+ import { useWidgetRef } from "./internal/use-widget-ref.js";
13
+
14
+ type SortProps = {
26
15
  sortColumn?: string | null | undefined;
27
16
  sortOrder?: Gtk.SortType | null | undefined;
28
17
  onSortChanged?: ((column: string | null, order: Gtk.SortType) => void) | null | undefined;
29
18
  };
30
19
 
31
- export type ColumnViewDeclarativeProps<T = unknown, S = unknown> = ColumnViewSortProps &
32
- Omit<CollectionItemSizeProps, "estimatedItemWidth"> &
33
- ControlledSelectionProps &
34
- ControlledExpansionProps & {
35
- items?: ItemNode<T>[] | undefined;
36
- sections?: SectionNode<S, T>[] | undefined;
37
- renderHeader?: ((info: { section: S }) => ReactNode) | null | undefined;
38
- columns: ColumnDef<T>[];
39
- };
20
+ type SortTarget = { view: Gtk.ColumnView; column: Gtk.ColumnViewColumn | null };
40
21
 
41
- /**
42
- * Props for {@link ColumnView}. Combines the underlying Gtk.ColumnView props with
43
- * declarative collection props: flat items or grouped sections, controlled selection
44
- * and expansion, sorting (sortColumn, sortOrder, onSortChanged), an optional section
45
- * header renderer, and the columns to render.
46
- */
47
- export type ColumnViewProps<T = unknown, S = unknown> = Omit<
48
- GtkColumnViewProps,
49
- "columns" | "model" | "headerFactory" | keyof ColumnViewDeclarativeProps<T, S>
50
- > &
51
- ColumnViewDeclarativeProps<T, S>;
52
-
53
- type NormalizedColumnViewProps<T, S> = ColumnViewDeclarativeProps<T, S> & {
54
- ref?: Ref<Gtk.ColumnView | null>;
55
- [key: string]: unknown;
22
+ type ColumnCellsProps = {
23
+ column: Column<never>;
24
+ collection: Collection;
25
+ expandedIds: string[] | null | undefined;
26
+ size: CellSize;
56
27
  };
57
28
 
58
- interface ColumnViewWiring<T, S> {
59
- setRef: (value: Gtk.ColumnView | null) => void;
60
- useHeader: boolean;
61
- headerStore: CellContainerStore;
62
- headerResolver: ItemResolver<T, S>;
63
- resolver: ItemResolver<unknown, unknown>;
64
- tree: TreeRenderContext;
65
- setInstance: (id: string, column: Gtk.ColumnViewColumn | null) => void;
66
- }
29
+ type ColumnListProps = Omit<ColumnCellsProps, "column"> & {
30
+ columns: Column<never>[];
31
+ };
32
+
33
+ const COLUMN_VIEW_PROPS = [
34
+ "items",
35
+ "sections",
36
+ "renderHeader",
37
+ "columns",
38
+ "selectedIds",
39
+ "onSelectionChanged",
40
+ "selectionMode",
41
+ "expandedIds",
42
+ "onExpandedChange",
43
+ "sortColumn",
44
+ "sortOrder",
45
+ "onSortChanged",
46
+ "estimatedItemHeight",
47
+ "children",
48
+ "ref",
49
+ ] as const satisfies (keyof ColumnViewProps)[];
50
+
51
+ const columnById = (view: Gtk.ColumnView, id: string): Gtk.ColumnViewColumn | null => {
52
+ const columns = view.getColumns();
53
+
54
+ for (let index = 0; index < columns.getNItems(); index++) {
55
+ const column = columns.getItem(index);
56
+
57
+ if (column instanceof Gtk.ColumnViewColumn && column.getId() === id) {
58
+ return column;
59
+ }
60
+ }
67
61
 
68
- const useColumnInstances = (): {
69
- instances: Map<string, Gtk.ColumnViewColumn>;
70
- setInstance: (id: string, column: Gtk.ColumnViewColumn | null) => void;
71
- } => {
72
- const [instances, setInstances] = useState<Map<string, Gtk.ColumnViewColumn>>(() => new Map());
73
- const setInstance = useCallback((id: string, column: Gtk.ColumnViewColumn | null): void => {
74
- setInstances((current) => {
75
- if (column === null) {
76
- if (!current.has(id)) return current;
77
- const next = new Map(current);
78
- next.delete(id);
79
- return next;
80
- }
81
- if (current.get(id) === column) return current;
82
- const next = new Map(current);
83
- next.set(id, column);
84
- return next;
85
- });
86
- }, []);
87
- return { instances, setInstance };
62
+ return null;
88
63
  };
89
64
 
90
- const buildRegistrations = <T,>(
91
- columns: ColumnDef<T>[],
92
- instances: Map<string, Gtk.ColumnViewColumn>,
93
- ): ColumnRegistration[] => {
94
- const result: ColumnRegistration[] = [];
95
- for (const column of columns) {
96
- const instance = instances.get(column.id);
97
- if (instance) result.push({ id: column.id, column: instance, sortable: column.sortable ?? false });
65
+ const getSortTarget = (view: Gtk.ColumnView | null, sortColumn: string | null | undefined): SortTarget | null => {
66
+ if (view === null || sortColumn === undefined) {
67
+ return null;
68
+ }
69
+
70
+ if (sortColumn === null) {
71
+ return { view, column: null };
98
72
  }
99
- return result;
73
+
74
+ const column = columnById(view, sortColumn);
75
+
76
+ return column === null ? null : { view, column };
100
77
  };
101
78
 
102
- const useColumnViewWiring = <T, S>(props: NormalizedColumnViewProps<T, S>): ColumnViewWiring<T, S> => {
103
- const { widgetRef, setRef, collection } = useCollectionWidget<Gtk.ColumnView, T, S>(props);
79
+ const applySort = (
80
+ sorting: { current: boolean },
81
+ view: Gtk.ColumnView,
82
+ column: Gtk.ColumnViewColumn | null,
83
+ order: Gtk.SortType | null | undefined,
84
+ ): void => {
85
+ sorting.current = true;
86
+
87
+ try {
88
+ view.sortByColumn(column, order ?? Gtk.SortType.ASCENDING);
89
+ } finally {
90
+ sorting.current = false;
91
+ }
92
+ };
104
93
 
105
- const useHeader = collection.useHeader;
106
- const headerStore = useCellContainers<Gtk.ColumnView>({
107
- object: useHeader ? widgetRef : null,
108
- installer: headerFactoryInstaller,
109
- estimatedHeight: props.estimatedItemHeight,
110
- });
94
+ const emitSortChanged = (
95
+ sorter: Gtk.ColumnViewSorter | null,
96
+ sorting: { current: boolean },
97
+ sort: SortProps,
98
+ ): void => {
99
+ if (sorter === null || sorting.current) {
100
+ return;
101
+ }
111
102
 
112
- const { instances, setInstance } = useColumnInstances();
113
- const registrations = useMemo<ColumnRegistration[]>(
114
- () => buildRegistrations(props.columns, instances),
115
- [props.columns, instances],
116
- );
103
+ sort.onSortChanged?.(sorter.getPrimarySortColumn()?.getId() ?? null, sorter.getPrimarySortOrder());
104
+ };
105
+
106
+ const syncSort = (
107
+ sorting: { current: boolean },
108
+ view: Gtk.ColumnView | null,
109
+ sortColumn: string | null | undefined,
110
+ sortOrder: Gtk.SortType | null | undefined,
111
+ ): void => {
112
+ const target = getSortTarget(view, sortColumn);
113
+
114
+ if (target === null) {
115
+ return;
116
+ }
117
117
 
118
- useSortHandler({
119
- columnView: widgetRef,
120
- sortColumn: props.sortColumn,
121
- sortOrder: props.sortOrder,
122
- onSortChanged: props.onSortChanged,
123
- columns: registrations,
118
+ applySort(sorting, target.view, target.column, sortOrder);
119
+ };
120
+
121
+ const useColumnSorting = (view: Gtk.ColumnView | null, sort: SortProps, columns: Column<never>[]): void => {
122
+ const sorting = useRef(false);
123
+ const sorter = view?.getSorter() ?? null;
124
+ const columnSorter = sorter instanceof Gtk.ColumnViewSorter ? sorter : null;
125
+
126
+ useSignal(columnSorter, "changed", (): void => {
127
+ emitSortChanged(columnSorter, sorting, sort);
124
128
  });
125
129
 
126
- const expandedIds = props.expandedIds;
127
- const tree = useMemo<TreeRenderContext>(
128
- () => createTreeRenderContext(expandedIds, collection.rowId),
129
- [expandedIds, collection.rowId],
130
- );
130
+ const { sortColumn, sortOrder } = sort;
131
131
 
132
- return {
133
- setRef,
134
- useHeader,
135
- headerStore,
136
- headerResolver: collection.headerResolver,
137
- resolver: collection.resolver as ItemResolver<unknown, unknown>,
138
- tree,
139
- setInstance,
140
- };
132
+ useLayoutEffect(() => {
133
+ syncSort(sorting, view, sortColumn, sortOrder);
134
+ }, [view, sortColumn, sortOrder, columns]);
135
+ };
136
+
137
+ const ColumnCells = ({ column, collection, expandedIds, size }: ColumnCellsProps): ReactNode => {
138
+ const cells = useItemCells(size);
139
+ const rest = omit(column, ["id", "renderCell", "sortable"]);
140
+
141
+ return (
142
+ <>
143
+ <GtkColumnViewColumn
144
+ id={column.id}
145
+ factory={<GtkSignalListItemFactory {...cells.handlers} />}
146
+ sorter={column.sortable === true ? <GtkCustomSorter /> : null}
147
+ {...rest}
148
+ />
149
+ <ItemPortals
150
+ store={cells}
151
+ render={column.renderCell}
152
+ collection={collection}
153
+ expandedIds={expandedIds}
154
+ />
155
+ </>
156
+ );
141
157
  };
142
158
 
159
+ const ColumnList = ({ columns, collection, expandedIds, size }: ColumnListProps): ReactNode =>
160
+ columns.map((column) => (
161
+ <ColumnCells
162
+ key={column.id}
163
+ column={column}
164
+ collection={collection}
165
+ expandedIds={expandedIds}
166
+ size={size}
167
+ />
168
+ ));
169
+
143
170
  /**
144
- * Renders a Gtk.ColumnView: a multi-column, scrollable list backed by a collection
145
- * model. Columns are declared through the columns prop, each a {@link ColumnDef}.
171
+ * Renders a Gtk.ColumnView from declarative items or sections and a columns array,
172
+ * with controlled selection, expansion, and sorting, per-column header menus, and
173
+ * section header rendering.
146
174
  */
147
- export const ColumnView = <T = unknown, S = unknown>(props: ColumnViewProps<T, S>): ReactNode => {
148
- const normalized = props as NormalizedColumnViewProps<T, S>;
149
- const wiring = useColumnViewWiring<T, S>(normalized);
150
- const {
151
- ref,
152
- items,
153
- sections,
154
- columns,
155
- selectedIds,
156
- selectionMode,
157
- onSelectionChanged,
158
- expandedIds,
159
- onExpandedChange,
160
- sortColumn,
161
- sortOrder,
162
- onSortChanged,
163
- renderHeader,
164
- estimatedItemHeight,
165
- ...intrinsicProps
166
- } = normalized;
175
+ function ColumnView<T = unknown, S = unknown>(props: ColumnViewProps<T, S>): ReactNode {
176
+ const { renderHeader, columns, expandedIds, sortColumn, sortOrder, onSortChanged, estimatedItemHeight, ref } =
177
+ props;
178
+
179
+ const rest = omit(props, COLUMN_VIEW_PROPS);
180
+ const [view, refCallback] = useWidgetRef<Gtk.ColumnView>(ref);
181
+ const size = { width: -1, height: estimatedItemHeight ?? -1 };
182
+ const { collection, selection } = useCollection(props);
183
+ const headerCells = useHeaderCells(size);
184
+ const columnList = columns as Column<never>[];
185
+ useColumnSorting(view, { sortColumn, sortOrder, onSortChanged }, columnList);
167
186
 
168
187
  return (
169
188
  <>
170
- <GtkColumnView {...intrinsicProps} ref={wiring.setRef}>
171
- {columns.map((column) => (
172
- <ColumnViewColumn
173
- key={column.id}
174
- {...column}
175
- resolver={wiring.resolver}
176
- tree={wiring.tree}
177
- estimatedItemHeight={estimatedItemHeight}
178
- onInstance={wiring.setInstance}
179
- />
180
- ))}
189
+ <GtkColumnView
190
+ ref={refCallback}
191
+ model={selection}
192
+ {...(renderHeader != null && {
193
+ headerFactory: <GtkSignalListItemFactory {...headerCells.handlers} />,
194
+ })}
195
+ {...rest}
196
+ >
197
+ <ColumnList columns={columnList} collection={collection} expandedIds={expandedIds} size={size} />
181
198
  </GtkColumnView>
182
- <HeaderRenderHost
183
- useHeader={wiring.useHeader}
184
- store={wiring.headerStore}
185
- resolver={wiring.headerResolver}
186
- renderHeader={renderHeader}
187
- />
199
+ {renderHeader != null && (
200
+ <HeaderPortals store={headerCells} render={renderHeader} collection={collection} />
201
+ )}
188
202
  </>
189
203
  );
190
- };
204
+ }
205
+
206
+ export { ColumnView };
package/src/drop-down.tsx CHANGED
@@ -1,192 +1,19 @@
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";
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";
13
6
 
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>;
27
- };
28
-
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);
32
- };
33
-
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);
40
- };
41
-
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;
60
- };
7
+ type DropDownComponent = <T = unknown, S = unknown>(props: DropDownProps<T, S>) => ReactNode;
61
8
 
62
9
  /**
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}.
10
+ * Renders a `Gtk.DropDown` backed by a collection model, with customizable renderers for the
11
+ * collapsed display, popup rows, and popup section headers, and controlled selection.
65
12
  */
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
- >;
13
+ const DropDown: DropDownComponent = DropDownImpl as DropDownComponent;
71
14
 
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;
15
+ function DropDownImpl(props: DropDownBaseProps): ReactNode {
16
+ return <DropDownBase {...props} component={GtkDropDown} />;
79
17
  }
80
18
 
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
- }
91
-
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);
100
-
101
- const listModel = useListModel<T, S>({ items: props.items, sections: props.sections });
102
-
103
- const useHeader = typeof props.renderHeader === "function";
104
-
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
- });
111
-
112
- useInstalledModel(widgetRef, listModel.model, (widget, value) => widget.setModel(value));
113
-
114
- const selectedPosition = useDropDownSelection<T, S>({
115
- widget,
116
- resolver: listModel.resolver,
117
- selectedId: props.selectedId,
118
- onSelectionChanged: props.onSelectionChanged,
119
- });
120
-
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
- };
139
-
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,
169
- });
170
-
171
- return (
172
- <>
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}
189
- />
190
- </>
191
- );
192
- };
19
+ export { DropDown };