@gtkx/components 2.0.0-beta.12 → 2.0.0-beta.13

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 (100) hide show
  1. package/README.md +1 -1
  2. package/dist/column-view.d.ts.map +1 -1
  3. package/dist/column-view.js +7 -3
  4. package/dist/column-view.js.map +1 -1
  5. package/dist/combo-row.d.ts.map +1 -1
  6. package/dist/combo-row.js +3 -1
  7. package/dist/combo-row.js.map +1 -1
  8. package/dist/grid-view.d.ts.map +1 -1
  9. package/dist/grid-view.js +3 -2
  10. package/dist/grid-view.js.map +1 -1
  11. package/dist/index.d.ts +1 -2
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +0 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/internal/cells.d.ts +5 -2
  16. package/dist/internal/cells.d.ts.map +1 -1
  17. package/dist/internal/cells.js +86 -43
  18. package/dist/internal/cells.js.map +1 -1
  19. package/dist/internal/collection-index.d.ts +3 -5
  20. package/dist/internal/collection-index.d.ts.map +1 -1
  21. package/dist/internal/collection-index.js +3 -7
  22. package/dist/internal/collection-index.js.map +1 -1
  23. package/dist/internal/collection-model.d.ts +15 -4
  24. package/dist/internal/collection-model.d.ts.map +1 -1
  25. package/dist/internal/collection-model.js +154 -73
  26. package/dist/internal/collection-model.js.map +1 -1
  27. package/dist/internal/collection.d.ts +3 -3
  28. package/dist/internal/collection.d.ts.map +1 -1
  29. package/dist/internal/collection.js +11 -9
  30. package/dist/internal/collection.js.map +1 -1
  31. package/dist/internal/controlled-sync.d.ts +2 -1
  32. package/dist/internal/controlled-sync.d.ts.map +1 -1
  33. package/dist/internal/controlled-sync.js +6 -1
  34. package/dist/internal/controlled-sync.js.map +1 -1
  35. package/dist/internal/drop-down.d.ts +10 -4
  36. package/dist/internal/drop-down.d.ts.map +1 -1
  37. package/dist/internal/drop-down.js +60 -8
  38. package/dist/internal/drop-down.js.map +1 -1
  39. package/dist/internal/expansion.d.ts.map +1 -1
  40. package/dist/internal/expansion.js +46 -19
  41. package/dist/internal/expansion.js.map +1 -1
  42. package/dist/internal/first-header.d.ts +11 -0
  43. package/dist/internal/first-header.d.ts.map +1 -0
  44. package/dist/internal/first-header.js +21 -0
  45. package/dist/internal/first-header.js.map +1 -0
  46. package/dist/internal/level-splice.d.ts +27 -0
  47. package/dist/internal/level-splice.d.ts.map +1 -0
  48. package/dist/internal/level-splice.js +98 -0
  49. package/dist/internal/level-splice.js.map +1 -0
  50. package/dist/internal/selection.d.ts.map +1 -1
  51. package/dist/internal/selection.js +23 -16
  52. package/dist/internal/selection.js.map +1 -1
  53. package/dist/internal/tree-expansion.d.ts +3 -1
  54. package/dist/internal/tree-expansion.d.ts.map +1 -1
  55. package/dist/internal/tree-expansion.js +31 -2
  56. package/dist/internal/tree-expansion.js.map +1 -1
  57. package/dist/internal/tree-order.d.ts +1 -0
  58. package/dist/internal/tree-order.d.ts.map +1 -1
  59. package/dist/internal/tree-order.js +2 -1
  60. package/dist/internal/tree-order.js.map +1 -1
  61. package/dist/internal/use-collection.d.ts +1 -0
  62. package/dist/internal/use-collection.d.ts.map +1 -1
  63. package/dist/internal/use-collection.js +6 -2
  64. package/dist/internal/use-collection.js.map +1 -1
  65. package/dist/list-item-factory.d.ts +1 -1
  66. package/dist/list-item-factory.d.ts.map +1 -1
  67. package/dist/list-item-factory.js +2 -2
  68. package/dist/list-item-factory.js.map +1 -1
  69. package/dist/list-view.d.ts.map +1 -1
  70. package/dist/list-view.js +8 -3
  71. package/dist/list-view.js.map +1 -1
  72. package/dist/types.d.ts +37 -47
  73. package/dist/types.d.ts.map +1 -1
  74. package/dist/types.js.map +1 -1
  75. package/package.json +8 -8
  76. package/src/column-view.tsx +8 -4
  77. package/src/combo-row.tsx +4 -1
  78. package/src/grid-view.tsx +3 -2
  79. package/src/index.ts +0 -4
  80. package/src/internal/cells.tsx +141 -53
  81. package/src/internal/collection-index.ts +7 -14
  82. package/src/internal/collection-model.ts +211 -76
  83. package/src/internal/collection.ts +14 -13
  84. package/src/internal/controlled-sync.ts +8 -1
  85. package/src/internal/drop-down.tsx +111 -14
  86. package/src/internal/expansion.ts +52 -20
  87. package/src/internal/first-header.ts +23 -0
  88. package/src/internal/level-splice.ts +157 -0
  89. package/src/internal/selection.tsx +24 -19
  90. package/src/internal/tree-expansion.ts +52 -2
  91. package/src/internal/tree-order.ts +3 -1
  92. package/src/internal/use-collection.tsx +8 -2
  93. package/src/list-item-factory.tsx +2 -1
  94. package/src/list-view.tsx +10 -2
  95. package/src/types.ts +39 -51
  96. package/dist/tab-collection.d.ts +0 -9
  97. package/dist/tab-collection.d.ts.map +0 -1
  98. package/dist/tab-collection.js +0 -114
  99. package/dist/tab-collection.js.map +0 -1
  100. package/src/tab-collection.tsx +0 -124
