@gtkx/components 2.0.0-beta.10 → 2.0.0-beta.11
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.
- package/README.md +8 -168
- package/dist/column-view.d.ts.map +1 -1
- package/dist/column-view.js +19 -9
- package/dist/column-view.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/cells.d.ts +8 -1
- package/dist/internal/cells.d.ts.map +1 -1
- package/dist/internal/cells.js +84 -19
- package/dist/internal/cells.js.map +1 -1
- package/dist/internal/collection-index.d.ts +3 -2
- package/dist/internal/collection-index.d.ts.map +1 -1
- package/dist/internal/collection-index.js +3 -3
- package/dist/internal/collection-index.js.map +1 -1
- package/dist/internal/collection-model.d.ts +23 -2
- package/dist/internal/collection-model.d.ts.map +1 -1
- package/dist/internal/collection-model.js +118 -98
- package/dist/internal/collection-model.js.map +1 -1
- package/dist/internal/collection.d.ts +5 -4
- package/dist/internal/collection.d.ts.map +1 -1
- package/dist/internal/collection.js +20 -21
- package/dist/internal/collection.js.map +1 -1
- package/dist/internal/controlled-sync.d.ts +6 -8
- package/dist/internal/controlled-sync.d.ts.map +1 -1
- package/dist/internal/controlled-sync.js +3 -3
- package/dist/internal/controlled-sync.js.map +1 -1
- package/dist/internal/drop-down.d.ts.map +1 -1
- package/dist/internal/drop-down.js +43 -44
- package/dist/internal/drop-down.js.map +1 -1
- package/dist/internal/expansion.js +4 -4
- package/dist/internal/expansion.js.map +1 -1
- package/dist/internal/selection.d.ts +1 -0
- package/dist/internal/selection.d.ts.map +1 -1
- package/dist/internal/selection.js +6 -6
- package/dist/internal/selection.js.map +1 -1
- package/dist/internal/tree-expansion.d.ts.map +1 -1
- package/dist/internal/tree-expansion.js +2 -11
- package/dist/internal/tree-expansion.js.map +1 -1
- package/dist/internal/tree-order.d.ts.map +1 -1
- package/dist/internal/tree-order.js +32 -76
- package/dist/internal/tree-order.js.map +1 -1
- package/dist/internal/use-collection.d.ts +5 -1
- package/dist/internal/use-collection.d.ts.map +1 -1
- package/dist/internal/use-collection.js +21 -7
- package/dist/internal/use-collection.js.map +1 -1
- package/dist/list-item-factory.d.ts +10 -0
- package/dist/list-item-factory.d.ts.map +1 -0
- package/dist/list-item-factory.js +14 -0
- package/dist/list-item-factory.js.map +1 -0
- package/dist/toast.d.ts +3 -5
- package/dist/toast.d.ts.map +1 -1
- package/dist/toast.js +3 -12
- package/dist/toast.js.map +1 -1
- package/dist/types.d.ts +33 -50
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +12 -10
- package/src/column-view.tsx +23 -12
- package/src/index.ts +3 -2
- package/src/internal/cells.tsx +137 -20
- package/src/internal/collection-index.ts +8 -6
- package/src/internal/collection-model.ts +156 -134
- package/src/internal/collection.ts +28 -24
- package/src/internal/controlled-sync.ts +9 -12
- package/src/internal/drop-down.tsx +64 -60
- package/src/internal/expansion.ts +4 -4
- package/src/internal/selection.tsx +8 -8
- package/src/internal/tree-expansion.ts +2 -14
- package/src/internal/tree-order.ts +33 -121
- package/src/internal/use-collection.tsx +29 -8
- package/src/list-item-factory.tsx +27 -0
- package/src/toast.tsx +4 -18
- package/src/types.ts +35 -51
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gtkx/components",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.0-beta.11",
|
|
4
|
+
"description": "Collection views, selection controls, and toasts for GTKX.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gtkx",
|
|
7
7
|
"gtk",
|
|
@@ -53,18 +53,20 @@
|
|
|
53
53
|
"node": ">=26.7.0"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@gtkx/react": "2.0.0-beta.
|
|
57
|
-
"@gtkx/runtime": "2.0.0-beta.
|
|
58
|
-
"@gtkx/utils": "2.0.0-beta.
|
|
56
|
+
"@gtkx/react": "2.0.0-beta.11",
|
|
57
|
+
"@gtkx/runtime": "2.0.0-beta.11",
|
|
58
|
+
"@gtkx/utils": "2.0.0-beta.11"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"react": "^19.
|
|
61
|
+
"@types/react": "^19.3",
|
|
62
|
+
"react": "^19.3"
|
|
62
63
|
},
|
|
63
64
|
"devDependencies": {
|
|
64
|
-
"@gtkx/config": "2.0.0-beta.
|
|
65
|
-
"@gtkx/testing": "2.0.0-beta.
|
|
66
|
-
"@gtkx/vitest": "2.0.0-beta.
|
|
67
|
-
"
|
|
65
|
+
"@gtkx/config": "2.0.0-beta.11",
|
|
66
|
+
"@gtkx/testing": "2.0.0-beta.11",
|
|
67
|
+
"@gtkx/vitest": "2.0.0-beta.11",
|
|
68
|
+
"@types/react": "^19.3.0",
|
|
69
|
+
"vitest": "^5.0.1"
|
|
68
70
|
},
|
|
69
71
|
"scripts": {
|
|
70
72
|
"release": "tsx ../../scripts/release-package.ts"
|
package/src/column-view.tsx
CHANGED
|
@@ -3,15 +3,17 @@ import * as Gtk from "@gtkx/gi/gtk";
|
|
|
3
3
|
import { GtkColumnView, GtkColumnViewColumn, GtkCustomSorter, GtkSignalListItemFactory } from "@gtkx/jsx/gtk";
|
|
4
4
|
import { useSignal } from "@gtkx/react";
|
|
5
5
|
import { omit } from "@gtkx/utils";
|
|
6
|
-
import {
|
|
6
|
+
import { useMemo, useRef } from "react";
|
|
7
7
|
import type { CellSize } from "./internal/cells.js";
|
|
8
8
|
import type { Collection } from "./internal/collection.js";
|
|
9
9
|
import type { ColumnViewColumn, ColumnViewProps, ExpanderDescriptions, SortProps } from "./types.js";
|
|
10
10
|
import { ItemPortals, useItemCells, useRowProps, useSectionHeader } from "./internal/cells.js";
|
|
11
|
+
import { useControlledSync } from "./internal/controlled-sync.js";
|
|
11
12
|
import { useCollection } from "./internal/use-collection.js";
|
|
12
13
|
import { useWidgetRef } from "./internal/use-widget-ref.js";
|
|
13
14
|
|
|
14
15
|
type SortTarget = { view: Gtk.ColumnView; column: Gtk.ColumnViewColumn | null };
|
|
16
|
+
type SortRequest = Pick<SortProps, "sortColumn" | "sortOrder">;
|
|
15
17
|
|
|
16
18
|
type ColumnCellsProps = {
|
|
17
19
|
column: ColumnViewColumn<never>;
|
|
@@ -43,7 +45,6 @@ const COLUMN_VIEW_PROPS = [
|
|
|
43
45
|
"sortOrder",
|
|
44
46
|
"onSortChanged",
|
|
45
47
|
"estimatedItemHeight",
|
|
46
|
-
"children",
|
|
47
48
|
"ref",
|
|
48
49
|
] as const satisfies (keyof ColumnViewProps)[];
|
|
49
50
|
|
|
@@ -93,16 +94,18 @@ const applySort = (
|
|
|
93
94
|
}
|
|
94
95
|
};
|
|
95
96
|
|
|
96
|
-
const
|
|
97
|
+
const didEmitSortChanged = (
|
|
97
98
|
sorter: Gtk.ColumnViewSorter | null,
|
|
98
99
|
sorting: RefObject<boolean>,
|
|
99
100
|
sort: SortProps,
|
|
100
|
-
):
|
|
101
|
+
): boolean => {
|
|
101
102
|
if (sorter === null || sorting.current) {
|
|
102
|
-
return;
|
|
103
|
+
return false;
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
sort.onSortChanged?.(sorter.getPrimarySortColumn()?.getId() ?? null, sorter.getPrimarySortOrder());
|
|
107
|
+
|
|
108
|
+
return true;
|
|
106
109
|
};
|
|
107
110
|
|
|
108
111
|
const syncSort = (
|
|
@@ -124,16 +127,24 @@ const useColumnSorting = (view: Gtk.ColumnView | null, sort: SortProps, columns:
|
|
|
124
127
|
const sorting = useRef(false);
|
|
125
128
|
const sorter = view?.getSorter() ?? null;
|
|
126
129
|
const columnSorter = sorter instanceof Gtk.ColumnViewSorter ? sorter : null;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
+
const { sortColumn, sortOrder } = sort;
|
|
131
|
+
const request = useMemo<SortRequest>(() => ({ sortColumn, sortOrder }), [sortColumn, sortOrder]);
|
|
132
|
+
const markDrift = useControlledSync({
|
|
133
|
+
value: request,
|
|
134
|
+
source: columns,
|
|
135
|
+
target: view,
|
|
136
|
+
apply: (value) => {
|
|
137
|
+
syncSort(sorting, view, value.sortColumn, value.sortOrder);
|
|
138
|
+
},
|
|
130
139
|
});
|
|
131
140
|
|
|
132
|
-
|
|
141
|
+
useSignal(columnSorter, "changed", (): void => {
|
|
142
|
+
const didEmit = didEmitSortChanged(columnSorter, sorting, sort);
|
|
133
143
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
144
|
+
if (sortColumn !== undefined && didEmit) {
|
|
145
|
+
markDrift();
|
|
146
|
+
}
|
|
147
|
+
});
|
|
137
148
|
};
|
|
138
149
|
|
|
139
150
|
const ColumnCells = ({
|
package/src/index.ts
CHANGED
|
@@ -2,8 +2,9 @@ export { ColumnView } from "./column-view.js";
|
|
|
2
2
|
export { ComboRow } from "./combo-row.js";
|
|
3
3
|
export { DropDown } from "./drop-down.js";
|
|
4
4
|
export { GridView } from "./grid-view.js";
|
|
5
|
+
export { ListItemFactory } from "./list-item-factory.js";
|
|
5
6
|
export { ListView } from "./list-view.js";
|
|
6
|
-
export { ToastProvider, useToast
|
|
7
|
+
export { ToastProvider, useToast } from "./toast.js";
|
|
7
8
|
export type {
|
|
8
9
|
ColumnViewColumn,
|
|
9
10
|
ColumnViewProps,
|
|
@@ -12,6 +13,7 @@ export type {
|
|
|
12
13
|
ExpanderDescriptions,
|
|
13
14
|
GridViewProps,
|
|
14
15
|
ListItem,
|
|
16
|
+
ListItemFactoryProps,
|
|
15
17
|
ListItemRenderArgs,
|
|
16
18
|
ListItemRenderer,
|
|
17
19
|
ListRowProps,
|
|
@@ -22,6 +24,5 @@ export type {
|
|
|
22
24
|
ListViewProps,
|
|
23
25
|
ToastController,
|
|
24
26
|
ToastOptions,
|
|
25
|
-
ToastOverlayController,
|
|
26
27
|
ToastProviderProps,
|
|
27
28
|
} from "./types.js";
|
package/src/internal/cells.tsx
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import type * as GObject from "@gtkx/gi/gobject";
|
|
2
2
|
import type { ReactElement, ReactNode } from "react";
|
|
3
3
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
4
|
-
import { GtkSignalListItemFactory, GtkTreeExpander } from "@gtkx/jsx/gtk";
|
|
4
|
+
import { GtkBox, GtkSignalListItemFactory, GtkTreeExpander } from "@gtkx/jsx/gtk";
|
|
5
5
|
import { createPortal, useProperty } from "@gtkx/react";
|
|
6
|
-
import {
|
|
7
|
-
import { memo, useInsertionEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
6
|
+
import { Fragment, memo, useInsertionEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
8
7
|
import type {
|
|
9
8
|
ExpanderDescriptions,
|
|
10
9
|
ListItem,
|
|
@@ -15,7 +14,8 @@ import type {
|
|
|
15
14
|
ListSectionRenderer,
|
|
16
15
|
} from "../types.js";
|
|
17
16
|
import type { Collection } from "./collection.js";
|
|
18
|
-
import { slotRefFor } from "./collection-model.js";
|
|
17
|
+
import { slotPathAt, type SlotRef, slotRefFor } from "./collection-model.js";
|
|
18
|
+
import { joinParts } from "./keys.js";
|
|
19
19
|
|
|
20
20
|
type CellSize = {
|
|
21
21
|
width: number;
|
|
@@ -84,6 +84,7 @@ type ItemSlotOptions = {
|
|
|
84
84
|
type PositionedHost = Gtk.ListItem | Gtk.ColumnViewRow;
|
|
85
85
|
|
|
86
86
|
type ItemSlot = {
|
|
87
|
+
itemKey: string;
|
|
87
88
|
item: ListItem;
|
|
88
89
|
row: Gtk.TreeListRow | null;
|
|
89
90
|
args: ListItemRenderArgs<unknown>;
|
|
@@ -100,6 +101,7 @@ type CollectionStateStore = {
|
|
|
100
101
|
};
|
|
101
102
|
|
|
102
103
|
type ItemIdentity = {
|
|
104
|
+
itemKey: string;
|
|
103
105
|
item: ListItem | undefined;
|
|
104
106
|
position: number;
|
|
105
107
|
isExpanded: boolean;
|
|
@@ -152,6 +154,23 @@ type HeaderPortalsProps = {
|
|
|
152
154
|
collection: Collection;
|
|
153
155
|
};
|
|
154
156
|
|
|
157
|
+
type NativeItemKeyStore = {
|
|
158
|
+
keyFor: (item: GObject.Object) => string;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
type NativeItemCellProps<T extends GObject.Object> = {
|
|
162
|
+
entry: CellEntry<Gtk.ListItem>;
|
|
163
|
+
render: ListItemRenderer<T>;
|
|
164
|
+
keys: NativeItemKeyStore;
|
|
165
|
+
size: CellSize;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
type NativeItemPortalsProps<T extends GObject.Object> = {
|
|
169
|
+
registry: CellRegistry<Gtk.ListItem>;
|
|
170
|
+
render: ListItemRenderer<T>;
|
|
171
|
+
size: CellSize;
|
|
172
|
+
};
|
|
173
|
+
|
|
155
174
|
type SectionHeaderSlot = {
|
|
156
175
|
factoryProps: { headerFactory?: ReactElement };
|
|
157
176
|
portals: ReactNode;
|
|
@@ -170,10 +189,14 @@ const HEADER_OPTIONS: CellRegistryOptions<Gtk.ListHeader> = { isHost: isListHead
|
|
|
170
189
|
const ROW_OPTIONS: CellRegistryOptions<Gtk.ColumnViewRow> = { isHost: isColumnViewRow };
|
|
171
190
|
const NO_SIZE: CellSize = { width: -1, height: -1 };
|
|
172
191
|
const NO_ROW_PROPS: ListRowProps = {};
|
|
173
|
-
const
|
|
192
|
+
const placeholders: WeakSet<Gtk.Widget> = new WeakSet();
|
|
174
193
|
|
|
175
|
-
const placeholder = (size: CellSize): Gtk.Widget =>
|
|
176
|
-
new Gtk.Box({ widthRequest: size.width, heightRequest: size.height });
|
|
194
|
+
const placeholder = (size: CellSize): Gtk.Widget => {
|
|
195
|
+
const widget = new Gtk.Box({ widthRequest: size.width, heightRequest: size.height });
|
|
196
|
+
placeholders.add(widget);
|
|
197
|
+
|
|
198
|
+
return widget;
|
|
199
|
+
};
|
|
177
200
|
|
|
178
201
|
function isListItem(value: GObject.Object): value is Gtk.ListItem {
|
|
179
202
|
return value instanceof Gtk.ListItem;
|
|
@@ -188,8 +211,12 @@ function isColumnViewRow(value: GObject.Object): value is Gtk.ColumnViewRow {
|
|
|
188
211
|
}
|
|
189
212
|
|
|
190
213
|
function prepareCell(host: CellHost, size: CellSize): void {
|
|
191
|
-
|
|
214
|
+
const child = host.getChild();
|
|
215
|
+
|
|
216
|
+
if (child === null) {
|
|
192
217
|
host.setChild(placeholder(size));
|
|
218
|
+
} else if (placeholders.has(child)) {
|
|
219
|
+
child.setSizeRequest(size.width, size.height);
|
|
193
220
|
}
|
|
194
221
|
}
|
|
195
222
|
|
|
@@ -274,7 +301,15 @@ function createCellRegistry<H extends FactoryHost>(options: CellRegistryOptions<
|
|
|
274
301
|
return {
|
|
275
302
|
handlers: createHandlers(state),
|
|
276
303
|
setSize: (next) => {
|
|
304
|
+
if (state.size.width === next.width && state.size.height === next.height) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
277
308
|
state.size = next;
|
|
309
|
+
|
|
310
|
+
for (const host of state.entries.keys()) {
|
|
311
|
+
state.prepare?.(host, next);
|
|
312
|
+
}
|
|
278
313
|
},
|
|
279
314
|
subscribe: (onChange) => subscribeRegistry(state, onChange),
|
|
280
315
|
getEntries: () => getRegistryEntries(state),
|
|
@@ -324,15 +359,25 @@ function itemArgs(
|
|
|
324
359
|
return args;
|
|
325
360
|
}
|
|
326
361
|
|
|
362
|
+
function keyForItem(ref: SlotRef, item: ListItem): string {
|
|
363
|
+
return joinParts([slotPathAt(ref.store, ref.slot), item.id]);
|
|
364
|
+
}
|
|
365
|
+
|
|
327
366
|
function slotFor(options: ItemSlotOptions): ItemSlot | null {
|
|
328
367
|
const ref = slotRefFor(options.item);
|
|
329
|
-
|
|
368
|
+
|
|
369
|
+
if (ref === null) {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const item = options.collection.itemAt(ref);
|
|
330
374
|
|
|
331
375
|
if (item === undefined) {
|
|
332
376
|
return null;
|
|
333
377
|
}
|
|
334
378
|
|
|
335
379
|
return {
|
|
380
|
+
itemKey: keyForItem(ref, item),
|
|
336
381
|
item,
|
|
337
382
|
row: options.row,
|
|
338
383
|
args: itemArgs(item, options.position, options.row, isRowWanted(options, item)),
|
|
@@ -352,6 +397,7 @@ function useItemSlot(
|
|
|
352
397
|
return identity.item === undefined
|
|
353
398
|
? null
|
|
354
399
|
: {
|
|
400
|
+
itemKey: identity.itemKey,
|
|
355
401
|
item: identity.item,
|
|
356
402
|
row,
|
|
357
403
|
args: itemArgs(identity.item, position, row, identity.isExpanded),
|
|
@@ -372,15 +418,17 @@ function itemIdentity(
|
|
|
372
418
|
const item = ref === null ? undefined : collection.itemAt(ref);
|
|
373
419
|
|
|
374
420
|
return {
|
|
421
|
+
itemKey: ref === null || item === undefined ? "" : keyForItem(ref, item),
|
|
375
422
|
item,
|
|
376
423
|
position: entry.host.getPosition(),
|
|
377
|
-
isExpanded: item
|
|
424
|
+
isExpanded: item !== undefined && (expandedIds?.includes(item.id) ?? false),
|
|
378
425
|
};
|
|
379
426
|
}
|
|
380
427
|
|
|
381
428
|
function isSameItemIdentity(previous: ItemIdentity, next: ItemIdentity): boolean {
|
|
382
429
|
return (
|
|
383
430
|
previous.item === next.item &&
|
|
431
|
+
previous.itemKey === next.itemKey &&
|
|
384
432
|
previous.position === next.position &&
|
|
385
433
|
previous.isExpanded === next.isExpanded
|
|
386
434
|
);
|
|
@@ -415,8 +463,30 @@ function createItemIdentityStore(): ItemIdentityStore {
|
|
|
415
463
|
};
|
|
416
464
|
}
|
|
417
465
|
|
|
466
|
+
function createNativeItemKeyStore(): NativeItemKeyStore {
|
|
467
|
+
const keys: WeakMap<GObject.Object, string> = new WeakMap();
|
|
468
|
+
let serial = 0;
|
|
469
|
+
|
|
470
|
+
return {
|
|
471
|
+
keyFor: (item) => {
|
|
472
|
+
const current = keys.get(item);
|
|
473
|
+
|
|
474
|
+
if (current !== undefined) {
|
|
475
|
+
return current;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
serial += 1;
|
|
479
|
+
const key = `gtkx-native-item-${String(serial)}`;
|
|
480
|
+
keys.set(item, key);
|
|
481
|
+
|
|
482
|
+
return key;
|
|
483
|
+
},
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
418
487
|
function renderedIdentity(entry: CellEntry<PositionedHost>, slot: ItemSlot | null): ItemIdentity {
|
|
419
488
|
return {
|
|
489
|
+
itemKey: slot?.itemKey ?? "",
|
|
420
490
|
item: slot?.item,
|
|
421
491
|
position: slot?.args.index ?? entry.host.getPosition(),
|
|
422
492
|
isExpanded: slot?.args.isExpanded ?? false,
|
|
@@ -506,7 +576,9 @@ function itemBody(
|
|
|
506
576
|
const renderItem = render as ListItemRenderer<unknown>;
|
|
507
577
|
const content = renderItem(slot.args);
|
|
508
578
|
|
|
509
|
-
return
|
|
579
|
+
return (
|
|
580
|
+
<Fragment key={slot.itemKey}>{hasExpander ? wrapExpander(slot, content, descriptions) : content}</Fragment>
|
|
581
|
+
);
|
|
510
582
|
}
|
|
511
583
|
|
|
512
584
|
function rowText(value: string | undefined): string | null {
|
|
@@ -531,21 +603,17 @@ function rowPropsFor(slot: ItemSlot | null, rowProps: ListRowPropsResolver<never
|
|
|
531
603
|
};
|
|
532
604
|
}
|
|
533
605
|
|
|
534
|
-
function setRowText(host: Gtk.ColumnViewRow, name: string, value: string | null): void {
|
|
535
|
-
setProperty(host, name, ROW_TEXT_DESCRIPTOR, value);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
606
|
function applyRowProps(
|
|
539
607
|
host: Gtk.ColumnViewRow,
|
|
540
608
|
props: ResolvedRowProps,
|
|
541
609
|
previous: ResolvedRowProps | null,
|
|
542
610
|
): void {
|
|
543
611
|
if (previous?.accessibleLabel !== props.accessibleLabel) {
|
|
544
|
-
|
|
612
|
+
host.accessibleLabel = props.accessibleLabel;
|
|
545
613
|
}
|
|
546
614
|
|
|
547
615
|
if (previous?.accessibleDescription !== props.accessibleDescription) {
|
|
548
|
-
|
|
616
|
+
host.accessibleDescription = props.accessibleDescription;
|
|
549
617
|
}
|
|
550
618
|
|
|
551
619
|
if (previous?.isActivatable !== props.isActivatable) {
|
|
@@ -592,13 +660,52 @@ function ItemRowImpl({ entry, rowProps, state, ...identity }: ItemRowProps): Rea
|
|
|
592
660
|
|
|
593
661
|
function HeaderCellImpl({ entry, render, collection }: HeaderCellProps): ReactNode {
|
|
594
662
|
const item = useProperty(entry.host, "item") ?? null;
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
663
|
+
|
|
664
|
+
return createPortal(headerBody(slotRefFor(item), render, collection), entry.host, entry.key);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function NativeItemCell<T extends GObject.Object>({
|
|
668
|
+
entry,
|
|
669
|
+
render,
|
|
670
|
+
keys,
|
|
671
|
+
size,
|
|
672
|
+
}: NativeItemCellProps<T>): ReactNode {
|
|
673
|
+
const position = useProperty(entry.host, "position");
|
|
674
|
+
const item = useProperty(entry.host, "item") ?? null;
|
|
675
|
+
let body: ReactNode = null;
|
|
676
|
+
|
|
677
|
+
if (item !== null && position !== undefined) {
|
|
678
|
+
const content = render({ item: item as T, index: position });
|
|
679
|
+
const child = content == null || typeof content === "boolean"
|
|
680
|
+
? <GtkBox widthRequest={size.width} heightRequest={size.height} />
|
|
681
|
+
: content;
|
|
682
|
+
body = <Fragment key={keys.keyFor(item)}>{child}</Fragment>;
|
|
683
|
+
}
|
|
598
684
|
|
|
599
685
|
return createPortal(body, entry.host, entry.key);
|
|
600
686
|
}
|
|
601
687
|
|
|
688
|
+
function headerBody(
|
|
689
|
+
ref: SlotRef | null,
|
|
690
|
+
render: ListSectionRenderer<never>,
|
|
691
|
+
collection: Collection,
|
|
692
|
+
): ReactNode {
|
|
693
|
+
if (ref === null) {
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const section = collection.sectionFor(ref.store.level.path);
|
|
698
|
+
|
|
699
|
+
if (section === undefined) {
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const renderHeader = render as ListSectionRenderer<unknown>;
|
|
704
|
+
const key = joinParts([ref.store.level.path, section.id]);
|
|
705
|
+
|
|
706
|
+
return <Fragment key={key}>{renderHeader({ section: section.value })}</Fragment>;
|
|
707
|
+
}
|
|
708
|
+
|
|
602
709
|
function usePortalEntries<H extends FactoryHost>(registry: CellRegistry<H>): CellEntry<H>[] {
|
|
603
710
|
return useSyncExternalStore(registry.subscribe, registry.getEntries);
|
|
604
711
|
}
|
|
@@ -647,6 +754,15 @@ const HeaderPortals = ({ registry, render, collection }: HeaderPortalsProps): Re
|
|
|
647
754
|
<HeaderCell key={entry.key} entry={entry} render={render} collection={collection} />
|
|
648
755
|
));
|
|
649
756
|
|
|
757
|
+
function NativeItemPortals<T extends GObject.Object>({ registry, render, size }: NativeItemPortalsProps<T>): ReactNode {
|
|
758
|
+
const entries = usePortalEntries(registry);
|
|
759
|
+
const [keys] = useState(createNativeItemKeyStore);
|
|
760
|
+
|
|
761
|
+
return entries.map((entry) => (
|
|
762
|
+
<NativeItemCell key={entry.key} entry={entry} render={render} keys={keys} size={size} />
|
|
763
|
+
));
|
|
764
|
+
}
|
|
765
|
+
|
|
650
766
|
const useSectionHeader = (
|
|
651
767
|
render: ListSectionRenderer<never> | null | undefined,
|
|
652
768
|
collection: Collection,
|
|
@@ -688,5 +804,6 @@ export {
|
|
|
688
804
|
useRowProps,
|
|
689
805
|
useSectionHeader,
|
|
690
806
|
ItemPortals,
|
|
807
|
+
NativeItemPortals,
|
|
691
808
|
type CellSize,
|
|
692
809
|
};
|
|
@@ -7,20 +7,22 @@ type Level = {
|
|
|
7
7
|
expandableFlags: boolean[];
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
+
type SectionIdentity = Pick<ListSection, "id" | "value">;
|
|
11
|
+
|
|
10
12
|
type CollectionIndex = {
|
|
11
13
|
isTree: boolean;
|
|
12
14
|
groups: Level[];
|
|
13
15
|
childLevel: (level: Level, slot: number) => Level | undefined;
|
|
14
16
|
levelFor: (levelPath: string) => Level | undefined;
|
|
15
17
|
itemAt: (levelPath: string, slot: number) => ListItem | undefined;
|
|
16
|
-
sectionFor: (levelPath: string) =>
|
|
18
|
+
sectionFor: (levelPath: string) => SectionIdentity | undefined;
|
|
17
19
|
};
|
|
18
20
|
|
|
19
21
|
type IndexState = {
|
|
20
22
|
isTree: boolean;
|
|
21
23
|
groups: Level[];
|
|
22
24
|
levels: Map<string, Level>;
|
|
23
|
-
|
|
25
|
+
sections: Map<string, SectionIdentity>;
|
|
24
26
|
};
|
|
25
27
|
|
|
26
28
|
type ParentItem = ListItem & { children: ListItem[] };
|
|
@@ -96,7 +98,7 @@ function buildGroups(state: IndexState, source: ListItem[], sections: ListSectio
|
|
|
96
98
|
|
|
97
99
|
return sections.map((section, group) => {
|
|
98
100
|
const path = encodePart(String(group));
|
|
99
|
-
state.
|
|
101
|
+
state.sections.set(path, { id: section.id, value: section.value });
|
|
100
102
|
|
|
101
103
|
return newLevel(state, path, section.data);
|
|
102
104
|
});
|
|
@@ -125,7 +127,7 @@ function createCollectionIndex(
|
|
|
125
127
|
isTree: isTreeSource(source, sections, isFlat),
|
|
126
128
|
groups: [],
|
|
127
129
|
levels: new Map(),
|
|
128
|
-
|
|
130
|
+
sections: new Map(),
|
|
129
131
|
};
|
|
130
132
|
|
|
131
133
|
state.groups = buildGroups(state, source, sections);
|
|
@@ -136,8 +138,8 @@ function createCollectionIndex(
|
|
|
136
138
|
childLevel: (level, slot) => childLevel(state, level, slot),
|
|
137
139
|
levelFor: (levelPath) => levelFor(state, levelPath),
|
|
138
140
|
itemAt: (levelPath, slot) => levelFor(state, levelPath)?.items[slot],
|
|
139
|
-
sectionFor: (levelPath) => state.
|
|
141
|
+
sectionFor: (levelPath) => state.sections.get(levelPath),
|
|
140
142
|
};
|
|
141
143
|
}
|
|
142
144
|
|
|
143
|
-
export { createCollectionIndex, type CollectionIndex, type Level };
|
|
145
|
+
export { createCollectionIndex, type CollectionIndex, type Level, type SectionIdentity };
|