@gtkx/components 0.21.0 → 1.0.0-rc.1

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 (185) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +173 -0
  3. package/dist/cell.d.ts +30 -0
  4. package/dist/cell.d.ts.map +1 -0
  5. package/dist/cell.js +45 -0
  6. package/dist/cell.js.map +1 -0
  7. package/dist/column-view-column.d.ts +24 -14
  8. package/dist/column-view-column.d.ts.map +1 -1
  9. package/dist/column-view-column.js +19 -40
  10. package/dist/column-view-column.js.map +1 -1
  11. package/dist/column-view.d.ts +25 -11
  12. package/dist/column-view.d.ts.map +1 -1
  13. package/dist/column-view.js +53 -83
  14. package/dist/column-view.js.map +1 -1
  15. package/dist/constraint-layout-apply.d.ts +50 -1
  16. package/dist/constraint-layout-apply.d.ts.map +1 -1
  17. package/dist/constraint-layout-apply.js.map +1 -1
  18. package/dist/constraint-layout.d.ts +8 -1
  19. package/dist/constraint-layout.d.ts.map +1 -1
  20. package/dist/constraint-layout.js +12 -9
  21. package/dist/constraint-layout.js.map +1 -1
  22. package/dist/drop-down.d.ts +26 -36
  23. package/dist/drop-down.d.ts.map +1 -1
  24. package/dist/drop-down.js +47 -87
  25. package/dist/drop-down.js.map +1 -1
  26. package/dist/fixed.d.ts +26 -0
  27. package/dist/fixed.d.ts.map +1 -0
  28. package/dist/fixed.js +40 -0
  29. package/dist/fixed.js.map +1 -0
  30. package/dist/grid-view.d.ts +13 -10
  31. package/dist/grid-view.d.ts.map +1 -1
  32. package/dist/grid-view.js +30 -16
  33. package/dist/grid-view.js.map +1 -1
  34. package/dist/grid.d.ts +27 -0
  35. package/dist/grid.d.ts.map +1 -0
  36. package/dist/grid.js +48 -0
  37. package/dist/grid.js.map +1 -0
  38. package/dist/hooks/use-cell-containers.d.ts +18 -0
  39. package/dist/hooks/use-cell-containers.d.ts.map +1 -0
  40. package/dist/hooks/{use-realized-slots.js → use-cell-containers.js} +18 -17
  41. package/dist/hooks/use-cell-containers.js.map +1 -0
  42. package/dist/hooks/use-collection-model.d.ts +26 -0
  43. package/dist/hooks/use-collection-model.d.ts.map +1 -0
  44. package/dist/hooks/use-collection-model.js +35 -0
  45. package/dist/hooks/use-collection-model.js.map +1 -0
  46. package/dist/hooks/use-collection-widget.d.ts +28 -0
  47. package/dist/hooks/use-collection-widget.d.ts.map +1 -0
  48. package/dist/hooks/use-collection-widget.js +21 -0
  49. package/dist/hooks/use-collection-widget.js.map +1 -0
  50. package/dist/hooks/use-drop-down-selection.d.ts +7 -6
  51. package/dist/hooks/use-drop-down-selection.d.ts.map +1 -1
  52. package/dist/hooks/use-drop-down-selection.js +6 -6
  53. package/dist/hooks/use-drop-down-selection.js.map +1 -1
  54. package/dist/hooks/use-expansion-model.d.ts +12 -0
  55. package/dist/hooks/use-expansion-model.d.ts.map +1 -0
  56. package/dist/hooks/use-expansion-model.js +71 -0
  57. package/dist/hooks/use-expansion-model.js.map +1 -0
  58. package/dist/hooks/use-header-menu.d.ts.map +1 -1
  59. package/dist/hooks/use-header-menu.js +12 -24
  60. package/dist/hooks/use-header-menu.js.map +1 -1
  61. package/dist/hooks/use-installed-model.d.ts +4 -0
  62. package/dist/hooks/use-installed-model.d.ts.map +1 -0
  63. package/dist/hooks/use-installed-model.js +15 -0
  64. package/dist/hooks/use-installed-model.js.map +1 -0
  65. package/dist/hooks/use-list-model.d.ts +14 -15
  66. package/dist/hooks/use-list-model.d.ts.map +1 -1
  67. package/dist/hooks/use-list-model.js +61 -34
  68. package/dist/hooks/use-list-model.js.map +1 -1
  69. package/dist/hooks/use-placed-child.d.ts +18 -0
  70. package/dist/hooks/use-placed-child.d.ts.map +1 -0
  71. package/dist/hooks/use-placed-child.js +47 -0
  72. package/dist/hooks/use-placed-child.js.map +1 -0
  73. package/dist/hooks/use-selection-model.d.ts +4 -3
  74. package/dist/hooks/use-selection-model.d.ts.map +1 -1
  75. package/dist/hooks/use-selection-model.js +8 -16
  76. package/dist/hooks/use-selection-model.js.map +1 -1
  77. package/dist/hooks/use-sort-handler.d.ts +10 -5
  78. package/dist/hooks/use-sort-handler.d.ts.map +1 -1
  79. package/dist/hooks/use-sort-handler.js +2 -2
  80. package/dist/hooks/use-sort-handler.js.map +1 -1
  81. package/dist/index.d.ts +11 -17
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +6 -13
  84. package/dist/index.js.map +1 -1
  85. package/dist/list-view.d.ts +17 -9
  86. package/dist/list-view.d.ts.map +1 -1
  87. package/dist/list-view.js +49 -24
  88. package/dist/list-view.js.map +1 -1
  89. package/dist/menu-items.d.ts.map +1 -1
  90. package/dist/menu-items.js.map +1 -1
  91. package/dist/menu.d.ts +6 -10
  92. package/dist/menu.d.ts.map +1 -1
  93. package/dist/menu.js +7 -9
  94. package/dist/menu.js.map +1 -1
  95. package/dist/overlay.d.ts +24 -0
  96. package/dist/overlay.d.ts.map +1 -0
  97. package/dist/overlay.js +49 -0
  98. package/dist/overlay.js.map +1 -0
  99. package/dist/size-group.d.ts +20 -0
  100. package/dist/size-group.d.ts.map +1 -0
  101. package/dist/size-group.js +57 -0
  102. package/dist/size-group.js.map +1 -0
  103. package/dist/types.d.ts +43 -173
  104. package/dist/types.d.ts.map +1 -1
  105. package/dist/types.js.map +1 -1
  106. package/dist/utils/{realized-slot-store.d.ts → cell-container-store.d.ts} +7 -9
  107. package/dist/utils/cell-container-store.d.ts.map +1 -0
  108. package/dist/utils/{realized-slot-store.js → cell-container-store.js} +9 -14
  109. package/dist/utils/cell-container-store.js.map +1 -0
  110. package/dist/utils/item-models.d.ts +10 -0
  111. package/dist/utils/item-models.d.ts.map +1 -0
  112. package/dist/utils/{position-only-model.js → item-models.js} +13 -14
  113. package/dist/utils/item-models.js.map +1 -0
  114. package/dist/utils/item-resolver.d.ts +14 -17
  115. package/dist/utils/item-resolver.d.ts.map +1 -1
  116. package/dist/utils/item-resolver.js +34 -43
  117. package/dist/utils/item-resolver.js.map +1 -1
  118. package/dist/utils/list-item-flatten.d.ts +16 -24
  119. package/dist/utils/list-item-flatten.d.ts.map +1 -1
  120. package/dist/utils/list-item-flatten.js +16 -36
  121. package/dist/utils/list-item-flatten.js.map +1 -1
  122. package/package.json +52 -53
  123. package/src/cell.tsx +144 -0
  124. package/src/column-view-column.tsx +57 -48
  125. package/src/column-view.tsx +130 -146
  126. package/src/constraint-layout-apply.ts +53 -1
  127. package/src/constraint-layout.tsx +18 -13
  128. package/src/drop-down.tsx +129 -196
  129. package/src/fixed.tsx +86 -0
  130. package/src/grid-view.tsx +52 -44
  131. package/src/grid.tsx +88 -0
  132. package/src/hooks/{use-realized-slots.ts → use-cell-containers.ts} +30 -30
  133. package/src/hooks/use-collection-model.ts +60 -0
  134. package/src/hooks/use-collection-widget.ts +47 -0
  135. package/src/hooks/use-drop-down-selection.ts +12 -13
  136. package/src/hooks/use-expansion-model.ts +82 -0
  137. package/src/hooks/use-header-menu.ts +24 -23
  138. package/src/hooks/use-installed-model.ts +20 -0
  139. package/src/hooks/use-list-model.ts +103 -87
  140. package/src/hooks/use-placed-child.ts +79 -0
  141. package/src/hooks/use-selection-model.ts +11 -18
  142. package/src/hooks/use-sort-handler.ts +13 -8
  143. package/src/index.ts +25 -27
  144. package/src/list-view.tsx +120 -57
  145. package/src/menu.tsx +12 -14
  146. package/src/overlay.tsx +88 -0
  147. package/src/size-group.tsx +96 -0
  148. package/src/types.ts +55 -206
  149. package/src/utils/{realized-slot-store.ts → cell-container-store.ts} +13 -25
  150. package/src/utils/{position-only-model.ts → item-models.ts} +26 -28
  151. package/src/utils/item-resolver.ts +54 -59
  152. package/src/utils/list-item-flatten.ts +33 -56
  153. package/dist/collection-view.d.ts +0 -30
  154. package/dist/collection-view.d.ts.map +0 -1
  155. package/dist/collection-view.js +0 -52
  156. package/dist/collection-view.js.map +0 -1
  157. package/dist/contexts/column-view-context.d.ts +0 -17
  158. package/dist/contexts/column-view-context.d.ts.map +0 -1
  159. package/dist/contexts/column-view-context.js +0 -10
  160. package/dist/contexts/column-view-context.js.map +0 -1
  161. package/dist/hooks/use-model-installation.d.ts +0 -9
  162. package/dist/hooks/use-model-installation.d.ts.map +0 -1
  163. package/dist/hooks/use-model-installation.js +0 -20
  164. package/dist/hooks/use-model-installation.js.map +0 -1
  165. package/dist/hooks/use-realized-slots.d.ts +0 -20
  166. package/dist/hooks/use-realized-slots.d.ts.map +0 -1
  167. package/dist/hooks/use-realized-slots.js.map +0 -1
  168. package/dist/list-portal-host.d.ts +0 -11
  169. package/dist/list-portal-host.d.ts.map +0 -1
  170. package/dist/list-portal-host.js +0 -8
  171. package/dist/list-portal-host.js.map +0 -1
  172. package/dist/list-slot.d.ts +0 -14
  173. package/dist/list-slot.d.ts.map +0 -1
  174. package/dist/list-slot.js +0 -29
  175. package/dist/list-slot.js.map +0 -1
  176. package/dist/utils/position-only-model.d.ts +0 -10
  177. package/dist/utils/position-only-model.d.ts.map +0 -1
  178. package/dist/utils/position-only-model.js.map +0 -1
  179. package/dist/utils/realized-slot-store.d.ts.map +0 -1
  180. package/dist/utils/realized-slot-store.js.map +0 -1
  181. package/src/collection-view.tsx +0 -125
  182. package/src/contexts/column-view-context.ts +0 -28
  183. package/src/hooks/use-model-installation.ts +0 -25
  184. package/src/list-portal-host.tsx +0 -27
  185. package/src/list-slot.tsx +0 -56
