New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@itwin/unified-selection

Package Overview
Dependencies
Maintainers
0
Versions
20
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@itwin/unified-selection - npm Package Compare versions

Comparing version 1.1.2 to 1.2.0

31

CHANGELOG.md
# @itwin/unified-selection
## 1.2.0
### Minor Changes
- [#800](https://github.com/iTwin/presentation/pull/800): Add support for Models and SubCategories selection that's going to be available in `@itwin/core-frontend` version `5`.
The changes in `@itwin/core-frontend` allow us to stop manually syncing `HiliteSet` with `SelectionSet` and rely on automatic syncing instead.
- [#800](https://github.com/iTwin/presentation/pull/800): `computeSelection`: Broadened the type of `elementIds` prop from `Id64String[]` to `Id64Arg`.
- [#802](https://github.com/iTwin/presentation/pull/802): Prefer `Symbol.dispose` over `dispose` for disposable objects.
The package contained a number of types for disposable objects, that had a requirement of `dispose` method being called on them after they are no longer needed. In conjunction with the `using` utility from `@itwin/core-bentley`, usage of such objects looked like this:
```ts
class MyDisposable() {
dispose() {
// do some cleanup
}
}
using(new MyDisposable(), (obj) => {
// do something with obj, it'll get disposed when the callback returns
});
```
In version `5.2`, TypeScript [introduced](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#using-declarations-and-explicit-resource-management) `Disposable` type and `using` declarations (from the upcoming [Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management) feature in ECMAScript). Now we're making use of those new utilities in this package (while still supporting the old `dispose` method), which allows using `MyDisposable` from the above snippet like this:
```ts
using obj = new MyDisposable();
// do something with obj, it'll get disposed when it goes out of scope
```
## 1.1.2

@@ -4,0 +35,0 @@

16

lib/cjs/unified-selection/CachingHiliteSetProvider.d.ts

@@ -29,3 +29,13 @@ import { ECClassHierarchyInspector, ECSqlQueryExecutor } from "@itwin/presentation-shared";

}): AsyncIterableIterator<HiliteSet>;
/** Disposes the cache. */
/**
* Disposes the cache.
*
* Optional to avoid breaking the API. Will be made required when the deprecated
* `dispose` is removed.
*/
[Symbol.dispose]?: () => void;
/**
* Disposes the cache.
* @deprecated in 1.2. Use `[Symbol.dispose]` instead.
*/
dispose(): void;

@@ -38,3 +48,5 @@ }

*/
export declare function createCachingHiliteSetProvider(props: CachingHiliteSetProviderProps): CachingHiliteSetProvider;
export declare function createCachingHiliteSetProvider(props: CachingHiliteSetProviderProps): CachingHiliteSetProvider & {
[Symbol.dispose]: () => void;
};
//# sourceMappingURL=CachingHiliteSetProvider.d.ts.map

@@ -47,3 +47,3 @@ "use strict";

}
dispose() {
[Symbol.dispose]() {
this._removeListener();

@@ -53,2 +53,6 @@ this._hiliteSetProviders = new Map();

}
/* c8 ignore next 3 */
dispose() {
this[Symbol.dispose]();
}
getHiliteSetProvider(imodelKey, imodelAccess) {

@@ -55,0 +59,0 @@ let provider = this._hiliteSetProviders.get(imodelKey);

18

lib/cjs/unified-selection/EnableUnifiedSelectionSyncWithIModel.d.ts

@@ -49,4 +49,9 @@ import { ECClassHierarchyInspector, ECSqlQueryExecutor } from "@itwin/presentation-shared";

* to reuse the cache and avoid creating new providers for each iModel.
*
* The type is defined in a way that makes it required for provider to have either the deprecated `dispose` or the
* new `Symbol.dispose` method.
*/
cachingHiliteSetProvider?: CachingHiliteSetProvider;
cachingHiliteSetProvider?: CachingHiliteSetProvider | (Omit<CachingHiliteSetProvider, "dispose"> & {
[Symbol.dispose]: () => void;
});
}

@@ -73,10 +78,10 @@ /**

private _cancelOngoingChanges;
private _unifiedSelectionListenerDisposeFunc;
private _imodelListenerDisposeFunc;
private _unregisterUnifiedSelectionListener;
private _unregisterIModelSelectionSetListener;
private _hasCustomCachingHiliteSetProvider;
constructor(props: EnableUnifiedSelectionSyncWithIModelProps);
dispose(): void;
/** Temporarily suspends tool selection synchronization until the returned `IDisposable` is disposed. */
[Symbol.dispose](): void;
/** Temporarily suspends tool selection synchronization until the returned disposable object is disposed. */
suspendIModelToolSelectionSync(): {
dispose: () => boolean;
[Symbol.dispose]: () => void;
};

@@ -91,4 +96,3 @@ private handleUnifiedSelectionChange;

private getSelectionSetChangeIds;
private idArgToIds;
}
//# sourceMappingURL=EnableUnifiedSelectionSyncWithIModel.d.ts.map

@@ -6,2 +6,54 @@ "use strict";

*--------------------------------------------------------------------------------------------*/
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
if (value !== null && value !== void 0) {
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
var dispose, inner;
if (async) {
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
dispose = value[Symbol.asyncDispose];
}
if (dispose === void 0) {
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
dispose = value[Symbol.dispose];
if (async) inner = dispose;
}
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
env.stack.push({ value: value, dispose: dispose, async: async });
}
else if (async) {
env.stack.push({ async: true });
}
return value;
};
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
return function (env) {
function fail(e) {
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
env.hasError = true;
}
var r, s = 0;
function next() {
while (r = env.stack.pop()) {
try {
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
if (r.dispose) {
var result = r.dispose.call(r.value);
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
}
else s |= 1;
}
catch (e) {
fail(e);
}
}
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
if (env.hasError) throw env.error;
}
return next();
};
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
});
Object.defineProperty(exports, "__esModule", { value: true });

