@gtkx/components 1.2.2 → 1.4.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 CHANGED
@@ -5,9 +5,9 @@
5
5
  <h1 align="center">GTKX</h1>
6
6
 
7
7
  <p align="center">
8
- The React framework for Linux.<br />
9
- Write native Linux applications with React and TypeScript.
10
- Built on the GNOME stack and standard web tooling.
8
+ The React framework for Linux, built on GTK.<br />
9
+ Write native GTK4 desktop applications with React and TypeScript.
10
+ Real GTK widgets, the GNOME stack, and standard web tooling.
11
11
  </p>
12
12
 
13
13
  <p align="center">
@@ -98,7 +98,7 @@ GTK4 is mature, and GtkBuilder XML can lay out a static interface, but nothing r
98
98
  - a React reconciler that exposes every GObject as a JSX element,
99
99
  - a CLI for scaffolding, development, and production builds,
100
100
  - a dev server with Fast Refresh that patches your running UI in place,
101
- - CSS-in-JS styling and high-level list, grid, and dialog components,
101
+ - CSS-in-JS styling, React Spring animations, React Navigation stack, tab, drawer, and split view navigators, and high-level list, grid, and dialog components,
102
102
  - a Testing Library-style API for querying and driving your widgets in tests,
103
103
  - and a Model Context Protocol (MCP) server that exposes your live app to AI agents.
104
104
 