@@ -2,107 +2,131 @@ import type * as Gio from "@gtkx/gi/gio";
2
2
  import type * as GObject from "@gtkx/gi/gobject";
3
3
  import type * as Gtk from "@gtkx/gi/gtk";
4
4
  import { useLayoutEffect, useMemo, useRef } from "react";
5
- import type { ListItem } from "../types.js";
6
- import {
7
- createControlledResolver,
8
- createModelResolver,
9
- createSectionHeaderResolver,
10
- type ItemResolver,
11
- type RowValue,
12
- } from "../utils/item-resolver.js";
13
- import { detectStructure, type ListStructure, structuralSignature } from "../utils/list-item-flatten.js";
5
+ import type { ItemNode, SectionNode } from "../types.js";
14
6
  import {
15
7
  createFlatModel,
16
8
  createSectionModel,
17
9
  createTreeModel,
18
10
  resizeFlatModel,
19
11
  retagRows,
20
- } from "../utils/position-only-model.js";
21
-
22
- export interface ControlledListMode<T, S> {
23
- items: ListItem<T, S>[] | undefined;
24
- autoexpand?: boolean | undefined;
25
- model?: never;
26
- }
12
+ } from "../utils/item-models.js";
13
+ import {
14
+ createItemResolver,
15
+ createSectionHeaderResolver,
16
+ createTreeResolver,
17
+ type ItemResolver,
18
+ type RowValue,
19
+ } from "../utils/item-resolver.js";
20
+ import { detectStructure, type ListStructure, structuralSignature } from "../utils/list-item-flatten.js";
27
21
 
