@gtkx/components 2.0.0-beta.10 → 2.0.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -168
- package/dist/column-view.d.ts.map +1 -1
- package/dist/column-view.js +19 -9
- package/dist/column-view.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/cells.d.ts +8 -1
- package/dist/internal/cells.d.ts.map +1 -1
- package/dist/internal/cells.js +84 -19
- package/dist/internal/cells.js.map +1 -1
- package/dist/internal/collection-index.d.ts +3 -2
- package/dist/internal/collection-index.d.ts.map +1 -1
- package/dist/internal/collection-index.js +3 -3
- package/dist/internal/collection-index.js.map +1 -1
- package/dist/internal/collection-model.d.ts +23 -2
- package/dist/internal/collection-model.d.ts.map +1 -1
- package/dist/internal/collection-model.js +118 -98
- package/dist/internal/collection-model.js.map +1 -1
- package/dist/internal/collection.d.ts +5 -4
- package/dist/internal/collection.d.ts.map +1 -1
- package/dist/internal/collection.js +20 -21
- package/dist/internal/collection.js.map +1 -1
- package/dist/internal/controlled-sync.d.ts +6 -8
- package/dist/internal/controlled-sync.d.ts.map +1 -1
- package/dist/internal/controlled-sync.js +3 -3
- package/dist/internal/controlled-sync.js.map +1 -1
- package/dist/internal/drop-down.d.ts.map +1 -1
- package/dist/internal/drop-down.js +43 -44
- package/dist/internal/drop-down.js.map +1 -1
- package/dist/internal/expansion.js +4 -4
- package/dist/internal/expansion.js.map +1 -1
- package/dist/internal/selection.d.ts +1 -0
- package/dist/internal/selection.d.ts.map +1 -1
- package/dist/internal/selection.js +6 -6
- package/dist/internal/selection.js.map +1 -1
- package/dist/internal/tree-expansion.d.ts.map +1 -1
- package/dist/internal/tree-expansion.js +2 -11
- package/dist/internal/tree-expansion.js.map +1 -1
- package/dist/internal/tree-order.d.ts.map +1 -1
- package/dist/internal/tree-order.js +32 -76
- package/dist/internal/tree-order.js.map +1 -1
- package/dist/internal/use-collection.d.ts +5 -1
- package/dist/internal/use-collection.d.ts.map +1 -1
- package/dist/internal/use-collection.js +21 -7
- package/dist/internal/use-collection.js.map +1 -1
- package/dist/list-item-factory.d.ts +10 -0
- package/dist/list-item-factory.d.ts.map +1 -0
- package/dist/list-item-factory.js +14 -0
- package/dist/list-item-factory.js.map +1 -0
- package/dist/toast.d.ts +3 -5
- package/dist/toast.d.ts.map +1 -1
- package/dist/toast.js +3 -12
- package/dist/toast.js.map +1 -1
- package/dist/types.d.ts +33 -50
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +12 -10
- package/src/column-view.tsx +23 -12
- package/src/index.ts +3 -2
- package/src/internal/cells.tsx +137 -20
- package/src/internal/collection-index.ts +8 -6
- package/src/internal/collection-model.ts +156 -134
- package/src/internal/collection.ts +28 -24
- package/src/internal/controlled-sync.ts +9 -12
- package/src/internal/drop-down.tsx +64 -60
- package/src/internal/expansion.ts +4 -4
- package/src/internal/selection.tsx +8 -8
- package/src/internal/tree-expansion.ts +2 -14
- package/src/internal/tree-order.ts +33 -121
- package/src/internal/use-collection.tsx +29 -8
- package/src/list-item-factory.tsx +27 -0
- package/src/toast.tsx +4 -18
- package/src/types.ts +35 -51
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import type { ReactNode, Ref } from "react";
|
|
1
2
|
import * as Gio from "@gtkx/gi/gio";
|
|
2
3
|
import * as GObject from "@gtkx/gi/gobject";
|
|
3
4
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
5
|
+
import { createElementComponent } from "@gtkx/react";
|
|
4
6
|
import { registerClass } from "@gtkx/runtime";
|
|
5
7
|
import type { CollectionIndex, Level } from "./collection-index.js";
|
|
6
8
|
import type { TreeExpansion } from "./tree-expansion.js";
|
|
@@ -25,35 +27,29 @@ type SlotRun = {
|
|
|
25
27
|
length: number;
|
|
26
28
|
};
|
|
27
29
|
|
|
28
|
-
type LevelSync = {
|
|
29
|
-
store: LevelStore;
|
|
30
|
-
level: Level;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
30
|
type ModelState = {
|
|
34
|
-
root:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
groupStores: LevelStore[];
|
|
31
|
+
root: CollectionRootStore | null;
|
|
32
|
+
model: Gtk.FlattenListModel | null;
|
|
33
|
+
groupStores: Map<number, LevelStore>;
|
|
38
34
|
trees: Map<LevelStore, Gtk.TreeListModel>;
|
|
39
|
-
treeModels: Gtk.TreeListModel[];
|
|
40
35
|
expansion: TreeExpansion;
|
|
41
36
|
index: CollectionIndex;
|
|
42
37
|
};
|
|
43
38
|
|
|
44
39
|
type CollectionModel = {
|
|
45
|
-
model: Gtk.FlattenListModel;
|
|
46
40
|
expansion: TreeExpansion;
|
|
47
41
|
rowAt: (position: number) => Gtk.TreeListRow | null;
|
|
42
|
+
bind: (root: CollectionRootStore, model: Gtk.FlattenListModel) => void;
|
|
43
|
+
clear: () => void;
|
|
48
44
|
sync: (index: CollectionIndex) => void;
|
|
49
45
|
};
|
|
50
46
|
|
|
47
|
+
type CollectionRootProps = { ref: Ref<CollectionRootStore> };
|
|
48
|
+
|
|
51
49
|
const STORE_CLASS_KEY = Symbol.for("gtkx.components.lazy-level-store");
|
|
50
|
+
const ROOT_CLASS_KEY = Symbol.for("gtkx.components.collection-root-store");
|
|
52
51
|
const SLOTS_KEY = Symbol.for("gtkx.components.lazy-level-store.slots");
|
|
53
|
-
const EMPTY_INDEX = createCollectionIndex(undefined,
|
|
54
|
-
const SLOTS = sharedSlots();
|
|
55
|
-
|
|
56
|
-
const newRootStore = (): Gio.ListStore => new Gio.ListStore({ itemType: GObject.TYPE_OBJECT });
|
|
52
|
+
const EMPTY_INDEX = createCollectionIndex(undefined, [], true);
|
|
57
53
|
|
|
58
54
|
function sharedSlots(): WeakMap<GObject.Object, SlotRef> {
|
|
59
55
|
const cached: unknown = Reflect.get(globalThis, SLOTS_KEY);
|
|
@@ -81,6 +77,19 @@ function registeredStoreClass(): typeof LazyLevelStore {
|
|
|
81
77
|
return LazyLevelStore;
|
|
82
78
|
}
|
|
83
79
|
|
|
80
|
+
function registeredRootClass(): unknown {
|
|
81
|
+
const cached: unknown = Reflect.get(globalThis, ROOT_CLASS_KEY);
|
|
82
|
+
|
|
83
|
+
if (typeof cached === "function") {
|
|
84
|
+
return cached;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
registerClass(CollectionRootStore, { typeName: "GtkxCollectionRootStore", implements: [Gio.ListModel] });
|
|
88
|
+
Reflect.set(globalThis, ROOT_CLASS_KEY, CollectionRootStore);
|
|
89
|
+
|
|
90
|
+
return CollectionRootStore;
|
|
91
|
+
}
|
|
92
|
+
|
|
84
93
|
function slotRefFor(value: GObject.Object | null): SlotRef | null {
|
|
85
94
|
const item = value instanceof Gtk.TreeListRow ? value.getItem() : value;
|
|
86
95
|
|
|
@@ -88,7 +97,7 @@ function slotRefFor(value: GObject.Object | null): SlotRef | null {
|
|
|
88
97
|
return null;
|
|
89
98
|
}
|
|
90
99
|
|
|
91
|
-
const ref =
|
|
100
|
+
const ref = sharedSlots().get(item);
|
|
92
101
|
|
|
93
102
|
return ref === undefined || ref.slot === -1 ? null : ref;
|
|
94
103
|
}
|
|
@@ -194,44 +203,7 @@ function emitFlips(store: LevelStore, flipped: Set<number>): void {
|
|
|
194
203
|
}
|
|
195
204
|
}
|
|
196
205
|
|
|
197
|
-
function
|
|
198
|
-
context: SyncContext,
|
|
199
|
-
store: LevelStore,
|
|
200
|
-
slot: number,
|
|
201
|
-
flipped: Set<number>,
|
|
202
|
-
): LevelSync | undefined {
|
|
203
|
-
const child = store.childStores.get(slot) ?? null;
|
|
204
|
-
|
|
205
|
-
if (child === null || flipped.has(slot)) {
|
|
206
|
-
return undefined;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const level = context.index.childLevel(store.level, slot);
|
|
210
|
-
|
|
211
|
-
return level === undefined ? undefined : { store: child, level };
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function childSyncs(context: SyncContext, store: LevelStore, overlap: number, flipped: Set<number>): LevelSync[] {
|
|
215
|
-
const syncs: LevelSync[] = [];
|
|
216
|
-
const slots = store.childStores.keys().toArray().toSorted((left, right) => left - right);
|
|
217
|
-
|
|
218
|
-
for (const slot of slots) {
|
|
219
|
-
if (slot >= overlap) {
|
|
220
|
-
continue;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const sync = childSync(context, store, slot, flipped);
|
|
224
|
-
|
|
225
|
-
if (sync !== undefined) {
|
|
226
|
-
syncs.push(sync);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return syncs;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function syncEntry(context: SyncContext, entry: LevelSync): LevelSync[] {
|
|
234
|
-
const { store, level } = entry;
|
|
206
|
+
function syncLevel(context: SyncContext, store: LevelStore, level: Level): void {
|
|
235
207
|
const previous = store.level;
|
|
236
208
|
const previousLength = previous.items.length;
|
|
237
209
|
const overlap = Math.min(previousLength, level.items.length);
|
|
@@ -242,17 +214,15 @@ function syncEntry(context: SyncContext, entry: LevelSync): LevelSync[] {
|
|
|
242
214
|
emitTailSplice(store, previousLength, level.items.length);
|
|
243
215
|
emitFlips(store, flipped);
|
|
244
216
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const pending: LevelSync[] = [{ store, level }];
|
|
217
|
+
for (const [slot, child] of store.childStores) {
|
|
218
|
+
if (slot >= overlap || flipped.has(slot)) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
250
221
|
|
|
251
|
-
|
|
252
|
-
const entry = pending.pop();
|
|
222
|
+
const childLevel = context.index.childLevel(level, slot);
|
|
253
223
|
|
|
254
|
-
if (
|
|
255
|
-
|
|
224
|
+
if (childLevel !== undefined) {
|
|
225
|
+
syncLevel(context, child, childLevel);
|
|
256
226
|
}
|
|
257
227
|
}
|
|
258
228
|
}
|
|
@@ -287,102 +257,81 @@ function childStoreFor(state: ModelState, object: GObject.Object): Gio.ListModel
|
|
|
287
257
|
}
|
|
288
258
|
|
|
289
259
|
function treeFor(state: ModelState, store: LevelStore): Gtk.TreeListModel {
|
|
290
|
-
|
|
291
|
-
state.trees.get(store) ?? Gtk.TreeListModel.new(store, false, false, (object) => childStoreFor(state, object))
|
|
292
|
-
);
|
|
293
|
-
}
|
|
260
|
+
const existing = state.trees.get(store);
|
|
294
261
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
return new Map();
|
|
262
|
+
if (existing !== undefined) {
|
|
263
|
+
return existing;
|
|
298
264
|
}
|
|
299
265
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
function pruneRebuiltLevels(state: ModelState, next: Map<LevelStore, Gtk.TreeListModel>): void {
|
|
304
|
-
const stores: Set<LevelStore> = new Set([...state.trees.keys(), ...next.keys()]);
|
|
266
|
+
const created = Gtk.TreeListModel.new(store, false, false, (object) => childStoreFor(state, object));
|
|
267
|
+
state.trees.set(store, created);
|
|
305
268
|
|
|
306
|
-
|
|
307
|
-
if (state.trees.get(store) !== next.get(store)) {
|
|
308
|
-
pruneSlots(state.expansion, store.level.path, () => true);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function adoptTrees(state: ModelState, index: CollectionIndex): void {
|
|
314
|
-
const next = nextTrees(state, index);
|
|
315
|
-
pruneRebuiltLevels(state, next);
|
|
316
|
-
state.trees = next;
|
|
317
|
-
state.treeModels = next.values().toArray();
|
|
269
|
+
return created;
|
|
318
270
|
}
|
|
319
271
|
|
|
320
|
-
function
|
|
321
|
-
|
|
272
|
+
function rowAt(state: ModelState, position: number): Gtk.TreeListRow | null {
|
|
273
|
+
const item = state.model?.getItem(position);
|
|
322
274
|
|
|
323
|
-
return
|
|
275
|
+
return item instanceof Gtk.TreeListRow ? item : null;
|
|
324
276
|
}
|
|
325
277
|
|
|
326
|
-
function
|
|
327
|
-
|
|
278
|
+
function pruneGroups(state: ModelState, previous: CollectionIndex): void {
|
|
279
|
+
const hasModeChanged = previous.isTree !== state.index.isTree;
|
|
328
280
|
|
|
329
|
-
for (const
|
|
330
|
-
const
|
|
281
|
+
for (const [group, store] of state.groupStores) {
|
|
282
|
+
const isRemoved = group >= state.index.groups.length;
|
|
331
283
|
|
|
332
|
-
if (
|
|
333
|
-
|
|
284
|
+
if (isRemoved || hasModeChanged) {
|
|
285
|
+
pruneSlots(state.expansion, store.level.path, () => true);
|
|
286
|
+
state.trees.delete(store);
|
|
334
287
|
}
|
|
335
288
|
|
|
336
|
-
|
|
289
|
+
if (isRemoved) {
|
|
290
|
+
state.groupStores.delete(group);
|
|
291
|
+
}
|
|
337
292
|
}
|
|
338
|
-
|
|
339
|
-
return null;
|
|
340
293
|
}
|
|
341
294
|
|
|
342
|
-
function
|
|
343
|
-
|
|
344
|
-
|
|
295
|
+
function syncRoot(state: ModelState, previous: CollectionIndex): void {
|
|
296
|
+
const before = previous.groups.length;
|
|
297
|
+
const after = state.index.groups.length;
|
|
345
298
|
|
|
346
|
-
|
|
347
|
-
|
|
299
|
+
if (previous.isTree !== state.index.isTree) {
|
|
300
|
+
state.root?.itemsChanged(0, before, after);
|
|
348
301
|
|
|
349
|
-
if (hasSameModels(state.rootModels, next)) {
|
|
350
302
|
return;
|
|
351
303
|
}
|
|
352
304
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
function adjustGroupStores(state: ModelState, context: SyncContext): void {
|
|
358
|
-
const { groups } = context.index;
|
|
359
|
-
state.groupStores.length = Math.min(state.groupStores.length, groups.length);
|
|
360
|
-
|
|
361
|
-
for (const level of groups.slice(state.groupStores.length)) {
|
|
362
|
-
state.groupStores.push(newLevelStore(level));
|
|
305
|
+
if (before !== after) {
|
|
306
|
+
const start = Math.min(before, after);
|
|
307
|
+
state.root?.itemsChanged(start, before - start, after - start);
|
|
363
308
|
}
|
|
364
309
|
}
|
|
365
310
|
|
|
366
|
-
function stepGroupStores(state: ModelState, context: SyncContext
|
|
367
|
-
for (const [group,
|
|
368
|
-
const
|
|
311
|
+
function stepGroupStores(state: ModelState, context: SyncContext): void {
|
|
312
|
+
for (const [group, store] of state.groupStores) {
|
|
313
|
+
const level = context.index.groups[group];
|
|
369
314
|
|
|
370
|
-
if (
|
|
315
|
+
if (level !== undefined && store.level !== level) {
|
|
371
316
|
syncLevel(context, store, level);
|
|
372
317
|
}
|
|
373
318
|
}
|
|
374
319
|
}
|
|
375
320
|
|
|
376
321
|
function syncModel(state: ModelState, index: CollectionIndex): void {
|
|
322
|
+
if (state.root === null) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
377
326
|
const context: SyncContext = { index, expansion: state.expansion };
|
|
378
|
-
const
|
|
327
|
+
const previous = state.index;
|
|
379
328
|
state.index = index;
|
|
380
329
|
state.expansion.isSyncing = true;
|
|
381
330
|
|
|
382
331
|
try {
|
|
383
|
-
|
|
384
|
-
syncRoot(state,
|
|
385
|
-
stepGroupStores(state, context
|
|
332
|
+
pruneGroups(state, previous);
|
|
333
|
+
syncRoot(state, previous);
|
|
334
|
+
stepGroupStores(state, context);
|
|
386
335
|
} finally {
|
|
387
336
|
state.expansion.isSyncing = false;
|
|
388
337
|
}
|
|
@@ -390,31 +339,91 @@ function syncModel(state: ModelState, index: CollectionIndex): void {
|
|
|
390
339
|
adoptIndex(state.expansion, index);
|
|
391
340
|
}
|
|
392
341
|
|
|
393
|
-
function
|
|
394
|
-
|
|
395
|
-
const
|
|
342
|
+
function clearModel(state: ModelState): void {
|
|
343
|
+
const root = state.root;
|
|
344
|
+
const previousLength = state.index.groups.length;
|
|
345
|
+
state.expansion.isSyncing = true;
|
|
396
346
|
|
|
347
|
+
try {
|
|
348
|
+
if (root !== null) {
|
|
349
|
+
root.state = null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
state.root = null;
|
|
353
|
+
state.model = null;
|
|
354
|
+
state.index = EMPTY_INDEX;
|
|
355
|
+
state.groupStores.clear();
|
|
356
|
+
state.trees.clear();
|
|
357
|
+
state.expansion.expanded.clear();
|
|
358
|
+
state.expansion.slots.clear();
|
|
359
|
+
adoptIndex(state.expansion, EMPTY_INDEX);
|
|
360
|
+
|
|
361
|
+
if (previousLength > 0) {
|
|
362
|
+
root?.itemsChanged(0, previousLength, 0);
|
|
363
|
+
}
|
|
364
|
+
} finally {
|
|
365
|
+
state.expansion.isSyncing = false;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function createCollectionModel(): CollectionModel {
|
|
397
370
|
const state: ModelState = {
|
|
398
|
-
root,
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
groupStores: [],
|
|
371
|
+
root: null,
|
|
372
|
+
model: null,
|
|
373
|
+
groupStores: new Map(),
|
|
402
374
|
trees: new Map(),
|
|
403
|
-
treeModels: [],
|
|
404
375
|
expansion: createTreeExpansion(EMPTY_INDEX),
|
|
405
376
|
index: EMPTY_INDEX,
|
|
406
377
|
};
|
|
407
378
|
|
|
408
379
|
return {
|
|
409
|
-
model: state.model,
|
|
410
380
|
expansion: state.expansion,
|
|
411
381
|
rowAt: (position) => rowAt(state, position),
|
|
382
|
+
bind: (root, model) => {
|
|
383
|
+
root.state = state;
|
|
384
|
+
state.root = root;
|
|
385
|
+
state.model = model;
|
|
386
|
+
},
|
|
387
|
+
clear: () => {
|
|
388
|
+
clearModel(state);
|
|
389
|
+
},
|
|
412
390
|
sync: (index) => {
|
|
413
391
|
syncModel(state, index);
|
|
414
392
|
},
|
|
415
393
|
};
|
|
416
394
|
}
|
|
417
395
|
|
|
396
|
+
class CollectionRootStore extends GObject.Object implements Gio.ListModelImpl {
|
|
397
|
+
declare itemsChanged: Gio.ListModel["itemsChanged"];
|
|
398
|
+
state: ModelState | null = null;
|
|
399
|
+
|
|
400
|
+
vfuncGetItemType(): bigint {
|
|
401
|
+
return GObject.TYPE_OBJECT;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
vfuncGetNItems(): number {
|
|
405
|
+
return this.state?.index.groups.length ?? 0;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
vfuncGetItem(position: number): GObject.Object | null {
|
|
409
|
+
const state = this.state;
|
|
410
|
+
const level = state?.index.groups[position];
|
|
411
|
+
|
|
412
|
+
if (state === null || level === undefined) {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
let store = state.groupStores.get(position);
|
|
417
|
+
|
|
418
|
+
if (store === undefined) {
|
|
419
|
+
store = newLevelStore(level);
|
|
420
|
+
state.groupStores.set(position, store);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return state.index.isTree ? treeFor(state, store) : store;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
418
427
|
class LazyLevelStore extends GObject.Object implements Gio.ListModelImpl {
|
|
419
428
|
declare itemsChanged: Gio.ListModel["itemsChanged"];
|
|
420
429
|
level: Level = { path: "", items: [], expandableFlags: [] };
|
|
@@ -427,7 +436,7 @@ class LazyLevelStore extends GObject.Object implements Gio.ListModelImpl {
|
|
|
427
436
|
const created = Gtk.StringObject.new("");
|
|
428
437
|
this.refs.set(position, ref);
|
|
429
438
|
this.objects.set(position, created);
|
|
430
|
-
|
|
439
|
+
sharedSlots().set(created, ref);
|
|
431
440
|
|
|
432
441
|
return created;
|
|
433
442
|
}
|
|
@@ -449,4 +458,17 @@ class LazyLevelStore extends GObject.Object implements Gio.ListModelImpl {
|
|
|
449
458
|
}
|
|
450
459
|
}
|
|
451
460
|
|
|
452
|
-
|
|
461
|
+
const CollectionRoot: (props: CollectionRootProps) => ReactNode = createElementComponent<CollectionRootProps>(
|
|
462
|
+
"GtkxCollectionRootStore",
|
|
463
|
+
registeredRootClass(),
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
export {
|
|
467
|
+
CollectionRoot,
|
|
468
|
+
createCollectionModel,
|
|
469
|
+
slotPathAt,
|
|
470
|
+
slotRefFor,
|
|
471
|
+
type CollectionModel,
|
|
472
|
+
type CollectionRootStore,
|
|
473
|
+
type SlotRef,
|
|
474
|
+
};
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
import type * as Gtk from "@gtkx/gi/gtk";
|
|
1
2
|
import type { ListItem } from "../types.js";
|
|
2
|
-
import type { CollectionIndex } from "./collection-index.js";
|
|
3
|
+
import type { CollectionIndex, SectionIdentity } from "./collection-index.js";
|
|
3
4
|
import type { CollectionModel, SlotRef } from "./collection-model.js";
|
|
4
5
|
import type { MatchedRows } from "./tree-order.js";
|
|
5
6
|
import { slotPathAt, slotRefFor } from "./collection-model.js";
|
|
6
7
|
import { findIds, findRows } from "./tree-order.js";
|
|
7
8
|
|
|
8
|
-
type Collection = Pick<CollectionModel, "
|
|
9
|
+
type Collection = Pick<CollectionModel, "expansion" | "rowAt"> & {
|
|
9
10
|
isTree: boolean;
|
|
10
11
|
itemAt: (ref: SlotRef) => ListItem | undefined;
|
|
11
|
-
sectionFor: (levelPath: string) =>
|
|
12
|
+
sectionFor: (levelPath: string) => SectionIdentity | undefined;
|
|
12
13
|
idAt: (position: number) => string | null;
|
|
13
14
|
idsAt: (positions: number[]) => string[];
|
|
14
15
|
pathAt: (position: number) => string | null;
|
|
@@ -19,42 +20,42 @@ type Collection = Pick<CollectionModel, "model" | "expansion" | "rowAt"> & {
|
|
|
19
20
|
const NO_ROWS: MatchedRows = { positions: [], ids: [] };
|
|
20
21
|
const NO_IDS: string[] = [];
|
|
21
22
|
|
|
22
|
-
function rowsFor(
|
|
23
|
+
function rowsFor(gtkModel: CollectionModel, ids: string[]): MatchedRows {
|
|
23
24
|
if (ids.length === 0) {
|
|
24
25
|
return NO_ROWS;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
const { expansion } =
|
|
28
|
+
const { expansion } = gtkModel;
|
|
28
29
|
|
|
29
30
|
return findRows(expansion.index, expansion.slots, new Set(ids));
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
function positionFor(
|
|
33
|
-
const [first = -1] = rowsFor(
|
|
33
|
+
function positionFor(gtkModel: CollectionModel, id: string): number {
|
|
34
|
+
const [first = -1] = rowsFor(gtkModel, [id]).positions;
|
|
34
35
|
|
|
35
36
|
return first;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
function refAt(
|
|
39
|
+
function refAt(model: Gtk.FlattenListModel | null, position: number): SlotRef | null {
|
|
39
40
|
if (position < 0) {
|
|
40
41
|
return null;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
return slotRefFor(
|
|
44
|
+
return slotRefFor(model?.getItem(position) ?? null);
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
function itemAt(index: CollectionIndex, ref: SlotRef): ListItem | undefined {
|
|
47
48
|
return index.itemAt(ref.store.level.path, ref.slot);
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
function idAt(
|
|
51
|
-
const ref = refAt(
|
|
51
|
+
function idAt(model: Gtk.FlattenListModel | null, index: CollectionIndex, position: number): string | null {
|
|
52
|
+
const ref = refAt(model, position);
|
|
52
53
|
|
|
53
54
|
return ref === null ? null : (itemAt(index, ref)?.id ?? null);
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
function pathAt(
|
|
57
|
-
const ref = refAt(
|
|
57
|
+
function pathAt(model: Gtk.FlattenListModel | null, position: number): string | null {
|
|
58
|
+
const ref = refAt(model, position);
|
|
58
59
|
|
|
59
60
|
if (ref === null) {
|
|
60
61
|
return null;
|
|
@@ -63,12 +64,12 @@ function pathAt(collectionModel: CollectionModel, position: number): string | nu
|
|
|
63
64
|
return slotPathAt(ref.store, ref.slot);
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
function idsAt(
|
|
67
|
+
function idsAt(gtkModel: CollectionModel, positions: number[]): string[] {
|
|
67
68
|
if (positions.length === 0) {
|
|
68
69
|
return NO_IDS;
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
const { expansion } =
|
|
72
|
+
const { expansion } = gtkModel;
|
|
72
73
|
|
|
73
74
|
return findIds(expansion.index, expansion.slots, new Set(positions));
|
|
74
75
|
}
|
|
@@ -79,19 +80,22 @@ function isCollectionIdle(collection: Collection): boolean {
|
|
|
79
80
|
return !expansion.isApplying && !expansion.isSyncing;
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
function createCollection(
|
|
83
|
+
function createCollection(
|
|
84
|
+
gtkModel: CollectionModel,
|
|
85
|
+
index: CollectionIndex,
|
|
86
|
+
model: Gtk.FlattenListModel | null,
|
|
87
|
+
): Collection {
|
|
83
88
|
return {
|
|
84
|
-
|
|
85
|
-
expansion: collectionModel.expansion,
|
|
89
|
+
expansion: gtkModel.expansion,
|
|
86
90
|
isTree: index.isTree,
|
|
87
|
-
rowAt:
|
|
91
|
+
rowAt: gtkModel.rowAt,
|
|
88
92
|
itemAt: (ref) => itemAt(index, ref),
|
|
89
93
|
sectionFor: index.sectionFor,
|
|
90
|
-
idAt: (position) => idAt(
|
|
91
|
-
idsAt: (positions) => idsAt(
|
|
92
|
-
pathAt: (position) => pathAt(
|
|
93
|
-
positionFor: (id) => positionFor(
|
|
94
|
-
rowsFor: (ids) => rowsFor(
|
|
94
|
+
idAt: (position) => idAt(model, index, position),
|
|
95
|
+
idsAt: (positions) => idsAt(gtkModel, positions),
|
|
96
|
+
pathAt: (position) => pathAt(model, position),
|
|
97
|
+
positionFor: (id) => positionFor(gtkModel, id),
|
|
98
|
+
rowsFor: (ids) => rowsFor(gtkModel, ids),
|
|
95
99
|
};
|
|
96
100
|
}
|
|
97
101
|
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import { useLatestRef } from "@gtkx/react/internal";
|
|
2
2
|
import { startTransition, useLayoutEffect, useState } from "react";
|
|
3
|
-
import type { Collection } from "./collection.js";
|
|
4
3
|
|
|
5
|
-
type
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
widget?: object | null | undefined;
|
|
11
|
-
apply: (ids: ControlledIds) => void;
|
|
4
|
+
type ControlledSyncOptions<T> = {
|
|
5
|
+
value: T;
|
|
6
|
+
source?: object | null | undefined;
|
|
7
|
+
target?: object | null | undefined;
|
|
8
|
+
apply: (value: T) => void;
|
|
12
9
|
};
|
|
13
10
|
|
|
14
|
-
function useControlledSync(options: ControlledSyncOptions): () => void {
|
|
15
|
-
const {
|
|
11
|
+
function useControlledSync<T>(options: ControlledSyncOptions<T>): () => void {
|
|
12
|
+
const { value, source, target } = options;
|
|
16
13
|
const [drift, setDrift] = useState(0);
|
|
17
14
|
|
|
18
15
|
const markDrift = (): void => {
|
|
@@ -24,8 +21,8 @@ function useControlledSync(options: ControlledSyncOptions): () => void {
|
|
|
24
21
|
const applyRef = useLatestRef(options.apply);
|
|
25
22
|
|
|
26
23
|
useLayoutEffect(() => {
|
|
27
|
-
applyRef.current(
|
|
28
|
-
}, [applyRef,
|
|
24
|
+
applyRef.current(value);
|
|
25
|
+
}, [applyRef, target, source, value, drift]);
|
|
29
26
|
|
|
30
27
|
return markDrift;
|
|
31
28
|
}
|