@@ -11,3 +63,2 @@ exports.IModelSelectionHandler = void 0;

const rxjs_1 = require("rxjs");
const core_bentley_1 = require("@itwin/core-bentley");
const CachingHiliteSetProvider_js_1 = require("./CachingHiliteSetProvider.js");

@@ -17,2 +68,3 @@ const HiliteSetProvider_js_1 = require("./HiliteSetProvider.js");

const IModel_js_1 = require("./types/IModel.js");
const Utils_js_1 = require("./Utils.js");
/**

@@ -25,3 +77,3 @@ * Enables synchronization between iModel selection and unified selection.

const selectionHandler = new IModelSelectionHandler(props);
return () => selectionHandler.dispose();
return () => selectionHandler[Symbol.dispose]();
}

@@ -42,4 +94,4 @@ /**

_cancelOngoingChanges = new rxjs_1.Subject();
_unifiedSelectionListenerDisposeFunc;
_imodelListenerDisposeFunc;
_unregisterUnifiedSelectionListener;
_unregisterIModelSelectionSetListener;
_hasCustomCachingHiliteSetProvider;

@@ -59,17 +111,15 @@ constructor(props) {

this._hiliteSetProvider = (0, HiliteSetProvider_js_1.createHiliteSetProvider)({ imodelAccess: this._imodelAccess });
this._imodelListenerDisposeFunc = this._imodelAccess.selectionSet.onChanged.addListener(this.onIModelSelectionChanged);
this._unifiedSelectionListenerDisposeFunc = this._selectionStorage.selectionChangeEvent.addListener(this.onUnifiedSelectionChanged);
// stop imodel from syncing tool selection with hilited list - we want to override that behavior
this._imodelAccess.hiliteSet.wantSyncWithSelectionSet = false;
this.applyCurrentHiliteSet({ activeSelectionAction: "clearAll" });
this._unregisterIModelSelectionSetListener = this._imodelAccess.selectionSet.onChanged.addListener(this.onIModelSelectionChanged);
this._unregisterUnifiedSelectionListener = this._selectionStorage.selectionChangeEvent.addListener(this.onUnifiedSelectionChanged);
this.applyCurrentHiliteSet({ activeSelectionAction: "clear" });
}
dispose() {
[Symbol.dispose]() {
this._cancelOngoingChanges.next();
this._imodelListenerDisposeFunc();
this._unifiedSelectionListenerDisposeFunc();
this._unregisterIModelSelectionSetListener();
this._unregisterUnifiedSelectionListener();
if (!this._hasCustomCachingHiliteSetProvider) {
this._cachingHiliteSetProvider.dispose();
(0, Utils_js_1.safeDispose)(this._cachingHiliteSetProvider);
}
}
/** Temporarily suspends tool selection synchronization until the returned `IDisposable` is disposed. */
/** Temporarily suspends tool selection synchronization until the returned disposable object is disposed. */
suspendIModelToolSelectionSync() {

@@ -79,30 +129,32 @@ const wasSuspended = this._isSuspended;

return {
dispose: () => (this._isSuspended = wasSuspended),
[Symbol.dispose]: () => {
this._isSuspended = wasSuspended;
},
};
}
handleUnifiedSelectionChange(changeType, selectables, source) {
if (changeType === "clear" || changeType === "replace") {
this.applyCurrentHiliteSet({ activeSelectionAction: changeType === "replace" && source === "Tool" ? "clearHilited" : "clearAll" });
return;
switch (changeType) {
case "clear":
case "replace":
return this.applyCurrentHiliteSet({ activeSelectionAction: source === "Tool" ? "keep" : "clear" });
case "add":
return void (0, rxjs_1.from)(this._hiliteSetProvider.getHiliteSet({ selectables }))
.pipe((0, rxjs_1.takeUntil)(this._cancelOngoingChanges))
.subscribe({
next: (set) => {
this.addHiliteSet(set);
},
});
case "remove":
return void (0, rxjs_1.from)(this._hiliteSetProvider.getHiliteSet({ selectables }))
.pipe((0, rxjs_1.takeUntil)(this._cancelOngoingChanges))
.subscribe({
next: (set) => {
this.removeHiliteSet(set);
},
complete: () => {
this.applyCurrentHiliteSet({ activeSelectionAction: "keep" });
},
});
}
if (changeType === "add") {
(0, rxjs_1.from)(this._hiliteSetProvider.getHiliteSet({ selectables }))
.pipe((0, rxjs_1.takeUntil)(this._cancelOngoingChanges))
.subscribe({
next: (set) => {
this.addHiliteSet(set);
},
});
return;
}
(0, rxjs_1.from)(this._hiliteSetProvider.getHiliteSet({ selectables }))
.pipe((0, rxjs_1.takeUntil)(this._cancelOngoingChanges))
.subscribe({
next: (set) => {
this.removeHiliteSet(set);
},
complete: () => {
this.applyCurrentHiliteSet({ activeSelectionAction: "keep" });
},
});
}

@@ -120,9 +172,16 @@ onUnifiedSelectionChanged = (args) => {

applyCurrentHiliteSet({ activeSelectionAction }) {
if (activeSelectionAction !== "keep") {
(0, core_bentley_1.using)(this.suspendIModelToolSelectionSync(), (_) => {
if (activeSelectionAction === "clear") {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
const _dispose = __addDisposableResource(env_1, this.suspendIModelToolSelectionSync(), false);
this._imodelAccess.hiliteSet.clear();
if (activeSelectionAction === "clearAll") {
this._imodelAccess.selectionSet.emptyAll();
}
});
this._imodelAccess.selectionSet.emptyAll();
}
catch (e_1) {
env_1.error = e_1;
env_1.hasError = true;
}
finally {
__disposeResources(env_1);
}
}

@@ -138,28 +197,67 @@ (0, rxjs_1.from)(this._cachingHiliteSetProvider.getHiliteSet({ imodelKey: this._imodelAccess.key }))

addHiliteSet(set) {
(0, core_bentley_1.using)(this.suspendIModelToolSelectionSync(), (_) => {
if (set.models && set.models.length) {
this._imodelAccess.hiliteSet.models.addIds(set.models);
const env_2 = { stack: [], error: void 0, hasError: false };
try {
const _dispose = __addDisposableResource(env_2, this.suspendIModelToolSelectionSync(), false);
if ("active" in this._imodelAccess.selectionSet) {
// the `active` property tells us we're using 5.0 core, which supports models and subcategories
// in selection set - we can simply add the set as a whole
this._imodelAccess.selectionSet.add({
models: set.models,
subcategories: set.subCategories,
elements: set.elements,
});
}
if (set.subCategories && set.subCategories.length) {
this._imodelAccess.hiliteSet.subcategories.addIds(set.subCategories);
else {
// pre-5.0 core requires adding models and subcategories to hilite set separately
if (set.models.length) {
this._imodelAccess.hiliteSet.models.addIds(set.models);
}
if (set.subCategories.length) {
this._imodelAccess.hiliteSet.subcategories.addIds(set.subCategories);
}
if (set.elements.length) {
this._imodelAccess.selectionSet.add(set.elements);
}
}
if (set.elements && set.elements.length) {
this._imodelAccess.hiliteSet.elements.addIds(set.elements);
this._imodelAccess.selectionSet.add(set.elements);
}
});
}
catch (e_2) {
env_2.error = e_2;
env_2.hasError = true;
}
finally {
__disposeResources(env_2);
}
}
removeHiliteSet(set) {
(0, core_bentley_1.using)(this.suspendIModelToolSelectionSync(), (_) => {
if (set.models.length) {
this._imodelAccess.hiliteSet.models.deleteIds(set.models);
const env_3 = { stack: [], error: void 0, hasError: false };
try {
const _dispose = __addDisposableResource(env_3, this.suspendIModelToolSelectionSync(), false);
if ("active" in this._imodelAccess.selectionSet) {
// the `active` property tells us we're using 5.0 core, which supports models and subcategories
// in selection set - we can simply remove the set as a whole
this._imodelAccess.selectionSet.remove({
models: set.models,
subcategories: set.subCategories,
elements: set.elements,
});
}
if (set.subCategories.length) {
this._imodelAccess.hiliteSet.subcategories.deleteIds(set.subCategories);
else {
if (set.models.length) {
this._imodelAccess.hiliteSet.models.deleteIds(set.models);
}
if (set.subCategories.length) {
this._imodelAccess.hiliteSet.subcategories.deleteIds(set.subCategories);
}
if (set.elements.length) {
this._imodelAccess.selectionSet.remove(set.elements);
}
}
if (set.elements.length) {
this._imodelAccess.hiliteSet.elements.deleteIds(set.elements);
this._imodelAccess.selectionSet.remove(set.elements);
}
});
}
catch (e_3) {
env_3.error = e_3;
env_3.hasError = true;
}
finally {
__disposeResources(env_3);
}
}

@@ -174,11 +272,10 @@ onIModelSelectionChanged = async (event) => {

}
const elementIds = this.getSelectionSetChangeIds(event);
const scopedSelection = (0, SelectionScope_js_1.computeSelection)({ queryExecutor: this._imodelAccess, elementIds, scope: this._activeScopeProvider() });
const ids = this.getSelectionSetChangeIds(event);
const scopedSelection = (0, rxjs_1.merge)(ids.elements
? (0, rxjs_1.from)((0, SelectionScope_js_1.computeSelection)({ queryExecutor: this._imodelAccess, elementIds: ids.elements, scope: this._activeScopeProvider() }))
: /* c8 ignore next */ rxjs_1.EMPTY, ids.models ? (0, rxjs_1.from)(ids.models).pipe((0, rxjs_1.map)((id) => ({ className: "BisCore.Model", id }))) : /* c8 ignore next */ rxjs_1.EMPTY, ids.subcategories ? (0, rxjs_1.from)(ids.subcategories).pipe((0, rxjs_1.map)((id) => ({ className: "BisCore.SubCategory", id }))) : /* c8 ignore next */ rxjs_1.EMPTY);
await this.handleIModelSelectionChange(event.type, scopedSelection);
};
async handleIModelSelectionChange(type, iterator) {
const selectables = [];
for await (const selectable of iterator) {
selectables.push(selectable);
}
async handleIModelSelectionChange(type, keys) {
const selectables = await (0, rxjs_1.firstValueFrom)(keys.pipe((0, rxjs_1.toArray)()));
const props = {

@@ -201,21 +298,11 @@ imodelKey: this._imodelAccess.key,

case IModel_js_1.CoreSelectionSetEventType.Add:
return event.additions ?? (event.added ? { elements: event.added } : /* c8 ignore next */ {});
case IModel_js_1.CoreSelectionSetEventType.Remove:
return event.removals ?? (event.removed ? { elements: event.removed } : /* c8 ignore next */ {});
case IModel_js_1.CoreSelectionSetEventType.Replace:
(0, core_bentley_1.assert)(!!event.added);
return this.idArgToIds(event.added);
default:
(0, core_bentley_1.assert)(!!event.removed);
return this.idArgToIds(event.removed);
return "active" in event.set ? event.set.active : { elements: event.set.elements };
}
}
idArgToIds(ids) {
if (typeof ids === "string") {
return [ids];
}
if (ids instanceof Set) {
return [...ids];
}
return ids;
}
}
exports.IModelSelectionHandler = IModelSelectionHandler;
//# sourceMappingURL=EnableUnifiedSelectionSyncWithIModel.js.map

@@ -0,1 +1,2 @@

import { Id64Arg } from "@itwin/core-bentley";
import { ECSqlQueryExecutor } from "@itwin/presentation-shared";

@@ -33,3 +34,3 @@ import { SelectableInstanceKey } from "./Selectable.js";

/** IDs of elements to compute selection for. */
elementIds: string[];
elementIds: Id64Arg;
/** Selection scope to compute selection with. */

@@ -36,0 +37,0 @@ scope: ElementSelectionScopeProps | {

@@ -20,15 +20,20 @@ "use strict";

}
const nonTransientKeys = elementIds.filter((key) => !core_bentley_1.Id64.isTransient(key));
const nonTransientIds = [];
for (const id of core_bentley_1.Id64.iterable(elementIds)) {
if (!core_bentley_1.Id64.isTransient(id)) {
nonTransientIds.push(id);
}
}
switch (scope.id) {
case "element":
yield* computeElementSelection(queryExecutor, nonTransientKeys, scope.ancestorLevel ?? 0);
yield* computeElementSelection(queryExecutor, nonTransientIds, scope.ancestorLevel ?? 0);
return;
case "category":
yield* computeCategorySelection(queryExecutor, nonTransientKeys);
yield* computeCategorySelection(queryExecutor, nonTransientIds);
return;
case "model":
yield* computeModelSelection(queryExecutor, nonTransientKeys);
yield* computeModelSelection(queryExecutor, nonTransientIds);
return;
case "functional":
yield* computeFunctionalElementSelection(queryExecutor, nonTransientKeys, scope.ancestorLevel ?? 0);
yield* computeFunctionalElementSelection(queryExecutor, nonTransientIds, scope.ancestorLevel ?? 0);
return;

@@ -35,0 +40,0 @@ }

@@ -1,4 +0,18 @@

import { Id64Arg } from "@itwin/core-bentley";
import { Id64Arg, Id64Set } from "@itwin/core-bentley";
import { Event } from "@itwin/presentation-shared";
/**
* A collection of geometric element, model and subcategory ids that can be added to
* a [[SelectionSet]] or [[HiliteSet]].
*
* **Warning:** This type was added to `@itwin/core-frontend` in 5.0.
*
* @see https://www.itwinjs.org/reference/core-frontend/selectionset/selectableids/
* @public
*/
export interface CoreSelectableIds {
elements?: Id64Arg;
models?: Id64Arg;
subcategories?: Id64Arg;
}
/**
* Identifies the type of changes made to the `CoreIModelSelectionSet` to produce a `CoreSelectionSetEvent`.

@@ -34,4 +48,16 @@ * @see https://www.itwinjs.org/reference/core-frontend/selectionset/selectionseteventtype/

added?: Id64Arg;
/**
* A collection of geometric element, model and subcategory ids that have been removed from selection set.
*
* **Warning:** This property was added to `@itwin/core-frontend` in 5.0.
*/
additions?: CoreSelectableIds;
/** The element Ids removed from the set. */
removed?: Id64Arg;
/**
* A collection of geometric element, model and subcategory ids that have been added to selection set.
*
* **Warning:** This property was added to `@itwin/core-frontend` in 5.0.
*/
removals?: CoreSelectableIds;
/** The affected `CoreIModelSelectionSet`. */

@@ -45,3 +71,3 @@ set: CoreIModelSelectionSet;

*/
export interface CoreIModelSelectionSet {
export type CoreIModelSelectionSet = {
/** Event called whenever elements are added or removed from this SelectionSet. */

@@ -53,2 +79,3 @@ onChanged: Event<(ev: CoreSelectionSetEventUnsafe) => void>;

emptyAll(): void;
} & ({
/** Add one or more Ids to the current selection set. */

@@ -58,3 +85,24 @@ add(elem: Id64Arg): boolean;

remove(elem: Id64Arg): boolean;
}
} | {
/**
* Get the active selection as a collection of geometric element, model and subcategory ids.
*/
readonly active: {
[P in keyof CoreSelectableIds]-?: Id64Set;
};
/**
* Adds a collection of geometric element, model and subcategory ids to this selection set.
*
* The overload was added only in 5.0 `@itwin/core-frontend`. The `active` property can be used
* to check if this `overload` is available.
*/
add: (ids: Id64Arg | CoreSelectableIds) => boolean;
/**
* Adds a collection of geometric element, model and subcategory ids to this selection set.
*
* The overload was added only in 5.0 `@itwin/core-frontend`. The `active` property can be used
* to check if this `overload` is available.
*/
remove: (ids: Id64Arg | CoreSelectableIds) => boolean;
});
/**

@@ -61,0 +109,0 @@ * A set if ID's optimized for performance-critical code which represents large sets of ID's as pairs of 32-bit integers.

@@ -18,2 +18,16 @@ import { Observable } from "rxjs";

export declare function releaseMainThreadOnItemsCount<T>(elementCount: number): (obs: Observable<T>) => Observable<T>;
/**
* A helper that disposes the given object, if it's disposable.
*
* The first option is to dispose using the deprecated `dispose` method if it exists on the object.
* If not, we use the new `Symbol.dispose` method. If that doesn't exist either, the object is
* considered as non-disposable and nothing is done with it.
*
* @internal
*/
export declare function safeDispose(disposable: {} | {
[Symbol.dispose]: () => void;
} | {
dispose: () => void;
}): void;
//# sourceMappingURL=Utils.d.ts.map

@@ -10,2 +10,3 @@ "use strict";

exports.releaseMainThreadOnItemsCount = releaseMainThreadOnItemsCount;
exports.safeDispose = safeDispose;
const rxjs_1 = require("rxjs");

@@ -55,2 +56,19 @@ const presentation_shared_1 = require("@itwin/presentation-shared");

}
/**
* A helper that disposes the given object, if it's disposable.
*
* The first option is to dispose using the deprecated `dispose` method if it exists on the object.
* If not, we use the new `Symbol.dispose` method. If that doesn't exist either, the object is
* considered as non-disposable and nothing is done with it.
*
* @internal
*/
function safeDispose(disposable) {
if ("dispose" in disposable) {
disposable.dispose();
}
else if (Symbol.dispose in disposable) {
disposable[Symbol.dispose]();
}
}
//# sourceMappingURL=Utils.js.map

@@ -29,3 +29,13 @@ import { ECClassHierarchyInspector, ECSqlQueryExecutor } from "@itwin/presentation-shared";

}): AsyncIterableIterator<HiliteSet>;
/** Disposes the cache. */
/**
* Disposes the cache.
*
* Optional to avoid breaking the API. Will be made required when the deprecated
* `dispose` is removed.
*/
[Symbol.dispose]?: () => void;
/**
* Disposes the cache.
* @deprecated in 1.2. Use `[Symbol.dispose]` instead.
*/
dispose(): void;

@@ -38,3 +48,5 @@ }

*/
export declare function createCachingHiliteSetProvider(props: CachingHiliteSetProviderProps): CachingHiliteSetProvider;
export declare function createCachingHiliteSetProvider(props: CachingHiliteSetProviderProps): CachingHiliteSetProvider & {
[Symbol.dispose]: () => void;
};
//# sourceMappingURL=CachingHiliteSetProvider.d.ts.map

@@ -44,3 +44,3 @@ /*---------------------------------------------------------------------------------------------

}
dispose() {
[Symbol.dispose]() {
this._removeListener();

@@ -50,2 +50,6 @@ this._hiliteSetProviders = new Map();

}
/* c8 ignore next 3 */
dispose() {
this[Symbol.dispose]();
}
getHiliteSetProvider(imodelKey, imodelAccess) {

@@ -52,0 +56,0 @@ let provider = this._hiliteSetProviders.get(imodelKey);

@@ -49,4 +49,9 @@ import { ECClassHierarchyInspector, ECSqlQueryExecutor } from "@itwin/presentation-shared";

* to reuse the cache and avoid creating new providers for each iModel.
*
* The type is defined in a way that makes it required for provider to have either the deprecated `dispose` or the
* new `Symbol.dispose` method.
*/
cachingHiliteSetProvider?: CachingHiliteSetProvider;
cachingHiliteSetProvider?: CachingHiliteSetProvider | (Omit<CachingHiliteSetProvider, "dispose"> & {
[Symbol.dispose]: () => void;
});
}

@@ -73,10 +78,10 @@ /**

private _cancelOngoingChanges;
private _unifiedSelectionListenerDisposeFunc;
private _imodelListenerDisposeFunc;
private _unregisterUnifiedSelectionListener;
private _unregisterIModelSelectionSetListener;
private _hasCustomCachingHiliteSetProvider;
constructor(props: EnableUnifiedSelectionSyncWithIModelProps);
dispose(): void;
/** Temporarily suspends tool selection synchronization until the returned `IDisposable` is disposed. */
[Symbol.dispose](): void;
/** Temporarily suspends tool selection synchronization until the returned disposable object is disposed. */
suspendIModelToolSelectionSync(): {
dispose: () => boolean;
[Symbol.dispose]: () => void;
};

@@ -91,4 +96,3 @@ private handleUnifiedSelectionChange;

private getSelectionSetChangeIds;
private idArgToIds;
}
//# sourceMappingURL=EnableUnifiedSelectionSyncWithIModel.d.ts.map

@@ -5,4 +5,55 @@ /*---------------------------------------------------------------------------------------------

*--------------------------------------------------------------------------------------------*/
import { from, Subject, takeUntil } from "rxjs";
import { assert, using } from "@itwin/core-bentley";
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
if (value !== null && value !== void 0) {
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
var dispose, inner;
if (async) {
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
dispose = value[Symbol.asyncDispose];
}
if (dispose === void 0) {
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
dispose = value[Symbol.dispose];
if (async) inner = dispose;
}
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
env.stack.push({ value: value, dispose: dispose, async: async });
}
else if (async) {
env.stack.push({ async: true });
}
return value;
};
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
return function (env) {
function fail(e) {
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
env.hasError = true;
}
var r, s = 0;
function next() {
while (r = env.stack.pop()) {
try {
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
if (r.dispose) {
var result = r.dispose.call(r.value);
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
}
else s |= 1;
}
catch (e) {
fail(e);
}
}
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
if (env.hasError) throw env.error;
}
return next();
};
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
});
import { EMPTY, firstValueFrom, from, map, merge, Subject, takeUntil, toArray } from "rxjs";
import { createCachingHiliteSetProvider } from "./CachingHiliteSetProvider.js";

