@gtkx/components 1.0.0 → 1.2.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 +1 -1
- package/dist/adw/toast.d.ts.map +1 -1
- package/dist/adw/toast.js +6 -2
- package/dist/adw/toast.js.map +1 -1
- package/dist/internal/cells.js +1 -1
- package/dist/internal/cells.js.map +1 -1
- package/dist/internal/collection-index.d.ts +4 -3
- package/dist/internal/collection-index.d.ts.map +1 -1
- package/dist/internal/collection-index.js +50 -33
- package/dist/internal/collection-index.js.map +1 -1
- package/dist/internal/collection-model.d.ts +3 -4
- package/dist/internal/collection-model.d.ts.map +1 -1
- package/dist/internal/collection-model.js +110 -74
- package/dist/internal/collection-model.js.map +1 -1
- package/dist/internal/collection.d.ts +2 -1
- package/dist/internal/collection.d.ts.map +1 -1
- package/dist/internal/collection.js +7 -6
- package/dist/internal/collection.js.map +1 -1
- package/dist/internal/expansion.d.ts.map +1 -1
- package/dist/internal/expansion.js +11 -19
- package/dist/internal/expansion.js.map +1 -1
- package/dist/internal/tree-expansion.d.ts +1 -2
- package/dist/internal/tree-expansion.d.ts.map +1 -1
- package/dist/internal/tree-expansion.js +12 -8
- package/dist/internal/tree-expansion.js.map +1 -1
- package/dist/internal/tree-order.d.ts +3 -4
- package/dist/internal/tree-order.d.ts.map +1 -1
- package/dist/internal/tree-order.js +61 -23
- package/dist/internal/tree-order.js.map +1 -1
- package/dist/types.d.ts +3 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +17 -9
- package/src/adw/toast.tsx +6 -2
- package/src/internal/cells.tsx +1 -1
- package/src/internal/collection-index.ts +63 -39
- package/src/internal/collection-model.ts +148 -85
- package/src/internal/collection.ts +10 -8
- package/src/internal/expansion.ts +12 -24
- package/src/internal/tree-expansion.ts +14 -9
- package/src/internal/tree-order.ts +98 -28
- package/src/types.ts +3 -2
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type * as Gtk from "@gtkx/gi/gtk";
|
|
2
1
|
import { useState } from "react";
|
|
3
2
|
import type { Collection } from "./collection.js";
|
|
4
3
|
import type { TreeExpansion } from "./tree-expansion.js";
|
|
@@ -8,7 +7,7 @@ import { useControlledSync } from "./controlled-sync.js";
|
|
|
8
7
|
import { joinParts } from "./keys.js";
|
|
9
8
|
import { trackPaths } from "./slots.js";
|
|
10
9
|
import { adoptOrder, markExpanded, orderFor } from "./tree-expansion.js";
|
|
11
|
-
import { walkVisible } from "./tree-order.js";
|
|
10
|
+
import { expandedPathsFor, walkVisible } from "./tree-order.js";
|
|
12
11
|
|
|
13
12
|
type ExpansionOptions = {
|
|
14
13
|
collection: Collection;
|
|
@@ -35,30 +34,20 @@ function newLastExpansion(): LastExpansion {
|
|
|
35
34
|
return { key: "" };
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
function wantedSet(expansion: TreeExpansion, expandedIds: string[]): Set<string> {
|
|
39
|
-
const wanted: Set<string> = new Set();
|
|
40
|
-
|
|
41
|
-
for (const id of expandedIds) {
|
|
42
|
-
for (const path of expansion.index.expandablePathsFor(id)) {
|
|
43
|
-
wanted.add(path);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return wanted;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
37
|
function hasSameExpansion(expanded: Set<string>, wanted: Set<string>): boolean {
|
|
51
38
|
return expanded.size === wanted.size && wanted.isSubsetOf(expanded);
|
|
52
39
|
}
|
|
53
40
|
|
|
54
|
-
function toggleOptions(
|
|
41
|
+
function toggleOptions(collection: Collection, wanted: Set<string>): WalkOptions {
|
|
42
|
+
const { expansion } = collection;
|
|
43
|
+
|
|
55
44
|
return {
|
|
56
45
|
index: expansion.index,
|
|
57
46
|
slots: trackPaths(wanted),
|
|
58
47
|
marks: trackPaths(wanted.symmetricDifference(expansion.expanded)),
|
|
59
48
|
visit: (row) => {
|
|
60
49
|
if (row.isMarked) {
|
|
61
|
-
|
|
50
|
+
collection.rowAt(row.position)?.setExpanded(row.isOpen);
|
|
62
51
|
}
|
|
63
52
|
},
|
|
64
53
|
};
|
|
@@ -74,14 +63,15 @@ function walkApplying(expansion: TreeExpansion, options: WalkOptions): VisibleOr
|
|
|
74
63
|
}
|
|
75
64
|
}
|
|
76
65
|
|
|
77
|
-
function applyWanted(
|
|
78
|
-
const
|
|
66
|
+
function applyWanted(collection: Collection, expandedIds: string[]): void {
|
|
67
|
+
const { expansion } = collection;
|
|
68
|
+
const wanted = expandedPathsFor(expansion.index, new Set(expandedIds));
|
|
79
69
|
|
|
80
70
|
if (hasSameExpansion(expansion.expanded, wanted)) {
|
|
81
71
|
return;
|
|
82
72
|
}
|
|
83
73
|
|
|
84
|
-
adoptOrder(expansion, walkApplying(expansion, toggleOptions(
|
|
74
|
+
adoptOrder(expansion, walkApplying(expansion, toggleOptions(collection, wanted)));
|
|
85
75
|
}
|
|
86
76
|
|
|
87
77
|
function reportExpansion(context: ExpansionContext): void {
|
|
@@ -97,18 +87,16 @@ function reportExpansion(context: ExpansionContext): void {
|
|
|
97
87
|
}
|
|
98
88
|
|
|
99
89
|
function applyControlledExpansion(context: ExpansionContext, expandedIds: string[] | null | undefined): void {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (tree === null) {
|
|
90
|
+
if (!context.collection.isTree) {
|
|
103
91
|
return;
|
|
104
92
|
}
|
|
105
93
|
|
|
106
|
-
applyWanted(context.collection
|
|
94
|
+
applyWanted(context.collection, expandedIds ?? []);
|
|
107
95
|
reportExpansion(context);
|
|
108
96
|
}
|
|
109
97
|
|
|
110
98
|
function isExpansionIdle(collection: Collection): boolean {
|
|
111
|
-
return collection.
|
|
99
|
+
return collection.isTree && isCollectionIdle(collection);
|
|
112
100
|
}
|
|
113
101
|
|
|
114
102
|
function didRowDrift(collection: Collection, change: ItemsChange): boolean {
|
|
@@ -14,7 +14,7 @@ type TreeExpansion = {
|
|
|
14
14
|
isSyncing: boolean;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
function
|
|
17
|
+
function dropLevel(expansion: TreeExpansion, path: string, pending: string[]): void {
|
|
18
18
|
expansion.expanded.delete(path);
|
|
19
19
|
const level = expansion.slots.get(path);
|
|
20
20
|
|
|
@@ -25,7 +25,19 @@ function dropSubtree(expansion: TreeExpansion, path: string): void {
|
|
|
25
25
|
expansion.slots.delete(path);
|
|
26
26
|
|
|
27
27
|
for (const slot of level) {
|
|
28
|
-
|
|
28
|
+
pending.push(path + encodePart(String(slot)));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function dropSubtree(expansion: TreeExpansion, path: string): void {
|
|
33
|
+
const pending: string[] = [path];
|
|
34
|
+
|
|
35
|
+
while (pending.length > 0) {
|
|
36
|
+
const current = pending.pop();
|
|
37
|
+
|
|
38
|
+
if (current !== undefined) {
|
|
39
|
+
dropLevel(expansion, current, pending);
|
|
40
|
+
}
|
|
29
41
|
}
|
|
30
42
|
}
|
|
31
43
|
|
|
@@ -87,12 +99,6 @@ function markExpanded(expansion: TreeExpansion, path: string, isExpanded: boolea
|
|
|
87
99
|
prunePath(expansion, path);
|
|
88
100
|
}
|
|
89
101
|
|
|
90
|
-
function resetExpansion(expansion: TreeExpansion): void {
|
|
91
|
-
expansion.expanded = new Set();
|
|
92
|
-
expansion.slots = new Map();
|
|
93
|
-
expansion.order = null;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
102
|
function adoptIndex(expansion: TreeExpansion, index: CollectionIndex): void {
|
|
97
103
|
expansion.index = index;
|
|
98
104
|
expansion.order = null;
|
|
@@ -105,6 +111,5 @@ export {
|
|
|
105
111
|
markExpanded,
|
|
106
112
|
orderFor,
|
|
107
113
|
pruneSlots,
|
|
108
|
-
resetExpansion,
|
|
109
114
|
type TreeExpansion,
|
|
110
115
|
};
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import type { ListItem } from "../types.js";
|
|
2
2
|
import type { CollectionIndex, Level } from "./collection-index.js";
|
|
3
3
|
import type { SlotMap } from "./slots.js";
|
|
4
|
-
import { encodePart } from "./keys.js";
|
|
5
4
|
|
|
6
5
|
type VisibleRow = {
|
|
7
|
-
level: Level;
|
|
8
|
-
slot: number;
|
|
9
6
|
item: ListItem;
|
|
10
7
|
position: number;
|
|
11
8
|
isOpen: boolean;
|
|
@@ -26,9 +23,12 @@ type WalkOptions = {
|
|
|
26
23
|
visit?: RowVisitor | undefined;
|
|
27
24
|
};
|
|
28
25
|
|
|
29
|
-
type
|
|
26
|
+
type LevelFrame = {
|
|
30
27
|
level: Level;
|
|
31
28
|
cursor: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type WalkFrame = LevelFrame & {
|
|
32
32
|
open: Set<number> | undefined;
|
|
33
33
|
marked: Set<number> | undefined;
|
|
34
34
|
};
|
|
@@ -40,23 +40,55 @@ type WalkState = {
|
|
|
40
40
|
order: VisibleOrder;
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
type OpenFrame = LevelFrame & {
|
|
44
|
+
owner: ListItem | null;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
type OpenWalk = {
|
|
48
|
+
index: CollectionIndex;
|
|
49
|
+
ids: Set<string>;
|
|
50
|
+
paths: Set<string>;
|
|
51
|
+
stack: OpenFrame[];
|
|
52
|
+
ancestors: Set<ListItem>;
|
|
53
|
+
};
|
|
54
|
+
|
|
44
55
|
const NO_ITEM: ListItem = { id: "", value: undefined };
|
|
45
56
|
|
|
46
|
-
|
|
57
|
+
function advanceStack<F extends LevelFrame>(
|
|
58
|
+
stack: F[],
|
|
59
|
+
onSlot: (frame: F, slot: number) => void,
|
|
60
|
+
onLeave?: (frame: F) => void,
|
|
61
|
+
): void {
|
|
62
|
+
const frame = stack.at(-1);
|
|
63
|
+
|
|
64
|
+
if (frame === undefined) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (frame.cursor >= frame.level.items.length) {
|
|
69
|
+
stack.pop();
|
|
70
|
+
onLeave?.(frame);
|
|
71
|
+
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const slot = frame.cursor;
|
|
76
|
+
frame.cursor += 1;
|
|
77
|
+
onSlot(frame, slot);
|
|
78
|
+
}
|
|
47
79
|
|
|
48
80
|
function frameFor(options: WalkOptions, level: Level): WalkFrame {
|
|
49
81
|
return { level, cursor: 0, open: options.slots.get(level.path), marked: options.marks?.get(level.path) };
|
|
50
82
|
}
|
|
51
83
|
|
|
52
|
-
function descend(state: WalkState,
|
|
53
|
-
const child = state.options.index.
|
|
84
|
+
function descend(state: WalkState, level: Level, slot: number): void {
|
|
85
|
+
const child = state.options.index.childLevel(level, slot);
|
|
54
86
|
|
|
55
|
-
if (child === undefined
|
|
87
|
+
if (child === undefined) {
|
|
56
88
|
return;
|
|
57
89
|
}
|
|
58
90
|
|
|
59
|
-
state.order.expandedPaths.push(path);
|
|
91
|
+
state.order.expandedPaths.push(child.path);
|
|
60
92
|
state.order.expandedIds.push(state.row.item.id);
|
|
61
93
|
state.stack.push(frameFor(state.options, child));
|
|
62
94
|
}
|
|
@@ -69,8 +101,6 @@ function visitSlot(state: WalkState, frame: WalkFrame, slot: number): void {
|
|
|
69
101
|
}
|
|
70
102
|
|
|
71
103
|
const { row } = state;
|
|
72
|
-
row.level = frame.level;
|
|
73
|
-
row.slot = slot;
|
|
74
104
|
row.item = item;
|
|
75
105
|
row.isOpen = frame.open?.has(slot) ?? false;
|
|
76
106
|
row.isMarked = frame.marked?.has(slot) ?? false;
|
|
@@ -78,35 +108,74 @@ function visitSlot(state: WalkState, frame: WalkFrame, slot: number): void {
|
|
|
78
108
|
row.position += 1;
|
|
79
109
|
|
|
80
110
|
if (row.isOpen) {
|
|
81
|
-
descend(state,
|
|
111
|
+
descend(state, frame.level, slot);
|
|
82
112
|
}
|
|
83
113
|
}
|
|
84
114
|
|
|
85
|
-
function
|
|
86
|
-
const
|
|
115
|
+
function walkVisible(options: WalkOptions): VisibleOrder {
|
|
116
|
+
const order: VisibleOrder = { expandedPaths: [], expandedIds: [] };
|
|
117
|
+
const row: VisibleRow = { item: NO_ITEM, position: 0, isOpen: false, isMarked: false };
|
|
118
|
+
const frames = options.index.groups.map((level) => frameFor(options, level));
|
|
119
|
+
const state: WalkState = { options, stack: frames.toReversed(), row, order };
|
|
87
120
|
|
|
88
|
-
|
|
89
|
-
state
|
|
121
|
+
const onSlot = (frame: WalkFrame, slot: number): void => {
|
|
122
|
+
visitSlot(state, frame, slot);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
while (state.stack.length > 0) {
|
|
126
|
+
advanceStack(state.stack, onSlot);
|
|
127
|
+
}
|
|
90
128
|
|
|
129
|
+
return order;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function openSlot(walk: OpenWalk, frame: OpenFrame, slot: number): void {
|
|
133
|
+
const item = frame.level.items[slot];
|
|
134
|
+
|
|
135
|
+
if (item === undefined || !walk.ids.has(item.id) || walk.ancestors.has(item)) {
|
|
91
136
|
return;
|
|
92
137
|
}
|
|
93
138
|
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
139
|
+
const child = walk.index.childLevel(frame.level, slot);
|
|
140
|
+
|
|
141
|
+
if (child === undefined) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
walk.paths.add(child.path);
|
|
146
|
+
walk.ancestors.add(item);
|
|
147
|
+
walk.stack.push({ level: child, cursor: 0, owner: item });
|
|
97
148
|
}
|
|
98
149
|
|
|
99
|
-
function
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
150
|
+
function leaveOpenFrame(walk: OpenWalk, frame: OpenFrame): void {
|
|
151
|
+
if (frame.owner !== null) {
|
|
152
|
+
walk.ancestors.delete(frame.owner);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
104
155
|
|
|
105
|
-
|
|
106
|
-
|
|
156
|
+
function expandedPathsFor(index: CollectionIndex, ids: Set<string>): Set<string> {
|
|
157
|
+
const paths: Set<string> = new Set();
|
|
158
|
+
|
|
159
|
+
if (ids.size === 0 || !index.isTree) {
|
|
160
|
+
return paths;
|
|
107
161
|
}
|
|
108
162
|
|
|
109
|
-
|
|
163
|
+
const frames = index.groups.map((level) => ({ level, cursor: 0, owner: null }));
|
|
164
|
+
const walk: OpenWalk = { index, ids, paths, stack: frames.toReversed(), ancestors: new Set() };
|
|
165
|
+
|
|
166
|
+
const onSlot = (frame: OpenFrame, slot: number): void => {
|
|
167
|
+
openSlot(walk, frame, slot);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const onLeave = (frame: OpenFrame): void => {
|
|
171
|
+
leaveOpenFrame(walk, frame);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
while (walk.stack.length > 0) {
|
|
175
|
+
advanceStack(walk.stack, onSlot, onLeave);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return paths;
|
|
110
179
|
}
|
|
111
180
|
|
|
112
181
|
function buildVisibleOrder(index: CollectionIndex, slots: SlotMap): VisibleOrder {
|
|
@@ -131,6 +200,7 @@ function findPositions(index: CollectionIndex, slots: SlotMap, ids: Set<string>)
|
|
|
131
200
|
|
|
132
201
|
export {
|
|
133
202
|
buildVisibleOrder,
|
|
203
|
+
expandedPathsFor,
|
|
134
204
|
findPositions,
|
|
135
205
|
walkVisible,
|
|
136
206
|
type VisibleOrder,
|
package/src/types.ts
CHANGED
|
@@ -17,7 +17,7 @@ type ListItem<T = unknown> = {
|
|
|
17
17
|
id: string;
|
|
18
18
|
/** Payload handed to the cell renderer as `ListItemRenderArgs.item`. */
|
|
19
19
|
value: T;
|
|
20
|
-
/** Child items nested under this one, which turn a plain item list into a tree. */
|
|
20
|
+
/** Child items nested under this one, which turn a plain item list into a tree, read only as rows are drawn. */
|
|
21
21
|
children?: ListItem<T>[] | undefined;
|
|
22
22
|
/** Hides the tree expander arrow even when the item has children, through `hide-expander`. */
|
|
23
23
|
shouldHideExpander?: boolean | undefined;
|
|
@@ -119,7 +119,8 @@ type ExpansionProps = {
|
|
|
119
119
|
/**
|
|
120
120
|
* Ids of the items to keep expanded; omitting it keeps every row collapsed, and `onExpandedChange` is how a
|
|
121
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.
|
|
122
|
+
* them expand together. An item whose children lead back to itself expands one level at a time, since a row
|
|
123
|
+
* repeating an item already expanded above it stays collapsed.
|
|
123
124
|
*/
|
|
124
125
|
expandedIds?: string[] | null | undefined;
|
|
125
126
|
/** Called with one id per expanded row, in visible order, whenever expansion changes. */
|