@@ -1,6 +1,7 @@
1
1
  import type * as Gtk from "@gtkx/gi/gtk";
2
2
  import type { ReactElement } from "react";
3
3
  import { GtkFlattenListModel } from "@gtkx/jsx/gtk";
4
+ import { useLatestRef } from "@gtkx/react/internal";
4
5
  import { useLayoutEffect, useMemo, useState } from "react";
5
6
  import type { ExpansionProps, ListItem, ListSection, SelectionProps } from "../types.js";
6
7
  import type { Collection } from "./collection.js";
@@ -14,6 +15,7 @@ type CollectionDataOptions = {
14
15
  items?: ListItem[] | undefined;
15
16
  sections?: ListSection[] | undefined;
16
17
  isFlat?: boolean | undefined;
18
+ onSectionsFilled?: (() => void) | undefined;
17
19
  };
18
20
 
19
21
  type CollectionOptions = CollectionDataOptions & SelectionProps & ExpansionProps;
@@ -44,9 +46,13 @@ function useCollectionData(options: CollectionDataOptions): CollectionDataResult
44
46
  return gtkModel.clear;
45
47
  }, [gtkModel, root, model]);
46
48
 
49
+ const onSectionsFilled = useLatestRef(options.onSectionsFilled);
50
+
47
51
  useLayoutEffect(() => {
48
- gtkModel.sync(index);
49
- }, [gtkModel, index, root, model]);
52
+ gtkModel.sync(index, () => {
53
+ onSectionsFilled.current?.();
54
+ });
55
+ }, [gtkModel, index, root, model, onSectionsFilled]);
50
56
 