28
- export interface UncontrolledListMode {
29
- model: Gio.ListModel;
30
- items?: never;
31
- autoexpand?: never;
32
- }
22
+ type ListModelInput<T, S> = {
23
+ items: ItemNode<T>[] | undefined;
24
+ sections?: SectionNode<S, T>[] | undefined;
25
+ };
33
26
 
34
- export interface ListModelResult<T, S> {
27
+ type ListModelResult<T, S> = {
35
28
  model: Gio.ListModel;
29
+ treeModel: Gtk.TreeListModel | null;
36
30
  resolver: ItemResolver<T, S>;
37
31
  headerResolver: ItemResolver<T, S>;
38
- }
32
+ rowValues: WeakMap<GObject.Object, RowValue<T>>;
33
+ };
34
+
35
+ type ListModelStructure = "sections" | ListStructure;
39
36
 
40
- interface ControlledState<T, S> {
37
+ type ListModelState<T, S> = {
41
38
  model: Gio.ListModel;
39
+ treeModel: Gtk.TreeListModel | null;
42
40
  flatModel: Gtk.StringList | undefined;
43
- items: ListItem<T, S>[] | undefined;
44
- structure: ListStructure;
45
- autoexpand: boolean;
41
+ items: ItemNode<T>[] | undefined;
42
+ sections: SectionNode<S, T>[] | undefined;
43
+ structure: ListModelStructure;
46
44
  signature: string;
47
- rowValues: WeakMap<GObject.Object, RowValue<T, S>>;
45
+ rowValues: WeakMap<GObject.Object, RowValue<T>>;
48
46
  placeholdersById: Map<string, GObject.Object>;
49
- }
47
+ };
48
+
49
+ const sectionRows = <T, S>(sections: SectionNode<S, T>[] | undefined): ItemNode<T>[] => {
50
+ if (sections === undefined) return [];
51
+ const rows: ItemNode<T>[] = [];
52
+ for (const section of sections) rows.push(...section.data);
53
+ return rows;
54
+ };
55
+
56
+ const sectionSignature = <T, S>(sections: SectionNode<S, T>[] | undefined): string => {
57
+ if (sections === undefined) return "";
58
+ const parts: string[] = [];
59
+ for (const section of sections) {
60
+ parts.push(`${section.id}{${structuralSignature(section.data)}}`);
61
+ }
62
+ return parts.join(",");
63
+ };
64
+
65
+ const signatureOf = <T, S>(
66
+ structure: ListModelStructure,
67
+ items: ItemNode<T>[] | undefined,
68
+ sections: SectionNode<S, T>[] | undefined,
69
+ ): string => {
70
+ if (structure === "sections") return `sections|${sectionSignature(sections)}`;
71
+ return structure === "flat" ? "" : `${structure}|${structuralSignature(items)}`;
72
+ };
50
73
 