@@ -157,6 +157,8 @@ Explore the [example apps](https://github.com/gtkx-org/gtkx/tree/main/examples):
157
157
  - [`hello-world`](https://github.com/gtkx-org/gtkx/tree/main/examples/hello-world): the counter above.
158
158
  - [`gtk-demo`](https://github.com/gtkx-org/gtkx/tree/main/examples/gtk-demo): a React port of the official GTK4 widget showcase, covering lists, dialogs, gestures, CSS, and OpenGL.
159
159
  - [`browser`](https://github.com/gtkx-org/gtkx/tree/main/examples/browser): a WebKitWebView-based web browser.
160
+ - [`animations`](https://github.com/gtkx-org/gtkx/tree/main/examples/animations): a tour of `@gtkx/animated`, React Spring animations driven by the GTK frame clock.
161
+ - [`navigation`](https://github.com/gtkx-org/gtkx/tree/main/examples/navigation): a tour of `@gtkx/navigation`, React Navigation's stack, tab, and drawer navigators rendered with libadwaita.
160
162
  - [`tutorial`](https://github.com/gtkx-org/gtkx/tree/main/examples/tutorial): the Tasks app the documentation builds.
161
163
 
162
164
  ## Status
@@ -1,14 +1,16 @@
1
1
  import type { ListItem } from "../types.js";
2
2
  import type { CollectionIndex } from "./collection-index.js";
3
3
  import type { CollectionModel, SlotRef } from "./collection-model.js";
4
+ import type { MatchedRows } from "./tree-order.js";
4
5
  type Collection = Pick<CollectionModel, "model" | "expansion" | "rowAt"> & {
5
6
  isTree: boolean;
6
7
  itemAt: (ref: SlotRef) => ListItem | undefined;
7
8
  sectionFor: (levelPath: string) => unknown;
8
9
  idAt: (position: number) => string | null;
10
+ idsAt: (positions: number[]) => string[];
9
11
  pathAt: (position: number) => string | null;
10
12
  positionFor: (id: string) => number;
11
- positionsFor: (ids: string[]) => number[];
13
+ rowsFor: (ids: string[]) => MatchedRows;
12
14
  };
13
15
  declare function isCollectionIdle(collection: Collection): boolean;
14
16
  declare function createCollection(collectionModel: CollectionModel, index: CollectionIndex): Collection;
@@ -1 +1 @@
1
- {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../../src/internal/collection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAItE,KAAK,UAAU,GAAG,IAAI,CAAC,eAAe,EAAE,OAAO,GAAG,WAAW,GAAG,OAAO,CAAC,GAAG;IACvE,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,QAAQ,GAAG,SAAS,CAAC;IAC/C,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IAC3C,IAAI,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IAC1C,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IAC5C,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,CAAC;CAC7C,CAAC;AAgDF,iBAAS,gBAAgB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAIzD;AAED,iBAAS,gBAAgB,CAAC,eAAe,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,GAAG,UAAU,CAa9F;AAED,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,KAAK,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../../src/internal/collection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAInD,KAAK,UAAU,GAAG,IAAI,CAAC,eAAe,EAAE,OAAO,GAAG,WAAW,GAAG,OAAO,CAAC,GAAG;IACvE,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,QAAQ,GAAG,SAAS,CAAC;IAC/C,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IAC3C,IAAI,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IAC1C,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,CAAC;IACzC,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IAC5C,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,WAAW,CAAC;CAC3C,CAAC;AA2DF,iBAAS,gBAAgB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAIzD;AAED,iBAAS,gBAAgB,CAAC,eAAe,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,GAAG,UAAU,CAc9F;AAED,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,KAAK,UAAU,EAAE,CAAC"}
@@ -1,15 +1,16 @@
1
1
  import { slotPathAt, slotRefFor } from "./collection-model.js";
2
- import { findPositions } from "./tree-order.js";
3
- const NO_POSITIONS = [];
4
- function positionsFor(collectionModel, ids) {
2
+ import { findIds, findRows } from "./tree-order.js";
3
+ const NO_ROWS = { positions: [], ids: [] };
4
+ const NO_IDS = [];
5
+ function rowsFor(collectionModel, ids) {
5
6
  if (ids.length === 0) {
6
- return NO_POSITIONS;
7
+ return NO_ROWS;
7
8
  }
8
9
  const { expansion } = collectionModel;
9
- return findPositions(expansion.index, expansion.slots, new Set(ids));
10
+ return findRows(expansion.index, expansion.slots, new Set(ids));
10
11
  }
11
12
  function positionFor(collectionModel, id) {
12
- const [first = -1] = positionsFor(collectionModel, [id]);
13
+ const [first = -1] = rowsFor(collectionModel, [id]).positions;
13
14
  return first;
14
15
  }
15
16
  function refAt(collectionModel, position) {
@@ -32,6 +33,13 @@ function pathAt(collectionModel, position) {
32
33
  }
33
34
  return slotPathAt(ref.store, ref.slot);
34
35
  }
36
+ function idsAt(collectionModel, positions) {
37
+ if (positions.length === 0) {
38
+ return NO_IDS;
39
+ }
40
+ const { expansion } = collectionModel;
41
+ return findIds(expansion.index, expansion.slots, new Set(positions));
42
+ }
35
43
  function isCollectionIdle(collection) {
36
44
  const { expansion } = collection;
37
45
  return !expansion.isApplying && !expansion.isSyncing;
@@ -45,9 +53,10 @@ function createCollection(collectionModel, index) {
45
53
  itemAt: (ref) => itemAt(index, ref),
46
54
  sectionFor: index.sectionFor,
47
55
  idAt: (position) => idAt(collectionModel, index, position),
56
+ idsAt: (positions) => idsAt(collectionModel, positions),
48
57
  pathAt: (position) => pathAt(collectionModel, position),
49
58
  positionFor: (id) => positionFor(collectionModel, id),
50
- positionsFor: (ids) => positionsFor(collectionModel, ids),
59
+ rowsFor: (ids) => rowsFor(collectionModel, ids),
51
60
  };
52
61
  }
53
62
  export { createCollection, isCollectionIdle };
@@ -1 +1 @@
1
- {"version":3,"file":"collection.js","sourceRoot":"","sources":["../../src/internal/collection.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ListItem } from \"../types.js\";\nimport type { CollectionIndex } from \"./collection-index.js\";\nimport type { CollectionModel, SlotRef } from \"./collection-model.js\";\nimport { slotPathAt, slotRefFor } from \"./collection-model.js\";\nimport { findPositions } from \"./tree-order.js\";\n\ntype Collection = Pick<CollectionModel, \"model\" | \"expansion\" | \"rowAt\"> & {\n isTree: boolean;\n itemAt: (ref: SlotRef) => ListItem | undefined;\n sectionFor: (levelPath: string) => unknown;\n idAt: (position: number) => string | null;\n pathAt: (position: number) => string | null;\n positionFor: (id: string) => number;\n positionsFor: (ids: string[]) => number[];\n};\n\nconst NO_POSITIONS: number[] = [];\n\nfunction positionsFor(collectionModel: CollectionModel, ids: string[]): number[] {\n if (ids.length === 0) {\n return NO_POSITIONS;\n }\n\n const { expansion } = collectionModel;\n\n return findPositions(expansion.index, expansion.slots, new Set(ids));\n}\n\nfunction positionFor(collectionModel: CollectionModel, id: string): number {\n const [first = -1] = positionsFor(collectionModel, [id]);\n\n return first;\n}\n\nfunction refAt(collectionModel: CollectionModel, position: number): SlotRef | null {\n if (position < 0) {\n return null;\n }\n\n return slotRefFor(collectionModel.model.getItem(position));\n}\n\nfunction itemAt(index: CollectionIndex, ref: SlotRef): ListItem | undefined {\n return index.itemAt(ref.store.level.path, ref.slot);\n}\n\nfunction idAt(collectionModel: CollectionModel, index: CollectionIndex, position: number): string | null {\n const ref = refAt(collectionModel, position);\n\n return ref === null ? null : (itemAt(index, ref)?.id ?? null);\n}\n\nfunction pathAt(collectionModel: CollectionModel, position: number): string | null {\n const ref = refAt(collectionModel, position);\n\n if (ref === null) {\n return null;\n }\n\n return slotPathAt(ref.store, ref.slot);\n}\n\nfunction isCollectionIdle(collection: Collection): boolean {\n const { expansion } = collection;\n\n return !expansion.isApplying && !expansion.isSyncing;\n}\n\nfunction createCollection(collectionModel: CollectionModel, index: CollectionIndex): Collection {\n return {\n model: collectionModel.model,\n expansion: collectionModel.expansion,\n isTree: index.isTree,\n rowAt: collectionModel.rowAt,\n itemAt: (ref) => itemAt(index, ref),\n sectionFor: index.sectionFor,\n idAt: (position) => idAt(collectionModel, index, position),\n pathAt: (position) => pathAt(collectionModel, position),\n positionFor: (id) => positionFor(collectionModel, id),\n positionsFor: (ids) => positionsFor(collectionModel, ids),\n };\n}\n\nexport { createCollection, isCollectionIdle, type Collection };\n"]}
1
+ {"version":3,"file":"collection.js","sourceRoot":"","sources":["../../src/internal/collection.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ListItem } from \"../types.js\";\nimport type { CollectionIndex } from \"./collection-index.js\";\nimport type { CollectionModel, SlotRef } from \"./collection-model.js\";\nimport type { MatchedRows } from \"./tree-order.js\";\nimport { slotPathAt, slotRefFor } from \"./collection-model.js\";\nimport { findIds, findRows } from \"./tree-order.js\";\n\ntype Collection = Pick<CollectionModel, \"model\" | \"expansion\" | \"rowAt\"> & {\n isTree: boolean;\n itemAt: (ref: SlotRef) => ListItem | undefined;\n sectionFor: (levelPath: string) => unknown;\n idAt: (position: number) => string | null;\n idsAt: (positions: number[]) => string[];\n pathAt: (position: number) => string | null;\n positionFor: (id: string) => number;\n rowsFor: (ids: string[]) => MatchedRows;\n};\n\nconst NO_ROWS: MatchedRows = { positions: [], ids: [] };\nconst NO_IDS: string[] = [];\n\nfunction rowsFor(collectionModel: CollectionModel, ids: string[]): MatchedRows {\n if (ids.length === 0) {\n return NO_ROWS;\n }\n\n const { expansion } = collectionModel;\n\n return findRows(expansion.index, expansion.slots, new Set(ids));\n}\n\nfunction positionFor(collectionModel: CollectionModel, id: string): number {\n const [first = -1] = rowsFor(collectionModel, [id]).positions;\n\n return first;\n}\n\nfunction refAt(collectionModel: CollectionModel, position: number): SlotRef | null {\n if (position < 0) {\n return null;\n }\n\n return slotRefFor(collectionModel.model.getItem(position));\n}\n\nfunction itemAt(index: CollectionIndex, ref: SlotRef): ListItem | undefined {\n return index.itemAt(ref.store.level.path, ref.slot);\n}\n\nfunction idAt(collectionModel: CollectionModel, index: CollectionIndex, position: number): string | null {\n const ref = refAt(collectionModel, position);\n\n return ref === null ? null : (itemAt(index, ref)?.id ?? null);\n}\n\nfunction pathAt(collectionModel: CollectionModel, position: number): string | null {\n const ref = refAt(collectionModel, position);\n\n if (ref === null) {\n return null;\n }\n\n return slotPathAt(ref.store, ref.slot);\n}\n\nfunction idsAt(collectionModel: CollectionModel, positions: number[]): string[] {\n if (positions.length === 0) {\n return NO_IDS;\n }\n\n const { expansion } = collectionModel;\n\n return findIds(expansion.index, expansion.slots, new Set(positions));\n}\n\nfunction isCollectionIdle(collection: Collection): boolean {\n const { expansion } = collection;\n\n return !expansion.isApplying && !expansion.isSyncing;\n}\n\nfunction createCollection(collectionModel: CollectionModel, index: CollectionIndex): Collection {\n return {\n model: collectionModel.model,\n expansion: collectionModel.expansion,\n isTree: index.isTree,\n rowAt: collectionModel.rowAt,\n itemAt: (ref) => itemAt(index, ref),\n sectionFor: index.sectionFor,\n idAt: (position) => idAt(collectionModel, index, position),\n idsAt: (positions) => idsAt(collectionModel, positions),\n pathAt: (position) => pathAt(collectionModel, position),\n positionFor: (id) => positionFor(collectionModel, id),\n rowsFor: (ids) => rowsFor(collectionModel, ids),\n };\n}\n\nexport { createCollection, isCollectionIdle, type Collection };\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../../src/internal/selection.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAGpC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAKzD,KAAK,gBAAgB,GAAG;IACpB,UAAU,EAAE,UAAU,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC;IAC1C,kBAAkB,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;IAClE,aAAa,CAAC,EAAE,GAAG,CAAC,aAAa,GAAG,IAAI,GAAG,SAAS,CAAC;IACrD,cAAc,EAAE,kBAAkB,CAAC;CACtC,CAAC;AAwKF,iBAAS,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,YAAY,CAuB7D;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
1
+ {"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../../src/internal/selection.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAGpC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAMzD,KAAK,gBAAgB,GAAG;IACpB,UAAU,EAAE,UAAU,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC;IAC1C,kBAAkB,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;IAClE,aAAa,CAAC,EAAE,GAAG,CAAC,aAAa,GAAG,IAAI,GAAG,SAAS,CAAC;IACrD,cAAc,EAAE,kBAAkB,CAAC;CACtC,CAAC;AAyOF,iBAAS,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,YAAY,CAwB7D;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -5,35 +5,42 @@ import { useState } from "react";
5
5
  import { isCollectionIdle } from "./collection.js";
6
6
  import { useControlledSync } from "./controlled-sync.js";
7
7
  import { joinParts } from "./keys.js";
8
+ function contiguousPositions(bitset, size) {
9
+ const first = bitset.getMinimum();
10
+ if (bitset.getMaximum() - first + 1 !== size) {
11
+ return null;
12
+ }
13
+ return Array.from({ length: size }, (_, offset) => first + offset);
14
+ }
8
15
  function selectedPositions(selection) {
9
16
  const bitset = selection.getSelection();
10
17
  const size = Number(bitset.getSize());
18
+ if (size === 0) {
19
+ return [];
20
+ }
21
+ const contiguous = contiguousPositions(bitset, size);
22
+ if (contiguous !== null) {
23
+ return contiguous;
24
+ }
11
25
  const positions = [];
12
26
  for (let index = 0; index < size; index++) {
13
27
  positions.push(bitset.getNth(index));
14
28
  }
15
29
  return positions;
16
30
  }
17
- function idsAt(collection, positions) {
18
- const ids = [];
19
- for (const position of positions) {
20
- const id = collection.idAt(position);
21
- if (id !== null) {
22
- ids.push(id);
23
- }
24
- }
25
- return ids;
26
- }
27
- function plannedPositions(context, ids) {
31
+ function plannedSelection(context, ids) {
28
32
  const { selection, collection } = context;
29
33
  if (selection === null || selection instanceof Gtk.NoSelection) {
30
34
  return null;
31
35
  }
32
- const positions = collection.positionsFor(ids);
33
- if (ids.length > 0 && positions.length === 0) {
36
+ const rows = collection.rowsFor(ids);
37
+ if (ids.length > 0 && rows.positions.length === 0) {
34
38
  return null;
35
39
  }
36
- return selection instanceof Gtk.SingleSelection ? positions.slice(0, 1) : positions;
40
+ if (selection instanceof Gtk.SingleSelection) {
41
+ return { positions: rows.positions.slice(0, 1), ids: rows.ids.slice(0, 1) };
42
+ }
43
+ return rows;
37
44
  }
38
45
  function applySingleSelection(selection, positions) {
39
46
  const [first] = positions;
@@ -43,19 +50,35 @@ function applySingleSelection(selection, positions) {
43
50
  }
44
51
  selection.selectItem(first, true);
45
52
  }
46
- function applyMultiSelection(selection, positions) {
47
- const selected = Gtk.Bitset.newEmpty();
53
+ function positionRuns(positions) {
54
+ const runs = [];
48
55
  for (const position of positions) {
49
- selected.add(position);
56
+ const last = runs.at(-1);
57
+ if (last !== undefined && last.start + last.length === position) {
58
+ last.length += 1;
59
+ }
60
+ else {
61
+ runs.push({ start: position, length: 1 });
62
+ }
63
+ }
64
+ return runs;
65
+ }
66
+ function positionBitset(positions) {
67
+ const selected = Gtk.Bitset.newEmpty();
68
+ for (const run of positionRuns(positions)) {
69
+ selected.addRange(run.start, run.length);
50
70
  }
51
- selection.setSelection(selected, Gtk.Bitset.newRange(0, selection.getNItems()));
71
+ return selected;
52
72
  }
53
73
  function applySelection(selection, positions) {
74
+ const selected = positionBitset(positions);
54
75
  if (selection instanceof Gtk.SingleSelection) {
55
76
  applySingleSelection(selection, positions);
56
- return;
57
77
  }
58
- applyMultiSelection(selection, positions);
78
+ else {
79
+ selection.setSelection(selected, Gtk.Bitset.newRange(0, selection.getNItems()));
80
+ }
81
+ return selected;
59
82
  }
60
83
  function reportSelection(context, ids) {
61
84
  const key = joinParts(ids);
@@ -78,40 +101,58 @@ function selectionElement(mode, props) {
78
101
  }
79
102
  return _jsx(GtkSingleSelection, { ...props, autoselect: false, canUnselect: true });
80
103
  }
104
+ function didApplyPlanned(context, selection, positions) {
105
+ const { state } = context;
106
+ state.isApplying = true;
107
+ try {
108
+ const selected = applySelection(selection, positions);
109
+ return selection.getSelection().equals(selected);
110
+ }
111
+ finally {
112
+ state.isApplying = false;
113
+ }
114
+ }
81
115
  function applyControlledSelection(context, selectedIds) {
82
116
  const { selection, collection } = context;
83
117
  if (selection === null) {
84
118
  return;
85
119
  }
86
- const planned = plannedPositions(context, selectedIds ?? []);
87
- if (planned !== null) {
88
- applySelection(selection, planned);
120
+ const planned = plannedSelection(context, selectedIds ?? []);
121
+ if (planned !== null && didApplyPlanned(context, selection, planned.positions)) {
122
+ reportSelection(context, planned.ids);
123
+ return;
124
+ }
125
+ reportSelection(context, collection.idsAt(selectedPositions(selection)));
126
+ }
127
+ function reportDrift(context, selectedIds, positions, onDrift) {
128
+ const planned = plannedSelection(context, selectedIds ?? []);
129
+ if (planned !== null && hasSelectionDrifted(positions, planned.positions)) {
130
+ onDrift();
89
131
  }
90
- reportSelection(context, idsAt(collection, selectedPositions(selection)));
91
132
  }
92
133
  function observeSelection(context, selectedIds, onDrift) {
93
- const { selection, collection } = context;
94
- if (selection === null) {
134
+ const { selection, collection, state } = context;
135
+ if (selection === null || state.isApplying) {
95
136
  return;
96
137
  }
97
138
  const positions = selectedPositions(selection);
98
- reportSelection(context, idsAt(collection, positions));
99
- if (!isCollectionIdle(collection)) {
100
- return;
101
- }
102
- const planned = plannedPositions(context, selectedIds ?? []);
103
- if (planned !== null && hasSelectionDrifted(positions, planned)) {
104
- onDrift();
139
+ reportSelection(context, collection.idsAt(positions));
140
+ if (isCollectionIdle(collection)) {
141
+ reportDrift(context, selectedIds, positions, onDrift);
105
142
  }
106
143
  }
107
144
  function newLastSelection() {
108
145
  return { selection: null, key: null };
109
146
  }
147
+ function newApplyState() {
148
+ return { isApplying: false };
149
+ }
110
150
  function useSelection(options) {
111
151
  const { collection, selectedIds, onSelectionChanged, selectionMode } = options;
112
152
  const [selection, setSelection] = useState(null);
113
153
  const [last] = useState(newLastSelection);
114
- const context = { selection, collection, last, onSelectionChanged };
154
+ const [state] = useState(newApplyState);
155
+ const context = { selection, collection, last, state, onSelectionChanged };
115
156
  const markDrift = useControlledSync({
116
157
  ids: selectedIds,
117
158
  collection,
@@ -1 +1 @@
1
- {"version":3,"file":"selection.js","sourceRoot":"","sources":["../../src/internal/selection.tsx"],"names":[],"mappings":"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","sourcesContent":["import type * as Gio from \"@gtkx/gi/gio\";\nimport type { ReactElement } from \"react\";\nimport * as Gtk from \"@gtkx/gi/gtk\";\nimport { GtkMultiSelection, GtkNoSelection, GtkSingleSelection } from \"@gtkx/jsx/gtk\";\nimport { useState } from \"react\";\nimport type { Collection } from \"./collection.js\";\nimport type { ItemsChangeHandler } from \"./expansion.js\";\nimport { isCollectionIdle } from \"./collection.js\";\nimport { useControlledSync } from \"./controlled-sync.js\";\nimport { joinParts } from \"./keys.js\";\n\ntype SelectionOptions = {\n collection: Collection;\n selectedIds?: string[] | null | undefined;\n onSelectionChanged?: ((ids: string[]) => void) | null | undefined;\n selectionMode?: Gtk.SelectionMode | null | undefined;\n onItemsChanged: ItemsChangeHandler;\n};\n\ntype LastSelection = {\n selection: Gtk.SelectionModel | null;\n key: string | null;\n};\n\ntype SelectionContext = {\n selection: Gtk.SelectionModel | null;\n collection: Collection;\n last: LastSelection;\n onSelectionChanged?: ((ids: string[]) => void) | null | undefined;\n};\n\ntype SelectionElementProps = {\n ref: (value: Gtk.SelectionModel | null) => void;\n model: Gio.ListModel;\n onSelectionChanged: () => void;\n onItemsChanged: ItemsChangeHandler;\n};\n\nfunction selectedPositions(selection: Gtk.SelectionModel): number[] {\n const bitset = selection.getSelection();\n const size = Number(bitset.getSize());\n const positions: number[] = [];\n\n for (let index = 0; index < size; index++) {\n positions.push(bitset.getNth(index));\n }\n\n return positions;\n}\n\nfunction idsAt(collection: Collection, positions: number[]): string[] {\n const ids: string[] = [];\n\n for (const position of positions) {\n const id = collection.idAt(position);\n\n if (id !== null) {\n ids.push(id);\n }\n }\n\n return ids;\n}\n\nfunction plannedPositions(context: SelectionContext, ids: string[]): number[] | null {\n const { selection, collection } = context;\n\n if (selection === null || selection instanceof Gtk.NoSelection) {\n return null;\n }\n\n const positions = collection.positionsFor(ids);\n\n if (ids.length > 0 && positions.length === 0) {\n return null;\n }\n\n return selection instanceof Gtk.SingleSelection ? positions.slice(0, 1) : positions;\n}\n\nfunction applySingleSelection(selection: Gtk.SingleSelection, positions: number[]): void {\n const [first] = positions;\n\n if (first === undefined) {\n selection.unselectAll();\n\n return;\n }\n\n selection.selectItem(first, true);\n}\n\nfunction applyMultiSelection(selection: Gtk.SelectionModel, positions: number[]): void {\n const selected = Gtk.Bitset.newEmpty();\n\n for (const position of positions) {\n selected.add(position);\n }\n\n selection.setSelection(selected, Gtk.Bitset.newRange(0, selection.getNItems()));\n}\n\nfunction applySelection(selection: Gtk.SelectionModel, positions: number[]): void {\n if (selection instanceof Gtk.SingleSelection) {\n applySingleSelection(selection, positions);\n\n return;\n }\n\n applyMultiSelection(selection, positions);\n}\n\nfunction reportSelection(context: SelectionContext, ids: string[]): void {\n const key = joinParts(ids);\n\n if (context.last.selection === context.selection && context.last.key === key) {\n return;\n }\n\n context.last.selection = context.selection;\n context.last.key = key;\n context.onSelectionChanged?.(ids);\n}\n\nfunction hasSelectionDrifted(actual: number[], planned: number[]): boolean {\n return actual.length !== planned.length || planned.some((position, index) => actual[index] !== position);\n}\n\nfunction selectionElement(mode: Gtk.SelectionMode | null | undefined, props: SelectionElementProps): ReactElement {\n if (mode === Gtk.SelectionMode.MULTIPLE) {\n return <GtkMultiSelection {...props} />;\n }\n\n if (mode === Gtk.SelectionMode.NONE) {\n return <GtkNoSelection {...props} />;\n }\n\n return <GtkSingleSelection {...props} autoselect={false} canUnselect />;\n}\n\nfunction applyControlledSelection(context: SelectionContext, selectedIds: string[] | null | undefined): void {\n const { selection, collection } = context;\n\n if (selection === null) {\n return;\n }\n\n const planned = plannedPositions(context, selectedIds ?? []);\n\n if (planned !== null) {\n applySelection(selection, planned);\n }\n\n reportSelection(context, idsAt(collection, selectedPositions(selection)));\n}\n\nfunction observeSelection(\n context: SelectionContext,\n selectedIds: string[] | null | undefined,\n onDrift: () => void,\n): void {\n const { selection, collection } = context;\n\n if (selection === null) {\n return;\n }\n\n const positions = selectedPositions(selection);\n reportSelection(context, idsAt(collection, positions));\n\n if (!isCollectionIdle(collection)) {\n return;\n }\n\n const planned = plannedPositions(context, selectedIds ?? []);\n\n if (planned !== null && hasSelectionDrifted(positions, planned)) {\n onDrift();\n }\n}\n\nfunction newLastSelection(): LastSelection {\n return { selection: null, key: null };\n}\n\nfunction useSelection(options: SelectionOptions): ReactElement {\n const { collection, selectedIds, onSelectionChanged, selectionMode } = options;\n const [selection, setSelection] = useState<Gtk.SelectionModel | null>(null);\n const [last] = useState<LastSelection>(newLastSelection);\n const context: SelectionContext = { selection, collection, last, onSelectionChanged };\n\n const markDrift = useControlledSync({\n ids: selectedIds,\n collection,\n widget: selection,\n apply: (ids) => {\n applyControlledSelection(context, ids);\n },\n });\n\n return selectionElement(selectionMode, {\n ref: setSelection,\n model: collection.model,\n onSelectionChanged: () => {\n observeSelection(context, selectedIds, markDrift);\n },\n onItemsChanged: options.onItemsChanged,\n });\n}\n\nexport { useSelection };\n"]}
1
+ {"version":3,"file":"selection.js","sourceRoot":"","sources":["../../src/internal/selection.tsx"],"names":[],"mappings":"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","sourcesContent":["import type * as Gio from \"@gtkx/gi/gio\";\nimport type { ReactElement } from \"react\";\nimport * as Gtk from \"@gtkx/gi/gtk\";\nimport { GtkMultiSelection, GtkNoSelection, GtkSingleSelection } from \"@gtkx/jsx/gtk\";\nimport { useState } from \"react\";\nimport type { Collection } from \"./collection.js\";\nimport type { ItemsChangeHandler } from \"./expansion.js\";\nimport type { MatchedRows } from \"./tree-order.js\";\nimport { isCollectionIdle } from \"./collection.js\";\nimport { useControlledSync } from \"./controlled-sync.js\";\nimport { joinParts } from \"./keys.js\";\n\ntype SelectionOptions = {\n collection: Collection;\n selectedIds?: string[] | null | undefined;\n onSelectionChanged?: ((ids: string[]) => void) | null | undefined;\n selectionMode?: Gtk.SelectionMode | null | undefined;\n onItemsChanged: ItemsChangeHandler;\n};\n\ntype LastSelection = {\n selection: Gtk.SelectionModel | null;\n key: string | null;\n};\n\ntype ApplyState = {\n isApplying: boolean;\n};\n\ntype SelectionContext = {\n selection: Gtk.SelectionModel | null;\n collection: Collection;\n last: LastSelection;\n state: ApplyState;\n onSelectionChanged?: ((ids: string[]) => void) | null | undefined;\n};\n\ntype PositionRun = {\n start: number;\n length: number;\n};\n\ntype SelectionElementProps = {\n ref: (value: Gtk.SelectionModel | null) => void;\n model: Gio.ListModel;\n onSelectionChanged: () => void;\n onItemsChanged: ItemsChangeHandler;\n};\n\nfunction contiguousPositions(bitset: Gtk.Bitset, size: number): number[] | null {\n const first = bitset.getMinimum();\n\n if (bitset.getMaximum() - first + 1 !== size) {\n return null;\n }\n\n return Array.from({ length: size }, (_, offset) => first + offset);\n}\n\nfunction selectedPositions(selection: Gtk.SelectionModel): number[] {\n const bitset = selection.getSelection();\n const size = Number(bitset.getSize());\n\n if (size === 0) {\n return [];\n }\n\n const contiguous = contiguousPositions(bitset, size);\n\n if (contiguous !== null) {\n return contiguous;\n }\n\n const positions: number[] = [];\n\n for (let index = 0; index < size; index++) {\n positions.push(bitset.getNth(index));\n }\n\n return positions;\n}\n\nfunction plannedSelection(context: SelectionContext, ids: string[]): MatchedRows | null {\n const { selection, collection } = context;\n\n if (selection === null || selection instanceof Gtk.NoSelection) {\n return null;\n }\n\n const rows = collection.rowsFor(ids);\n\n if (ids.length > 0 && rows.positions.length === 0) {\n return null;\n }\n\n if (selection instanceof Gtk.SingleSelection) {\n return { positions: rows.positions.slice(0, 1), ids: rows.ids.slice(0, 1) };\n }\n\n return rows;\n}\n\nfunction applySingleSelection(selection: Gtk.SingleSelection, positions: number[]): void {\n const [first] = positions;\n\n if (first === undefined) {\n selection.unselectAll();\n\n return;\n }\n\n selection.selectItem(first, true);\n}\n\nfunction positionRuns(positions: number[]): PositionRun[] {\n const runs: PositionRun[] = [];\n\n for (const position of positions) {\n const last = runs.at(-1);\n\n if (last !== undefined && last.start + last.length === position) {\n last.length += 1;\n } else {\n runs.push({ start: position, length: 1 });\n }\n }\n\n return runs;\n}\n\nfunction positionBitset(positions: number[]): Gtk.Bitset {\n const selected = Gtk.Bitset.newEmpty();\n\n for (const run of positionRuns(positions)) {\n selected.addRange(run.start, run.length);\n }\n\n return selected;\n}\n\nfunction applySelection(selection: Gtk.SelectionModel, positions: number[]): Gtk.Bitset {\n const selected = positionBitset(positions);\n\n if (selection instanceof Gtk.SingleSelection) {\n applySingleSelection(selection, positions);\n } else {\n selection.setSelection(selected, Gtk.Bitset.newRange(0, selection.getNItems()));\n }\n\n return selected;\n}\n\nfunction reportSelection(context: SelectionContext, ids: string[]): void {\n const key = joinParts(ids);\n\n if (context.last.selection === context.selection && context.last.key === key) {\n return;\n }\n\n context.last.selection = context.selection;\n context.last.key = key;\n context.onSelectionChanged?.(ids);\n}\n\nfunction hasSelectionDrifted(actual: number[], planned: number[]): boolean {\n return actual.length !== planned.length || planned.some((position, index) => actual[index] !== position);\n}\n\nfunction selectionElement(mode: Gtk.SelectionMode | null | undefined, props: SelectionElementProps): ReactElement {\n if (mode === Gtk.SelectionMode.MULTIPLE) {\n return <GtkMultiSelection {...props} />;\n }\n\n if (mode === Gtk.SelectionMode.NONE) {\n return <GtkNoSelection {...props} />;\n }\n\n return <GtkSingleSelection {...props} autoselect={false} canUnselect />;\n}\n\nfunction didApplyPlanned(context: SelectionContext, selection: Gtk.SelectionModel, positions: number[]): boolean {\n const { state } = context;\n state.isApplying = true;\n\n try {\n const selected = applySelection(selection, positions);\n\n return selection.getSelection().equals(selected);\n } finally {\n state.isApplying = false;\n }\n}\n\nfunction applyControlledSelection(context: SelectionContext, selectedIds: string[] | null | undefined): void {\n const { selection, collection } = context;\n\n if (selection === null) {\n return;\n }\n\n const planned = plannedSelection(context, selectedIds ?? []);\n\n if (planned !== null && didApplyPlanned(context, selection, planned.positions)) {\n reportSelection(context, planned.ids);\n\n return;\n }\n\n reportSelection(context, collection.idsAt(selectedPositions(selection)));\n}\n\nfunction reportDrift(\n context: SelectionContext,\n selectedIds: string[] | null | undefined,\n positions: number[],\n onDrift: () => void,\n): void {\n const planned = plannedSelection(context, selectedIds ?? []);\n\n if (planned !== null && hasSelectionDrifted(positions, planned.positions)) {\n onDrift();\n }\n}\n\nfunction observeSelection(\n context: SelectionContext,\n selectedIds: string[] | null | undefined,\n onDrift: () => void,\n): void {\n const { selection, collection, state } = context;\n\n if (selection === null || state.isApplying) {\n return;\n }\n\n const positions = selectedPositions(selection);\n reportSelection(context, collection.idsAt(positions));\n\n if (isCollectionIdle(collection)) {\n reportDrift(context, selectedIds, positions, onDrift);\n }\n}\n\nfunction newLastSelection(): LastSelection {\n return { selection: null, key: null };\n}\n\nfunction newApplyState(): ApplyState {\n return { isApplying: false };\n}\n\nfunction useSelection(options: SelectionOptions): ReactElement {\n const { collection, selectedIds, onSelectionChanged, selectionMode } = options;\n const [selection, setSelection] = useState<Gtk.SelectionModel | null>(null);\n const [last] = useState<LastSelection>(newLastSelection);\n const [state] = useState<ApplyState>(newApplyState);\n const context: SelectionContext = { selection, collection, last, state, onSelectionChanged };\n\n const markDrift = useControlledSync({\n ids: selectedIds,\n collection,\n widget: selection,\n apply: (ids) => {\n applyControlledSelection(context, ids);\n },\n });\n\n return selectionElement(selectionMode, {\n ref: setSelection,\n model: collection.model,\n onSelectionChanged: () => {\n observeSelection(context, selectedIds, markDrift);\n },\n onItemsChanged: options.onItemsChanged,\n });\n}\n\nexport { useSelection };\n"]}
@@ -11,6 +11,10 @@ type VisibleOrder = {
11
11
  expandedPaths: string[];
12
12
  expandedIds: string[];
13
13
  };
14
+ type MatchedRows = {
15
+ positions: number[];
16
+ ids: string[];
17
+ };
14
18
  type RowVisitor = (row: VisibleRow) => void;
15
19
  type WalkOptions = {
16
20
  index: CollectionIndex;
@@ -21,6 +25,7 @@ type WalkOptions = {
21
25
  declare function walkVisible(options: WalkOptions): VisibleOrder;
22
26
  declare function expandedPathsFor(index: CollectionIndex, ids: Set<string>): Set<string>;
23
27
  declare function buildVisibleOrder(index: CollectionIndex, slots: SlotMap): VisibleOrder;
24
- declare function findPositions(index: CollectionIndex, slots: SlotMap, ids: Set<string>): number[];
25
- export { buildVisibleOrder, expandedPathsFor, findPositions, walkVisible, type VisibleOrder, type WalkOptions, };
28
+ declare function findRows(index: CollectionIndex, slots: SlotMap, ids: Set<string>): MatchedRows;
29
+ declare function findIds(index: CollectionIndex, slots: SlotMap, positions: Set<number>): string[];
30
+ export { buildVisibleOrder, expandedPathsFor, findIds, findRows, walkVisible, type MatchedRows, type VisibleOrder, type WalkOptions, };
26
31
  //# sourceMappingURL=tree-order.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tree-order.d.ts","sourceRoot":"","sources":["../../src/internal/tree-order.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAS,MAAM,uBAAuB,CAAC;AACpE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,KAAK,UAAU,GAAG;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,KAAK,YAAY,GAAG;IAChB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,EAAE,CAAC;CACzB,CAAC;AAEF,KAAK,UAAU,GAAG,CAAC,GAAG,EAAE,UAAU,KAAK,IAAI,CAAC;AAE5C,KAAK,WAAW,GAAG;IACf,KAAK,EAAE,eAAe,CAAC;IACvB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,KAAK,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;CAClC,CAAC;AA2FF,iBAAS,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,YAAY,CAevD;AA0BD,iBAAS,gBAAgB,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAuB/E;AAED,iBAAS,iBAAiB,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,OAAO,GAAG,YAAY,CAE/E;AAED,iBAAS,aAAa,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAczF;AAED,OAAO,EACH,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,KAAK,YAAY,EACjB,KAAK,WAAW,GACnB,CAAC"}
1
+ {"version":3,"file":"tree-order.d.ts","sourceRoot":"","sources":["../../src/internal/tree-order.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAS,MAAM,uBAAuB,CAAC;AACpE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,KAAK,UAAU,GAAG;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,KAAK,YAAY,GAAG;IAChB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,EAAE,CAAC;CACzB,CAAC;AAEF,KAAK,WAAW,GAAG;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,GAAG,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAEF,KAAK,UAAU,GAAG,CAAC,GAAG,EAAE,UAAU,KAAK,IAAI,CAAC;AAE5C,KAAK,WAAW,GAAG;IACf,KAAK,EAAE,eAAe,CAAC;IACvB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,KAAK,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;CAClC,CAAC;AA2FF,iBAAS,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,YAAY,CAevD;AA0BD,iBAAS,gBAAgB,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAuB/E;AAED,iBAAS,iBAAiB,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,OAAO,GAAG,YAAY,CAE/E;AAED,iBAAS,QAAQ,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,WAAW,CAkBvF;AAED,iBAAS,OAAO,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAgBzF;AAED,OAAO,EACH,iBAAiB,EACjB,gBAAgB,EAChB,OAAO,EACP,QAAQ,EACR,WAAW,EACX,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,WAAW,GACnB,CAAC"}
@@ -92,18 +92,35 @@ function expandedPathsFor(index, ids) {
92
92
  function buildVisibleOrder(index, slots) {
93
93
  return walkVisible({ index, slots });
94
94
  }
95
- function findPositions(index, slots, ids) {
95
+ function findRows(index, slots, ids) {
96
+ const positions = [];
96
97
  const found = [];
97
98
  walkVisible({
98
99
  index,
99
100
  slots,
100
101
  visit: (row) => {
101
- if (ids.has(row.item.id)) {
102
- found.push(row.position);
102
+ if (!ids.has(row.item.id)) {
103
+ return;
103
104
  }
105
+ positions.push(row.position);
106
+ found.push(row.item.id);
107
+ },
108
+ });
109
+ return { positions, ids: found };
110
+ }
111
+ function findIds(index, slots, positions) {
112
+ const found = [];
113
+ walkVisible({
114
+ index,
115
+ slots,
116
+ visit: (row) => {
117
+ if (!positions.has(row.position)) {
118
+ return;
119
+ }
120
+ found.push(row.item.id);
104
121
  },
105
122
  });
106
123
  return found;
107
124
  }
108
- export { buildVisibleOrder, expandedPathsFor, findPositions, walkVisible, };
125
+ export { buildVisibleOrder, expandedPathsFor, findIds, findRows, walkVisible, };
109
126
  //# sourceMappingURL=tree-order.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tree-order.js","sourceRoot":"","sources":["../../src/internal/tree-order.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ListItem } from \"../types.js\";\nimport type { CollectionIndex, Level } from \"./collection-index.js\";\nimport type { SlotMap } from \"./slots.js\";\n\ntype VisibleRow = {\n item: ListItem;\n position: number;\n isOpen: boolean;\n isMarked: boolean;\n};\n\ntype VisibleOrder = {\n expandedPaths: string[];\n expandedIds: string[];\n};\n\ntype RowVisitor = (row: VisibleRow) => void;\n\ntype WalkOptions = {\n index: CollectionIndex;\n slots: SlotMap;\n marks?: SlotMap | undefined;\n visit?: RowVisitor | undefined;\n};\n\ntype LevelFrame = {\n level: Level;\n cursor: number;\n};\n\ntype WalkFrame = LevelFrame & {\n open: Set<number> | undefined;\n marked: Set<number> | undefined;\n};\n\ntype WalkState = {\n options: WalkOptions;\n stack: WalkFrame[];\n row: VisibleRow;\n order: VisibleOrder;\n};\n\ntype OpenFrame = LevelFrame & {\n owner: ListItem | null;\n};\n\ntype OpenWalk = {\n index: CollectionIndex;\n ids: Set<string>;\n paths: Set<string>;\n stack: OpenFrame[];\n ancestors: Set<ListItem>;\n};\n\nconst NO_ITEM: ListItem = { id: \"\", value: undefined };\n\nfunction advanceStack<F extends LevelFrame>(\n stack: F[],\n onSlot: (frame: F, slot: number) => void,\n onLeave?: (frame: F) => void,\n): void {\n const frame = stack.at(-1);\n\n if (frame === undefined) {\n return;\n }\n\n if (frame.cursor >= frame.level.items.length) {\n stack.pop();\n onLeave?.(frame);\n\n return;\n }\n\n const slot = frame.cursor;\n frame.cursor += 1;\n onSlot(frame, slot);\n}\n\nfunction frameFor(options: WalkOptions, level: Level): WalkFrame {\n return { level, cursor: 0, open: options.slots.get(level.path), marked: options.marks?.get(level.path) };\n}\n\nfunction descend(state: WalkState, level: Level, slot: number): void {\n const child = state.options.index.childLevel(level, slot);\n\n if (child === undefined) {\n return;\n }\n\n state.order.expandedPaths.push(child.path);\n state.order.expandedIds.push(state.row.item.id);\n state.stack.push(frameFor(state.options, child));\n}\n\nfunction visitSlot(state: WalkState, frame: WalkFrame, slot: number): void {\n const item = frame.level.items[slot];\n\n if (item === undefined) {\n return;\n }\n\n const { row } = state;\n row.item = item;\n row.isOpen = frame.open?.has(slot) ?? false;\n row.isMarked = frame.marked?.has(slot) ?? false;\n state.options.visit?.(row);\n row.position += 1;\n\n if (row.isOpen) {\n descend(state, frame.level, slot);\n }\n}\n\nfunction walkVisible(options: WalkOptions): VisibleOrder {\n const order: VisibleOrder = { expandedPaths: [], expandedIds: [] };\n const row: VisibleRow = { item: NO_ITEM, position: 0, isOpen: false, isMarked: false };\n const frames = options.index.groups.map((level) => frameFor(options, level));\n const state: WalkState = { options, stack: frames.toReversed(), row, order };\n\n const onSlot = (frame: WalkFrame, slot: number): void => {\n visitSlot(state, frame, slot);\n };\n\n while (state.stack.length > 0) {\n advanceStack(state.stack, onSlot);\n }\n\n return order;\n}\n\nfunction openSlot(walk: OpenWalk, frame: OpenFrame, slot: number): void {\n const item = frame.level.items[slot];\n\n if (item === undefined || !walk.ids.has(item.id) || walk.ancestors.has(item)) {\n return;\n }\n\n const child = walk.index.childLevel(frame.level, slot);\n\n if (child === undefined) {\n return;\n }\n\n walk.paths.add(child.path);\n walk.ancestors.add(item);\n walk.stack.push({ level: child, cursor: 0, owner: item });\n}\n\nfunction leaveOpenFrame(walk: OpenWalk, frame: OpenFrame): void {\n if (frame.owner !== null) {\n walk.ancestors.delete(frame.owner);\n }\n}\n\nfunction expandedPathsFor(index: CollectionIndex, ids: Set<string>): Set<string> {\n const paths: Set<string> = new Set();\n\n if (ids.size === 0 || !index.isTree) {\n return paths;\n }\n\n const frames = index.groups.map((level) => ({ level, cursor: 0, owner: null }));\n const walk: OpenWalk = { index, ids, paths, stack: frames.toReversed(), ancestors: new Set() };\n\n const onSlot = (frame: OpenFrame, slot: number): void => {\n openSlot(walk, frame, slot);\n };\n\n const onLeave = (frame: OpenFrame): void => {\n leaveOpenFrame(walk, frame);\n };\n\n while (walk.stack.length > 0) {\n advanceStack(walk.stack, onSlot, onLeave);\n }\n\n return paths;\n}\n\nfunction buildVisibleOrder(index: CollectionIndex, slots: SlotMap): VisibleOrder {\n return walkVisible({ index, slots });\n}\n\nfunction findPositions(index: CollectionIndex, slots: SlotMap, ids: Set<string>): number[] {\n const found: number[] = [];\n\n walkVisible({\n index,\n slots,\n visit: (row) => {\n if (ids.has(row.item.id)) {\n found.push(row.position);\n }\n },\n });\n\n return found;\n}\n\nexport {\n buildVisibleOrder,\n expandedPathsFor,\n findPositions,\n walkVisible,\n type VisibleOrder,\n type WalkOptions,\n};\n"]}
1
+ {"version":3,"file":"tree-order.js","sourceRoot":"","sources":["../../src/internal/tree-order.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ListItem } from \"../types.js\";\nimport type { CollectionIndex, Level } from \"./collection-index.js\";\nimport type { SlotMap } from \"./slots.js\";\n\ntype VisibleRow = {\n item: ListItem;\n position: number;\n isOpen: boolean;\n isMarked: boolean;\n};\n\ntype VisibleOrder = {\n expandedPaths: string[];\n expandedIds: string[];\n};\n\ntype MatchedRows = {\n positions: number[];\n ids: string[];\n};\n\ntype RowVisitor = (row: VisibleRow) => void;\n\ntype WalkOptions = {\n index: CollectionIndex;\n slots: SlotMap;\n marks?: SlotMap | undefined;\n visit?: RowVisitor | undefined;\n};\n\ntype LevelFrame = {\n level: Level;\n cursor: number;\n};\n\ntype WalkFrame = LevelFrame & {\n open: Set<number> | undefined;\n marked: Set<number> | undefined;\n};\n\ntype WalkState = {\n options: WalkOptions;\n stack: WalkFrame[];\n row: VisibleRow;\n order: VisibleOrder;\n};\n\ntype OpenFrame = LevelFrame & {\n owner: ListItem | null;\n};\n\ntype OpenWalk = {\n index: CollectionIndex;\n ids: Set<string>;\n paths: Set<string>;\n stack: OpenFrame[];\n ancestors: Set<ListItem>;\n};\n\nconst NO_ITEM: ListItem = { id: \"\", value: undefined };\n\nfunction advanceStack<F extends LevelFrame>(\n stack: F[],\n onSlot: (frame: F, slot: number) => void,\n onLeave?: (frame: F) => void,\n): void {\n const frame = stack.at(-1);\n\n if (frame === undefined) {\n return;\n }\n\n if (frame.cursor >= frame.level.items.length) {\n stack.pop();\n onLeave?.(frame);\n\n return;\n }\n\n const slot = frame.cursor;\n frame.cursor += 1;\n onSlot(frame, slot);\n}\n\nfunction frameFor(options: WalkOptions, level: Level): WalkFrame {\n return { level, cursor: 0, open: options.slots.get(level.path), marked: options.marks?.get(level.path) };\n}\n\nfunction descend(state: WalkState, level: Level, slot: number): void {\n const child = state.options.index.childLevel(level, slot);\n\n if (child === undefined) {\n return;\n }\n\n state.order.expandedPaths.push(child.path);\n state.order.expandedIds.push(state.row.item.id);\n state.stack.push(frameFor(state.options, child));\n}\n\nfunction visitSlot(state: WalkState, frame: WalkFrame, slot: number): void {\n const item = frame.level.items[slot];\n\n if (item === undefined) {\n return;\n }\n\n const { row } = state;\n row.item = item;\n row.isOpen = frame.open?.has(slot) ?? false;\n row.isMarked = frame.marked?.has(slot) ?? false;\n state.options.visit?.(row);\n row.position += 1;\n\n if (row.isOpen) {\n descend(state, frame.level, slot);\n }\n}\n\nfunction walkVisible(options: WalkOptions): VisibleOrder {\n const order: VisibleOrder = { expandedPaths: [], expandedIds: [] };\n const row: VisibleRow = { item: NO_ITEM, position: 0, isOpen: false, isMarked: false };\n const frames = options.index.groups.map((level) => frameFor(options, level));\n const state: WalkState = { options, stack: frames.toReversed(), row, order };\n\n const onSlot = (frame: WalkFrame, slot: number): void => {\n visitSlot(state, frame, slot);\n };\n\n while (state.stack.length > 0) {\n advanceStack(state.stack, onSlot);\n }\n\n return order;\n}\n\nfunction openSlot(walk: OpenWalk, frame: OpenFrame, slot: number): void {\n const item = frame.level.items[slot];\n\n if (item === undefined || !walk.ids.has(item.id) || walk.ancestors.has(item)) {\n return;\n }\n\n const child = walk.index.childLevel(frame.level, slot);\n\n if (child === undefined) {\n return;\n }\n\n walk.paths.add(child.path);\n walk.ancestors.add(item);\n walk.stack.push({ level: child, cursor: 0, owner: item });\n}\n\nfunction leaveOpenFrame(walk: OpenWalk, frame: OpenFrame): void {\n if (frame.owner !== null) {\n walk.ancestors.delete(frame.owner);\n }\n}\n\nfunction expandedPathsFor(index: CollectionIndex, ids: Set<string>): Set<string> {\n const paths: Set<string> = new Set();\n\n if (ids.size === 0 || !index.isTree) {\n return paths;\n }\n\n const frames = index.groups.map((level) => ({ level, cursor: 0, owner: null }));\n const walk: OpenWalk = { index, ids, paths, stack: frames.toReversed(), ancestors: new Set() };\n\n const onSlot = (frame: OpenFrame, slot: number): void => {\n openSlot(walk, frame, slot);\n };\n\n const onLeave = (frame: OpenFrame): void => {\n leaveOpenFrame(walk, frame);\n };\n\n while (walk.stack.length > 0) {\n advanceStack(walk.stack, onSlot, onLeave);\n }\n\n return paths;\n}\n\nfunction buildVisibleOrder(index: CollectionIndex, slots: SlotMap): VisibleOrder {\n return walkVisible({ index, slots });\n}\n\nfunction findRows(index: CollectionIndex, slots: SlotMap, ids: Set<string>): MatchedRows {\n const positions: number[] = [];\n const found: string[] = [];\n\n walkVisible({\n index,\n slots,\n visit: (row) => {\n if (!ids.has(row.item.id)) {\n return;\n }\n\n positions.push(row.position);\n found.push(row.item.id);\n },\n });\n\n return { positions, ids: found };\n}\n\nfunction findIds(index: CollectionIndex, slots: SlotMap, positions: Set<number>): string[] {\n const found: string[] = [];\n\n walkVisible({\n index,\n slots,\n visit: (row) => {\n if (!positions.has(row.position)) {\n return;\n }\n\n found.push(row.item.id);\n },\n });\n\n return found;\n}\n\nexport {\n buildVisibleOrder,\n expandedPathsFor,\n findIds,\n findRows,\n walkVisible,\n type MatchedRows,\n type VisibleOrder,\n type WalkOptions,\n};\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gtkx/components",
3
- "version": "1.2.2",
4
- "description": "High-level GTKX components for declarative collection views, menus, and containers with attach-time placement",
3
+ "version": "1.4.0",
4
+ "description": "High-level GTK components: collection views, menus, containers.",
5
5
  "keywords": [
6
6
  "gtkx",
7
7
  "gtk",
@@ -54,18 +54,18 @@
54
54
  "node": ">=24"
55
55
  },
56
56
  "dependencies": {
57
- "@gtkx/react": "1.2.2",
58
- "@gtkx/runtime": "1.2.2",
59
- "@gtkx/utils": "1.2.2"
57
+ "@gtkx/react": "1.4.0",
58
+ "@gtkx/utils": "1.4.0",
59
+ "@gtkx/runtime": "1.4.0"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "react": "^19.2"
63
63
  },
64
64
  "devDependencies": {
65
- "vitest": "^4.1.10",
66
- "@gtkx/config": "1.2.2",
67
- "@gtkx/testing": "1.2.2",
68
- "@gtkx/vitest": "1.2.2"
65
+ "vitest": "^4.1.11",
66
+ "@gtkx/config": "1.4.0",
67
+ "@gtkx/testing": "1.4.0",
68
+ "@gtkx/vitest": "1.4.0"
69
69
  },
70
70
  "scripts": {
71
71
  "release": "tsx ../../scripts/release-package.ts"
@@ -1,33 +1,36 @@
1
1
  import type { ListItem } from "../types.js";
2
2
  import type { CollectionIndex } from "./collection-index.js";
3
3
  import type { CollectionModel, SlotRef } from "./collection-model.js";
4
+ import type { MatchedRows } from "./tree-order.js";
4
5
  import { slotPathAt, slotRefFor } from "./collection-model.js";
5
- import { findPositions } from "./tree-order.js";
6
+ import { findIds, findRows } from "./tree-order.js";
6
7
 
7
8
  type Collection = Pick<CollectionModel, "model" | "expansion" | "rowAt"> & {
8
9
  isTree: boolean;
9
10
  itemAt: (ref: SlotRef) => ListItem | undefined;
10
11
  sectionFor: (levelPath: string) => unknown;
11
12
  idAt: (position: number) => string | null;
13
+ idsAt: (positions: number[]) => string[];
12
14
  pathAt: (position: number) => string | null;
13
15
  positionFor: (id: string) => number;
14
- positionsFor: (ids: string[]) => number[];
16
+ rowsFor: (ids: string[]) => MatchedRows;
15
17
  };
16
18
 
17
- const NO_POSITIONS: number[] = [];
19
+ const NO_ROWS: MatchedRows = { positions: [], ids: [] };
20
+ const NO_IDS: string[] = [];
18
21
 
19
- function positionsFor(collectionModel: CollectionModel, ids: string[]): number[] {
22
+ function rowsFor(collectionModel: CollectionModel, ids: string[]): MatchedRows {
20
23
  if (ids.length === 0) {
21
- return NO_POSITIONS;
24
+ return NO_ROWS;
22
25
  }
23
26
 
24
27
  const { expansion } = collectionModel;
25
28
 
26
- return findPositions(expansion.index, expansion.slots, new Set(ids));
29
+ return findRows(expansion.index, expansion.slots, new Set(ids));
27
30
  }
28
31
 
29
32
  function positionFor(collectionModel: CollectionModel, id: string): number {
30
- const [first = -1] = positionsFor(collectionModel, [id]);
33
+ const [first = -1] = rowsFor(collectionModel, [id]).positions;
31
34
 
32
35
  return first;
33
36
  }
@@ -60,6 +63,16 @@ function pathAt(collectionModel: CollectionModel, position: number): string | nu
60
63
  return slotPathAt(ref.store, ref.slot);
61
64
  }
62
65
 
66
+ function idsAt(collectionModel: CollectionModel, positions: number[]): string[] {
67
+ if (positions.length === 0) {
68
+ return NO_IDS;
69
+ }
70
+
71
+ const { expansion } = collectionModel;
72
+
73
+ return findIds(expansion.index, expansion.slots, new Set(positions));
74
+ }
75
+
63
76
  function isCollectionIdle(collection: Collection): boolean {
64
77
  const { expansion } = collection;
65
78
 
@@ -75,9 +88,10 @@ function createCollection(collectionModel: CollectionModel, index: CollectionInd
75
88
  itemAt: (ref) => itemAt(index, ref),
76
89
  sectionFor: index.sectionFor,
77
90
  idAt: (position) => idAt(collectionModel, index, position),
91
+ idsAt: (positions) => idsAt(collectionModel, positions),
78
92
  pathAt: (position) => pathAt(collectionModel, position),
79
93
  positionFor: (id) => positionFor(collectionModel, id),
80
- positionsFor: (ids) => positionsFor(collectionModel, ids),
94
+ rowsFor: (ids) => rowsFor(collectionModel, ids),
81
95
  };
82
96
  }
83
97
 
@@ -5,6 +5,7 @@ import { GtkMultiSelection, GtkNoSelection, GtkSingleSelection } from "@gtkx/jsx
5
5
  import { useState } from "react";
6
6
  import type { Collection } from "./collection.js";
7
7
  import type { ItemsChangeHandler } from "./expansion.js";
8
+ import type { MatchedRows } from "./tree-order.js";
8
9
  import { isCollectionIdle } from "./collection.js";
9
10
  import { useControlledSync } from "./controlled-sync.js";
10
11
  import { joinParts } from "./keys.js";
@@ -22,13 +23,23 @@ type LastSelection = {
22
23
  key: string | null;
23
24
  };
24
25
 
26
+ type ApplyState = {
27
+ isApplying: boolean;
28
+ };
29
+
25
30
  type SelectionContext = {
26
31
  selection: Gtk.SelectionModel | null;
27
32
  collection: Collection;
28
33
  last: LastSelection;
34
+ state: ApplyState;
29
35
  onSelectionChanged?: ((ids: string[]) => void) | null | undefined;
30
36
  };
31
37
 
38
+ type PositionRun = {
39
+ start: number;
40
+ length: number;
41
+ };
42
+
32
43
  type SelectionElementProps = {
33
44
  ref: (value: Gtk.SelectionModel | null) => void;
34
45
  model: Gio.ListModel;
@@ -36,46 +47,57 @@ type SelectionElementProps = {
36
47
  onItemsChanged: ItemsChangeHandler;
37
48
  };
38
49
 
50
+ function contiguousPositions(bitset: Gtk.Bitset, size: number): number[] | null {
51
+ const first = bitset.getMinimum();
52
+
53
+ if (bitset.getMaximum() - first + 1 !== size) {
54
+ return null;
55
+ }
56
+
57
+ return Array.from({ length: size }, (_, offset) => first + offset);
58
+ }
59
+
39
60
  function selectedPositions(selection: Gtk.SelectionModel): number[] {
40
61
  const bitset = selection.getSelection();
41
62
  const size = Number(bitset.getSize());
42
- const positions: number[] = [];
43
63
 
44
- for (let index = 0; index < size; index++) {
45
- positions.push(bitset.getNth(index));
64
+ if (size === 0) {
65
+ return [];
46
66
  }
47
67
 
48
- return positions;
49
- }
68
+ const contiguous = contiguousPositions(bitset, size);
50
69
 
51
- function idsAt(collection: Collection, positions: number[]): string[] {
52
- const ids: string[] = [];
70
+ if (contiguous !== null) {
71
+ return contiguous;
72
+ }
53
73
 
54
- for (const position of positions) {
55
- const id = collection.idAt(position);
74
+ const positions: number[] = [];
56
75
 
57
- if (id !== null) {
58
- ids.push(id);
59
- }
76
+ for (let index = 0; index < size; index++) {
77
+ positions.push(bitset.getNth(index));
60
78
  }
61
79
 
62
- return ids;
80
+ return positions;
63
81
  }
64
82
 
65
- function plannedPositions(context: SelectionContext, ids: string[]): number[] | null {
83
+ function plannedSelection(context: SelectionContext, ids: string[]): MatchedRows | null {
66
84
  const { selection, collection } = context;
67
85
 
68
86
  if (selection === null || selection instanceof Gtk.NoSelection) {
69
87
  return null;
70
88
  }
71
89
 
72
- const positions = collection.positionsFor(ids);
90
+ const rows = collection.rowsFor(ids);
73
91
 
74
- if (ids.length > 0 && positions.length === 0) {
92
+ if (ids.length > 0 && rows.positions.length === 0) {
75
93
  return null;
76
94
  }
77
95
 
78
- return selection instanceof Gtk.SingleSelection ? positions.slice(0, 1) : positions;
96
+ if (selection instanceof Gtk.SingleSelection) {
97
+ return { positions: rows.positions.slice(0, 1), ids: rows.ids.slice(0, 1) };
98
+ }
99
+
100
+ return rows;
79
101
  }
80
102
 
81
103
  function applySingleSelection(selection: Gtk.SingleSelection, positions: number[]): void {
@@ -90,24 +112,42 @@ function applySingleSelection(selection: Gtk.SingleSelection, positions: number[
90
112
  selection.selectItem(first, true);
91
113
  }
92
114
 
93
- function applyMultiSelection(selection: Gtk.SelectionModel, positions: number[]): void {
94
- const selected = Gtk.Bitset.newEmpty();
115
+ function positionRuns(positions: number[]): PositionRun[] {
116
+ const runs: PositionRun[] = [];
95
117
 
96
118
  for (const position of positions) {
97
- selected.add(position);
119
+ const last = runs.at(-1);
120
+
121
+ if (last !== undefined && last.start + last.length === position) {
122
+ last.length += 1;
123
+ } else {
124
+ runs.push({ start: position, length: 1 });
125
+ }
126
+ }
127
+
128
+ return runs;
129
+ }
130
+
131
+ function positionBitset(positions: number[]): Gtk.Bitset {
132
+ const selected = Gtk.Bitset.newEmpty();
133
+
134
+ for (const run of positionRuns(positions)) {
135
+ selected.addRange(run.start, run.length);
98
136
  }
99
137
 
100
- selection.setSelection(selected, Gtk.Bitset.newRange(0, selection.getNItems()));
138
+ return selected;
101
139
  }
102
140
 
103
- function applySelection(selection: Gtk.SelectionModel, positions: number[]): void {
141
+ function applySelection(selection: Gtk.SelectionModel, positions: number[]): Gtk.Bitset {
142
+ const selected = positionBitset(positions);
143
+
104
144
  if (selection instanceof Gtk.SingleSelection) {
105
145
  applySingleSelection(selection, positions);
106
-
107
- return;
146
+ } else {
147
+ selection.setSelection(selected, Gtk.Bitset.newRange(0, selection.getNItems()));
108
148
  }
109
149
 
110
- applyMultiSelection(selection, positions);
150
+ return selected;
111
151
  }
112
152
 
113
153
  function reportSelection(context: SelectionContext, ids: string[]): void {
@@ -138,6 +178,19 @@ function selectionElement(mode: Gtk.SelectionMode | null | undefined, props: Sel
138
178
  return <GtkSingleSelection {...props} autoselect={false} canUnselect />;
139
179
  }
140
180
 
181
+ function didApplyPlanned(context: SelectionContext, selection: Gtk.SelectionModel, positions: number[]): boolean {
182
+ const { state } = context;
183
+ state.isApplying = true;
184
+
185
+ try {
186
+ const selected = applySelection(selection, positions);
187
+
188
+ return selection.getSelection().equals(selected);
189
+ } finally {
190
+ state.isApplying = false;
191
+ }
192
+ }
193
+
141
194
  function applyControlledSelection(context: SelectionContext, selectedIds: string[] | null | undefined): void {
142
195
  const { selection, collection } = context;
143
196
 
@@ -145,37 +198,46 @@ function applyControlledSelection(context: SelectionContext, selectedIds: string
145
198
  return;
146
199
  }
147
200
 
148
- const planned = plannedPositions(context, selectedIds ?? []);
201
+ const planned = plannedSelection(context, selectedIds ?? []);
149
202
 
150
- if (planned !== null) {
151
- applySelection(selection, planned);
203
+ if (planned !== null && didApplyPlanned(context, selection, planned.positions)) {
204
+ reportSelection(context, planned.ids);
205
+
206
+ return;
152
207
  }
153
208
 
154
- reportSelection(context, idsAt(collection, selectedPositions(selection)));
209
+ reportSelection(context, collection.idsAt(selectedPositions(selection)));
155
210
  }
156
211
 
157
- function observeSelection(
212
+ function reportDrift(
158
213
  context: SelectionContext,
159
214
  selectedIds: string[] | null | undefined,
215
+ positions: number[],
160
216
  onDrift: () => void,
161
217
  ): void {
162
- const { selection, collection } = context;
218
+ const planned = plannedSelection(context, selectedIds ?? []);
163
219
 
164
- if (selection === null) {
165
- return;
220
+ if (planned !== null && hasSelectionDrifted(positions, planned.positions)) {
221
+ onDrift();
166
222
  }
223
+ }
167
224
 
168
- const positions = selectedPositions(selection);
169
- reportSelection(context, idsAt(collection, positions));
225
+ function observeSelection(
226
+ context: SelectionContext,
227
+ selectedIds: string[] | null | undefined,
228
+ onDrift: () => void,
229
+ ): void {
230
+ const { selection, collection, state } = context;
170
231
 
171
- if (!isCollectionIdle(collection)) {
232
+ if (selection === null || state.isApplying) {
172
233
  return;
173
234
  }
174
235
 
175
- const planned = plannedPositions(context, selectedIds ?? []);
236
+ const positions = selectedPositions(selection);
237
+ reportSelection(context, collection.idsAt(positions));
176
238
 
177
- if (planned !== null && hasSelectionDrifted(positions, planned)) {
178
- onDrift();
239
+ if (isCollectionIdle(collection)) {
240
+ reportDrift(context, selectedIds, positions, onDrift);
179
241
  }
180
242
  }
181
243
 
@@ -183,11 +245,16 @@ function newLastSelection(): LastSelection {
183
245
  return { selection: null, key: null };
184
246
  }
185
247
 
248
+ function newApplyState(): ApplyState {
249
+ return { isApplying: false };
250
+ }
251
+
186
252
  function useSelection(options: SelectionOptions): ReactElement {
187
253
  const { collection, selectedIds, onSelectionChanged, selectionMode } = options;
188
254
  const [selection, setSelection] = useState<Gtk.SelectionModel | null>(null);
189
255
  const [last] = useState<LastSelection>(newLastSelection);
190
- const context: SelectionContext = { selection, collection, last, onSelectionChanged };
256
+ const [state] = useState<ApplyState>(newApplyState);
257
+ const context: SelectionContext = { selection, collection, last, state, onSelectionChanged };
191
258
 
192
259
  const markDrift = useControlledSync({
193
260
  ids: selectedIds,
@@ -14,6 +14,11 @@ type VisibleOrder = {
14
14
  expandedIds: string[];
15
15
  };
16
16
 
17
+ type MatchedRows = {
18
+ positions: number[];
19
+ ids: string[];
20
+ };
21
+
17
22
  type RowVisitor = (row: VisibleRow) => void;
18
23
 
19
24
  type WalkOptions = {
@@ -182,16 +187,38 @@ function buildVisibleOrder(index: CollectionIndex, slots: SlotMap): VisibleOrder
182
187
  return walkVisible({ index, slots });
183
188
  }
184
189
 
185
- function findPositions(index: CollectionIndex, slots: SlotMap, ids: Set<string>): number[] {
186
- const found: number[] = [];
190
+ function findRows(index: CollectionIndex, slots: SlotMap, ids: Set<string>): MatchedRows {
191
+ const positions: number[] = [];
192
+ const found: string[] = [];
187
193
 
188
194
  walkVisible({
189
195
  index,
190
196
  slots,
191
197
  visit: (row) => {
192
- if (ids.has(row.item.id)) {
193
- found.push(row.position);
198
+ if (!ids.has(row.item.id)) {
199
+ return;
194
200
  }
201
+
202
+ positions.push(row.position);
203
+ found.push(row.item.id);
204
+ },
205
+ });
206
+
207
+ return { positions, ids: found };
208
+ }
209
+
210
+ function findIds(index: CollectionIndex, slots: SlotMap, positions: Set<number>): string[] {
211
+ const found: string[] = [];
212
+
213
+ walkVisible({
214
+ index,
215
+ slots,
216
+ visit: (row) => {
217
+ if (!positions.has(row.position)) {
218
+ return;
219
+ }
220
+
221
+ found.push(row.item.id);
195
222
  },
196
223
  });
197
224
 
@@ -201,8 +228,10 @@ function findPositions(index: CollectionIndex, slots: SlotMap, ids: Set<string>)
201
228
  export {
202
229
  buildVisibleOrder,
203
230
  expandedPathsFor,
204
- findPositions,
231
+ findIds,
232
+ findRows,
205
233
  walkVisible,
234
+ type MatchedRows,
206
235
  type VisibleOrder,
207
236
  type WalkOptions,
208
237
  };