@gtkx/components 2.0.0-beta.10 → 2.0.0-beta.12
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 +20 -10
- package/dist/column-view.js.map +1 -1
- package/dist/grid-view.js +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/cells.d.ts +10 -7
- package/dist/internal/cells.d.ts.map +1 -1
- package/dist/internal/cells.js +88 -51
- 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 +44 -45
- 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/keys.d.ts.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 +1 -1
- package/dist/internal/tree-expansion.d.ts.map +1 -1
- package/dist/internal/tree-expansion.js +3 -12
- package/dist/internal/tree-expansion.js.map +1 -1
- package/dist/internal/tree-order.d.ts +1 -1
- package/dist/internal/tree-order.d.ts.map +1 -1
- package/dist/internal/tree-order.js +33 -77
- 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/internal/use-widget-ref.d.ts.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/list-view.js +1 -1
- package/dist/tab-collection.d.ts +9 -0
- package/dist/tab-collection.d.ts.map +1 -0
- package/dist/tab-collection.js +114 -0
- package/dist/tab-collection.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 +67 -51
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +13 -21
- package/src/column-view.tsx +23 -12
- package/src/index.ts +7 -2
- package/src/internal/cells.tsx +155 -103
- 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 +63 -63
- package/src/internal/expansion.ts +4 -4
- package/src/internal/selection.tsx +8 -8
- package/src/internal/tree-expansion.ts +3 -23
- package/src/internal/tree-order.ts +34 -130
- package/src/internal/use-collection.tsx +29 -8
- package/src/list-item-factory.tsx +27 -0
- package/src/tab-collection.tsx +124 -0
- package/src/toast.tsx +4 -18
- package/src/types.ts +81 -55
package/src/internal/cells.tsx
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type * as GObject from "@gtkx/gi/gobject";
|
|
2
|
-
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
import type { ReactElement, ReactNode, RefObject } 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 {
|
|
6
|
+
import { useLatestRef } from "@gtkx/react/internal";
|
|
7
|
+
import { createRef, Fragment, memo, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
8
8
|
import type {
|
|
9
9
|
ExpanderDescriptions,
|
|
10
10
|
ListItem,
|
|
@@ -15,7 +15,8 @@ import type {
|
|
|
15
15
|
ListSectionRenderer,
|
|
16
16
|
} from "../types.js";
|
|
17
17
|
import type { Collection } from "./collection.js";
|
|
18
|
-
import { slotRefFor } from "./collection-model.js";
|
|
18
|
+
import { slotPathAt, type SlotRef, slotRefFor } from "./collection-model.js";
|
|
19
|
+
import { joinParts } from "./keys.js";
|
|
19
20
|
|
|
20
21
|
type CellSize = {
|
|
21
22
|
width: number;
|
|
@@ -34,7 +35,7 @@ type CellHost = FactoryHost & {
|
|
|
34
35
|
type CellEntry<H extends FactoryHost> = {
|
|
35
36
|
key: string;
|
|
36
37
|
host: H;
|
|
37
|
-
identity:
|
|
38
|
+
identity: RefObject<ItemIdentity | null>;
|
|
38
39
|
};
|
|
39
40
|
|
|
40
41
|
type FactoryHandlers = {
|
|
@@ -84,6 +85,7 @@ type ItemSlotOptions = {
|
|
|
84
85
|
type PositionedHost = Gtk.ListItem | Gtk.ColumnViewRow;
|
|
85
86
|
|
|
86
87
|
type ItemSlot = {
|
|
88
|
+
itemKey: string;
|
|
87
89
|
item: ListItem;
|
|
88
90
|
row: Gtk.TreeListRow | null;
|
|
89
91
|
args: ListItemRenderArgs<unknown>;
|
|
@@ -94,26 +96,17 @@ type CollectionState = {
|
|
|
94
96
|
expandedIds: string[] | null | undefined;
|
|
95
97
|
};
|
|
96
98
|
|
|
97
|
-
type CollectionStateStore = {
|
|
98
|
-
read: () => CollectionState;
|
|
99
|
-
write: (state: CollectionState) => void;
|
|
100
|
-
};
|
|
101
|
-
|
|
102
99
|
type ItemIdentity = {
|
|
100
|
+
itemKey: string;
|
|
103
101
|
item: ListItem | undefined;
|
|
104
102
|
position: number;
|
|
105
103
|
isExpanded: boolean;
|
|
106
104
|
};
|
|
107
105
|
|
|
108
|
-
type ItemIdentityStore = {
|
|
109
|
-
read: () => ItemIdentity | null;
|
|
110
|
-
write: (identity: ItemIdentity) => void;
|
|
111
|
-
};
|
|
112
|
-
|
|
113
106
|
type ItemCellProps = {
|
|
114
107
|
entry: CellEntry<Gtk.ListItem>;
|
|
115
108
|
render: ListItemRenderer<never>;
|
|
116
|
-
state:
|
|
109
|
+
state: RefObject<CollectionState>;
|
|
117
110
|
hasExpander: boolean;
|
|
118
111
|
expanderDescriptions: ExpanderDescriptions | null | undefined;
|
|
119
112
|
} & ItemIdentity;
|
|
@@ -130,7 +123,7 @@ type ItemPortalsProps = {
|
|
|
130
123
|
type ItemRowProps = {
|
|
131
124
|
entry: CellEntry<Gtk.ColumnViewRow>;
|
|
132
125
|
rowProps: ListRowPropsResolver<never>;
|
|
133
|
-
state:
|
|
126
|
+
state: RefObject<CollectionState>;
|
|
134
127
|
} & ItemIdentity;
|
|
135
128
|
|
|
136
129
|
type RowPortalsProps = {
|
|
@@ -152,6 +145,23 @@ type HeaderPortalsProps = {
|
|
|
152
145
|
collection: Collection;
|
|
153
146
|
};
|
|
154
147
|
|
|
148
|
+
type NativeItemKeyStore = {
|
|
149
|
+
keyFor: (item: GObject.Object) => string;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
type NativeItemCellProps<T extends GObject.Object> = {
|
|
153
|
+
entry: CellEntry<Gtk.ListItem>;
|
|
154
|
+
render: ListItemRenderer<T>;
|
|
155
|
+
keys: NativeItemKeyStore;
|
|
156
|
+
size: CellSize;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
type NativeItemPortalsProps<T extends GObject.Object> = {
|
|
160
|
+
registry: CellRegistry<Gtk.ListItem>;
|
|
161
|
+
render: ListItemRenderer<T>;
|
|
162
|
+
size: CellSize;
|
|
163
|
+
};
|
|
164
|
+
|
|
155
165
|
type SectionHeaderSlot = {
|
|
156
166
|
factoryProps: { headerFactory?: ReactElement };
|
|
157
167
|
portals: ReactNode;
|
|
@@ -170,10 +180,14 @@ const HEADER_OPTIONS: CellRegistryOptions<Gtk.ListHeader> = { isHost: isListHead
|
|
|
170
180
|
const ROW_OPTIONS: CellRegistryOptions<Gtk.ColumnViewRow> = { isHost: isColumnViewRow };
|
|
171
181
|
const NO_SIZE: CellSize = { width: -1, height: -1 };
|
|
172
182
|
const NO_ROW_PROPS: ListRowProps = {};
|
|
173
|
-
const
|
|
183
|
+
const placeholders: WeakSet<Gtk.Widget> = new WeakSet();
|
|
184
|
+
|
|
185
|
+
const placeholder = (size: CellSize): Gtk.Widget => {
|
|
186
|
+
const widget = new Gtk.Box({ widthRequest: size.width, heightRequest: size.height });
|
|
187
|
+
placeholders.add(widget);
|
|
174
188
|
|
|
175
|
-
|
|
176
|
-
|
|
189
|
+
return widget;
|
|
190
|
+
};
|
|
177
191
|
|
|
178
192
|
function isListItem(value: GObject.Object): value is Gtk.ListItem {
|
|
179
193
|
return value instanceof Gtk.ListItem;
|
|
@@ -188,8 +202,12 @@ function isColumnViewRow(value: GObject.Object): value is Gtk.ColumnViewRow {
|
|
|
188
202
|
}
|
|
189
203
|
|
|
190
204
|
function prepareCell(host: CellHost, size: CellSize): void {
|
|
191
|
-
|
|
205
|
+
const child = host.getChild();
|
|
206
|
+
|
|
207
|
+
if (child === null) {
|
|
192
208
|
host.setChild(placeholder(size));
|
|
209
|
+
} else if (placeholders.has(child)) {
|
|
210
|
+
child.setSizeRequest(size.width, size.height);
|
|
193
211
|
}
|
|
194
212
|
}
|
|
195
213
|
|
|
@@ -204,7 +222,7 @@ function notifyRegistry<H extends FactoryHost>(state: CellRegistryState<H>): voi
|
|
|
204
222
|
function createEntry<H extends FactoryHost>(state: CellRegistryState<H>, host: H): CellEntry<H> {
|
|
205
223
|
state.serial += 1;
|
|
206
224
|
|
|
207
|
-
return { key: `gtkx-cell-${String(state.serial)}`, host, identity:
|
|
225
|
+
return { key: `gtkx-cell-${String(state.serial)}`, host, identity: createRef<ItemIdentity>() };
|
|
208
226
|
}
|
|
209
227
|
|
|
210
228
|
function setupHost<H extends FactoryHost>(state: CellRegistryState<H>, host: H): void {
|
|
@@ -274,7 +292,15 @@ function createCellRegistry<H extends FactoryHost>(options: CellRegistryOptions<
|
|
|
274
292
|
return {
|
|
275
293
|
handlers: createHandlers(state),
|
|
276
294
|
setSize: (next) => {
|
|
295
|
+
if (state.size.width === next.width && state.size.height === next.height) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
277
299
|
state.size = next;
|
|
300
|
+
|
|
301
|
+
for (const host of state.entries.keys()) {
|
|
302
|
+
state.prepare?.(host, next);
|
|
303
|
+
}
|
|
278
304
|
},
|
|
279
305
|
subscribe: (onChange) => subscribeRegistry(state, onChange),
|
|
280
306
|
getEntries: () => getRegistryEntries(state),
|
|
@@ -324,15 +350,25 @@ function itemArgs(
|
|
|
324
350
|
return args;
|
|
325
351
|
}
|
|
326
352
|
|
|
353
|
+
function keyForItem(ref: SlotRef, item: ListItem): string {
|
|
354
|
+
return joinParts([slotPathAt(ref.store, ref.slot), item.id]);
|
|
355
|
+
}
|
|
356
|
+
|
|
327
357
|
function slotFor(options: ItemSlotOptions): ItemSlot | null {
|
|
328
358
|
const ref = slotRefFor(options.item);
|
|
329
|
-
|
|
359
|
+
|
|
360
|
+
if (ref === null) {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const item = options.collection.itemAt(ref);
|
|
330
365
|
|
|
331
366
|
if (item === undefined) {
|
|
332
367
|
return null;
|
|
333
368
|
}
|
|
334
369
|
|
|
335
370
|
return {
|
|
371
|
+
itemKey: keyForItem(ref, item),
|
|
336
372
|
item,
|
|
337
373
|
row: options.row,
|
|
338
374
|
args: itemArgs(item, options.position, options.row, isRowWanted(options, item)),
|
|
@@ -341,7 +377,7 @@ function slotFor(options: ItemSlotOptions): ItemSlot | null {
|
|
|
341
377
|
|
|
342
378
|
function useItemSlot(
|
|
343
379
|
entry: CellEntry<PositionedHost>,
|
|
344
|
-
state:
|
|
380
|
+
state: RefObject<CollectionState>,
|
|
345
381
|
identity: ItemIdentity,
|
|
346
382
|
): ItemSlot | null {
|
|
347
383
|
const position = useProperty(entry.host, "position");
|
|
@@ -352,13 +388,14 @@ function useItemSlot(
|
|
|
352
388
|
return identity.item === undefined
|
|
353
389
|
? null
|
|
354
390
|
: {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
391
|
+
itemKey: identity.itemKey,
|
|
392
|
+
item: identity.item,
|
|
393
|
+
row,
|
|
394
|
+
args: itemArgs(identity.item, position, row, identity.isExpanded),
|
|
395
|
+
};
|
|
359
396
|
}
|
|
360
397
|
|
|
361
|
-
const { collection, expandedIds } = state.
|
|
398
|
+
const { collection, expandedIds } = state.current;
|
|
362
399
|
|
|
363
400
|
return slotFor({ item, position, row, collection, expandedIds });
|
|
364
401
|
}
|
|
@@ -372,22 +409,24 @@ function itemIdentity(
|
|
|
372
409
|
const item = ref === null ? undefined : collection.itemAt(ref);
|
|
373
410
|
|
|
374
411
|
return {
|
|
412
|
+
itemKey: ref === null || item === undefined ? "" : keyForItem(ref, item),
|
|
375
413
|
item,
|
|
376
414
|
position: entry.host.getPosition(),
|
|
377
|
-
isExpanded: item
|
|
415
|
+
isExpanded: item !== undefined && (expandedIds?.includes(item.id) ?? false),
|
|
378
416
|
};
|
|
379
417
|
}
|
|
380
418
|
|
|
381
419
|
function isSameItemIdentity(previous: ItemIdentity, next: ItemIdentity): boolean {
|
|
382
420
|
return (
|
|
383
421
|
previous.item === next.item &&
|
|
422
|
+
previous.itemKey === next.itemKey &&
|
|
384
423
|
previous.position === next.position &&
|
|
385
424
|
previous.isExpanded === next.isExpanded
|
|
386
425
|
);
|
|
387
426
|
}
|
|
388
427
|
|
|
389
428
|
function isSameItemCell(previous: ItemCellProps, next: ItemCellProps): boolean {
|
|
390
|
-
const identity = previous.entry.identity.
|
|
429
|
+
const identity = previous.entry.identity.current ?? previous;
|
|
391
430
|
|
|
392
431
|
return (
|
|
393
432
|
previous.entry === next.entry &&
|
|
@@ -399,24 +438,35 @@ function isSameItemCell(previous: ItemCellProps, next: ItemCellProps): boolean {
|
|
|
399
438
|
}
|
|
400
439
|
|
|
401
440
|
function isSameItemRow(previous: ItemRowProps, next: ItemRowProps): boolean {
|
|
402
|
-
const identity = previous.entry.identity.
|
|
441
|
+
const identity = previous.entry.identity.current ?? previous;
|
|
403
442
|
|
|
404
443
|
return previous.entry === next.entry && previous.rowProps === next.rowProps && isSameItemIdentity(identity, next);
|
|
405
444
|
}
|
|
406
445
|
|
|
407
|
-
function
|
|
408
|
-
|
|
446
|
+
function createNativeItemKeyStore(): NativeItemKeyStore {
|
|
447
|
+
const keys: WeakMap<GObject.Object, string> = new WeakMap();
|
|
448
|
+
let serial = 0;
|
|
409
449
|
|
|
410
450
|
return {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
451
|
+
keyFor: (item) => {
|
|
452
|
+
const current = keys.get(item);
|
|
453
|
+
|
|
454
|
+
if (current !== undefined) {
|
|
455
|
+
return current;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
serial += 1;
|
|
459
|
+
const key = `gtkx-native-item-${String(serial)}`;
|
|
460
|
+
keys.set(item, key);
|
|
461
|
+
|
|
462
|
+
return key;
|
|
414
463
|
},
|
|
415
464
|
};
|
|
416
465
|
}
|
|
417
466
|
|
|
418
467
|
function renderedIdentity(entry: CellEntry<PositionedHost>, slot: ItemSlot | null): ItemIdentity {
|
|
419
468
|
return {
|
|
469
|
+
itemKey: slot?.itemKey ?? "",
|
|
420
470
|
item: slot?.item,
|
|
421
471
|
position: slot?.args.index ?? entry.host.getPosition(),
|
|
422
472
|
isExpanded: slot?.args.isExpanded ?? false,
|
|
@@ -424,35 +474,12 @@ function renderedIdentity(entry: CellEntry<PositionedHost>, slot: ItemSlot | nul
|
|
|
424
474
|
}
|
|
425
475
|
|
|
426
476
|
function useRememberIdentity(entry: CellEntry<PositionedHost>, slot: ItemSlot | null): void {
|
|
477
|
+
const identityRef = entry.identity;
|
|
427
478
|
useLayoutEffect(() => {
|
|
428
|
-
|
|
479
|
+
identityRef.current = renderedIdentity(entry, slot);
|
|
429
480
|
});
|
|
430
481
|
}
|
|
431
482
|
|
|
432
|
-
function createCollectionStateStore(initial: CollectionState): CollectionStateStore {
|
|
433
|
-
let current = initial;
|
|
434
|
-
|
|
435
|
-
return {
|
|
436
|
-
read: () => current,
|
|
437
|
-
write: (state) => {
|
|
438
|
-
current = state;
|
|
439
|
-
},
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
function useCollectionState(
|
|
444
|
-
collection: Collection,
|
|
445
|
-
expandedIds: string[] | null | undefined,
|
|
446
|
-
): CollectionStateStore {
|
|
447
|
-
const [state] = useState(() => createCollectionStateStore({ collection, expandedIds }));
|
|
448
|
-
|
|
449
|
-
useInsertionEffect(() => {
|
|
450
|
-
state.write({ collection, expandedIds });
|
|
451
|
-
}, [state, collection, expandedIds]);
|
|
452
|
-
|
|
453
|
-
return state;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
483
|
function expanderDescriptionFor(
|
|
457
484
|
slot: ItemSlot,
|
|
458
485
|
descriptions: ExpanderDescriptions | null | undefined,
|
|
@@ -474,21 +501,19 @@ function wrapExpander(
|
|
|
474
501
|
const { item, row } = slot;
|
|
475
502
|
|
|
476
503
|
const expander: ReactNode =
|
|
477
|
-
row === null
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
</GtkTreeExpander>
|
|
491
|
-
);
|
|
504
|
+
row === null ? (
|
|
505
|
+
content
|
|
506
|
+
) : (
|
|
507
|
+
<GtkTreeExpander
|
|
508
|
+
listRow={row}
|
|
509
|
+
hideExpander={item.shouldHideExpander ?? false}
|
|
510
|
+
indentForDepth={item.shouldIndentForDepth ?? true}
|
|
511
|
+
indentForIcon={item.shouldIndentForIcon ?? true}
|
|
512
|
+
accessibleDescription={expanderDescriptionFor(slot, descriptions)}
|
|
513
|
+
>
|
|
514
|
+
{content}
|
|
515
|
+
</GtkTreeExpander>
|
|
516
|
+
);
|
|
492
517
|
|
|
493
518
|
return expander;
|
|
494
519
|
}
|
|
@@ -506,14 +531,13 @@ function itemBody(
|
|
|
506
531
|
const renderItem = render as ListItemRenderer<unknown>;
|
|
507
532
|
const content = renderItem(slot.args);
|
|
508
533
|
|
|
509
|
-
return hasExpander ? wrapExpander(slot, content, descriptions) : content
|
|
534
|
+
return <Fragment key={slot.itemKey}>{hasExpander ? wrapExpander(slot, content, descriptions) : content}</Fragment>;
|
|
510
535
|
}
|
|
511
536
|
|
|
512
537
|
function rowText(value: string | undefined): string | null {
|
|
513
538
|
return value ?? null;
|
|
514
539
|
}
|
|
515
540
|
|
|
516
|
-
/* eslint-disable-next-line unicorn/consistent-boolean-name -- reads the flag off a row, paired with rowText */
|
|
517
541
|
function rowFlag(value: boolean | undefined): boolean {
|
|
518
542
|
return value ?? true;
|
|
519
543
|
}
|
|
@@ -531,21 +555,13 @@ function rowPropsFor(slot: ItemSlot | null, rowProps: ListRowPropsResolver<never
|
|
|
531
555
|
};
|
|
532
556
|
}
|
|
533
557
|
|
|
534
|
-
function
|
|
535
|
-
setProperty(host, name, ROW_TEXT_DESCRIPTOR, value);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
function applyRowProps(
|
|
539
|
-
host: Gtk.ColumnViewRow,
|
|
540
|
-
props: ResolvedRowProps,
|
|
541
|
-
previous: ResolvedRowProps | null,
|
|
542
|
-
): void {
|
|
558
|
+
function applyRowProps(host: Gtk.ColumnViewRow, props: ResolvedRowProps, previous: ResolvedRowProps | null): void {
|
|
543
559
|
if (previous?.accessibleLabel !== props.accessibleLabel) {
|
|
544
|
-
|
|
560
|
+
host.accessibleLabel = props.accessibleLabel;
|
|
545
561
|
}
|
|
546
562
|
|
|
547
563
|
if (previous?.accessibleDescription !== props.accessibleDescription) {
|
|
548
|
-
|
|
564
|
+
host.accessibleDescription = props.accessibleDescription;
|
|
549
565
|
}
|
|
550
566
|
|
|
551
567
|
if (previous?.isActivatable !== props.isActivatable) {
|
|
@@ -592,13 +608,46 @@ function ItemRowImpl({ entry, rowProps, state, ...identity }: ItemRowProps): Rea
|
|
|
592
608
|
|
|
593
609
|
function HeaderCellImpl({ entry, render, collection }: HeaderCellProps): ReactNode {
|
|
594
610
|
const item = useProperty(entry.host, "item") ?? null;
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
611
|
+
|
|
612
|
+
return createPortal(headerBody(slotRefFor(item), render, collection), entry.host, entry.key);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function NativeItemCell<T extends GObject.Object>({ entry, render, keys, size }: NativeItemCellProps<T>): ReactNode {
|
|
616
|
+
const position = useProperty(entry.host, "position");
|
|
617
|
+
const item = useProperty(entry.host, "item") ?? null;
|
|
618
|
+
let body: ReactNode = null;
|
|
619
|
+
|
|
620
|
+
if (item !== null && position !== undefined) {
|
|
621
|
+
const content = render({ item: item as T, index: position });
|
|
622
|
+
const child =
|
|
623
|
+
content == null || typeof content === "boolean" ? (
|
|
624
|
+
<GtkBox widthRequest={size.width} heightRequest={size.height} />
|
|
625
|
+
) : (
|
|
626
|
+
content
|
|
627
|
+
);
|
|
628
|
+
body = <Fragment key={keys.keyFor(item)}>{child}</Fragment>;
|
|
629
|
+
}
|
|
598
630
|
|
|
599
631
|
return createPortal(body, entry.host, entry.key);
|
|
600
632
|
}
|
|
601
633
|
|
|
634
|
+
function headerBody(ref: SlotRef | null, render: ListSectionRenderer<never>, collection: Collection): ReactNode {
|
|
635
|
+
if (ref === null) {
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const section = collection.sectionFor(ref.store.level.path);
|
|
640
|
+
|
|
641
|
+
if (section === undefined) {
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const renderHeader = render as ListSectionRenderer<unknown>;
|
|
646
|
+
const key = joinParts([ref.store.level.path, section.id]);
|
|
647
|
+
|
|
648
|
+
return <Fragment key={key}>{renderHeader({ section: section.value })}</Fragment>;
|
|
649
|
+
}
|
|
650
|
+
|
|
602
651
|
function usePortalEntries<H extends FactoryHost>(registry: CellRegistry<H>): CellEntry<H>[] {
|
|
603
652
|
return useSyncExternalStore(registry.subscribe, registry.getEntries);
|
|
604
653
|
}
|
|
@@ -612,7 +661,7 @@ const ItemPortals = ({
|
|
|
612
661
|
expanderDescriptions,
|
|
613
662
|
}: ItemPortalsProps): ReactNode => {
|
|
614
663
|
const entries = usePortalEntries(registry);
|
|
615
|
-
const state =
|
|
664
|
+
const state = useLatestRef({ collection, expandedIds });
|
|
616
665
|
|
|
617
666
|
return entries.map((entry) => (
|
|
618
667
|
<ItemCell
|
|
@@ -629,7 +678,7 @@ const ItemPortals = ({
|
|
|
629
678
|
|
|
630
679
|
const RowPortals = ({ registry, rowProps, collection, expandedIds }: RowPortalsProps): ReactNode => {
|
|
631
680
|
const entries = usePortalEntries(registry);
|
|
632
|
-
const state =
|
|
681
|
+
const state = useLatestRef({ collection, expandedIds });
|
|
633
682
|
|
|
634
683
|
return entries.map((entry) => (
|
|
635
684
|
<ItemRow
|
|
@@ -647,6 +696,15 @@ const HeaderPortals = ({ registry, render, collection }: HeaderPortalsProps): Re
|
|
|
647
696
|
<HeaderCell key={entry.key} entry={entry} render={render} collection={collection} />
|
|
648
697
|
));
|
|
649
698
|
|
|
699
|
+
function NativeItemPortals<T extends GObject.Object>({ registry, render, size }: NativeItemPortalsProps<T>): ReactNode {
|
|
700
|
+
const entries = usePortalEntries(registry);
|
|
701
|
+
const [keys] = useState(createNativeItemKeyStore);
|
|
702
|
+
|
|
703
|
+
return entries.map((entry) => (
|
|
704
|
+
<NativeItemCell key={entry.key} entry={entry} render={render} keys={keys} size={size} />
|
|
705
|
+
));
|
|
706
|
+
}
|
|
707
|
+
|
|
650
708
|
const useSectionHeader = (
|
|
651
709
|
render: ListSectionRenderer<never> | null | undefined,
|
|
652
710
|
collection: Collection,
|
|
@@ -683,10 +741,4 @@ const useRowProps = (
|
|
|
683
741
|
};
|
|
684
742
|
};
|
|
685
743
|
|
|
686
|
-
export {
|
|
687
|
-
useItemCells,
|
|
688
|
-
useRowProps,
|
|
689
|
-
useSectionHeader,
|
|
690
|
-
ItemPortals,
|
|
691
|
-
type CellSize,
|
|
692
|
-
};
|
|
744
|
+
export { useItemCells, useRowProps, useSectionHeader, ItemPortals, NativeItemPortals, type CellSize };
|
|
@@ -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 };
|