51
- const controlledSignature = <T, S>(
52
- structure: ListStructure,
53
- autoexpand: boolean,
54
- items: ListItem<T, S>[] | undefined,
55
- ): string => (structure === "flat" ? "" : `${structure}|${autoexpand ? 1 : 0}|${structuralSignature(items)}`);
74
+ type ListModelInternalInput<T, S> = {
75
+ items: ItemNode<T>[] | undefined;
76
+ sections: SectionNode<S, T>[] | undefined;
77
+ structure: ListModelStructure;
78
+ signature: string;
79
+ };
56
80
 
57
- const buildControlledState = <T, S>(
58
- items: ListItem<T, S>[] | undefined,
59
- autoexpand: boolean,
60
- structure: ListStructure,
61
- signature: string,
62
- ): ControlledState<T, S> => {
63
- const rowValues = new WeakMap<GObject.Object, RowValue<T, S>>();
81
+ const buildState = <T, S>(input: ListModelInternalInput<T, S>): ListModelState<T, S> => {
82
+ const { items, sections, structure, signature } = input;
83
+ const rowValues = new WeakMap<GObject.Object, RowValue<T>>();
64
84
  const placeholdersById = new Map<string, GObject.Object>();
65
85
  let model: Gio.ListModel;
86
+ let treeModel: Gtk.TreeListModel | null = null;
66
87
  let flatModel: Gtk.StringList | undefined;
67
88
  if (structure === "sections") {
68
- model = createSectionModel(items ?? []);
89
+ model = createSectionModel(sections ?? []);
69
90
  } else if (structure === "tree") {
70
- model = createTreeModel(items ?? [], autoexpand, rowValues, placeholdersById);
91
+ treeModel = createTreeModel(items ?? [], rowValues, placeholdersById);
92
+ model = treeModel;
71
93
  } else {
72
94
  flatModel = createFlatModel(items?.length ?? 0);
73
95
  model = flatModel;
74
96
  }
75
- return { model, flatModel, items, structure, autoexpand, signature, rowValues, placeholdersById };
97
+ return { model, treeModel, flatModel, items, sections, structure, signature, rowValues, placeholdersById };
76
98
  };
77
99
 
78
- const resolveControlledState = <T, S>(
79
- prev: ControlledState<T, S> | null,
80
- items: ListItem<T, S>[] | undefined,
81
- autoexpand: boolean,
82
- ): ControlledState<T, S> => {
83
- if (prev !== null && prev.items === items && prev.autoexpand === autoexpand) return prev;
84
- const structure = detectStructure(items);
85
- const signature = controlledSignature(structure, autoexpand, items);
100
+ const resolveState = <T, S>(
101
+ prev: ListModelState<T, S> | null,
102
+ items: ItemNode<T>[] | undefined,
103
+ sections: SectionNode<S, T>[] | undefined,
104
+ ): ListModelState<T, S> => {
105
+ if (prev !== null && prev.items === items && prev.sections === sections) {
106
+ return prev;
107
+ }
108
+ const structure: ListModelStructure = sections !== undefined ? "sections" : detectStructure(items);
109
+ const signature = signatureOf(structure, items, sections);
110
+ const input: ListModelInternalInput<T, S> = { items, sections, structure, signature };
86
111
  if (prev === null || prev.structure !== structure) {
87
- return buildControlledState(items, autoexpand, structure, signature);
112
+ return buildState(input);
88
113
  }
89
114
  if (structure !== "flat" && prev.signature !== signature) {
90
- return buildControlledState(items, autoexpand, structure, signature);
115
+ return buildState(input);
91
116
  }
92
- if (structure !== "flat") {
117
+ if (structure === "tree") {
93
118
  retagRows(items ?? [], prev.rowValues, prev.placeholdersById);
94
119
  }
95
120
  prev.items = items;
96
- prev.autoexpand = autoexpand;
121
+ prev.sections = sections;
97
122
  prev.signature = signature;
98
123
  return prev;
99
124
  };
100
125
 
101
- const useControlledModel = <T, S>(mode: ControlledListMode<T, S>): ListModelResult<T, S> => {
102
- const { items } = mode;
103
- const autoexpand = mode.autoexpand ?? false;
104
- const stateRef = useRef<ControlledState<T, S> | null>(null);
105
- const state = resolveControlledState(stateRef.current, items, autoexpand);
126
+ export const useListModel = <T, S>(input: ListModelInput<T, S>): ListModelResult<T, S> => {
127
+ const { items, sections } = input;
128
+ const stateRef = useRef<ListModelState<T, S> | null>(null);
129
+ const state = resolveState(stateRef.current, items, sections);
106
130
  stateRef.current = state;
107
131
  const { structure, flatModel } = state;
108
132
 
@@ -112,34 +136,26 @@ const useControlledModel = <T, S>(mode: ControlledListMode<T, S>): ListModelResu
112
136
  }
113
137
  }, [flatModel, items]);
114
138
 
115
- const resolver = useMemo(
116
- () => createControlledResolver(items, structure !== "flat" && autoexpand, state.rowValues),
117
- [items, structure, autoexpand, state.rowValues],
118
- );
139
+ const resolver = useMemo(() => {
140
+ if (structure === "sections") {
141
+ return createItemResolver<T, S>(sectionRows(sections), true, state.rowValues);
142
+ }
143
+ if (structure === "tree" && state.treeModel !== null) {
144
+ return createTreeResolver<T, S>(items, state.rowValues, state.treeModel);
145
+ }
146
+ return createItemResolver<T, S>(items, false, state.rowValues);
147
+ }, [items, sections, structure, state.rowValues, state.treeModel]);
119
148
 
120
149
  const headerResolver = useMemo(
121
- () => createSectionHeaderResolver(structure === "sections" ? items : undefined),
122
- [items, structure],
150
+ () => createSectionHeaderResolver<T, S>(structure === "sections" ? sections : undefined),
151
+ [sections, structure],
123
152
  );
124
153
 
125
- return { model: state.model, resolver, headerResolver };
154
+ return {
155
+ model: state.model,
156
+ treeModel: state.treeModel,
157
+ resolver,
158
+ headerResolver,
159
+ rowValues: state.rowValues,
160
+ };
126
161
  };
127
-
128
- const useUncontrolledModel = <T, S>(mode: UncontrolledListMode): ListModelResult<T, S> => {
129
- const { model } = mode;
130
- const resolver = useMemo<ItemResolver<T, S>>(() => createModelResolver(model), [model]);
131
- const headerResolver = useMemo<ItemResolver<T, S>>(() => createSectionHeaderResolver<T, S>(undefined), []);
132
- return { model, resolver, headerResolver };
133
- };
134
-
135
- export const useListModel = <T, S>(mode: ControlledListMode<T, S> | UncontrolledListMode): ListModelResult<T, S> => {
136
- const controlled = useControlledModel<T, S>(
137
- mode.model === undefined ? { items: mode.items, autoexpand: mode.autoexpand } : { items: [] },
138
- );
139
- const uncontrolled = useUncontrolledModel<T, S>(
140
- mode.model === undefined ? { model: EMPTY_MODEL } : { model: mode.model },
141
- );
142
- return mode.model === undefined ? controlled : uncontrolled;
143
- };
144
-
145
- const EMPTY_MODEL: Gio.ListModel = createFlatModel(0);
@@ -0,0 +1,79 @@
1
+ import type * as GObject from "@gtkx/gi/gobject";
2
+ import { createPortal, rootElement } from "@gtkx/react";
3
+ import { useMergeRefs } from "@gtkx/react/internal";
4
+ import {
5
+ type Context,
6
+ createContext,
7
+ type ReactNode,
8
+ type Ref,
9
+ type RefCallback,
10
+ type RefObject,
11
+ useCallback,
12
+ useContext,
13
+ useLayoutEffect,
14
+ useRef,
15
+ } from "react";
16
+
17
+ export type ParentContext<T> = {
18
+ Context: Context<RefObject<T | null> | null>;
19
+ useParentRef: () => RefObject<T | null>;
20
+ };
21
+
22
+ export const createParentContext = <T>(orphanMessage: string): ParentContext<T> => {
23
+ const context = createContext<RefObject<T | null> | null>(null);
24
+ return {
25
+ Context: context,
26
+ useParentRef: (): RefObject<T | null> => {
27
+ const ref = useContext(context);
28
+ if (!ref) throw new Error(orphanMessage);
29
+ return ref;
30
+ },
31
+ };
32
+ };
33
+
34
+ export type PlacedChildRender<T> = (ref: RefCallback<T>) => ReactNode;
35
+
36
+ export type PlacedChildOptions<T extends GObject.Object, P> = {
37
+ render: PlacedChildRender<T>;
38
+ ref?: Ref<T | null> | undefined;
39
+ placement: P;
40
+ samePlacement: (a: P, b: P) => boolean;
41
+ place: (object: T, placement: P, previous: P | undefined) => void;
42
+ release: (object: T) => void;
43
+ };
44
+
45
+ export const usePlacedChild = <T extends GObject.Object, P>(options: PlacedChildOptions<T, P>): ReactNode => {
46
+ const { render, ref, placement, samePlacement, place, release } = options;
47
+ const objectRef = useRef<T | null>(null);
48
+ const appliedRef = useRef<{ object: T; placement: P } | null>(null);
49
+ const captureObject = useCallback<RefCallback<T>>((object) => {
50
+ objectRef.current = object;
51
+ }, []);
52
+ const setObjectRef = useMergeRefs<T>(ref, captureObject);
53
+
54
+ useLayoutEffect(() => {
55
+ const object = objectRef.current;
56
+ const applied = appliedRef.current;
57
+ if (applied !== null && applied.object !== object) {
58
+ release(applied.object);
59
+ appliedRef.current = null;
60
+ }
61
+ if (object === null) return;
62
+ const current = appliedRef.current;
63
+ if (current === null || !samePlacement(current.placement, placement)) {
64
+ place(object, placement, current?.placement);
65
+ appliedRef.current = { object, placement };
66
+ }
67
+ });
68
+
69
+ useLayoutEffect(
70
+ () => () => {
71
+ const applied = appliedRef.current;
72
+ if (applied !== null) release(applied.object);
73
+ appliedRef.current = null;
74
+ },
75
+ [],
76
+ );
77
+
78
+ return createPortal(render(setObjectRef), rootElement);
79
+ };
@@ -1,16 +1,17 @@
1
1
  import type * as Gio from "@gtkx/gi/gio";
2
2
  import * as Gtk from "@gtkx/gi/gtk";
3
3
  import { useSignal } from "@gtkx/react";
4
+ import { isSameArray } from "@gtkx/utils";
4
5
  import { useLayoutEffect, useRef } from "react";
5
6
  import type { ItemResolver } from "../utils/item-resolver.js";
6
7
 
7
- export interface SelectionModelOptions<T, S> {
8
+ type SelectionModelOptions<T, S> = {
8
9
  base: Gio.ListModel;
9
10
  selectionMode: Gtk.SelectionMode | null | undefined;
10
- selected: string[] | null | undefined;
11
+ selectedIds: string[] | null | undefined;
11
12
  onSelectionChanged: ((ids: string[]) => void) | null | undefined;
12
13
  resolver: ItemResolver<T, S>;
13
- }
14
+ };
14
15
 
15
16
  type WrappingSelectionModel = (Gtk.SingleSelection | Gtk.MultiSelection | Gtk.NoSelection) & Gtk.SelectionModel;
16
17
 
@@ -36,7 +37,7 @@ const applySelectedPositions = (model: Gtk.SelectionModel, positions: number[]):
36
37
  return;
37
38
  }