51
57
  return {
52
58
  collection,
@@ -10,6 +10,7 @@ import { NativeItemPortals, useItemCells } from "./internal/cells.js";
10
10
  */
11
11
  function ListItemFactory<T extends GObject.Object>({
12
12
  renderItem,
13
+ rowProps,
13
14
  estimatedItemHeight = -1,
14
15
  estimatedItemWidth = -1,
15
16
  }: ListItemFactoryProps<T>): ReactNode {
@@ -19,7 +20,7 @@ function ListItemFactory<T extends GObject.Object>({
19
20
  return (
20
21
  <>
21
22
  <GtkSignalListItemFactory {...cells.handlers} />
22
- <NativeItemPortals registry={cells} render={renderItem} size={size} />
23
+ <NativeItemPortals registry={cells} render={renderItem} rowProps={rowProps} size={size} />
23
24
  </>
24
25
  );
25
26
  }
package/src/list-view.tsx CHANGED
@@ -1,9 +1,12 @@
1
+ import type * as Gtk from "@gtkx/gi/gtk";
1
2
  import type { ReactNode } from "react";
2
3
  import { GtkListView, GtkSignalListItemFactory } from "@gtkx/jsx/gtk";
3
4
  import { omit } from "@gtkx/utils";
4
5
  import type { ListViewProps } from "./types.js";
5
6
  import { ItemPortals, useItemCells, useSectionHeader } from "./internal/cells.js";
7
+ import { showFirstHeader } from "./internal/first-header.js";
6
8
  import { useCollection } from "./internal/use-collection.js";
9
+ import { useWidgetRef } from "./internal/use-widget-ref.js";
7
10
 
8
11
  const LIST_VIEW_PROPS = [
9
12
  "items",
@@ -11,6 +14,7 @@ const LIST_VIEW_PROPS = [
11
14
  "isFlat",
12
15
  "renderItem",
13
16
  "renderHeader",
17
+ "rowProps",
14
18
  "selectedIds",
15
19
  "onSelectionChanged",
16
20
  "selectionMode",
@@ -19,6 +23,7 @@ const LIST_VIEW_PROPS = [
19
23
  "expanderDescriptions",
20
24
  "estimatedItemHeight",
21
25
  "estimatedItemWidth",
26
+ "ref",
22
27
  ] as const satisfies (keyof ListViewProps)[];
23
28
 
24
29
  /**
@@ -26,17 +31,19 @@ const LIST_VIEW_PROPS = [
26
31
  * controlled selection, controlled tree expansion, and estimated item sizing.
27
32
  */
28
33
  function ListView<T = unknown, S = unknown>(props: ListViewProps<T, S>): ReactNode {
29
- const { renderItem, renderHeader, expandedIds, expanderDescriptions } = props;
34
+ const { renderItem, renderHeader, rowProps, expandedIds, expanderDescriptions, ref } = props;
30
35
  const { estimatedItemHeight, estimatedItemWidth } = props;
31
36
  const rest = omit(props, LIST_VIEW_PROPS);
32
37
  const size = { width: estimatedItemWidth ?? -1, height: estimatedItemHeight ?? -1 };
33
- const { collection, selection } = useCollection(props);
38
+ const [view, refCallback] = useWidgetRef<Gtk.ListView>(ref);
39
+ const { collection, selection } = useCollection({ ...props, onSectionsFilled: () => showFirstHeader(view) });
34
40
  const itemCells = useItemCells(size);
35
41
  const header = useSectionHeader(renderHeader, collection, size);
36
42
 
37
43
  return (
38
44
  <>
39
45
  <GtkListView
46
+ ref={refCallback}
40
47
  model={selection}
41
48
  factory={<GtkSignalListItemFactory {...itemCells.handlers} />}
42
49
  {...header.factoryProps}
@@ -45,6 +52,7 @@ function ListView<T = unknown, S = unknown>(props: ListViewProps<T, S>): ReactNo
45
52
  <ItemPortals
46
53
  registry={itemCells}
47
54
  render={renderItem}
55
+ rowProps={rowProps}
48
56
  collection={collection}
49
57
  expandedIds={expandedIds}
50
58
  expanderDescriptions={expanderDescriptions}
package/src/types.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type * as Adw from "@gtkx/gi/adw";
2
2
  import type * as GObject from "@gtkx/gi/gobject";
3
3
  import type * as Gtk from "@gtkx/gi/gtk";
4
- import type { AdwComboRowProps, AdwTabPageProps, AdwToastProps } from "@gtkx/jsx/adw";
4
+ import type { AdwComboRowProps, AdwToastProps } from "@gtkx/jsx/adw";
5
5
  import type {
6
6
  GtkColumnViewColumnProps,
7
7
  GtkColumnViewProps,
@@ -17,7 +17,10 @@ import type { ReactNode, RefObject } from "react";
17
17
  * arbitrary value. Nested items form a tree.
18
18
  */
19
19
  type ListItem<T = unknown> = {
20
- /** Stable identifier, unique across the collection, used to preserve identity across updates and selection. */
20
+ /**
21
+ * Stable identifier that names the item in selection and expansion, so keep it unique across the collection.
22
+ * A row keeps its component state while its id stays among the same siblings.
23
+ */
21
24
  id: string;
22
25
  /** Payload handed to the cell renderer as `ListItemRenderArgs.item`. */
23
26
  value: T;
@@ -45,7 +48,10 @@ type ListSection<S = unknown, T = unknown> = {
45
48
  type ListItemRenderArgs<T> = {
46
49
  /** Value of the `ListItem` being rendered. */
47
50
  item: T;
48
- /** Position of the item in the flattened item list, which excludes section headers and collapsed tree rows. */
51
+ /**
52
+ * Position of the item in the flattened item list, which excludes section headers and collapsed tree rows.
53
+ * A drop-down popup filtered by `enableSearch` still passes this position, not the row's place in the results.
54
+ */
49
55
  index: number;
50
56
  /** Depth of the item within a tree, starting at zero for top-level items. */
51
57
  depth?: number | undefined;
@@ -54,9 +60,10 @@ type ListItemRenderArgs<T> = {
54
60
  };
55
61
 
56
62
  /**
57
- * Properties applied to the row that carries one item's cells. A Gtk.ColumnViewRow is not a widget, so
58
- * these are the only handles it offers; anything omitted falls back to the row's own default, which
59
- * leaves the accessibility strings unset rather than setting them to an empty string.
63
+ * Properties applied to the row that carries one item: the Gtk.ListItem of a list or grid, or the
64
+ * Gtk.ColumnViewRow of a table. Neither is a widget, so these are the only handles they offer; anything
65
+ * omitted falls back to the row's own default, which leaves the accessibility strings unset rather than
66
+ * setting them to an empty string.
60
67
  */
61
68
  type ListRowProps = {
62
69
  /** Name a screen reader announces for the row, through `accessible-label`. */
@@ -94,6 +101,14 @@ type ItemSizeProps = {
94
101
 
95
102
  type ListItemFactoryProps<T extends GObject.Object> = ItemSizeProps & {
96
103
  renderItem: ListItemRenderer<T>;
104
+ /** Resolves the props of the row carrying one model object, such as its screen-reader label. */
105
+ rowProps?: ListRowPropsResolver<T> | null | undefined;
106
+ };
107
+
108
+ /** Per-row properties shared by the collection views. */
109
+ type RowPropsProps<T> = {
110
+ /** Resolves the props of the row carrying one item, such as its screen-reader label. */
111
+ rowProps?: ListRowPropsResolver<T> | null | undefined;
97
112
  };
98
113
 
99
114
  /** The flat-collection hint shared by {@link ListView} and {@link ColumnView}. */
@@ -113,7 +128,10 @@ type SelectionProps = {
113
128
  * user's selection is adopted into it.
114
129
  */
115
130
  selectedIds?: string[] | null | undefined;
116
- /** Called with one id per selected row whenever the selection changes, and once on mount. */
131
+ /**
132
+ * Called with one id per selected row when the user or the widget changes the selection, or when an item
133
+ * `selectedIds` names is not in the collection; a `selectedIds` change is not reported back.
134
+ */
117
135
  onSelectionChanged?: ((ids: string[]) => void) | null | undefined;
118
136
  /** How much the user may select, one item at a time unless `MULTIPLE` or `NONE` is given. */
119
137
  selectionMode?: Gtk.SelectionMode | null | undefined;
@@ -138,7 +156,10 @@ type ExpansionProps = {
138
156
  * user's expansion is adopted into it.
139
157
  */
140
158
  expandedIds?: string[] | null | undefined;
141
- /** Called with one id per expanded row, in visible order, whenever expansion changes. */
159
+ /**
160
+ * Called with one id per expanded row, in visible order, when the user or the widget changes expansion, or
161
+ * when a row `expandedIds` names cannot expand; an `expandedIds` change is not reported back.
162
+ */
142
163
  onExpandedChange?: ((ids: string[]) => void) | null | undefined;
143
164
  /**
144
165
  * Wording announced for the expander of every row that can expand, describing what activating it does.
@@ -182,6 +203,11 @@ type ColumnViewColumn<T = unknown> = Omit<GtkColumnViewColumnProps, "factory" |
182
203
  renderCell: ListItemRenderer<T>;
183
204
  /** Makes the column header clickable, reporting the choice through `onSortChanged`. */
184
205
  isSortable?: boolean | undefined;
206
+ /**
207
+ * Draws the tree expander and depth indentation in this column. Without a visible column marked this way,
208
+ * the first visible column draws them.
209
+ */
210
+ hasExpander?: boolean | undefined;
185
211
  /** Menu element popped up as the column header's context menu, on a right-click. */
186
212
  headerMenu?: ReactNode;
187
213
  };
@@ -192,11 +218,10 @@ type ColumnViewOwnProps<T, S> = SelectionProps &
192
218
  SortProps &
193
219
  SourceProps<T, S> &
194
220
  FlatnessProps &
221
+ RowPropsProps<T> &
195
222
  Omit<ItemSizeProps, "estimatedItemWidth"> & {
196
223
  /** Columns to render, in order; each carries its own cell renderer. */
197
224
  columns: ColumnViewColumn<T>[];
198
- /** Resolves the props of the row carrying one item's cells, such as its screen-reader label. */
199
- rowProps?: ListRowPropsResolver<T> | null | undefined;
200
225
  };
201
226
 
202
227
  /**
@@ -235,7 +260,8 @@ type DropDownProps<T = unknown, S = unknown> = DropDownWidgetProps<GtkDropDownPr
235
260
 
236
261
  /** The declarative collection props {@link GridView} adds on top of Gtk.GridView's own. */
237
262
  type GridViewOwnProps<T> = ItemSizeProps &
238
- SelectionProps & {
263
+ SelectionProps &
264
+ RowPropsProps<T> & {
239
265
  /** Items to render as cells; a grid is always flat, so `ListItem.children` is ignored. */
240
266
  items?: ListItem<T>[] | undefined;
241
267
  /** Renders the contents of one cell. */
@@ -253,7 +279,8 @@ type ListViewOwnProps<T, S> = ItemSizeProps &
253
279
  SelectionProps &
254
280
  ExpansionProps &
255
281
  SourceProps<T, S> &
256
- FlatnessProps & {
282
+ FlatnessProps &
283
+ RowPropsProps<T> & {
257
284
  /** Renders the contents of one row. */
258
285
  renderItem: ListItemRenderer<T>;
259
286
  };
@@ -294,42 +321,6 @@ type ToastProviderProps = {
294
321
  children?: ReactNode | undefined;
295
322
  };
296
323
 
297
- /** Describes the JSX page rendered for each value in a {@link TabCollection}. */
298
- type TabCollectionOptions<T> = {
299
- /** Native page props and JSX content. Call getPage in event handlers to access the mounted page. The collection owns its ref and parent relationship. */
300
- pageProps: (item: T, getPage: () => Adw.TabPage | null) => Omit<AdwTabPageProps, "ref" | "parent">;
301
- };
302
-
303
- /** Options for adding a page to a {@link TabCollection}. */
304
- type TabAppendOptions = {
305
- /** Inserts a related page after this parent and its descendants, using native tab ancestry. */
306
- parent?: Adw.TabPage | undefined;
307
- /** Selects the new page by default. */
308
- select?: boolean | undefined;
309
- };
310
-
311
- /**
312
- * Owns independent JSX tab roots across native view transfers. Its lifetime can span several windows.
313
- * Remove a page after accepting its native close request, remove a view's pages before destroying that
314
- * view, and dispose the collection when its owner is finished.
315
- */
316
- type TabCollection<T> = {
317
- /** Renders synchronously and returns the native page, including from an overview's create-tab handler. */
318
- append: (view: Adw.TabView, item: T, options?: TabAppendOptions) => Adw.TabPage;
319
- /** Returns a managed page's current value, or undefined for an unmanaged page. */
320
- get: (page: Adw.TabPage) => T | undefined;
321
- /** Updates the value and rerenders the existing JSX root, preserving component state and native identity. */
322
- update: (page: Adw.TabPage, item: T) => void;
323
- /** Unmounts a managed page from its current native owner; already removed pages are ignored. */
324
- remove: (page: Adw.TabPage) => void;
325
- /** Unmounts this collection's pages currently belonging to the given view, including transferred pages. */
326
- removeView: (view: Adw.TabView) => void;
327
- /** Moves a managed page to another view, appending within its pinned or unpinned group by default. */
328
- transfer: (page: Adw.TabPage, target: Adw.TabView, position?: number) => void;
329
- /** Unmounts every managed page. The collection may subsequently be reused. */
330
- dispose: () => void;
331
- };
332
-
333
324
  export {
334
325
  type SelectionProps,
335
326
  type ExpanderDescriptions,
@@ -354,7 +345,4 @@ export {
354
345
  type ToastOptions,
355
346
  type ToastController,
356
347
  type ToastProviderProps,
357
- type TabAppendOptions,
358
- type TabCollection,
359
- type TabCollectionOptions,
360
348
  };
@@ -1,9 +0,0 @@
1
- import type { TabCollection, TabCollectionOptions } from "./types.js";
2
- /**
3
- * Creates a collection of JSX pages whose React state survives native transfers and source-window
4
- * closure. Call append from an event handler because it commits synchronously. Page content lives in
5
- * independent roots: pass context-dependent values through the item or wrap the content in providers.
6
- */
7
- declare const createTabCollection: <T>(options: TabCollectionOptions<T>) => TabCollection<T>;
8
- export { createTabCollection };
9
- //# sourceMappingURL=tab-collection.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tab-collection.d.ts","sourceRoot":"","sources":["../src/tab-collection.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AA6BtE;;;;GAIG;AACH,QAAA,MAAM,mBAAmB,GAAI,CAAC,WAAY,oBAAoB,CAAC,CAAC,CAAC,KAAG,aAAa,CAAC,CAAC,CAkFlF,CAAC;AAEF,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
@@ -1,114 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import * as Adw from "@gtkx/gi/adw";
3
- import { AdwTabPage } from "@gtkx/jsx/adw";
4
- import { createRoot, flushSync } from "@gtkx/react";
5
- import { getClassType } from "@gtkx/runtime";
6
- import { createRef, useEffect } from "react";
7
- const ManagedTab = ({ handle, item, options, forget }) => {
8
- const { page: pageRef, parent, getPage } = handle;
9
- useEffect(() => () => forget(handle), [handle, forget]);
10
- return _jsx(AdwTabPage, { ...options.pageProps(item, getPage), ref: pageRef, parent: parent });
11
- };
12
- const ownerOf = (page) => {
13
- const owner = page.getChild().getAncestor(getClassType(Adw.TabView));
14
- if (!(owner instanceof Adw.TabView))
15
- throw new Error("The tab has no native view");
16
- return owner;
17
- };
18
- /**
19
- * Creates a collection of JSX pages whose React state survives native transfers and source-window
20
- * closure. Call append from an event handler because it commits synchronously. Page content lives in
21
- * independent roots: pass context-dependent values through the item or wrap the content in providers.
22
- */
23
- const createTabCollection = (options) => {
24
- const handles = new Set();
25
- const forget = (handle) => {
26
- handles.delete(handle);
27
- };
28
- const find = (page) => [...handles].find((handle) => handle.page.current === page);
29
- const render = (handle) => {
30
- handle.root.render(_jsx(ManagedTab, { handle: handle, item: handle.item, options: options, forget: forget }));
31
- };
32
- const remove = (handle) => {
33
- handles.delete(handle);
34
- handle.root.unmount();
35
- };
36
- return {
37
- append: (view, item, appendOptions = {}) => {
38
- if (appendOptions.parent && ownerOf(appendOptions.parent) !== view) {
39
- throw new RangeError("The parent page belongs to another tab view");
40
- }
41
- const pageRef = createRef();
42
- const handle = {
43
- root: createRoot(view),
44
- page: pageRef,
45
- getPage: () => pageRef.current,
46
- parent: appendOptions.parent,
47
- item,
48
- };
49
- handles.add(handle);
50
- try {
51
- flushSync(() => render(handle));
52
- }
53
- catch (error) {
54
- remove(handle);
55
- throw error;
56
- }
57
- const page = handle.page.current;
58
- if (page === null) {
59
- remove(handle);
60
- throw new Error("The tab page did not render");
61
- }
62
- if (appendOptions.parent === undefined) {
63
- view.reorderPage(page, page.getPinned() ? view.getNPinnedPages() - 1 : view.getNPages() - 1);
64
- }
65
- if (appendOptions.select !== false)
66
- view.setSelectedPage(page);
67
- return page;
68
- },
69
- get: (page) => find(page)?.item,
70
- update: (page, item) => {
71
- const handle = find(page);
72
- if (handle === undefined)
73
- throw new Error("The tab is not managed by this collection");
74
- handle.item = item;
75
- render(handle);
76
- },
77
- remove: (page) => {
78
- const handle = find(page);
79
- if (handle)
80
- remove(handle);
81
- },
82
- removeView: (view) => {
83
- const pages = Array.from({ length: view.getNPages() }, (_, index) => view.getNthPage(index));
84
- for (const page of pages) {
85
- const handle = find(page);
86
- if (handle)
87
- remove(handle);
88
- }
89
- },
90
- transfer: (page, target, position) => {
91
- if (find(page) === undefined)
92
- throw new Error("The tab is not managed by this collection");
93
- const source = ownerOf(page);
94
- const minimum = page.getPinned() ? 0 : target.getNPinnedPages();
95
- const maximum = page.getPinned() ? target.getNPinnedPages() : target.getNPages();
96
- const index = position ?? maximum;
97
- if (!Number.isInteger(index) || index < minimum || index > maximum) {
98
- throw new RangeError("The position is outside the tab's pinned or unpinned group");
99
- }
100
- if (source === target) {
101
- target.reorderPage(page, Math.min(index, maximum - 1));
102
- }
103
- else {
104
- source.transferPage(page, target, index);
105
- }
106
- },
107
- dispose: () => {
108
- for (const handle of [...handles])
109
- remove(handle);
110
- },
111
- };
112
- };
113
- export { createTabCollection };
114
- //# sourceMappingURL=tab-collection.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tab-collection.js","sourceRoot":"","sources":["../src/tab-collection.tsx"],"names":[],"mappings":"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","sourcesContent":["import * as Adw from \"@gtkx/gi/adw\";\nimport { AdwTabPage } from \"@gtkx/jsx/adw\";\nimport { createRoot, flushSync, type Root } from \"@gtkx/react\";\nimport { getClassType } from \"@gtkx/runtime\";\nimport { createRef, type RefObject, useEffect } from \"react\";\nimport type { TabCollection, TabCollectionOptions } from \"./types.js\";\n\ntype TabHandle<T> = {\n root: Root;\n page: RefObject<Adw.TabPage | null>;\n getPage: () => Adw.TabPage | null;\n parent: Adw.TabPage | undefined;\n item: T;\n};\n\ntype ManagedTabProps<T> = {\n handle: TabHandle<T>;\n item: T;\n options: TabCollectionOptions<T>;\n forget: (handle: TabHandle<T>) => void;\n};\n\nconst ManagedTab = <T,>({ handle, item, options, forget }: ManagedTabProps<T>) => {\n const { page: pageRef, parent, getPage } = handle;\n useEffect(() => () => forget(handle), [handle, forget]);\n return <AdwTabPage {...options.pageProps(item, getPage)} ref={pageRef} parent={parent} />;\n};\n\nconst ownerOf = (page: Adw.TabPage): Adw.TabView => {\n const owner = page.getChild().getAncestor(getClassType(Adw.TabView));\n if (!(owner instanceof Adw.TabView)) throw new Error(\"The tab has no native view\");\n return owner;\n};\n\n/**\n * Creates a collection of JSX pages whose React state survives native transfers and source-window\n * closure. Call append from an event handler because it commits synchronously. Page content lives in\n * independent roots: pass context-dependent values through the item or wrap the content in providers.\n */\nconst createTabCollection = <T,>(options: TabCollectionOptions<T>): TabCollection<T> => {\n const handles = new Set<TabHandle<T>>();\n const forget = (handle: TabHandle<T>) => {\n handles.delete(handle);\n };\n const find = (page: Adw.TabPage) => [...handles].find((handle) => handle.page.current === page);\n const render = (handle: TabHandle<T>) => {\n handle.root.render(<ManagedTab handle={handle} item={handle.item} options={options} forget={forget} />);\n };\n const remove = (handle: TabHandle<T>) => {\n handles.delete(handle);\n handle.root.unmount();\n };\n\n return {\n append: (view, item, appendOptions = {}) => {\n if (appendOptions.parent && ownerOf(appendOptions.parent) !== view) {\n throw new RangeError(\"The parent page belongs to another tab view\");\n }\n const pageRef = createRef<Adw.TabPage>();\n const handle: TabHandle<T> = {\n root: createRoot(view),\n page: pageRef,\n getPage: () => pageRef.current,\n parent: appendOptions.parent,\n item,\n };\n handles.add(handle);\n try {\n flushSync(() => render(handle));\n } catch (error) {\n remove(handle);\n throw error;\n }\n const page = handle.page.current;\n if (page === null) {\n remove(handle);\n throw new Error(\"The tab page did not render\");\n }\n if (appendOptions.parent === undefined) {\n view.reorderPage(page, page.getPinned() ? view.getNPinnedPages() - 1 : view.getNPages() - 1);\n }\n if (appendOptions.select !== false) view.setSelectedPage(page);\n return page;\n },\n get: (page) => find(page)?.item,\n update: (page, item) => {\n const handle = find(page);\n if (handle === undefined) throw new Error(\"The tab is not managed by this collection\");\n handle.item = item;\n render(handle);\n },\n remove: (page) => {\n const handle = find(page);\n if (handle) remove(handle);\n },\n removeView: (view) => {\n const pages = Array.from({ length: view.getNPages() }, (_, index) => view.getNthPage(index));\n for (const page of pages) {\n const handle = find(page);\n if (handle) remove(handle);\n }\n },\n transfer: (page, target, position) => {\n if (find(page) === undefined) throw new Error(\"The tab is not managed by this collection\");\n const source = ownerOf(page);\n const minimum = page.getPinned() ? 0 : target.getNPinnedPages();\n const maximum = page.getPinned() ? target.getNPinnedPages() : target.getNPages();\n const index = position ?? maximum;\n if (!Number.isInteger(index) || index < minimum || index > maximum) {\n throw new RangeError(\"The position is outside the tab's pinned or unpinned group\");\n }\n if (source === target) {\n target.reorderPage(page, Math.min(index, maximum - 1));\n } else {\n source.transferPage(page, target, index);\n }\n },\n dispose: () => {\n for (const handle of [...handles]) remove(handle);\n },\n };\n};\n\nexport { createTabCollection };\n"]}
@@ -1,124 +0,0 @@
1
- import * as Adw from "@gtkx/gi/adw";
2
- import { AdwTabPage } from "@gtkx/jsx/adw";
3
- import { createRoot, flushSync, type Root } from "@gtkx/react";
4
- import { getClassType } from "@gtkx/runtime";
5
- import { createRef, type RefObject, useEffect } from "react";
6
- import type { TabCollection, TabCollectionOptions } from "./types.js";
7
-
8
- type TabHandle<T> = {
9
- root: Root;
10
- page: RefObject<Adw.TabPage | null>;
11
- getPage: () => Adw.TabPage | null;
12
- parent: Adw.TabPage | undefined;
13
- item: T;
14
- };
15
-
16
- type ManagedTabProps<T> = {
17
- handle: TabHandle<T>;
18
- item: T;
19
- options: TabCollectionOptions<T>;
20
- forget: (handle: TabHandle<T>) => void;
21
- };
22
-
23
- const ManagedTab = <T,>({ handle, item, options, forget }: ManagedTabProps<T>) => {
24
- const { page: pageRef, parent, getPage } = handle;
25
- useEffect(() => () => forget(handle), [handle, forget]);
26
- return <AdwTabPage {...options.pageProps(item, getPage)} ref={pageRef} parent={parent} />;
27
- };
28
-
29
- const ownerOf = (page: Adw.TabPage): Adw.TabView => {
30
- const owner = page.getChild().getAncestor(getClassType(Adw.TabView));
31
- if (!(owner instanceof Adw.TabView)) throw new Error("The tab has no native view");
32
- return owner;
33
- };
34
-
35
- /**
36
- * Creates a collection of JSX pages whose React state survives native transfers and source-window
37
- * closure. Call append from an event handler because it commits synchronously. Page content lives in
38
- * independent roots: pass context-dependent values through the item or wrap the content in providers.
39
- */
40
- const createTabCollection = <T,>(options: TabCollectionOptions<T>): TabCollection<T> => {
41
- const handles = new Set<TabHandle<T>>();
42
- const forget = (handle: TabHandle<T>) => {
43
- handles.delete(handle);
44
- };
45
- const find = (page: Adw.TabPage) => [...handles].find((handle) => handle.page.current === page);
46
- const render = (handle: TabHandle<T>) => {
47
- handle.root.render(<ManagedTab handle={handle} item={handle.item} options={options} forget={forget} />);
48
- };
49
- const remove = (handle: TabHandle<T>) => {
50
- handles.delete(handle);
51
- handle.root.unmount();
52
- };
53
-
54
- return {
55
- append: (view, item, appendOptions = {}) => {
56
- if (appendOptions.parent && ownerOf(appendOptions.parent) !== view) {
57
- throw new RangeError("The parent page belongs to another tab view");
58
- }
59
- const pageRef = createRef<Adw.TabPage>();
60
- const handle: TabHandle<T> = {
61
- root: createRoot(view),
62
- page: pageRef,
63
- getPage: () => pageRef.current,
64
- parent: appendOptions.parent,
65
- item,
66
- };
67
- handles.add(handle);
68
- try {
69
- flushSync(() => render(handle));
70
- } catch (error) {
71
- remove(handle);
72
- throw error;
73
- }
74
- const page = handle.page.current;
75
- if (page === null) {
76
- remove(handle);
77
- throw new Error("The tab page did not render");
78
- }
79
- if (appendOptions.parent === undefined) {
80
- view.reorderPage(page, page.getPinned() ? view.getNPinnedPages() - 1 : view.getNPages() - 1);
81
- }
82
- if (appendOptions.select !== false) view.setSelectedPage(page);
83
- return page;
84
- },
85
- get: (page) => find(page)?.item,
86
- update: (page, item) => {
87
- const handle = find(page);
88
- if (handle === undefined) throw new Error("The tab is not managed by this collection");
89
- handle.item = item;
90
- render(handle);
91
- },
92
- remove: (page) => {
93
- const handle = find(page);
94
- if (handle) remove(handle);
95
- },
96
- removeView: (view) => {
97
- const pages = Array.from({ length: view.getNPages() }, (_, index) => view.getNthPage(index));
98
- for (const page of pages) {
99
- const handle = find(page);
100
- if (handle) remove(handle);
101
- }
102
- },
103
- transfer: (page, target, position) => {
104
- if (find(page) === undefined) throw new Error("The tab is not managed by this collection");
105
- const source = ownerOf(page);
106
- const minimum = page.getPinned() ? 0 : target.getNPinnedPages();
107
- const maximum = page.getPinned() ? target.getNPinnedPages() : target.getNPages();
108
- const index = position ?? maximum;
109
- if (!Number.isInteger(index) || index < minimum || index > maximum) {
110
- throw new RangeError("The position is outside the tab's pinned or unpinned group");
111
- }
112
- if (source === target) {
113
- target.reorderPage(page, Math.min(index, maximum - 1));
114
- } else {
115
- source.transferPage(page, target, index);
116
- }
117
- },
118
- dispose: () => {
119
- for (const handle of [...handles]) remove(handle);
120
- },
121
- };
122
- };
123
-
124
- export { createTabCollection };