@gtkx/components 2.0.0-beta.11 → 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.
- package/README.md +4 -4
- package/dist/column-view.d.ts.map +1 -1
- package/dist/column-view.js +8 -4
- package/dist/column-view.js.map +1 -1
- package/dist/combo-row.d.ts.map +1 -1
- package/dist/combo-row.js +3 -1
- package/dist/combo-row.js.map +1 -1
- package/dist/grid-view.d.ts.map +1 -1
- package/dist/grid-view.js +4 -3
- package/dist/grid-view.js.map +1 -1
- package/dist/internal/cells.d.ts +8 -9
- package/dist/internal/cells.d.ts.map +1 -1
- package/dist/internal/cells.js +98 -83
- package/dist/internal/cells.js.map +1 -1
- package/dist/internal/collection-index.d.ts +3 -5
- package/dist/internal/collection-index.d.ts.map +1 -1
- package/dist/internal/collection-index.js +3 -7
- package/dist/internal/collection-index.js.map +1 -1
- package/dist/internal/collection-model.d.ts +15 -4
- package/dist/internal/collection-model.d.ts.map +1 -1
- package/dist/internal/collection-model.js +154 -73
- package/dist/internal/collection-model.js.map +1 -1
- package/dist/internal/collection.d.ts +3 -3
- package/dist/internal/collection.d.ts.map +1 -1
- package/dist/internal/collection.js +11 -9
- package/dist/internal/collection.js.map +1 -1
- package/dist/internal/controlled-sync.d.ts +2 -1
- package/dist/internal/controlled-sync.d.ts.map +1 -1
- package/dist/internal/controlled-sync.js +6 -1
- package/dist/internal/controlled-sync.js.map +1 -1
- package/dist/internal/drop-down.d.ts +10 -4
- package/dist/internal/drop-down.d.ts.map +1 -1
- package/dist/internal/drop-down.js +61 -9
- package/dist/internal/drop-down.js.map +1 -1
- package/dist/internal/expansion.d.ts.map +1 -1
- package/dist/internal/expansion.js +46 -19
- package/dist/internal/expansion.js.map +1 -1
- package/dist/internal/first-header.d.ts +11 -0
- package/dist/internal/first-header.d.ts.map +1 -0
- package/dist/internal/first-header.js +21 -0
- package/dist/internal/first-header.js.map +1 -0
- package/dist/internal/keys.d.ts.map +1 -1
- package/dist/internal/level-splice.d.ts +27 -0
- package/dist/internal/level-splice.d.ts.map +1 -0
- package/dist/internal/level-splice.js +98 -0
- package/dist/internal/level-splice.js.map +1 -0
- package/dist/internal/selection.d.ts.map +1 -1
- package/dist/internal/selection.js +23 -16
- package/dist/internal/selection.js.map +1 -1
- package/dist/internal/tree-expansion.d.ts +3 -1
- package/dist/internal/tree-expansion.d.ts.map +1 -1
- package/dist/internal/tree-expansion.js +31 -2
- package/dist/internal/tree-expansion.js.map +1 -1
- package/dist/internal/tree-order.d.ts +2 -1
- package/dist/internal/tree-order.d.ts.map +1 -1
- package/dist/internal/tree-order.js +3 -2
- package/dist/internal/tree-order.js.map +1 -1
- package/dist/internal/use-collection.d.ts +1 -0
- package/dist/internal/use-collection.d.ts.map +1 -1
- package/dist/internal/use-collection.js +6 -2
- 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 +1 -1
- package/dist/list-item-factory.d.ts.map +1 -1
- package/dist/list-item-factory.js +3 -3
- package/dist/list-item-factory.js.map +1 -1
- package/dist/list-view.d.ts.map +1 -1
- package/dist/list-view.js +9 -4
- package/dist/list-view.js.map +1 -1
- package/dist/types.d.ts +35 -12
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +10 -20
- package/src/column-view.tsx +8 -4
- package/src/combo-row.tsx +4 -1
- package/src/grid-view.tsx +3 -2
- package/src/internal/cells.tsx +174 -151
- package/src/internal/collection-index.ts +7 -14
- package/src/internal/collection-model.ts +211 -76
- package/src/internal/collection.ts +14 -13
- package/src/internal/controlled-sync.ts +8 -1
- package/src/internal/drop-down.tsx +112 -19
- package/src/internal/expansion.ts +52 -20
- package/src/internal/first-header.ts +23 -0
- package/src/internal/level-splice.ts +157 -0
- package/src/internal/selection.tsx +24 -19
- package/src/internal/tree-expansion.ts +43 -1
- package/src/internal/tree-order.ts +4 -10
- package/src/internal/use-collection.tsx +8 -2
- package/src/list-item-factory.tsx +2 -1
- package/src/list-view.tsx +10 -2
- package/src/types.ts +54 -24
package/src/internal/cells.tsx
CHANGED
|
@@ -1,9 +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
4
|
import { GtkBox, GtkSignalListItemFactory, GtkTreeExpander } from "@gtkx/jsx/gtk";
|
|
5
5
|
import { createPortal, useProperty } from "@gtkx/react";
|
|
6
|
-
import {
|
|
6
|
+
import { useLatestRef } from "@gtkx/react/internal";
|
|
7
|
+
import { createRef, Fragment, memo, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
7
8
|
import type {
|
|
8
9
|
ExpanderDescriptions,
|
|
9
10
|
ListItem,
|
|
@@ -14,7 +15,7 @@ import type {
|
|
|
14
15
|
ListSectionRenderer,
|
|
15
16
|
} from "../types.js";
|
|
16
17
|
import type { Collection } from "./collection.js";
|
|
17
|
-
import {
|
|
18
|
+
import { type LazyLevelStore, levelStoreFor, slotRefFor } from "./collection-model.js";
|
|
18
19
|
import { joinParts } from "./keys.js";
|
|
19
20
|
|
|
20
21
|
type CellSize = {
|
|
@@ -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 = {
|
|
@@ -86,6 +87,7 @@ type PositionedHost = Gtk.ListItem | Gtk.ColumnViewRow;
|
|
|
86
87
|
type ItemSlot = {
|
|
87
88
|
itemKey: string;
|
|
88
89
|
item: ListItem;
|
|
90
|
+
position: number | undefined;
|
|
89
91
|
row: Gtk.TreeListRow | null;
|
|
90
92
|
args: ListItemRenderArgs<unknown>;
|
|
91
93
|
};
|
|
@@ -95,27 +97,19 @@ type CollectionState = {
|
|
|
95
97
|
expandedIds: string[] | null | undefined;
|
|
96
98
|
};
|
|
97
99
|
|
|
98
|
-
type CollectionStateStore = {
|
|
99
|
-
read: () => CollectionState;
|
|
100
|
-
write: (state: CollectionState) => void;
|
|
101
|
-
};
|
|
102
|
-
|
|
103
100
|
type ItemIdentity = {
|
|
104
101
|
itemKey: string;
|
|
105
102
|
item: ListItem | undefined;
|
|
106
103
|
position: number;
|
|
104
|
+
index: number;
|
|
107
105
|
isExpanded: boolean;
|
|
108
106
|
};
|
|
109
107
|
|
|
110
|
-
type ItemIdentityStore = {
|
|
111
|
-
read: () => ItemIdentity | null;
|
|
112
|
-
write: (identity: ItemIdentity) => void;
|
|
113
|
-
};
|
|
114
|
-
|
|
115
108
|
type ItemCellProps = {
|
|
116
109
|
entry: CellEntry<Gtk.ListItem>;
|
|
117
110
|
render: ListItemRenderer<never>;
|
|
118
|
-
|
|
111
|
+
rowProps: ListRowPropsResolver<never> | null | undefined;
|
|
112
|
+
state: RefObject<CollectionState>;
|
|
119
113
|
hasExpander: boolean;
|
|
120
114
|
expanderDescriptions: ExpanderDescriptions | null | undefined;
|
|
121
115
|
} & ItemIdentity;
|
|
@@ -123,16 +117,26 @@ type ItemCellProps = {
|
|
|
123
117
|
type ItemPortalsProps = {
|
|
124
118
|
registry: CellRegistry<Gtk.ListItem>;
|
|
125
119
|
render: ListItemRenderer<never>;
|
|
120
|
+
rowProps?: ListRowPropsResolver<never> | null | undefined;
|
|
126
121
|
collection: Collection;
|
|
127
122
|
hasExpander?: boolean | undefined;
|
|
128
123
|
expandedIds?: string[] | null | undefined;
|
|
129
124
|
expanderDescriptions?: ExpanderDescriptions | null | undefined;
|
|
130
125
|
};
|
|
131
126
|
|
|
127
|
+
type PendingList<H extends FactoryHost> = {
|
|
128
|
+
entries: CellEntry<H>[];
|
|
129
|
+
isPending: boolean;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
type ItemCellListProps = Omit<ItemPortalsProps, "registry"> & PendingList<Gtk.ListItem>;
|
|
133
|
+
|
|
134
|
+
type ItemRowListProps = Omit<RowPortalsProps, "registry"> & PendingList<Gtk.ColumnViewRow>;
|
|
135
|
+
|
|
132
136
|
type ItemRowProps = {
|
|
133
137
|
entry: CellEntry<Gtk.ColumnViewRow>;
|
|
134
138
|
rowProps: ListRowPropsResolver<never>;
|
|
135
|
-
state:
|
|
139
|
+
state: RefObject<CollectionState>;
|
|
136
140
|
} & ItemIdentity;
|
|
137
141
|
|
|
138
142
|
type RowPortalsProps = {
|
|
@@ -145,7 +149,7 @@ type RowPortalsProps = {
|
|
|
145
149
|
type HeaderCellProps = {
|
|
146
150
|
entry: CellEntry<Gtk.ListHeader>;
|
|
147
151
|
render: ListSectionRenderer<never>;
|
|
148
|
-
|
|
152
|
+
version: number;
|
|
149
153
|
};
|
|
150
154
|
|
|
151
155
|
type HeaderPortalsProps = {
|
|
@@ -161,6 +165,7 @@ type NativeItemKeyStore = {
|
|
|
161
165
|
type NativeItemCellProps<T extends GObject.Object> = {
|
|
162
166
|
entry: CellEntry<Gtk.ListItem>;
|
|
163
167
|
render: ListItemRenderer<T>;
|
|
168
|
+
rowProps: ListRowPropsResolver<T> | null | undefined;
|
|
164
169
|
keys: NativeItemKeyStore;
|
|
165
170
|
size: CellSize;
|
|
166
171
|
};
|
|
@@ -168,6 +173,7 @@ type NativeItemCellProps<T extends GObject.Object> = {
|
|
|
168
173
|
type NativeItemPortalsProps<T extends GObject.Object> = {
|
|
169
174
|
registry: CellRegistry<Gtk.ListItem>;
|
|
170
175
|
render: ListItemRenderer<T>;
|
|
176
|
+
rowProps: ListRowPropsResolver<T> | null | undefined;
|
|
171
177
|
size: CellSize;
|
|
172
178
|
};
|
|
173
179
|
|
|
@@ -184,11 +190,14 @@ type RowSlot = {
|
|
|
184
190
|
const ItemCell = memo(ItemCellImpl, isSameItemCell);
|
|
185
191
|
const ItemRow = memo(ItemRowImpl, isSameItemRow);
|
|
186
192
|
const HeaderCell = memo(HeaderCellImpl);
|
|
193
|
+
const ItemCellList = memo(ItemCellListImpl, isWaitingForSync);
|
|
194
|
+
const ItemRowList = memo(ItemRowListImpl, isWaitingForSync);
|
|
187
195
|
const CELL_OPTIONS: CellRegistryOptions<Gtk.ListItem> = { isHost: isListItem, prepare: prepareCell };
|
|
188
196
|
const HEADER_OPTIONS: CellRegistryOptions<Gtk.ListHeader> = { isHost: isListHeader, prepare: prepareCell };
|
|
189
197
|
const ROW_OPTIONS: CellRegistryOptions<Gtk.ColumnViewRow> = { isHost: isColumnViewRow };
|
|
190
198
|
const NO_SIZE: CellSize = { width: -1, height: -1 };
|
|
191
199
|
const NO_ROW_PROPS: ListRowProps = {};
|
|
200
|
+
const DEFAULT_ROW_PROPS = resolveRowProps(NO_ROW_PROPS);
|
|
192
201
|
const placeholders: WeakSet<Gtk.Widget> = new WeakSet();
|
|
193
202
|
|
|
194
203
|
const placeholder = (size: CellSize): Gtk.Widget => {
|
|
@@ -231,7 +240,7 @@ function notifyRegistry<H extends FactoryHost>(state: CellRegistryState<H>): voi
|
|
|
231
240
|
function createEntry<H extends FactoryHost>(state: CellRegistryState<H>, host: H): CellEntry<H> {
|
|
232
241
|
state.serial += 1;
|
|
233
242
|
|
|
234
|
-
return { key: `gtkx-cell-${String(state.serial)}`, host, identity:
|
|
243
|
+
return { key: `gtkx-cell-${String(state.serial)}`, host, identity: createRef<ItemIdentity>() };
|
|
235
244
|
}
|
|
236
245
|
|
|
237
246
|
function setupHost<H extends FactoryHost>(state: CellRegistryState<H>, host: H): void {
|
|
@@ -338,13 +347,27 @@ function isRowWanted(options: ItemSlotOptions, item: ListItem): boolean {
|
|
|
338
347
|
return options.expandedIds?.includes(item.id) ?? false;
|
|
339
348
|
}
|
|
340
349
|
|
|
350
|
+
function collectionIndex(collection: Collection, item: ListItem, position: number | undefined): number {
|
|
351
|
+
if (position === undefined) {
|
|
352
|
+
return 0;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (collection.idAt(position) === item.id) {
|
|
356
|
+
return position;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const index = collection.positionFor(item.id);
|
|
360
|
+
|
|
361
|
+
return index === -1 ? position : index;
|
|
362
|
+
}
|
|
363
|
+
|
|
341
364
|
function itemArgs(
|
|
342
365
|
item: ListItem,
|
|
343
|
-
|
|
366
|
+
index: number,
|
|
344
367
|
row: Gtk.TreeListRow | null,
|
|
345
368
|
isExpanded: boolean,
|
|
346
369
|
): ListItemRenderArgs<unknown> {
|
|
347
|
-
const args: ListItemRenderArgs<unknown> = { item: item.value, index
|
|
370
|
+
const args: ListItemRenderArgs<unknown> = { item: item.value, index };
|
|
348
371
|
|
|
349
372
|
if (row === null) {
|
|
350
373
|
return args;
|
|
@@ -359,10 +382,6 @@ function itemArgs(
|
|
|
359
382
|
return args;
|
|
360
383
|
}
|
|
361
384
|
|
|
362
|
-
function keyForItem(ref: SlotRef, item: ListItem): string {
|
|
363
|
-
return joinParts([slotPathAt(ref.store, ref.slot), item.id]);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
385
|
function slotFor(options: ItemSlotOptions): ItemSlot | null {
|
|
367
386
|
const ref = slotRefFor(options.item);
|
|
368
387
|
|
|
@@ -377,35 +396,41 @@ function slotFor(options: ItemSlotOptions): ItemSlot | null {
|
|
|
377
396
|
}
|
|
378
397
|
|
|
379
398
|
return {
|
|
380
|
-
itemKey:
|
|
399
|
+
itemKey: ref.key,
|
|
381
400
|
item,
|
|
401
|
+
position: options.position,
|
|
382
402
|
row: options.row,
|
|
383
|
-
args: itemArgs(
|
|
403
|
+
args: itemArgs(
|
|
404
|
+
item,
|
|
405
|
+
collectionIndex(options.collection, item, options.position),
|
|
406
|
+
options.row,
|
|
407
|
+
isRowWanted(options, item),
|
|
408
|
+
),
|
|
384
409
|
};
|
|
385
410
|
}
|
|
386
411
|
|
|
387
412
|
function useItemSlot(
|
|
388
413
|
entry: CellEntry<PositionedHost>,
|
|
389
|
-
state:
|
|
414
|
+
state: RefObject<CollectionState>,
|
|
390
415
|
identity: ItemIdentity,
|
|
391
416
|
): ItemSlot | null {
|
|
392
417
|
const position = useProperty(entry.host, "position");
|
|
393
418
|
const item = useProperty(entry.host, "item") ?? null;
|
|
394
419
|
const row = item instanceof Gtk.TreeListRow ? item : null;
|
|
420
|
+
const { collection, expandedIds } = state.current;
|
|
395
421
|
|
|
396
422
|
if (position === identity.position) {
|
|
397
423
|
return identity.item === undefined
|
|
398
424
|
? null
|
|
399
425
|
: {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
426
|
+
itemKey: identity.itemKey,
|
|
427
|
+
item: identity.item,
|
|
428
|
+
position,
|
|
429
|
+
row,
|
|
430
|
+
args: itemArgs(identity.item, identity.index, row, identity.isExpanded),
|
|
431
|
+
};
|
|
405
432
|
}
|
|
406
433
|
|
|
407
|
-
const { collection, expandedIds } = state.read();
|
|
408
|
-
|
|
409
434
|
return slotFor({ item, position, row, collection, expandedIds });
|
|
410
435
|
}
|
|
411
436
|
|
|
@@ -416,11 +441,13 @@ function itemIdentity(
|
|
|
416
441
|
): ItemIdentity {
|
|
417
442
|
const ref = slotRefFor(entry.host.getItem());
|
|
418
443
|
const item = ref === null ? undefined : collection.itemAt(ref);
|
|
444
|
+
const position = entry.host.getPosition();
|
|
419
445
|
|
|
420
446
|
return {
|
|
421
|
-
itemKey: ref === null || item === undefined ? "" :
|
|
447
|
+
itemKey: ref === null || item === undefined ? "" : ref.key,
|
|
422
448
|
item,
|
|
423
|
-
position
|
|
449
|
+
position,
|
|
450
|
+
index: item === undefined ? 0 : collectionIndex(collection, item, position),
|
|
424
451
|
isExpanded: item !== undefined && (expandedIds?.includes(item.id) ?? false),
|
|
425
452
|
};
|
|
426
453
|
}
|
|
@@ -430,37 +457,32 @@ function isSameItemIdentity(previous: ItemIdentity, next: ItemIdentity): boolean
|
|
|
430
457
|
previous.item === next.item &&
|
|
431
458
|
previous.itemKey === next.itemKey &&
|
|
432
459
|
previous.position === next.position &&
|
|
460
|
+
previous.index === next.index &&
|
|
433
461
|
previous.isExpanded === next.isExpanded
|
|
434
462
|
);
|
|
435
463
|
}
|
|
436
464
|
|
|
437
465
|
function isSameItemCell(previous: ItemCellProps, next: ItemCellProps): boolean {
|
|
438
|
-
const identity = previous.entry.identity.
|
|
466
|
+
const identity = previous.entry.identity.current ?? previous;
|
|
439
467
|
|
|
440
468
|
return (
|
|
441
469
|
previous.entry === next.entry &&
|
|
442
470
|
previous.render === next.render &&
|
|
471
|
+
previous.rowProps === next.rowProps &&
|
|
443
472
|
previous.hasExpander === next.hasExpander &&
|
|
444
473
|
previous.expanderDescriptions === next.expanderDescriptions &&
|
|
445
474
|
isSameItemIdentity(identity, next)
|
|
446
475
|
);
|
|
447
476
|
}
|
|
448
477
|
|
|
449
|
-
function
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
return previous.entry === next.entry && previous.rowProps === next.rowProps && isSameItemIdentity(identity, next);
|
|
478
|
+
function isWaitingForSync<H extends FactoryHost>(_previous: PendingList<H>, next: PendingList<H>): boolean {
|
|
479
|
+
return next.isPending;
|
|
453
480
|
}
|
|
454
481
|
|
|
455
|
-
function
|
|
456
|
-
|
|
482
|
+
function isSameItemRow(previous: ItemRowProps, next: ItemRowProps): boolean {
|
|
483
|
+
const identity = previous.entry.identity.current ?? previous;
|
|
457
484
|
|
|
458
|
-
return
|
|
459
|
-
read: () => current,
|
|
460
|
-
write: (identity) => {
|
|
461
|
-
current = identity;
|
|
462
|
-
},
|
|
463
|
-
};
|
|
485
|
+
return previous.entry === next.entry && previous.rowProps === next.rowProps && isSameItemIdentity(identity, next);
|
|
464
486
|
}
|
|
465
487
|
|
|
466
488
|
function createNativeItemKeyStore(): NativeItemKeyStore {
|
|
@@ -488,41 +510,19 @@ function renderedIdentity(entry: CellEntry<PositionedHost>, slot: ItemSlot | nul
|
|
|
488
510
|
return {
|
|
489
511
|
itemKey: slot?.itemKey ?? "",
|
|
490
512
|
item: slot?.item,
|
|
491
|
-
position: slot?.
|
|
513
|
+
position: slot?.position ?? entry.host.getPosition(),
|
|
514
|
+
index: slot?.args.index ?? 0,
|
|
492
515
|
isExpanded: slot?.args.isExpanded ?? false,
|
|
493
516
|
};
|
|
494
517
|
}
|
|
495
518
|
|
|
496
519
|
function useRememberIdentity(entry: CellEntry<PositionedHost>, slot: ItemSlot | null): void {
|
|
520
|
+
const identityRef = entry.identity;
|
|
497
521
|
useLayoutEffect(() => {
|
|
498
|
-
|
|
522
|
+
identityRef.current = renderedIdentity(entry, slot);
|
|
499
523
|
});
|
|
500
524
|
}
|
|
501
525
|
|
|
502
|
-
function createCollectionStateStore(initial: CollectionState): CollectionStateStore {
|
|
503
|
-
let current = initial;
|
|
504
|
-
|
|
505
|
-
return {
|
|
506
|
-
read: () => current,
|
|
507
|
-
write: (state) => {
|
|
508
|
-
current = state;
|
|
509
|
-
},
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
function useCollectionState(
|
|
514
|
-
collection: Collection,
|
|
515
|
-
expandedIds: string[] | null | undefined,
|
|
516
|
-
): CollectionStateStore {
|
|
517
|
-
const [state] = useState(() => createCollectionStateStore({ collection, expandedIds }));
|
|
518
|
-
|
|
519
|
-
useInsertionEffect(() => {
|
|
520
|
-
state.write({ collection, expandedIds });
|
|
521
|
-
}, [state, collection, expandedIds]);
|
|
522
|
-
|
|
523
|
-
return state;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
526
|
function expanderDescriptionFor(
|
|
527
527
|
slot: ItemSlot,
|
|
528
528
|
descriptions: ExpanderDescriptions | null | undefined,
|
|
@@ -544,21 +544,19 @@ function wrapExpander(
|
|
|
544
544
|
const { item, row } = slot;
|
|
545
545
|
|
|
546
546
|
const expander: ReactNode =
|
|
547
|
-
row === null
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
</GtkTreeExpander>
|
|
561
|
-
);
|
|
547
|
+
row === null ? (
|
|
548
|
+
content
|
|
549
|
+
) : (
|
|
550
|
+
<GtkTreeExpander
|
|
551
|
+
listRow={row}
|
|
552
|
+
hideExpander={item.shouldHideExpander ?? false}
|
|
553
|
+
indentForDepth={item.shouldIndentForDepth ?? true}
|
|
554
|
+
indentForIcon={item.shouldIndentForIcon ?? true}
|
|
555
|
+
accessibleDescription={expanderDescriptionFor(slot, descriptions)}
|
|
556
|
+
>
|
|
557
|
+
{content}
|
|
558
|
+
</GtkTreeExpander>
|
|
559
|
+
);
|
|
562
560
|
|
|
563
561
|
return expander;
|
|
564
562
|
}
|
|
@@ -576,24 +574,18 @@ function itemBody(
|
|
|
576
574
|
const renderItem = render as ListItemRenderer<unknown>;
|
|
577
575
|
const content = renderItem(slot.args);
|
|
578
576
|
|
|
579
|
-
return (
|
|
580
|
-
<Fragment key={slot.itemKey}>{hasExpander ? wrapExpander(slot, content, descriptions) : content}</Fragment>
|
|
581
|
-
);
|
|
577
|
+
return <Fragment key={slot.itemKey}>{hasExpander ? wrapExpander(slot, content, descriptions) : content}</Fragment>;
|
|
582
578
|
}
|
|
583
579
|
|
|
584
580
|
function rowText(value: string | undefined): string | null {
|
|
585
581
|
return value ?? null;
|
|
586
582
|
}
|
|
587
583
|
|
|
588
|
-
/* eslint-disable-next-line unicorn/consistent-boolean-name -- reads the flag off a row, paired with rowText */
|
|
589
584
|
function rowFlag(value: boolean | undefined): boolean {
|
|
590
585
|
return value ?? true;
|
|
591
586
|
}
|
|
592
587
|
|
|
593
|
-
function
|
|
594
|
-
const resolve = rowProps as ListRowPropsResolver<unknown>;
|
|
595
|
-
const props = slot === null ? NO_ROW_PROPS : resolve(slot.args);
|
|
596
|
-
|
|
588
|
+
function resolveRowProps(props: ListRowProps): ResolvedRowProps {
|
|
597
589
|
return {
|
|
598
590
|
accessibleLabel: rowText(props.accessibleLabel),
|
|
599
591
|
accessibleDescription: rowText(props.accessibleDescription),
|
|
@@ -603,11 +595,13 @@ function rowPropsFor(slot: ItemSlot | null, rowProps: ListRowPropsResolver<never
|
|
|
603
595
|
};
|
|
604
596
|
}
|
|
605
597
|
|
|
606
|
-
function
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
598
|
+
function rowPropsFor(slot: ItemSlot | null, rowProps: ListRowPropsResolver<never>): ResolvedRowProps {
|
|
599
|
+
const resolve = rowProps as ListRowPropsResolver<unknown>;
|
|
600
|
+
|
|
601
|
+
return resolveRowProps(slot === null ? NO_ROW_PROPS : resolve(slot.args));
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function applyRowProps(host: PositionedHost, props: ResolvedRowProps, previous: ResolvedRowProps | null): void {
|
|
611
605
|
if (previous?.accessibleLabel !== props.accessibleLabel) {
|
|
612
606
|
host.accessibleLabel = props.accessibleLabel;
|
|
613
607
|
}
|
|
@@ -629,9 +623,24 @@ function applyRowProps(
|
|
|
629
623
|
}
|
|
630
624
|
}
|
|
631
625
|
|
|
626
|
+
function useAppliedRowProps(host: PositionedHost, props: ResolvedRowProps | null): void {
|
|
627
|
+
const previous = useRef<ResolvedRowProps | null>(null);
|
|
628
|
+
|
|
629
|
+
useLayoutEffect(() => {
|
|
630
|
+
if (props === null && previous.current === null) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const next = props ?? DEFAULT_ROW_PROPS;
|
|
635
|
+
applyRowProps(host, next, previous.current);
|
|
636
|
+
previous.current = props === null ? null : next;
|
|
637
|
+
}, [host, props]);
|
|
638
|
+
}
|
|
639
|
+
|
|
632
640
|
function ItemCellImpl({
|
|
633
641
|
entry,
|
|
634
642
|
render,
|
|
643
|
+
rowProps,
|
|
635
644
|
state,
|
|
636
645
|
hasExpander,
|
|
637
646
|
expanderDescriptions,
|
|
@@ -639,6 +648,7 @@ function ItemCellImpl({
|
|
|
639
648
|
}: ItemCellProps): ReactNode {
|
|
640
649
|
const slot = useItemSlot(entry, state, identity);
|
|
641
650
|
useRememberIdentity(entry, slot);
|
|
651
|
+
useAppliedRowProps(entry.host, rowProps == null ? null : rowPropsFor(slot, rowProps));
|
|
642
652
|
const body = itemBody(slot, render, hasExpander, expanderDescriptions);
|
|
643
653
|
|
|
644
654
|
return createPortal(body, entry.host, entry.key);
|
|
@@ -647,61 +657,56 @@ function ItemCellImpl({
|
|
|
647
657
|
function ItemRowImpl({ entry, rowProps, state, ...identity }: ItemRowProps): ReactNode {
|
|
648
658
|
const slot = useItemSlot(entry, state, identity);
|
|
649
659
|
useRememberIdentity(entry, slot);
|
|
650
|
-
|
|
651
|
-
const previous = useRef<ResolvedRowProps | null>(null);
|
|
652
|
-
|
|
653
|
-
useLayoutEffect(() => {
|
|
654
|
-
applyRowProps(entry.host, props, previous.current);
|
|
655
|
-
previous.current = props;
|
|
656
|
-
}, [entry.host, props]);
|
|
660
|
+
useAppliedRowProps(entry.host, rowPropsFor(slot, rowProps));
|
|
657
661
|
|
|
658
662
|
return null;
|
|
659
663
|
}
|
|
660
664
|
|
|
661
|
-
function HeaderCellImpl({ entry, render
|
|
665
|
+
function HeaderCellImpl({ entry, render }: HeaderCellProps): ReactNode {
|
|
662
666
|
const item = useProperty(entry.host, "item") ?? null;
|
|
663
667
|
|
|
664
|
-
return createPortal(headerBody(
|
|
668
|
+
return createPortal(headerBody(levelStoreFor(item), render), entry.host, entry.key);
|
|
665
669
|
}
|
|
666
670
|
|
|
667
671
|
function NativeItemCell<T extends GObject.Object>({
|
|
668
672
|
entry,
|
|
669
673
|
render,
|
|
674
|
+
rowProps,
|
|
670
675
|
keys,
|
|
671
676
|
size,
|
|
672
677
|
}: NativeItemCellProps<T>): ReactNode {
|
|
673
678
|
const position = useProperty(entry.host, "position");
|
|
674
679
|
const item = useProperty(entry.host, "item") ?? null;
|
|
680
|
+
const args = item !== null && position !== undefined ? { item: item as T, index: position } : null;
|
|
681
|
+
useAppliedRowProps(
|
|
682
|
+
entry.host,
|
|
683
|
+
rowProps == null ? null : resolveRowProps(args === null ? NO_ROW_PROPS : rowProps(args)),
|
|
684
|
+
);
|
|
675
685
|
let body: ReactNode = null;
|
|
676
686
|
|
|
677
|
-
if (item !== null &&
|
|
678
|
-
const content = render(
|
|
679
|
-
const child =
|
|
680
|
-
|
|
681
|
-
|
|
687
|
+
if (item !== null && args !== null) {
|
|
688
|
+
const content = render(args);
|
|
689
|
+
const child =
|
|
690
|
+
content == null || typeof content === "boolean" ? (
|
|
691
|
+
<GtkBox widthRequest={size.width} heightRequest={size.height} />
|
|
692
|
+
) : (
|
|
693
|
+
content
|
|
694
|
+
);
|
|
682
695
|
body = <Fragment key={keys.keyFor(item)}>{child}</Fragment>;
|
|
683
696
|
}
|
|
684
697
|
|
|
685
698
|
return createPortal(body, entry.host, entry.key);
|
|
686
699
|
}
|
|
687
700
|
|
|
688
|
-
function headerBody(
|
|
689
|
-
|
|
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);
|
|
701
|
+
function headerBody(store: LazyLevelStore | null, render: ListSectionRenderer<never>): ReactNode {
|
|
702
|
+
const section = store?.level.section;
|
|
698
703
|
|
|
699
|
-
if (section === undefined) {
|
|
704
|
+
if (store === null || section === undefined) {
|
|
700
705
|
return null;
|
|
701
706
|
}
|
|
702
707
|
|
|
703
708
|
const renderHeader = render as ListSectionRenderer<unknown>;
|
|
704
|
-
const key = joinParts([
|
|
709
|
+
const key = joinParts([store.key, section.id]);
|
|
705
710
|
|
|
706
711
|
return <Fragment key={key}>{renderHeader({ section: section.value })}</Fragment>;
|
|
707
712
|
}
|
|
@@ -710,33 +715,44 @@ function usePortalEntries<H extends FactoryHost>(registry: CellRegistry<H>): Cel
|
|
|
710
715
|
return useSyncExternalStore(registry.subscribe, registry.getEntries);
|
|
711
716
|
}
|
|
712
717
|
|
|
713
|
-
|
|
714
|
-
|
|
718
|
+
function useCollectionVersion(collection: Collection): number {
|
|
719
|
+
return useSyncExternalStore(collection.subscribe, collection.getVersion);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function ItemCellListImpl({
|
|
723
|
+
entries,
|
|
715
724
|
render,
|
|
725
|
+
rowProps,
|
|
716
726
|
collection,
|
|
717
727
|
hasExpander,
|
|
718
728
|
expandedIds,
|
|
719
729
|
expanderDescriptions,
|
|
720
|
-
}:
|
|
721
|
-
const
|
|
722
|
-
const state = useCollectionState(collection, expandedIds);
|
|
730
|
+
}: ItemCellListProps): ReactNode {
|
|
731
|
+
const state = useLatestRef({ collection, expandedIds });
|
|
723
732
|
|
|
724
733
|
return entries.map((entry) => (
|
|
725
734
|
<ItemCell
|
|
726
735
|
key={entry.key}
|
|
727
736
|
entry={entry}
|
|
728
737
|
render={render}
|
|
738
|
+
rowProps={rowProps}
|
|
729
739
|
state={state}
|
|
730
740
|
hasExpander={hasExpander ?? true}
|
|
731
741
|
expanderDescriptions={expanderDescriptions}
|
|
732
742
|
{...itemIdentity(entry, collection, expandedIds)}
|
|
733
743
|
/>
|
|
734
744
|
));
|
|
735
|
-
}
|
|
745
|
+
}
|
|
736
746
|
|
|
737
|
-
const
|
|
747
|
+
const ItemPortals = ({ registry, ...props }: ItemPortalsProps): ReactNode => {
|
|
738
748
|
const entries = usePortalEntries(registry);
|
|
739
|
-
|
|
749
|
+
useCollectionVersion(props.collection);
|
|
750
|
+
|
|
751
|
+
return <ItemCellList {...props} entries={entries} isPending={props.collection.isPending()} />;
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
function ItemRowListImpl({ entries, rowProps, collection, expandedIds }: ItemRowListProps): ReactNode {
|
|
755
|
+
const state = useLatestRef({ collection, expandedIds });
|
|
740
756
|
|
|
741
757
|
return entries.map((entry) => (
|
|
742
758
|
<ItemRow
|
|
@@ -747,19 +763,33 @@ const RowPortals = ({ registry, rowProps, collection, expandedIds }: RowPortalsP
|
|
|
747
763
|
{...itemIdentity(entry, collection, expandedIds)}
|
|
748
764
|
/>
|
|
749
765
|
));
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const RowPortals = ({ registry, ...props }: RowPortalsProps): ReactNode => {
|
|
769
|
+
const entries = usePortalEntries(registry);
|
|
770
|
+
useCollectionVersion(props.collection);
|
|
771
|
+
|
|
772
|
+
return <ItemRowList {...props} entries={entries} isPending={props.collection.isPending()} />;
|
|
750
773
|
};
|
|
751
774
|
|
|
752
|
-
const HeaderPortals = ({ registry, render, collection }: HeaderPortalsProps): ReactNode =>
|
|
753
|
-
usePortalEntries(registry)
|
|
754
|
-
|
|
755
|
-
));
|
|
775
|
+
const HeaderPortals = ({ registry, render, collection }: HeaderPortalsProps): ReactNode => {
|
|
776
|
+
const entries = usePortalEntries(registry);
|
|
777
|
+
const version = useCollectionVersion(collection);
|
|
756
778
|
|
|
757
|
-
|
|
779
|
+
return entries.map((entry) => <HeaderCell key={entry.key} entry={entry} render={render} version={version} />);
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
function NativeItemPortals<T extends GObject.Object>({
|
|
783
|
+
registry,
|
|
784
|
+
render,
|
|
785
|
+
rowProps,
|
|
786
|
+
size,
|
|
787
|
+
}: NativeItemPortalsProps<T>): ReactNode {
|
|
758
788
|
const entries = usePortalEntries(registry);
|
|
759
789
|
const [keys] = useState(createNativeItemKeyStore);
|
|
760
790
|
|
|
761
791
|
return entries.map((entry) => (
|
|
762
|
-
<NativeItemCell key={entry.key} entry={entry} render={render} keys={keys} size={size} />
|
|
792
|
+
<NativeItemCell key={entry.key} entry={entry} render={render} rowProps={rowProps} keys={keys} size={size} />
|
|
763
793
|
));
|
|
764
794
|
}
|
|
765
795
|
|
|
@@ -799,11 +829,4 @@ const useRowProps = (
|
|
|
799
829
|
};
|
|
800
830
|
};
|
|
801
831
|
|
|
802
|
-
export {
|
|
803
|
-
useItemCells,
|
|
804
|
-
useRowProps,
|
|
805
|
-
useSectionHeader,
|
|
806
|
-
ItemPortals,
|
|
807
|
-
NativeItemPortals,
|
|
808
|
-
type CellSize,
|
|
809
|
-
};
|
|
832
|
+
export { useItemCells, useRowProps, useSectionHeader, ItemPortals, NativeItemPortals, type CellSize };
|