38
39
  const requested = bitsetOf(positions);
39
- const changed = model.getSelection();
40
+ const changed = model.getSelection().copy();
40
41
  changed.difference(requested);
41
42
  if (changed.isEmpty()) return;
42
43
  model.setSelection(requested, changed);
@@ -66,22 +67,14 @@ const idsToPositions = <T, S>(ids: string[] | null | undefined, resolver: ItemRe
66
67
  if (ids === undefined || ids === null) return [];
67
68
  const positions: number[] = [];
68
69
  for (const id of ids) {
69
- const position = resolver.positionOf(id);
70
+ const position = resolver.positionOfId(id);
70
71
  if (position >= 0) positions.push(position);
71
72
  }
72
73
  return positions;
73
74
  };
74
75
 
75
- const sameIds = (a: string[], b: string[]): boolean => {
76
- if (a.length !== b.length) return false;
77
- for (let index = 0; index < a.length; index++) {
78
- if (a[index] !== b[index]) return false;
79
- }
80
- return true;
81
- };
82
-
83
76
  export const useSelectionModel = <T, S>(options: SelectionModelOptions<T, S>): Gtk.SelectionModel => {
84
- const { base, selectionMode, selected, onSelectionChanged, resolver } = options;
77
+ const { base, selectionMode, selectedIds, onSelectionChanged, resolver } = options;
85
78
  const mode = selectionMode ?? Gtk.SelectionMode.SINGLE;
86
79
 
87
80
  const modelRef = useRef<WrappingSelectionModel | null>(null);
@@ -102,7 +95,7 @@ export const useSelectionModel = <T, S>(options: SelectionModelOptions<T, S>): G
102
95
  const callback = onChangedRef.current;
103
96
  if (!callback) return;
104
97
  const ids = readSelectedIds(model, resolverRef.current);
105
- if (lastReportedRef.current !== null && sameIds(lastReportedRef.current, ids)) return;
98
+ if (lastReportedRef.current !== null && isSameArray(lastReportedRef.current, ids)) return;
106
99
  lastReportedRef.current = ids;
107
100
  callback(ids);
108
101
  };
