@gtkx/components 2.0.0-beta.1 → 2.0.0-beta.10

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.
@@ -11,7 +11,7 @@ import { adoptIndex, createTreeExpansion, pruneSlots } from "./tree-expansion.js
11
11
  type LevelStore = Gio.ListModel & LazyLevelStore;
12
12
 
13
13
  type SlotRef = {
14
- store: LevelStore;
14
+ store: LazyLevelStore;
15
15
  slot: number;
16
16
  };
17
17
 
@@ -93,22 +93,13 @@ function slotRefFor(value: GObject.Object | null): SlotRef | null {
93
93
  return ref === undefined || ref.slot === -1 ? null : ref;
94
94
  }
95
95
 
96
- function slotPathAt(store: LevelStore, slot: number): string {
96
+ function slotPathAt(store: LazyLevelStore, slot: number): string {
97
97
  return store.level.path + encodePart(String(slot));
98
98
  }
99
99
 
100
- function growStore(store: LevelStore): void {
101
- for (let slot = store.refs.length; slot < store.level.items.length; slot++) {
102
- store.refs.push({ store, slot });
103
- store.objects.push(null);
104
- store.childStores.push(null);
105
- }
106
- }
107
-
108
100
  function newLevelStore(level: Level): LevelStore {
109
101
  const store = new (registeredStoreClass())() as LevelStore;
110
102
  store.level = level;
111
- growStore(store);
112
103
 
113
104
  return store;
114
105
  }
@@ -116,6 +107,10 @@ function newLevelStore(level: Level): LevelStore {
116
107
  function collectFlips(previous: Level, next: Level, overlap: number): Set<number> {
117
108
  const flipped: Set<number> = new Set();
118
109
 
110
+ if (previous.expandableFlags.length === 0 && next.expandableFlags.length === 0) {
111
+ return flipped;
112
+ }
113
+
119
114
  for (let slot = 0; slot < overlap; slot++) {
120
115
  if ((previous.expandableFlags[slot] ?? false) !== (next.expandableFlags[slot] ?? false)) {
121
116
  flipped.add(slot);
@@ -133,30 +128,30 @@ function pruneFlips(context: SyncContext, store: LevelStore, flipped: Set<number
133
128
  pruneSlots(context.expansion, store.level.path, (slot) => flipped.has(slot));
134
129
 
135
130
  for (const slot of flipped) {
136
- store.childStores[slot] = null;
131
+ store.childStores.delete(slot);
137
132
  }
138
133
  }
139
134
 
140
135
  function detachRefs(store: LevelStore, nextLength: number): void {
141
- for (let slot = nextLength; slot < store.refs.length; slot++) {
142
- const ref = store.refs[slot];
143
-
144
- if (ref !== undefined) {
145
- ref.slot = -1;
136
+ for (const [slot, ref] of store.refs) {
137
+ if (slot < nextLength) {
138
+ continue;
146
139
  }
140
+
141
+ ref.slot = -1;
142
+ store.refs.delete(slot);
143
+ store.objects.delete(slot);
144
+ store.childStores.delete(slot);
147
145
  }
148
146
  }
149
147
 
150
- function shrinkStore(context: SyncContext, store: LevelStore, nextLength: number): void {
151
- if (store.refs.length <= nextLength) {
148
+ function shrinkStore(context: SyncContext, store: LevelStore, previousLength: number, nextLength: number): void {
149
+ if (previousLength <= nextLength) {
152
150
  return;
153
151
  }
154
152
 
155
153
  detachRefs(store, nextLength);
156
154
  pruneSlots(context.expansion, store.level.path, (slot) => slot >= nextLength);
157
- store.refs.length = nextLength;
158
- store.objects.length = nextLength;
159
- store.childStores.length = nextLength;
160
155
  }
161
156
 
162
157
  function emitTailSplice(store: LevelStore, previousLength: number, nextLength: number): void {
@@ -205,7 +200,7 @@ function childSync(
205
200
  slot: number,
206
201
  flipped: Set<number>,
207
202
  ): LevelSync | undefined {
208
- const child = store.childStores[slot] ?? null;
203
+ const child = store.childStores.get(slot) ?? null;
209
204
 
210
205
  if (child === null || flipped.has(slot)) {
211
206
  return undefined;
@@ -218,8 +213,13 @@ function childSync(
218
213
 
219
214
  function childSyncs(context: SyncContext, store: LevelStore, overlap: number, flipped: Set<number>): LevelSync[] {
220
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
+ }
221
222
 
222
- for (let slot = 0; slot < overlap; slot++) {
223
223
  const sync = childSync(context, store, slot, flipped);
224
224
 
225
225
  if (sync !== undefined) {
@@ -232,13 +232,13 @@ function childSyncs(context: SyncContext, store: LevelStore, overlap: number, fl
232
232
 
233
233
  function syncEntry(context: SyncContext, entry: LevelSync): LevelSync[] {
234
234
  const { store, level } = entry;
235
- const previousLength = store.refs.length;
235
+ const previous = store.level;
236
+ const previousLength = previous.items.length;
236
237
  const overlap = Math.min(previousLength, level.items.length);
237
- const flipped = collectFlips(store.level, level, overlap);
238
+ const flipped = collectFlips(previous, level, overlap);
238
239
  store.level = level;
239
240
  pruneFlips(context, store, flipped);
240
- shrinkStore(context, store, level.items.length);
241
- growStore(store);
241
+ shrinkStore(context, store, previousLength, level.items.length);
242
242
  emitTailSplice(store, previousLength, level.items.length);
243
243
  emitFlips(store, flipped);
244
244
 
@@ -257,8 +257,8 @@ function syncLevel(context: SyncContext, store: LevelStore, level: Level): void
257
257
  }
258
258
  }
259
259
 
260
- function ensureChildStore(index: CollectionIndex, store: LevelStore, slot: number): LevelStore | null {
261
- const existing = store.childStores[slot] ?? null;
260
+ function ensureChildStore(index: CollectionIndex, store: LazyLevelStore, slot: number): LevelStore | null {
261
+ const existing = store.childStores.get(slot) ?? null;
262
262
 
263
263
  if (existing !== null || !(store.level.expandableFlags[slot] ?? false)) {
264
264
  return existing;
@@ -271,7 +271,7 @@ function ensureChildStore(index: CollectionIndex, store: LevelStore, slot: numbe
271
271
  }
272
272
 
273
273
  const created = newLevelStore(level);
274
- store.childStores[slot] = created;
274
+ store.childStores.set(slot, created);
275
275
 
276
276
  return created;
277
277
  }
@@ -418,13 +418,15 @@ function createCollectionModel(): CollectionModel {
418
418
  class LazyLevelStore extends GObject.Object implements Gio.ListModelImpl {
419
419
  declare itemsChanged: Gio.ListModel["itemsChanged"];
420
420
  level: Level = { path: "", items: [], expandableFlags: [] };
421
- refs: SlotRef[] = [];
422
- objects: (Gtk.StringObject | null)[] = [];
423
- childStores: (LevelStore | null)[] = [];
421
+ refs: Map<number, SlotRef> = new Map();
422
+ objects: Map<number, Gtk.StringObject> = new Map();
423
+ childStores: Map<number, LevelStore> = new Map();
424
424
 
425
- private createItem(position: number, ref: SlotRef): Gtk.StringObject {
425
+ private createItem(position: number): Gtk.StringObject {
426
+ const ref = { store: this, slot: position };
426
427
  const created = Gtk.StringObject.new("");
427
- this.objects[position] = created;
428
+ this.refs.set(position, ref);
429
+ this.objects.set(position, created);
428
430
  SLOTS.set(created, ref);
429
431
 
430
432
  return created;
@@ -435,17 +437,15 @@ class LazyLevelStore extends GObject.Object implements Gio.ListModelImpl {
435
437
  }
436
438
 
437
439
  vfuncGetNItems(): number {
438
- return this.refs.length;
440
+ return this.level.items.length;
439
441
  }
440
442
 
441
443
  vfuncGetItem(position: number): GObject.Object | null {
442
- const ref = this.refs[position];
443
-
444
- if (ref === undefined) {
444
+ if (position < 0 || position >= this.level.items.length) {
445
445
  return null;
446
446
  }
447
447
 
448
- return this.objects[position] ?? this.createItem(position, ref);
448
+ return this.objects.get(position) ?? this.createItem(position);
449
449
  }
450
450
  }
451
451
 
package/src/list-view.tsx CHANGED
@@ -8,6 +8,7 @@ import { useCollection } from "./internal/use-collection.js";
8
8
  const LIST_VIEW_PROPS = [
9
9
  "items",
10
10
  "sections",
11
+ "isFlat",
11
12
  "renderItem",
12
13
  "renderHeader",
13
14
  "selectedIds",
package/src/types.ts CHANGED
@@ -90,6 +90,16 @@ type ItemSizeProps = {
90
90
  estimatedItemWidth?: number | undefined;
91
91
  };
92
92
 
93
+ /** The flat-collection hint shared by {@link ListView} and {@link ColumnView}. */
94
+ type FlatnessProps = {
95
+ /**
96
+ * Declares every item a leaf, so the view skips tree discovery when synchronising a large collection
97
+ * instead of scanning each item for `children`. Must not be set when any item has children: those
98
+ * items would render as plain rows that never expand.
99
+ */
100
+ isFlat?: boolean | undefined;
101
+ };
102
+
93
103
  /** Controlled selection shared by the multi-item collection views. */
94
104
  type SelectionProps = {
95
105
  /**
@@ -172,6 +182,7 @@ type ColumnViewOwnProps<T, S> = SelectionProps &
172
182
  ExpansionProps &
173
183
  SortProps &
174
184
  SourceProps<T, S> &
185
+ FlatnessProps &
175
186
  Omit<ItemSizeProps, "estimatedItemWidth"> & {
176
187
  /** Columns to render, in order; each carries its own cell renderer. */
177
188
  columns: ColumnViewColumn<T>[];
@@ -242,7 +253,8 @@ type GridViewProps<T = unknown> = Omit<GtkGridViewProps, "model" | "factory" | k
242
253
  type ListViewOwnProps<T, S> = ItemSizeProps &
243
254
  SelectionProps &
244
255
  ExpansionProps &
245
- SourceProps<T, S> & {
256
+ SourceProps<T, S> &
257
+ FlatnessProps & {
246
258
  /** Renders the contents of one row. */
247
259
  renderItem: ListItemRenderer<T>;
248
260
  /** Renders the header shown above each section. */