From c6ab4bd32ea3c16dd76edff14d9592b5c6fd9c88 Mon Sep 17 00:00:00 2001 From: "Saulius.Skliutas" <24278440+saskliutas@users.noreply.github.com> Date: Tue, 9 Jun 2026 18:05:12 +0300 Subject: [PATCH 1/5] Expose event source name that is used when selection storage is cleared --- packages/unified-selection/api/unified-selection.api.md | 3 +++ .../unified-selection/api/unified-selection.exports.csv | 1 + packages/unified-selection/src/unified-selection.ts | 1 + .../src/unified-selection/IModelHiliteSetProvider.ts | 4 ++-- .../src/unified-selection/SelectionStorage.ts | 9 ++++++--- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/unified-selection/api/unified-selection.api.md b/packages/unified-selection/api/unified-selection.api.md index c4646820a..382c4aa2b 100644 --- a/packages/unified-selection/api/unified-selection.api.md +++ b/packages/unified-selection/api/unified-selection.api.md @@ -28,6 +28,9 @@ interface CachingHiliteSetProviderProps { selectionStorage: SelectionStorage; } +// @public +export const CLEAR_SELECTION_STORAGE_SOURCE = "Unified selection storage: clear"; + // @public export function computeSelection(props: ComputeSelectionProps): AsyncIterableIterator; diff --git a/packages/unified-selection/api/unified-selection.exports.csv b/packages/unified-selection/api/unified-selection.exports.csv index ede0322fa..bef9d1632 100644 --- a/packages/unified-selection/api/unified-selection.exports.csv +++ b/packages/unified-selection/api/unified-selection.exports.csv @@ -2,6 +2,7 @@ sep=; Release Tag;API Item Type;API Item Name public;interface;CachingHiliteSetProvider deprecated;interface;CachingHiliteSetProvider +public;const;CLEAR_SELECTION_STORAGE_SOURCE public;function;computeSelection public;function;createCachingHiliteSetProvider deprecated;function;createCachingHiliteSetProvider diff --git a/packages/unified-selection/src/unified-selection.ts b/packages/unified-selection/src/unified-selection.ts index c60401f34..078614dbc 100644 --- a/packages/unified-selection/src/unified-selection.ts +++ b/packages/unified-selection/src/unified-selection.ts @@ -27,3 +27,4 @@ export { StorageSelectionChangeEventArgs, StorageSelectionChangesListener, } from "./unified-selection/SelectionChangeEvent.js"; +export { CLEAR_SELECTION_STORAGE_SOURCE } from "./unified-selection/SelectionStorage.js"; diff --git a/packages/unified-selection/src/unified-selection/IModelHiliteSetProvider.ts b/packages/unified-selection/src/unified-selection/IModelHiliteSetProvider.ts index d7d08ad29..e9e4cc8a6 100644 --- a/packages/unified-selection/src/unified-selection/IModelHiliteSetProvider.ts +++ b/packages/unified-selection/src/unified-selection/IModelHiliteSetProvider.ts @@ -10,7 +10,7 @@ import { eachValueFrom } from "rxjs-for-await"; import { ECClassHierarchyInspector, ECSqlQueryExecutor } from "@itwin/presentation-shared"; import { createHiliteSetProvider, HiliteSet, HiliteSetProvider } from "./HiliteSetProvider.js"; import { StorageSelectionChangeEventArgs } from "./SelectionChangeEvent.js"; -import { IMODEL_CLOSE_SELECTION_CLEAR_SOURCE, SelectionStorage } from "./SelectionStorage.js"; +import { CLEAR_SELECTION_STORAGE_SOURCE, SelectionStorage } from "./SelectionStorage.js"; /** * Props for creating an `IModelHiliteSetProvider` instance. @@ -81,7 +81,7 @@ class IModelHiliteSetProviderImpl implements IModelHiliteSetProvider { this._removeListener = this._selectionStorage.selectionChangeEvent.addListener( (args: StorageSelectionChangeEventArgs) => { this._cache.delete(args.imodelKey); - if (args.changeType === "clear" && args.source === IMODEL_CLOSE_SELECTION_CLEAR_SOURCE) { + if (args.changeType === "clear" && args.source === CLEAR_SELECTION_STORAGE_SOURCE) { this._hiliteSetProviders.delete(args.imodelKey); } }, diff --git a/packages/unified-selection/src/unified-selection/SelectionStorage.ts b/packages/unified-selection/src/unified-selection/SelectionStorage.ts index 98286d243..6714ac3a8 100644 --- a/packages/unified-selection/src/unified-selection/SelectionStorage.ts +++ b/packages/unified-selection/src/unified-selection/SelectionStorage.ts @@ -131,8 +131,11 @@ export function createStorage(): SelectionStorage { return new SelectionStorageImpl(); } -/** @internal */ -export const IMODEL_CLOSE_SELECTION_CLEAR_SOURCE = "Unified selection storage: clear"; +/** + * The source name used when selection change is caused by clearing the selection storage. Used when change is performed by `clearStorage`. + * @public + */ +export const CLEAR_SELECTION_STORAGE_SOURCE = "Unified selection storage: clear"; class SelectionStorageImpl implements SelectionStorage { private _storage = new Map(); @@ -170,7 +173,7 @@ class SelectionStorageImpl implements SelectionStorage { public clearStorage(props: IModelKeyProp): void { const imodelKey = getIModelKey(props); - this.clearSelection({ source: IMODEL_CLOSE_SELECTION_CLEAR_SOURCE, imodelKey }); + this.clearSelection({ source: CLEAR_SELECTION_STORAGE_SOURCE, imodelKey }); this._storage.delete(imodelKey); } From c521149e124ffb5d997b03026e646cf3e6e0ea96 Mon Sep 17 00:00:00 2001 From: "Saulius.Skliutas" <24278440+saskliutas@users.noreply.github.com> Date: Tue, 9 Jun 2026 18:13:20 +0300 Subject: [PATCH 2/5] change --- .changeset/good-ghosts-dress.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .changeset/good-ghosts-dress.md diff --git a/.changeset/good-ghosts-dress.md b/.changeset/good-ghosts-dress.md new file mode 100644 index 000000000..bde99f82c --- /dev/null +++ b/.changeset/good-ghosts-dress.md @@ -0,0 +1,17 @@ +--- +"@itwin/unified-selection": minor +--- + +Expose `CLEAR_SELECTION_STORAGE_SOURCE` constant that is used as selection change event source when selection storage is cleared. This should allow consumers to detect when selection change happened due to selection storage being cleared. + +```ts +import { createStorage, CLEAR_SELECTION_STORAGE_SOURCE } from "@itwin/unified-selection"; + +const storage = createStorage(); +storage.selectionChangeEvent.addListener((args) => { + if (args.source === CLEAR_SELECTION_STORAGE_SOURCE) { + // ignore change if it was caused by clearing selection storage + } + // handle selection change +}) +``` From cc0433d287f4dd9744ceaffc4222c4826a05154b Mon Sep 17 00:00:00 2001 From: Saulius Skliutas <24278440+saskliutas@users.noreply.github.com> Date: Wed, 10 Jun 2026 09:44:41 +0300 Subject: [PATCH 3/5] Update .changeset/good-ghosts-dress.md Co-authored-by: Grigas <35135765+grigasp@users.noreply.github.com> --- .changeset/good-ghosts-dress.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.changeset/good-ghosts-dress.md b/.changeset/good-ghosts-dress.md index bde99f82c..20db85481 100644 --- a/.changeset/good-ghosts-dress.md +++ b/.changeset/good-ghosts-dress.md @@ -13,5 +13,8 @@ storage.selectionChangeEvent.addListener((args) => { // ignore change if it was caused by clearing selection storage } // handle selection change -}) +}); + +// this will trigger the `selectionChangeEvent` with `{ source: CLEAR_SELECTION_STORAGE_SOURCE }` +storage.clearStorage({ imodelKey: imodel.key }); ``` From 4011ed31a70009cc03b97c9a32ac5dbadb5f1516 Mon Sep 17 00:00:00 2001 From: "Saulius.Skliutas" <24278440+saskliutas@users.noreply.github.com> Date: Wed, 10 Jun 2026 09:48:35 +0300 Subject: [PATCH 4/5] test(unified-selection): assert clearStorage emits event with CLEAR_SELECTION_STORAGE_SOURCE Add assertion to verify clearStorage triggers selectionChangeEvent with the correct source constant, preventing accidental regressions if the source string changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../unified-selection/src/test/SelectionStorage.test.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/unified-selection/src/test/SelectionStorage.test.ts b/packages/unified-selection/src/test/SelectionStorage.test.ts index 72bf02a7f..1c1cc7eb4 100644 --- a/packages/unified-selection/src/test/SelectionStorage.test.ts +++ b/packages/unified-selection/src/test/SelectionStorage.test.ts @@ -5,7 +5,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { SelectableInstanceKey, Selectables } from "../unified-selection/Selectable.js"; -import { createStorage, SelectionStorage } from "../unified-selection/SelectionStorage.js"; +import { CLEAR_SELECTION_STORAGE_SOURCE, createStorage, SelectionStorage } from "../unified-selection/SelectionStorage.js"; import { createSelectableInstanceKey } from "./_helpers/SelectablesCreator.js"; const generateSelection = (): SelectableInstanceKey[] => { @@ -36,6 +36,12 @@ describe("SelectionStorage", () => { selectionStorage.clearStorage({ imodelKey }); expect(Selectables.isEmpty(selectionStorage.getSelection({ imodelKey }))).toBe(true); expect(listenerSpy, "Expected selectionChange.onSelectionChange to be called").toHaveBeenCalledOnce(); + expect(listenerSpy).toHaveBeenCalledWith( + expect.objectContaining({ + source: CLEAR_SELECTION_STORAGE_SOURCE, + }), + selectionStorage, + ); }); }); From 807dc8b5dc68b806fbc88e5eca43ab89bf606cbf Mon Sep 17 00:00:00 2001 From: "Saulius.Skliutas" <24278440+saskliutas@users.noreply.github.com> Date: Wed, 10 Jun 2026 09:50:43 +0300 Subject: [PATCH 5/5] Format --- .../src/test/SelectionStorage.test.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/unified-selection/src/test/SelectionStorage.test.ts b/packages/unified-selection/src/test/SelectionStorage.test.ts index 1c1cc7eb4..16cea8832 100644 --- a/packages/unified-selection/src/test/SelectionStorage.test.ts +++ b/packages/unified-selection/src/test/SelectionStorage.test.ts @@ -5,7 +5,11 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { SelectableInstanceKey, Selectables } from "../unified-selection/Selectable.js"; -import { CLEAR_SELECTION_STORAGE_SOURCE, createStorage, SelectionStorage } from "../unified-selection/SelectionStorage.js"; +import { + CLEAR_SELECTION_STORAGE_SOURCE, + createStorage, + SelectionStorage, +} from "../unified-selection/SelectionStorage.js"; import { createSelectableInstanceKey } from "./_helpers/SelectablesCreator.js"; const generateSelection = (): SelectableInstanceKey[] => { @@ -37,9 +41,7 @@ describe("SelectionStorage", () => { expect(Selectables.isEmpty(selectionStorage.getSelection({ imodelKey }))).toBe(true); expect(listenerSpy, "Expected selectionChange.onSelectionChange to be called").toHaveBeenCalledOnce(); expect(listenerSpy).toHaveBeenCalledWith( - expect.objectContaining({ - source: CLEAR_SELECTION_STORAGE_SOURCE, - }), + expect.objectContaining({ source: CLEAR_SELECTION_STORAGE_SOURCE }), selectionStorage, ); });