@@ -111,11 +104,11 @@ export const useSelectionModel = <T, S>(options: SelectionModelOptions<T, S>): G
111
104
 
112
105
  useLayoutEffect(() => {
113
106
  if (model.getModel() !== base) model.setModel(base);
114
- if (selected !== undefined && selected !== null) {
115
- applySelectedPositions(model, idsToPositions(selected, resolver));
107
+ if (selectedIds !== undefined && selectedIds !== null) {
108
+ applySelectedPositions(model, idsToPositions(selectedIds, resolver));
116
109
  }
117
110
  report();
118
- }, [model, base, selected, resolver]);
111
+ }, [model, base, selectedIds, resolver]);
119
112
 
120
113
  return model;
121
114
  };
@@ -1,15 +1,20 @@
1
1
  import * as Gtk from "@gtkx/gi/gtk";
2
- import { type GObjectTarget, resolveGobjectTarget } from "@gtkx/react/internal";
2
+ import { type ObjectProp, resolveObjectProp } from "@gtkx/react/internal";
3
3
  import { useLayoutEffect, useRef } from "react";
4
- import type { ColumnRegistration } from "../contexts/column-view-context.js";
5
4
 
6
- export interface SortHandlerOptions {
7
- columnView: GObjectTarget<Gtk.ColumnView>;
5
+ export type ColumnRegistration = {
6
+ id: string;
7
+ column: Gtk.ColumnViewColumn;
8
+ sortable: boolean;
9
+ };
10
+
11
+ type SortHandlerOptions = {
12
+ columnView: ObjectProp<Gtk.ColumnView>;
8
13
  sortColumn: string | null | undefined;
9
14
  sortOrder: Gtk.SortType | null | undefined;
10
15
  onSortChanged: ((column: string | null, order: Gtk.SortType) => void) | null | undefined;
11
16
  columns: ColumnRegistration[];
12
- }
17
+ };
13
18
 