@@ -12,2 +63,3 @@ import { createHiliteSetProvider } from "./HiliteSetProvider.js";

import { CoreSelectionSetEventType } from "./types/IModel.js";
import { safeDispose } from "./Utils.js";
/**

@@ -20,3 +72,3 @@ * Enables synchronization between iModel selection and unified selection.

const selectionHandler = new IModelSelectionHandler(props);
return () => selectionHandler.dispose();
return () => selectionHandler[Symbol.dispose]();
}

@@ -37,4 +89,4 @@ /**

_cancelOngoingChanges = new Subject();
_unifiedSelectionListenerDisposeFunc;
_imodelListenerDisposeFunc;
_unregisterUnifiedSelectionListener;
_unregisterIModelSelectionSetListener;
_hasCustomCachingHiliteSetProvider;

@@ -54,17 +106,15 @@ constructor(props) {

this._hiliteSetProvider = createHiliteSetProvider({ imodelAccess: this._imodelAccess });
this._imodelListenerDisposeFunc = this._imodelAccess.selectionSet.onChanged.addListener(this.onIModelSelectionChanged);
this._unifiedSelectionListenerDisposeFunc = this._selectionStorage.selectionChangeEvent.addListener(this.onUnifiedSelectionChanged);
// stop imodel from syncing tool selection with hilited list - we want to override that behavior
this._imodelAccess.hiliteSet.wantSyncWithSelectionSet = false;
this.applyCurrentHiliteSet({ activeSelectionAction: "clearAll" });
this._unregisterIModelSelectionSetListener = this._imodelAccess.selectionSet.onChanged.addListener(this.onIModelSelectionChanged);
this._unregisterUnifiedSelectionListener = this._selectionStorage.selectionChangeEvent.addListener(this.onUnifiedSelectionChanged);
this.applyCurrentHiliteSet({ activeSelectionAction: "clear" });
}
dispose() {
[Symbol.dispose]() {
this._cancelOngoingChanges.next();
this._imodelListenerDisposeFunc();
this._unifiedSelectionListenerDisposeFunc();
this._unregisterIModelSelectionSetListener();
this._unregisterUnifiedSelectionListener();
if (!this._hasCustomCachingHiliteSetProvider) {
this._cachingHiliteSetProvider.dispose();
safeDispose(this._cachingHiliteSetProvider);
}
}
/** Temporarily suspends tool selection synchronization until the returned `IDisposable` is disposed. */
/** Temporarily suspends tool selection synchronization until the returned disposable object is disposed. */
suspendIModelToolSelectionSync() {

@@ -74,30 +124,32 @@ const wasSuspended = this._isSuspended;

return {
dispose: () => (this._isSuspended = wasSuspended),
[Symbol.dispose]: () => {
this._isSuspended = wasSuspended;
},
};
}
handleUnifiedSelectionChange(changeType, selectables, source) {
if (changeType === "clear" || changeType === "replace") {
this.applyCurrentHiliteSet({ activeSelectionAction: changeType === "replace" && source === "Tool" ? "clearHilited" : "clearAll" });
return;
switch (changeType) {
case "clear":
case "replace":
return this.applyCurrentHiliteSet({ activeSelectionAction: source === "Tool" ? "keep" : "clear" });
case "add":
return void from(this._hiliteSetProvider.getHiliteSet({ selectables }))
.pipe(takeUntil(this._cancelOngoingChanges))
.subscribe({
next: (set) => {
this.addHiliteSet(set);
},
});
case "remove":
return void from(this._hiliteSetProvider.getHiliteSet({ selectables }))
.pipe(takeUntil(this._cancelOngoingChanges))
.subscribe({
next: (set) => {
this.removeHiliteSet(set);
},
complete: () => {
this.applyCurrentHiliteSet({ activeSelectionAction: "keep" });
},
});
}
if (changeType === "add") {
from(this._hiliteSetProvider.getHiliteSet({ selectables }))
.pipe(takeUntil(this._cancelOngoingChanges))
.subscribe({
next: (set) => {
this.addHiliteSet(set);
},
});
return;
}
from(this._hiliteSetProvider.getHiliteSet({ selectables }))
.pipe(takeUntil(this._cancelOngoingChanges))
.subscribe({
next: (set) => {
this.removeHiliteSet(set);
},
complete: () => {
this.applyCurrentHiliteSet({ activeSelectionAction: "keep" });
},
});
}

@@ -115,9 +167,16 @@ onUnifiedSelectionChanged = (args) => {

applyCurrentHiliteSet({ activeSelectionAction }) {
if (activeSelectionAction !== "keep") {
using(this.suspendIModelToolSelectionSync(), (_) => {
if (activeSelectionAction === "clear") {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
const _dispose = __addDisposableResource(env_1, this.suspendIModelToolSelectionSync(), false);
this._imodelAccess.hiliteSet.clear();
if (activeSelectionAction === "clearAll") {
this._imodelAccess.selectionSet.emptyAll();
}
});
this._imodelAccess.selectionSet.emptyAll();
}
catch (e_1) {
env_1.error = e_1;
env_1.hasError = true;
}
finally {
__disposeResources(env_1);
}
}

@@ -133,28 +192,67 @@ from(this._cachingHiliteSetProvider.getHiliteSet({ imodelKey: this._imodelAccess.key }))

addHiliteSet(set) {
using(this.suspendIModelToolSelectionSync(), (_) => {
if (set.models && set.models.length) {
this._imodelAccess.hiliteSet.models.addIds(set.models);
const env_2 = { stack: [], error: void 0, hasError: false };
try {
const _dispose = __addDisposableResource(env_2, this.suspendIModelToolSelectionSync(), false);
if ("active" in this._imodelAccess.selectionSet) {
// the `active` property tells us we're using 5.0 core, which supports models and subcategories
// in selection set - we can simply add the set as a whole
this._imodelAccess.selectionSet.add({
models: set.models,
subcategories: set.subCategories,
elements: set.elements,
});
}
if (set.subCategories && set.subCategories.length) {
this._imodelAccess.hiliteSet.subcategories.addIds(set.subCategories);
else {
// pre-5.0 core requires adding models and subcategories to hilite set separately
if (set.models.length) {
this._imodelAccess.hiliteSet.models.addIds(set.models);
}
if (set.subCategories.length) {
this._imodelAccess.hiliteSet.subcategories.addIds(set.subCategories);
}
if (set.elements.length) {
this._imodelAccess.selectionSet.add(set.elements);
}
}
if (set.elements && set.elements.length) {
this._imodelAccess.hiliteSet.elements.addIds(set.elements);
this._imodelAccess.selectionSet.add(set.elements);
}
});
}
catch (e_2) {
env_2.error = e_2;
env_2.hasError = true;
}
finally {
__disposeResources(env_2);
}
}
removeHiliteSet(set) {
using(this.suspendIModelToolSelectionSync(), (_) => {
if (set.models.length) {
this._imodelAccess.hiliteSet.models.deleteIds(set.models);
const env_3 = { stack: [], error: void 0, hasError: false };
try {
const _dispose = __addDisposableResource(env_3, this.suspendIModelToolSelectionSync(), false);
if ("active" in this._imodelAccess.selectionSet) {
// the `active` property tells us we're using 5.0 core, which supports models and subcategories
// in selection set - we can simply remove the set as a whole
this._imodelAccess.selectionSet.remove({
models: set.models,
subcategories: set.subCategories,
elements: set.elements,
});
}
if (set.subCategories.length) {
this._imodelAccess.hiliteSet.subcategories.deleteIds(set.subCategories);
else {
if (set.models.length) {
this._imodelAccess.hiliteSet.models.deleteIds(set.models);
}
if (set.subCategories.length) {
this._imodelAccess.hiliteSet.subcategories.deleteIds(set.subCategories);
}
if (set.elements.length) {
this._imodelAccess.selectionSet.remove(set.elements);
}
}
if (set.elements.length) {
this._imodelAccess.hiliteSet.elements.deleteIds(set.elements);
this._imodelAccess.selectionSet.remove(set.elements);
}
});
}
catch (e_3) {
env_3.error = e_3;
env_3.hasError = true;
}
finally {
__disposeResources(env_3);
}
}

@@ -169,11 +267,10 @@ onIModelSelectionChanged = async (event) => {

}
const elementIds = this.getSelectionSetChangeIds(event);
const scopedSelection = computeSelection({ queryExecutor: this._imodelAccess, elementIds, scope: this._activeScopeProvider() });
const ids = this.getSelectionSetChangeIds(event);
const scopedSelection = merge(ids.elements
? from(computeSelection({ queryExecutor: this._imodelAccess, elementIds: ids.elements, scope: this._activeScopeProvider() }))
: /* c8 ignore next */ EMPTY, ids.models ? from(ids.models).pipe(map((id) => ({ className: "BisCore.Model", id }))) : /* c8 ignore next */ EMPTY, ids.subcategories ? from(ids.subcategories).pipe(map((id) => ({ className: "BisCore.SubCategory", id }))) : /* c8 ignore next */ EMPTY);
await this.handleIModelSelectionChange(event.type, scopedSelection);
};
async handleIModelSelectionChange(type, iterator) {
const selectables = [];
for await (const selectable of iterator) {
selectables.push(selectable);
}
async handleIModelSelectionChange(type, keys) {
const selectables = await firstValueFrom(keys.pipe(toArray()));
const props = {

@@ -196,20 +293,10 @@ imodelKey: this._imodelAccess.key,

case CoreSelectionSetEventType.Add:
return event.additions ?? (event.added ? { elements: event.added } : /* c8 ignore next */ {});
case CoreSelectionSetEventType.Remove:
return event.removals ?? (event.removed ? { elements: event.removed } : /* c8 ignore next */ {});
case CoreSelectionSetEventType.Replace:
assert(!!event.added);
return this.idArgToIds(event.added);
default:
assert(!!event.removed);
return this.idArgToIds(event.removed);
return "active" in event.set ? event.set.active : { elements: event.set.elements };
}
}
idArgToIds(ids) {
if (typeof ids === "string") {
return [ids];
}
if (ids instanceof Set) {
return [...ids];
}
return ids;
}
}
//# sourceMappingURL=EnableUnifiedSelectionSyncWithIModel.js.map

@@ -0,1 +1,2 @@

import { Id64Arg } from "@itwin/core-bentley";
import { ECSqlQueryExecutor } from "@itwin/presentation-shared";

@@ -33,3 +34,3 @@ import { SelectableInstanceKey } from "./Selectable.js";

/** IDs of elements to compute selection for. */
elementIds: string[];
elementIds: Id64Arg;
/** Selection scope to compute selection with. */

@@ -36,0 +37,0 @@ scope: ElementSelectionScopeProps | {

@@ -17,15 +17,20 @@ /*---------------------------------------------------------------------------------------------

}
const nonTransientKeys = elementIds.filter((key) => !Id64.isTransient(key));
const nonTransientIds = [];
for (const id of Id64.iterable(elementIds)) {
if (!Id64.isTransient(id)) {
nonTransientIds.push(id);
}
}
switch (scope.id) {
case "element":
yield* computeElementSelection(queryExecutor, nonTransientKeys, scope.ancestorLevel ?? 0);
yield* computeElementSelection(queryExecutor, nonTransientIds, scope.ancestorLevel ?? 0);
return;
case "category":
yield* computeCategorySelection(queryExecutor, nonTransientKeys);
yield* computeCategorySelection(queryExecutor, nonTransientIds);
return;
case "model":
yield* computeModelSelection(queryExecutor, nonTransientKeys);
yield* computeModelSelection(queryExecutor, nonTransientIds);
return;
case "functional":
yield* computeFunctionalElementSelection(queryExecutor, nonTransientKeys, scope.ancestorLevel ?? 0);
yield* computeFunctionalElementSelection(queryExecutor, nonTransientIds, scope.ancestorLevel ?? 0);
return;

@@ -32,0 +37,0 @@ }

@@ -1,4 +0,18 @@

import { Id64Arg } from "@itwin/core-bentley";
import { Id64Arg, Id64Set } from "@itwin/core-bentley";
import { Event } from "@itwin/presentation-shared";
/**
* A collection of geometric element, model and subcategory ids that can be added to
* a [[SelectionSet]] or [[HiliteSet]].
*
* **Warning:** This type was added to `@itwin/core-frontend` in 5.0.
*
* @see https://www.itwinjs.org/reference/core-frontend/selectionset/selectableids/
* @public
*/
export interface CoreSelectableIds {
elements?: Id64Arg;
models?: Id64Arg;
subcategories?: Id64Arg;
}
/**
* Identifies the type of changes made to the `CoreIModelSelectionSet` to produce a `CoreSelectionSetEvent`.

@@ -34,4 +48,16 @@ * @see https://www.itwinjs.org/reference/core-frontend/selectionset/selectionseteventtype/

added?: Id64Arg;
/**
* A collection of geometric element, model and subcategory ids that have been removed from selection set.
*
* **Warning:** This property was added to `@itwin/core-frontend` in 5.0.
*/
additions?: CoreSelectableIds;
/** The element Ids removed from the set. */
removed?: Id64Arg;
/**
* A collection of geometric element, model and subcategory ids that have been added to selection set.
*
* **Warning:** This property was added to `@itwin/core-frontend` in 5.0.
*/
removals?: CoreSelectableIds;
/** The affected `CoreIModelSelectionSet`. */

@@ -45,3 +71,3 @@ set: CoreIModelSelectionSet;

*/
export interface CoreIModelSelectionSet {
export type CoreIModelSelectionSet = {
/** Event called whenever elements are added or removed from this SelectionSet. */

@@ -53,2 +79,3 @@ onChanged: Event<(ev: CoreSelectionSetEventUnsafe) => void>;

emptyAll(): void;
} & ({
/** Add one or more Ids to the current selection set. */

@@ -58,3 +85,24 @@ add(elem: Id64Arg): boolean;

remove(elem: Id64Arg): boolean;
}
} | {
/**
* Get the active selection as a collection of geometric element, model and subcategory ids.
*/
readonly active: {
[P in keyof CoreSelectableIds]-?: Id64Set;
};
/**
* Adds a collection of geometric element, model and subcategory ids to this selection set.
*
* The overload was added only in 5.0 `@itwin/core-frontend`. The `active` property can be used
* to check if this `overload` is available.
*/
add: (ids: Id64Arg | CoreSelectableIds) => boolean;
/**
* Adds a collection of geometric element, model and subcategory ids to this selection set.
*
* The overload was added only in 5.0 `@itwin/core-frontend`. The `active` property can be used
* to check if this `overload` is available.
*/
remove: (ids: Id64Arg | CoreSelectableIds) => boolean;
});
/**

@@ -61,0 +109,0 @@ * A set if ID's optimized for performance-critical code which represents large sets of ID's as pairs of 32-bit integers.

@@ -18,2 +18,16 @@ import { Observable } from "rxjs";

export declare function releaseMainThreadOnItemsCount<T>(elementCount: number): (obs: Observable<T>) => Observable<T>;
/**
* A helper that disposes the given object, if it's disposable.
*
* The first option is to dispose using the deprecated `dispose` method if it exists on the object.
* If not, we use the new `Symbol.dispose` method. If that doesn't exist either, the object is
* considered as non-disposable and nothing is done with it.
*
* @internal
*/
export declare function safeDispose(disposable: {} | {
[Symbol.dispose]: () => void;
} | {
dispose: () => void;
}): void;
//# sourceMappingURL=Utils.d.ts.map

@@ -49,2 +49,19 @@ /*---------------------------------------------------------------------------------------------

}
/**
* A helper that disposes the given object, if it's disposable.
*
* The first option is to dispose using the deprecated `dispose` method if it exists on the object.
* If not, we use the new `Symbol.dispose` method. If that doesn't exist either, the object is
* considered as non-disposable and nothing is done with it.
*
* @internal
*/
export function safeDispose(disposable) {
if ("dispose" in disposable) {
disposable.dispose();
}
else if (Symbol.dispose in disposable) {
disposable[Symbol.dispose]();
}
}
//# sourceMappingURL=Utils.js.map
{
"name": "@itwin/unified-selection",
"version": "1.1.2",
"version": "1.2.0",
"description": "Package for managing unified selection in iTwin.js applications.",

@@ -5,0 +5,0 @@ "license": "MIT",

@@ -125,3 +125,6 @@ # @itwin/unified-selection

import { createCachingHiliteSetProvider } from "@itwin/unified-selection";
const selectionHiliteProvider = createCachingHiliteSetProvider({
// Note the use of `using` keyword here. The caching provider registers a selection change listener and should be disposed, in case
// its lifetime is shorter than that of `SelectionStorage`, to unregister the listener. The `using` keyword ensures that the provider
// is disposed when it goes out of scope.
using selectionHiliteProvider = createCachingHiliteSetProvider({
selectionStorage,

@@ -131,5 +134,2 @@ imodelProvider: (imodelKey: string) => getIModelByKey(imodelKey),

const selectionHiliteSet = await selectionHiliteProvider.getHiliteSet({ imodel.key });
// The caching provider registers a selection change listener and should be disposed, in case its lifetime
// is shorter than that of `SelectionStorage`, to unregister the listener.
selectionHiliteProvider.dispose();
```

@@ -136,0 +136,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc