@gtkx/components 1.0.0-rc.4 → 1.0.0
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 +5 -5
- package/dist/column-view.d.ts +2 -2
- package/dist/column-view.d.ts.map +1 -1
- package/dist/column-view.js +14 -12
- package/dist/column-view.js.map +1 -1
- package/dist/grid-view.js +1 -1
- package/dist/grid-view.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/cells.d.ts +31 -23
- package/dist/internal/cells.d.ts.map +1 -1
- package/dist/internal/cells.js +168 -77
- package/dist/internal/cells.js.map +1 -1
- package/dist/internal/collection-index.d.ts +6 -10
- package/dist/internal/collection-index.d.ts.map +1 -1
- package/dist/internal/collection-index.js +32 -69
- 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 +210 -95
- package/dist/internal/collection-model.js.map +1 -1
- package/dist/internal/collection.d.ts +8 -11
- package/dist/internal/collection.d.ts.map +1 -1
- package/dist/internal/collection.js +38 -49
- package/dist/internal/collection.js.map +1 -1
- package/dist/internal/controlled-sync.d.ts +11 -0
- package/dist/internal/controlled-sync.d.ts.map +1 -0
- package/dist/internal/controlled-sync.js +19 -0
- package/dist/internal/controlled-sync.js.map +1 -0
- package/dist/internal/drop-down.d.ts.map +1 -1
- package/dist/internal/drop-down.js +27 -15
- package/dist/internal/drop-down.js.map +1 -1
- package/dist/internal/expansion.d.ts +3 -2
- package/dist/internal/expansion.d.ts.map +1 -1
- package/dist/internal/expansion.js +80 -47
- package/dist/internal/expansion.js.map +1 -1
- package/dist/internal/keys.d.ts +5 -0
- package/dist/internal/keys.d.ts.map +1 -0
- package/dist/internal/keys.js +13 -0
- package/dist/internal/keys.js.map +1 -0
- package/dist/internal/selection.d.ts +3 -2
- package/dist/internal/selection.d.ts.map +1 -1
- package/dist/internal/selection.js +68 -35
- package/dist/internal/selection.js.map +1 -1
- package/dist/internal/slots.d.ts +10 -0
- package/dist/internal/slots.d.ts.map +1 -0
- package/dist/internal/slots.js +43 -0
- package/dist/internal/slots.js.map +1 -0
- package/dist/internal/tree-expansion.d.ts +20 -0
- package/dist/internal/tree-expansion.d.ts.map +1 -0
- package/dist/internal/tree-expansion.js +68 -0
- package/dist/internal/tree-expansion.js.map +1 -0
- package/dist/internal/tree-order.d.ts +27 -0
- package/dist/internal/tree-order.d.ts.map +1 -0
- package/dist/internal/tree-order.js +71 -0
- package/dist/internal/tree-order.js.map +1 -0
- package/dist/internal/use-collection.d.ts +1 -1
- package/dist/internal/use-collection.d.ts.map +1 -1
- package/dist/internal/use-collection.js +4 -4
- package/dist/internal/use-collection.js.map +1 -1
- package/dist/list-view.d.ts.map +1 -1
- package/dist/list-view.js +6 -6
- package/dist/list-view.js.map +1 -1
- package/dist/types.d.ts +70 -21
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +7 -7
- package/src/column-view.tsx +28 -25
- package/src/grid-view.tsx +1 -1
- package/src/index.ts +3 -0
- package/src/internal/cells.tsx +339 -117
- package/src/internal/collection-index.ts +44 -92
- package/src/internal/collection-model.ts +259 -107
- package/src/internal/collection.ts +45 -67
- package/src/internal/controlled-sync.ts +34 -0
- package/src/internal/drop-down.tsx +41 -20
- package/src/internal/expansion.ts +104 -67
- package/src/internal/keys.ts +18 -0
- package/src/internal/selection.tsx +100 -51
- package/src/internal/slots.ts +67 -0
- package/src/internal/tree-expansion.ts +110 -0
- package/src/internal/tree-order.ts +138 -0
- package/src/internal/use-collection.tsx +5 -5
- package/src/list-view.tsx +14 -10
- package/src/types.ts +76 -19
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { decodePartAt, encodePart } from "./keys.js";
|
|
2
|
+
|
|
3
|
+
type SlotKey = {
|
|
4
|
+
levelPath: string;
|
|
5
|
+
slot: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type SlotMap = Map<string, Set<number>>;
|
|
9
|
+
|
|
10
|
+
function getLevelBoundary(path: string): number {
|
|
11
|
+
let offset = 0;
|
|
12
|
+
let boundary = 0;
|
|
13
|
+
|
|
14
|
+
while (offset < path.length) {
|
|
15
|
+
const part = decodePartAt(path, offset);
|
|
16
|
+
|
|
17
|
+
if (part === null) {
|
|
18
|
+
return boundary;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
boundary = offset;
|
|
22
|
+
offset += encodePart(part).length;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return boundary;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getSlotKey(path: string): SlotKey | null {
|
|
29
|
+
const boundary = getLevelBoundary(path);
|
|
30
|
+
const part = decodePartAt(path, boundary);
|
|
31
|
+
|
|
32
|
+
if (part === null) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { levelPath: path.slice(0, boundary), slot: Number(part) };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function trackPath(slots: SlotMap, path: string): void {
|
|
40
|
+
const key = getSlotKey(path);
|
|
41
|
+
|
|
42
|
+
if (key === null) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const level = slots.get(key.levelPath);
|
|
47
|
+
|
|
48
|
+
if (level === undefined) {
|
|
49
|
+
slots.set(key.levelPath, new Set([key.slot]));
|
|
50
|
+
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
level.add(key.slot);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function trackPaths(paths: Iterable<string>): SlotMap {
|
|
58
|
+
const slots: SlotMap = new Map();
|
|
59
|
+
|
|
60
|
+
for (const path of paths) {
|
|
61
|
+
trackPath(slots, path);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return slots;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { getSlotKey, trackPath, trackPaths, type SlotMap };
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { CollectionIndex } from "./collection-index.js";
|
|
2
|
+
import type { SlotMap } from "./slots.js";
|
|
3
|
+
import type { VisibleOrder } from "./tree-order.js";
|
|
4
|
+
import { encodePart } from "./keys.js";
|
|
5
|
+
import { getSlotKey, trackPath, trackPaths } from "./slots.js";
|
|
6
|
+
import { buildVisibleOrder } from "./tree-order.js";
|
|
7
|
+
|
|
8
|
+
type TreeExpansion = {
|
|
9
|
+
index: CollectionIndex;
|
|
10
|
+
expanded: Set<string>;
|
|
11
|
+
slots: SlotMap;
|
|
12
|
+
order: VisibleOrder | null;
|
|
13
|
+
isApplying: boolean;
|
|
14
|
+
isSyncing: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function dropSubtree(expansion: TreeExpansion, path: string): void {
|
|
18
|
+
expansion.expanded.delete(path);
|
|
19
|
+
const level = expansion.slots.get(path);
|
|
20
|
+
|
|
21
|
+
if (level === undefined) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
expansion.slots.delete(path);
|
|
26
|
+
|
|
27
|
+
for (const slot of level) {
|
|
28
|
+
dropSubtree(expansion, path + encodePart(String(slot)));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function createTreeExpansion(index: CollectionIndex): TreeExpansion {
|
|
33
|
+
return { index, expanded: new Set(), slots: new Map(), order: null, isApplying: false, isSyncing: false };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function orderFor(expansion: TreeExpansion): VisibleOrder {
|
|
37
|
+
expansion.order ??= buildVisibleOrder(expansion.index, expansion.slots);
|
|
38
|
+
|
|
39
|
+
return expansion.order;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function adoptOrder(expansion: TreeExpansion, order: VisibleOrder): void {
|
|
43
|
+
expansion.expanded = new Set(order.expandedPaths);
|
|
44
|
+
expansion.slots = trackPaths(order.expandedPaths);
|
|
45
|
+
expansion.order = order;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function prunePath(expansion: TreeExpansion, path: string): void {
|
|
49
|
+
const key = getSlotKey(path);
|
|
50
|
+
|
|
51
|
+
if (key !== null) {
|
|
52
|
+
expansion.slots.get(key.levelPath)?.delete(key.slot);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
dropSubtree(expansion, path);
|
|
56
|
+
expansion.order = null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function pruneSlots(expansion: TreeExpansion, levelPath: string, isPruned: (slot: number) => boolean): void {
|
|
60
|
+
const level = expansion.slots.get(levelPath);
|
|
61
|
+
|
|
62
|
+
if (level === undefined) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (const slot of level) {
|
|
67
|
+
if (!isPruned(slot)) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
level.delete(slot);
|
|
72
|
+
dropSubtree(expansion, levelPath + encodePart(String(slot)));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
expansion.order = null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function markExpanded(expansion: TreeExpansion, path: string, isExpanded: boolean): void {
|
|
79
|
+
if (isExpanded) {
|
|
80
|
+
expansion.expanded.add(path);
|
|
81
|
+
trackPath(expansion.slots, path);
|
|
82
|
+
expansion.order = null;
|
|
83
|
+
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
prunePath(expansion, path);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function resetExpansion(expansion: TreeExpansion): void {
|
|
91
|
+
expansion.expanded = new Set();
|
|
92
|
+
expansion.slots = new Map();
|
|
93
|
+
expansion.order = null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function adoptIndex(expansion: TreeExpansion, index: CollectionIndex): void {
|
|
97
|
+
expansion.index = index;
|
|
98
|
+
expansion.order = null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export {
|
|
102
|
+
adoptIndex,
|
|
103
|
+
adoptOrder,
|
|
104
|
+
createTreeExpansion,
|
|
105
|
+
markExpanded,
|
|
106
|
+
orderFor,
|
|
107
|
+
pruneSlots,
|
|
108
|
+
resetExpansion,
|
|
109
|
+
type TreeExpansion,
|
|
110
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { ListItem } from "../types.js";
|
|
2
|
+
import type { CollectionIndex, Level } from "./collection-index.js";
|
|
3
|
+
import type { SlotMap } from "./slots.js";
|
|
4
|
+
import { encodePart } from "./keys.js";
|
|
5
|
+
|
|
6
|
+
type VisibleRow = {
|
|
7
|
+
level: Level;
|
|
8
|
+
slot: number;
|
|
9
|
+
item: ListItem;
|
|
10
|
+
position: number;
|
|
11
|
+
isOpen: boolean;
|
|
12
|
+
isMarked: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type VisibleOrder = {
|
|
16
|
+
expandedPaths: string[];
|
|
17
|
+
expandedIds: string[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type RowVisitor = (row: VisibleRow) => void;
|
|
21
|
+
|
|
22
|
+
type WalkOptions = {
|
|
23
|
+
index: CollectionIndex;
|
|
24
|
+
slots: SlotMap;
|
|
25
|
+
marks?: SlotMap | undefined;
|
|
26
|
+
visit?: RowVisitor | undefined;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type WalkFrame = {
|
|
30
|
+
level: Level;
|
|
31
|
+
cursor: number;
|
|
32
|
+
open: Set<number> | undefined;
|
|
33
|
+
marked: Set<number> | undefined;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type WalkState = {
|
|
37
|
+
options: WalkOptions;
|
|
38
|
+
stack: WalkFrame[];
|
|
39
|
+
row: VisibleRow;
|
|
40
|
+
order: VisibleOrder;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const NO_LEVEL: Level = { path: "", items: [], expandableFlags: [] };
|
|
44
|
+
const NO_ITEM: ListItem = { id: "", value: undefined };
|
|
45
|
+
|
|
46
|
+
const getRowPath = (row: VisibleRow): string => row.level.path + encodePart(String(row.slot));
|
|
47
|
+
|
|
48
|
+
function frameFor(options: WalkOptions, level: Level): WalkFrame {
|
|
49
|
+
return { level, cursor: 0, open: options.slots.get(level.path), marked: options.marks?.get(level.path) };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function descend(state: WalkState, path: string): void {
|
|
53
|
+
const child = state.options.index.children.get(path);
|
|
54
|
+
|
|
55
|
+
if (child === undefined || child.items.length === 0) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
state.order.expandedPaths.push(path);
|
|
60
|
+
state.order.expandedIds.push(state.row.item.id);
|
|
61
|
+
state.stack.push(frameFor(state.options, child));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function visitSlot(state: WalkState, frame: WalkFrame, slot: number): void {
|
|
65
|
+
const item = frame.level.items[slot];
|
|
66
|
+
|
|
67
|
+
if (item === undefined) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const { row } = state;
|
|
72
|
+
row.level = frame.level;
|
|
73
|
+
row.slot = slot;
|
|
74
|
+
row.item = item;
|
|
75
|
+
row.isOpen = frame.open?.has(slot) ?? false;
|
|
76
|
+
row.isMarked = frame.marked?.has(slot) ?? false;
|
|
77
|
+
state.options.visit?.(row);
|
|
78
|
+
row.position += 1;
|
|
79
|
+
|
|
80
|
+
if (row.isOpen) {
|
|
81
|
+
descend(state, getRowPath(row));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function advanceFrame(state: WalkState): void {
|
|
86
|
+
const frame = state.stack.at(-1);
|
|
87
|
+
|
|
88
|
+
if (frame === undefined || frame.cursor >= frame.level.items.length) {
|
|
89
|
+
state.stack.pop();
|
|
90
|
+
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const slot = frame.cursor;
|
|
95
|
+
frame.cursor += 1;
|
|
96
|
+
visitSlot(state, frame, slot);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function walkVisible(options: WalkOptions): VisibleOrder {
|
|
100
|
+
const order: VisibleOrder = { expandedPaths: [], expandedIds: [] };
|
|
101
|
+
const row: VisibleRow = { level: NO_LEVEL, slot: 0, item: NO_ITEM, position: 0, isOpen: false, isMarked: false };
|
|
102
|
+
const frames = options.index.groups.map((level) => frameFor(options, level));
|
|
103
|
+
const state: WalkState = { options, stack: frames.toReversed(), row, order };
|
|
104
|
+
|
|
105
|
+
while (state.stack.length > 0) {
|
|
106
|
+
advanceFrame(state);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return order;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function buildVisibleOrder(index: CollectionIndex, slots: SlotMap): VisibleOrder {
|
|
113
|
+
return walkVisible({ index, slots });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function findPositions(index: CollectionIndex, slots: SlotMap, ids: Set<string>): number[] {
|
|
117
|
+
const found: number[] = [];
|
|
118
|
+
|
|
119
|
+
walkVisible({
|
|
120
|
+
index,
|
|
121
|
+
slots,
|
|
122
|
+
visit: (row) => {
|
|
123
|
+
if (ids.has(row.item.id)) {
|
|
124
|
+
found.push(row.position);
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return found;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export {
|
|
133
|
+
buildVisibleOrder,
|
|
134
|
+
findPositions,
|
|
135
|
+
walkVisible,
|
|
136
|
+
type VisibleOrder,
|
|
137
|
+
type WalkOptions,
|
|
138
|
+
};
|
|
@@ -23,13 +23,13 @@ type CollectionResult = {
|
|
|
23
23
|
|
|
24
24
|
function useCollectionData(options: CollectionDataOptions): Collection {
|
|
25
25
|
const { items, sections, isFlat } = options;
|
|
26
|
-
const [
|
|
26
|
+
const [collectionModel] = useState(createCollectionModel);
|
|
27
27
|
const index = useMemo(() => createCollectionIndex(items, sections, isFlat === true), [items, sections, isFlat]);
|
|
28
|
-
const collection = useMemo(() => createCollection(
|
|
28
|
+
const collection = useMemo(() => createCollection(collectionModel, index), [collectionModel, index]);
|
|
29
29
|
|
|
30
30
|
useLayoutEffect(() => {
|
|
31
|
-
|
|
32
|
-
}, [
|
|
31
|
+
collectionModel.sync(index);
|
|
32
|
+
}, [collectionModel, index]);
|
|
33
33
|
|
|
34
34
|
return collection;
|
|
35
35
|
}
|
|
@@ -54,4 +54,4 @@ function useCollection(options: CollectionOptions): CollectionResult {
|
|
|
54
54
|
return { collection, selection };
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
export { useCollection, useCollectionData
|
|
57
|
+
export { useCollection, useCollectionData };
|
package/src/list-view.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import type { ReactNode } from "react";
|
|
|
2
2
|
import { GtkListView, GtkSignalListItemFactory } from "@gtkx/jsx/gtk";
|
|
3
3
|
import { omit } from "@gtkx/utils";
|
|
4
4
|
import type { ListViewProps } from "./types.js";
|
|
5
|
-
import {
|
|
5
|
+
import { ItemPortals, useItemCells, useSectionHeader } from "./internal/cells.js";
|
|
6
6
|
import { useCollection } from "./internal/use-collection.js";
|
|
7
7
|
|
|
8
8
|
const LIST_VIEW_PROPS = [
|
|
@@ -15,6 +15,7 @@ const LIST_VIEW_PROPS = [
|
|
|
15
15
|
"selectionMode",
|
|
16
16
|
"expandedIds",
|
|
17
17
|
"onExpandedChange",
|
|
18
|
+
"expanderDescriptions",
|
|
18
19
|
"estimatedItemHeight",
|
|
19
20
|
"estimatedItemWidth",
|
|
20
21
|
] as const satisfies (keyof ListViewProps)[];
|
|
@@ -24,27 +25,30 @@ const LIST_VIEW_PROPS = [
|
|
|
24
25
|
* controlled selection, controlled tree expansion, and estimated item sizing.
|
|
25
26
|
*/
|
|
26
27
|
function ListView<T = unknown, S = unknown>(props: ListViewProps<T, S>): ReactNode {
|
|
27
|
-
const { renderItem, renderHeader, expandedIds,
|
|
28
|
+
const { renderItem, renderHeader, expandedIds, expanderDescriptions } = props;
|
|
29
|
+
const { estimatedItemHeight, estimatedItemWidth } = props;
|
|
28
30
|
const rest = omit(props, LIST_VIEW_PROPS);
|
|
29
31
|
const size = { width: estimatedItemWidth ?? -1, height: estimatedItemHeight ?? -1 };
|
|
30
32
|
const { collection, selection } = useCollection(props);
|
|
31
33
|
const itemCells = useItemCells(size);
|
|
32
|
-
const
|
|
34
|
+
const header = useSectionHeader(renderHeader, collection, size);
|
|
33
35
|
|
|
34
36
|
return (
|
|
35
37
|
<>
|
|
36
38
|
<GtkListView
|
|
37
39
|
model={selection}
|
|
38
40
|
factory={<GtkSignalListItemFactory {...itemCells.handlers} />}
|
|
39
|
-
{...
|
|
40
|
-
headerFactory: <GtkSignalListItemFactory {...headerCells.handlers} />,
|
|
41
|
-
})}
|
|
41
|
+
{...header.factoryProps}
|
|
42
42
|
{...rest}
|
|
43
43
|
/>
|
|
44
|
-
<ItemPortals
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
<ItemPortals
|
|
45
|
+
registry={itemCells}
|
|
46
|
+
render={renderItem}
|
|
47
|
+
collection={collection}
|
|
48
|
+
expandedIds={expandedIds}
|
|
49
|
+
expanderDescriptions={expanderDescriptions}
|
|
50
|
+
/>
|
|
51
|
+
{header.portals}
|
|
48
52
|
</>
|
|
49
53
|
);
|
|
50
54
|
}
|
package/src/types.ts
CHANGED
|
@@ -13,18 +13,18 @@ import type { ReactNode } from "react";
|
|
|
13
13
|
* arbitrary value. Nested items form a tree.
|
|
14
14
|
*/
|
|
15
15
|
type ListItem<T = unknown> = {
|
|
16
|
-
/** Stable identifier used to track the item across updates and selection. */
|
|
16
|
+
/** Stable identifier used to track the item across updates and selection, naming every row that carries it. */
|
|
17
17
|
id: string;
|
|
18
18
|
/** Payload handed to the cell renderer as `ListItemRenderArgs.item`. */
|
|
19
19
|
value: T;
|
|
20
20
|
/** Child items nested under this one, which turn a plain item list into a tree. */
|
|
21
21
|
children?: ListItem<T>[] | undefined;
|
|
22
|
-
/** Hides the tree expander arrow even when the item has children
|
|
23
|
-
|
|
24
|
-
/** Adds indentation matching the item's depth in the tree
|
|
25
|
-
|
|
26
|
-
/** Reserves indentation space for an expander icon
|
|
27
|
-
|
|
22
|
+
/** Hides the tree expander arrow even when the item has children, through `hide-expander`. */
|
|
23
|
+
shouldHideExpander?: boolean | undefined;
|
|
24
|
+
/** Adds indentation matching the item's depth in the tree, through `indent-for-depth`. */
|
|
25
|
+
shouldIndentForDepth?: boolean | undefined;
|
|
26
|
+
/** Reserves indentation space for an expander icon, through `indent-for-icon`. */
|
|
27
|
+
shouldIndentForIcon?: boolean | undefined;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
/** A group of items rendered under a shared section header. */
|
|
@@ -49,6 +49,24 @@ type ListItemRenderArgs<T> = {
|
|
|
49
49
|
isExpanded?: boolean | undefined;
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Properties applied to the row that carries one item's cells. A Gtk.ColumnViewRow is not a widget, so
|
|
54
|
+
* these are the only handles it offers; anything omitted falls back to the row's own default, which
|
|
55
|
+
* leaves the accessibility strings unset rather than setting them to an empty string.
|
|
56
|
+
*/
|
|
57
|
+
type ListRowProps = {
|
|
58
|
+
/** Name a screen reader announces for the row, through `accessible-label`. */
|
|
59
|
+
accessibleLabel?: string | undefined;
|
|
60
|
+
/** Longer description a screen reader announces for the row, through `accessible-description`. */
|
|
61
|
+
accessibleDescription?: string | undefined;
|
|
62
|
+
/** Whether activating the row emits the view's `activate` signal; rows are activatable by default. */
|
|
63
|
+
isActivatable?: boolean | undefined;
|
|
64
|
+
/** Whether the row takes keyboard focus; rows are focusable by default. */
|
|
65
|
+
isFocusable?: boolean | undefined;
|
|
66
|
+
/** Whether clicking or keying the row tries to select it; rows are selectable by default. */
|
|
67
|
+
isSelectable?: boolean | undefined;
|
|
68
|
+
};
|
|
69
|
+
|
|
52
70
|
/** Arguments passed to a {@link ListSectionRenderer} when rendering one section header. */
|
|
53
71
|
type ListSectionRenderArgs<S> = {
|
|
54
72
|
/** Value of the `ListSection` whose header is being rendered. */
|
|
@@ -59,6 +77,8 @@ type ListSectionRenderArgs<S> = {
|
|
|
59
77
|
type ListItemRenderer<T> = (args: ListItemRenderArgs<T>) => ReactNode;
|
|
60
78
|
/** Renders the contents of one section header of a collection view. */
|
|
61
79
|
type ListSectionRenderer<S> = (args: ListSectionRenderArgs<S>) => ReactNode;
|
|
80
|
+
/** Returns the {@link ListRowProps} to apply to the row displaying one item, re-run whenever the item renders. */
|
|
81
|
+
type ListRowPropsResolver<T> = (args: ListItemRenderArgs<T>) => ListRowProps;
|
|
62
82
|
|
|
63
83
|
/** Size a collection view requests for each cell before its contents render, keeping scroll estimates steady. */
|
|
64
84
|
type ItemSizeProps = {
|
|
@@ -70,20 +90,56 @@ type ItemSizeProps = {
|
|
|
70
90
|
|
|
71
91
|
/** Controlled selection shared by the multi-item collection views. */
|
|
72
92
|
type SelectionProps = {
|
|
73
|
-
/**
|
|
93
|
+
/**
|
|
94
|
+
* Ids of the items to keep selected; omitting it keeps nothing selected, and `onSelectionChanged` is how a
|
|
95
|
+
* user's selection is adopted into it. An id repeated in several branches of a tree names every matching row,
|
|
96
|
+
* and a single-selection view takes the first of them.
|
|
97
|
+
*/
|
|
74
98
|
selectedIds?: string[] | null | undefined;
|
|
75
|
-
/** Called with
|
|
99
|
+
/** Called with one id per selected row whenever the selection changes, and once on mount. */
|
|
76
100
|
onSelectionChanged?: ((ids: string[]) => void) | null | undefined;
|
|
77
101
|
/** How much the user may select, one item at a time unless `MULTIPLE` or `NONE` is given. */
|
|
78
102
|
selectionMode?: Gtk.SelectionMode | null | undefined;
|
|
79
103
|
};
|
|
80
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Wording a screen reader announces for the control that expands and collapses a row. GTK names that control
|
|
107
|
+
* after the row's own content and reports whether it is expanded, but says nothing about what activating it
|
|
108
|
+
* does, so these strings supply that and belong in the application's own language.
|
|
109
|
+
*/
|
|
110
|
+
type ExpanderDescriptions = {
|
|
111
|
+
/** Announced while the row is collapsed, such as "Expand". */
|
|
112
|
+
expand: string;
|
|
113
|
+
/** Announced while the row is expanded, such as "Collapse". */
|
|
114
|
+
collapse: string;
|
|
115
|
+
};
|
|
116
|
+
|
|
81
117
|
/** Controlled expansion for the views that turn nested `ListItem.children` into a tree. */
|
|
82
118
|
type ExpansionProps = {
|
|
83
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* Ids of the items to keep expanded; omitting it keeps every row collapsed, and `onExpandedChange` is how a
|
|
121
|
+
* user's expansion is adopted into it. An id repeated in several branches names every matching row, so all of
|
|
122
|
+
* them expand together.
|
|
123
|
+
*/
|
|
84
124
|
expandedIds?: string[] | null | undefined;
|
|
85
|
-
/** Called with
|
|
125
|
+
/** Called with one id per expanded row, in visible order, whenever expansion changes. */
|
|
86
126
|
onExpandedChange?: ((ids: string[]) => void) | null | undefined;
|
|
127
|
+
/**
|
|
128
|
+
* Wording announced for the expander of every row that can expand, describing what activating it does.
|
|
129
|
+
* Omitting it leaves those expanders described only by the row they carry, and rows that cannot expand or
|
|
130
|
+
* that hide their expander are never described.
|
|
131
|
+
*/
|
|
132
|
+
expanderDescriptions?: ExpanderDescriptions | null | undefined;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/** Controlled sorting for the views whose headers can sort the collection. */
|
|
136
|
+
type SortProps = {
|
|
137
|
+
/** Id of the column the view is sorted by, making sorting controlled. */
|
|
138
|
+
sortColumn?: string | null | undefined;
|
|
139
|
+
/** Direction `sortColumn` is sorted in, defaulting to ascending. */
|
|
140
|
+
sortOrder?: Gtk.SortType | null | undefined;
|
|
141
|
+
/** Called when the user sorts from a header, with the primary column's id, or null, and its order. */
|
|
142
|
+
onSortChanged?: ((column: string | null, order: Gtk.SortType) => void) | null | undefined;
|
|
87
143
|
};
|
|
88
144
|
|
|
89
145
|
/** The data a collection view renders, either as a plain item list or grouped into sections. */
|
|
@@ -111,29 +167,26 @@ type ColumnViewColumn<T = unknown> = Omit<GtkColumnViewColumnProps, "factory" |
|
|
|
111
167
|
/** The declarative collection props {@link ColumnView} adds on top of Gtk.ColumnView's own. */
|
|
112
168
|
type ColumnViewOwnProps<T, S> = SelectionProps &
|
|
113
169
|
ExpansionProps &
|
|
170
|
+
SortProps &
|
|
114
171
|
SourceProps<T, S> &
|
|
115
172
|
Omit<ItemSizeProps, "estimatedItemWidth"> & {
|
|
116
173
|
/** Columns to render, in order; each carries its own cell renderer. */
|
|
117
174
|
columns: ColumnViewColumn<T>[];
|
|
118
175
|
/** Renders the header shown above each section. */
|
|
119
176
|
renderHeader?: ListSectionRenderer<S> | null | undefined;
|
|
120
|
-
/**
|
|
121
|
-
|
|
122
|
-
/** Direction `sortColumn` is sorted in, defaulting to ascending. */
|
|
123
|
-
sortOrder?: Gtk.SortType | null | undefined;
|
|
124
|
-
/** Called when the user sorts from a header, with the primary column's id, or null, and its order. */
|
|
125
|
-
onSortChanged?: ((column: string | null, order: Gtk.SortType) => void) | null | undefined;
|
|
177
|
+
/** Resolves the props of the row carrying one item's cells, such as its screen-reader label. */
|
|
178
|
+
rowProps?: ListRowPropsResolver<T> | null | undefined;
|
|
126
179
|
};
|
|
127
180
|
|
|
128
181
|
/**
|
|
129
182
|
* Props for {@link ColumnView}. Combines the underlying Gtk.ColumnView props with
|
|
130
183
|
* declarative collection props: flat items or grouped sections, controlled selection
|
|
131
184
|
* and expansion, sorting (sortColumn, sortOrder, onSortChanged), an optional section
|
|
132
|
-
* header renderer, and the columns to render.
|
|
185
|
+
* header renderer, per-row props, and the columns to render.
|
|
133
186
|
*/
|
|
134
187
|
type ColumnViewProps<T = unknown, S = unknown> = Omit<
|
|
135
188
|
GtkColumnViewProps,
|
|
136
|
-
"columns" | "model" | "headerFactory" | keyof ColumnViewOwnProps<T, S>
|
|
189
|
+
"columns" | "model" | "headerFactory" | "rowFactory" | keyof ColumnViewOwnProps<T, S>
|
|
137
190
|
> &
|
|
138
191
|
ColumnViewOwnProps<T, S>;
|
|
139
192
|
|
|
@@ -207,14 +260,18 @@ ListViewOwnProps<T, S>;
|
|
|
207
260
|
|
|
208
261
|
export {
|
|
209
262
|
type SelectionProps,
|
|
263
|
+
type ExpanderDescriptions,
|
|
210
264
|
type ExpansionProps,
|
|
265
|
+
type SortProps,
|
|
211
266
|
type DropDownOwnProps,
|
|
212
267
|
type DropDownWidgetProps,
|
|
213
268
|
type ListItem,
|
|
214
269
|
type ListSection,
|
|
215
270
|
type ListItemRenderArgs,
|
|
271
|
+
type ListRowProps,
|
|
216
272
|
type ListSectionRenderArgs,
|
|
217
273
|
type ListItemRenderer,
|
|
274
|
+
type ListRowPropsResolver,
|
|
218
275
|
type ListSectionRenderer,
|
|
219
276
|
type ColumnViewColumn,
|
|
220
277
|
type ColumnViewProps,
|