14
19
  const neutralSorter = (): Gtk.CustomSorter => Gtk.CustomSorter.new(() => 0);
15
20
 
@@ -46,10 +51,10 @@ const primarySort = (sorter: Gtk.Sorter): { column: Gtk.ColumnViewColumn | null;
46
51
  ? { column: sorter.getPrimarySortColumn(), order: sorter.getPrimarySortOrder() }
47
52
  : { column: null, order: Gtk.SortType.ASCENDING };
48
53
 
49
- interface SorterSubscription {
54
+ type SorterSubscription = {
50
55
  sorter: Gtk.Sorter | null;
51
56
  handler: ((change: Gtk.SorterChange) => void) | null;
52
- }
57
+ };
53
58
 
54
59
  const disconnectSorter = (subscription: SorterSubscription): void => {
55
60
  if (subscription.sorter !== null && subscription.handler !== null) {
@@ -89,7 +94,7 @@ export const useSortHandler = (options: SortHandlerOptions): void => {
89
94
  };
90
95
 
91
96
  useLayoutEffect(() => {
92
- const view = resolveGobjectTarget(columnView);
97
+ const view = resolveObjectProp(columnView);
93
98
  if (view === null) return;
94
99
  ensureColumnSorters(columns, activeId);
95
100
  suppressRef.current = true;
package/src/index.ts CHANGED
@@ -1,31 +1,29 @@
1
- /**
2
- * Higher-level GTKX components: declarative, opinionated abstractions layered on
3
- * top of the generated `@gtkx/jsx` element bindings and the `@gtkx/react`
4
- * runtime. Each component pairs a GObject widget with a gtkx-authored prop
5
- * surface (item models, render callbacks, selection, sorting) and is exported
6
- * under an unprefixed name to distinguish it from the raw `Gtk*`/`Adw*`/`G*`
7
- * intrinsic element it builds upon.
8
- *
9
- * @packageDocumentation
10
- */
11
-
12
- export { ColumnView, type ColumnViewComponentProps } from "./column-view.js";
13
- export { ColumnViewColumn, type ColumnViewColumnComponentProps } from "./column-view-column.js";
1
+ export {
2
+ type ColumnDef,
3
+ type ColumnDefDeclarativeProps,
4
+ ColumnView,
5
+ type ColumnViewDeclarativeProps,
6
+ type ColumnViewProps,
7
+ type ColumnViewSortProps,
8
+ } from "./column-view.js";
14
9
  export { ConstraintLayout, type ConstraintLayoutProps } from "./constraint-layout.js";
15
- export { ComboRow, type ComboRowComponentProps, DropDown, type DropDownComponentProps } from "./drop-down.js";
16
- export { GridView, type GridViewComponentProps } from "./grid-view.js";
17
- export { ListView, type ListViewComponentProps } from "./list-view.js";
18
- export { Menu, type MenuProps } from "./menu.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";
19
19
  export type {
20
- ColumnViewColumnProps,
21
- ColumnViewProps,
22
- ConstraintGuideProps,
23
- ConstraintProps,
24
- ConstraintVflProps,
25
- DropDownProps,
26
- GridViewProps,
27
- ListItem,
28
- ListViewProps,
20
+ ChildProps,
21
+ CollectionItemSizeProps,
22
+ ControlledExpansionProps,
23
+ ControlledSelectionProps,
24
+ ItemNode,
29
25
  MenuEntry,
30
- MenuItemsProps,
26
+ RenderItemProps,
27
+ SectionNode,
28
+ WidgetProps,
31
29
  } from "./types.js";