@itwin/tree-widget-react 3.17.0 → 3.17.1

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +11 -2
  2. package/lib/cjs/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.js +3 -2
  3. package/lib/cjs/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.js.map +1 -1
  4. package/lib/cjs/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.d.ts +3 -3
  5. package/lib/cjs/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.js +22 -21
  6. package/lib/cjs/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.js.map +1 -1
  7. package/lib/cjs/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.js +57 -107
  8. package/lib/cjs/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.js.map +1 -1
  9. package/lib/cjs/tree-widget-react/components/trees/common/CategoriesVisibilityUtils.js +22 -12
  10. package/lib/cjs/tree-widget-react/components/trees/common/CategoriesVisibilityUtils.js.map +1 -1
  11. package/lib/cjs/tree-widget-react/components/trees/common/internal/Utils.d.ts +25 -0
  12. package/lib/cjs/tree-widget-react/components/trees/common/internal/Utils.js +83 -0
  13. package/lib/cjs/tree-widget-react/components/trees/common/internal/Utils.js.map +1 -0
  14. package/lib/cjs/tree-widget-react/components/trees/models-tree/ModelsTreeDefinition.d.ts +1 -0
  15. package/lib/cjs/tree-widget-react/components/trees/models-tree/ModelsTreeDefinition.js +78 -23
  16. package/lib/cjs/tree-widget-react/components/trees/models-tree/ModelsTreeDefinition.js.map +1 -1
  17. package/lib/cjs/tree-widget-react/components/trees/models-tree/Utils.d.ts +5 -3
  18. package/lib/cjs/tree-widget-react/components/trees/models-tree/Utils.js +5 -14
  19. package/lib/cjs/tree-widget-react/components/trees/models-tree/Utils.js.map +1 -1
  20. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/AlwaysAndNeverDrawnElementInfo.js +5 -4
  21. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/AlwaysAndNeverDrawnElementInfo.js.map +1 -1
  22. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/FilteredTree.d.ts +7 -1
  23. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/FilteredTree.js +52 -0
  24. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/FilteredTree.js.map +1 -1
  25. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/ModelsTreeIdsCache.d.ts +15 -0
  26. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/ModelsTreeIdsCache.js +134 -3
  27. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/ModelsTreeIdsCache.js.map +1 -1
  28. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/ModelsTreeVisibilityHandler.js +241 -120
  29. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/ModelsTreeVisibilityHandler.js.map +1 -1
  30. package/lib/cjs/tree-widget-react-internal.d.ts +1 -1
  31. package/lib/cjs/tree-widget-react-internal.js +1 -1
  32. package/lib/cjs/tree-widget-react-internal.js.map +1 -1
  33. package/lib/esm/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.js +3 -2
  34. package/lib/esm/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.js.map +1 -1
  35. package/lib/esm/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.d.ts +3 -3
  36. package/lib/esm/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.js +23 -22
  37. package/lib/esm/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.js.map +1 -1
  38. package/lib/esm/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.js +59 -109
  39. package/lib/esm/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.js.map +1 -1
  40. package/lib/esm/tree-widget-react/components/trees/common/CategoriesVisibilityUtils.js +23 -13
  41. package/lib/esm/tree-widget-react/components/trees/common/CategoriesVisibilityUtils.js.map +1 -1
  42. package/lib/esm/tree-widget-react/components/trees/common/internal/Utils.d.ts +25 -0
  43. package/lib/esm/tree-widget-react/components/trees/common/internal/Utils.js +74 -0
  44. package/lib/esm/tree-widget-react/components/trees/common/internal/Utils.js.map +1 -0
  45. package/lib/esm/tree-widget-react/components/trees/models-tree/ModelsTreeDefinition.d.ts +1 -0
  46. package/lib/esm/tree-widget-react/components/trees/models-tree/ModelsTreeDefinition.js +74 -19
  47. package/lib/esm/tree-widget-react/components/trees/models-tree/ModelsTreeDefinition.js.map +1 -1
  48. package/lib/esm/tree-widget-react/components/trees/models-tree/Utils.d.ts +5 -3
  49. package/lib/esm/tree-widget-react/components/trees/models-tree/Utils.js +4 -13
  50. package/lib/esm/tree-widget-react/components/trees/models-tree/Utils.js.map +1 -1
  51. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/AlwaysAndNeverDrawnElementInfo.js +4 -3
  52. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/AlwaysAndNeverDrawnElementInfo.js.map +1 -1
  53. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/FilteredTree.d.ts +7 -1
  54. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/FilteredTree.js +52 -0
  55. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/FilteredTree.js.map +1 -1
  56. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/ModelsTreeIdsCache.d.ts +15 -0
  57. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/ModelsTreeIdsCache.js +133 -2
  58. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/ModelsTreeIdsCache.js.map +1 -1
  59. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/ModelsTreeVisibilityHandler.js +224 -103
  60. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/ModelsTreeVisibilityHandler.js.map +1 -1
  61. package/lib/esm/tree-widget-react-internal.d.ts +1 -1
  62. package/lib/esm/tree-widget-react-internal.js +1 -1
  63. package/lib/esm/tree-widget-react-internal.js.map +1 -1
  64. package/package.json +1 -1
@@ -7,10 +7,11 @@ import { assert, Id64 } from "@itwin/core-bentley";
7
7
  import { PerModelCategoryVisibility } from "@itwin/core-frontend";
8
8
  import { HierarchyNode, HierarchyNodeKey } from "@itwin/presentation-hierarchies";
9
9
  import { enableCategoryDisplay, loadCategoriesFromViewport } from "../../common/CategoriesVisibilityUtils.js";
10
- import { reduceWhile, toVoidPromise } from "../../common/Rxjs.js";
10
+ import { getTooltipOptions, mergeVisibilityStatuses, releaseMainThreadOnItemsCount, setDifference, setIntersection } from "../../common/internal/Utils.js";
11
+ import { toVoidPromise } from "../../common/Rxjs.js";
11
12
  import { createVisibilityStatus } from "../../common/Tooltip.js";
12
13
  import { createVisibilityHandlerResult } from "../../common/UseHierarchyVisibility.js";
13
- import { getIdsFromChildrenTree, releaseMainThreadOnItemsCount } from "../Utils.js";
14
+ import { getIdsFromChildrenTree } from "../Utils.js";
14
15
  import { AlwaysAndNeverDrawnElementInfo } from "./AlwaysAndNeverDrawnElementInfo.js";
15
16
  import { createFilteredTree, parseCategoryKey } from "./FilteredTree.js";
16
17
  import { ModelsTreeNode } from "./ModelsTreeNode.js";
@@ -88,7 +89,9 @@ class ModelsTreeVisibilityHandlerImpl {
88
89
  if (!HierarchyNode.isInstancesNode(node)) {
89
90
  return of(createVisibilityStatus("disabled"));
90
91
  }
91
- if (node.filtering?.filteredChildrenIdentifierPaths?.length && !node.filtering.isFilterTarget) {
92
+ // Only call getFilteredNodeVisibility when node is not a filter target, is not a child of filter target and has filtered children.
93
+ // Otherwise, it can be handled normally.
94
+ if (node.filtering?.filteredChildrenIdentifierPaths?.length && !node.filtering.isFilterTarget && !node.filtering.hasFilterTargetAncestor) {
92
95
  return this.getFilteredNodeVisibility({ node });
93
96
  }
94
97
  if (ModelsTreeNode.isSubjectNode(node)) {
@@ -109,13 +112,18 @@ class ModelsTreeVisibilityHandlerImpl {
109
112
  });
110
113
  }
111
114
  const categoryId = ModelsTreeNode.getCategoryId(node);
115
+ const rootCategoryIds = getRootCategoryIds({ parentKeys: node.parentKeys, modelId });
116
+ assert(rootCategoryIds !== undefined);
112
117
  if (!categoryId) {
113
118
  return of(createVisibilityStatus("disabled"));
114
119
  }
115
120
  return this.getElementDisplayStatus({
116
121
  elementIds: node.key.instanceKeys.map(({ id }) => id),
122
+ parentKeys: node.parentKeys,
123
+ rootCategoryIds,
117
124
  modelId,
118
125
  categoryId,
126
+ totalChildrenCount: node.extendedData?.childrenCount ?? 0,
119
127
  });
120
128
  }
121
129
  getFilteredNodeVisibility(props) {
@@ -135,14 +143,53 @@ class ModelsTreeVisibilityHandlerImpl {
135
143
  })));
136
144
  }
137
145
  if (elements?.size) {
138
- observables.push(from(elements).pipe(releaseMainThreadOnItemsCount(50), mergeMap(([categoryKey, elementsMap]) => {
146
+ const filterTargetElements = new Array();
147
+ elements.forEach((elementsMap) => elementsMap.forEach(({ isFilterTarget }, elementId) => {
148
+ if (isFilterTarget) {
149
+ filterTargetElements.push(elementId);
150
+ }
151
+ }));
152
+ let childrenCountMapObs;
153
+ if (HierarchyNode.isClassGroupingNode(props.node)) {
154
+ const groupingNodesFilterTargets = props.node.extendedData?.filterTargets;
155
+ const nestedFilterTargetElements = filterTargetElements.filter((filterTarget) => !groupingNodesFilterTargets?.has(filterTarget));
156
+ // Only need to request children count for indirect children filter targets.
157
+ // Direct children filter targets already have children count stored in grouping nodes extended data.
158
+ childrenCountMapObs = this.#idsCache.getAllChildrenCount({ elementIds: nestedFilterTargetElements });
159
+ }
160
+ else {
161
+ childrenCountMapObs = this.#idsCache.getAllChildrenCount({ elementIds: filterTargetElements });
162
+ }
163
+ observables.push(childrenCountMapObs.pipe(
164
+ // Get children count for all filter target elements.
165
+ map((elementCountMap) => {
166
+ if (!HierarchyNode.isClassGroupingNode(props.node) || !props.node.extendedData?.filterTargets) {
167
+ return elementCountMap;
168
+ }
169
+ // Need to add children count (stored in grouping nodes' extended data) of direct children filter targets.
170
+ const groupingNodesFilterTargets = props.node.extendedData.filterTargets;
171
+ groupingNodesFilterTargets.forEach((value, key) => elementCountMap.set(key, value.childrenCount));
172
+ return elementCountMap;
173
+ }), mergeMap((elementCountMap) => from(elements).pipe(releaseMainThreadOnItemsCount(50), mergeMap(([categoryKey, elementsMap]) => {
139
174
  const { modelId, categoryId } = parseCategoryKey(categoryKey);
175
+ let totalChildrenCount = 0;
176
+ elementsMap.forEach((_, elementId) => {
177
+ const childCount = elementCountMap.get(elementId);
178
+ if (childCount) {
179
+ totalChildrenCount += childCount;
180
+ }
181
+ });
182
+ const parentElementIdsPath = getParentElementsIdsPath({ parentKeys: props.node.parentKeys, modelId });
183
+ const rootCategoryIds = getRootCategoryIds({ parentKeys: props.node.parentKeys, modelId });
140
184
  return this.getElementsDisplayStatus({
141
185
  elementIds: [...elementsMap.keys()],
186
+ rootCategoryIds: rootCategoryIds ?? categoryId,
187
+ parentElementIdsPath,
142
188
  modelId,
143
189
  categoryId,
190
+ childrenCount: totalChildrenCount,
144
191
  });
145
- })));
192
+ })))));
146
193
  }
147
194
  return merge(...observables);
148
195
  }), mergeVisibilityStatuses());
@@ -264,25 +311,33 @@ class ModelsTreeVisibilityHandlerImpl {
264
311
  return createVisibilityHandlerResult(this, props, result, this.#props.overrides?.getCategoryDisplayStatus);
265
312
  }
266
313
  getClassGroupingNodeDisplayStatus(node) {
267
- const { elementIds, modelId, categoryId } = this.getGroupingNodeInfo(node);
314
+ const { elementIds, modelId, categoryId, childrenCount } = this.getGroupingNodeInfo(node);
315
+ const rootCategoryIds = getRootCategoryIds({ parentKeys: node.parentKeys, modelId });
316
+ assert(rootCategoryIds !== undefined);
268
317
  const result = this.getElementsDisplayStatus({
269
318
  elementIds,
270
319
  modelId,
271
320
  categoryId,
321
+ rootCategoryIds,
322
+ parentElementIdsPath: getParentElementsIdsPath({ parentKeys: node.parentKeys, modelId }),
323
+ childrenCount: childrenCount ?? 0,
272
324
  });
273
325
  return createVisibilityHandlerResult(this, { node }, result, this.#props.overrides?.getElementGroupingNodeDisplayStatus);
274
326
  }
275
- getElementDisplayStatus({ elementIds, modelId, categoryId, }) {
327
+ getElementDisplayStatus({ parentKeys, totalChildrenCount, elementIds, modelId, rootCategoryIds, categoryId, }) {
276
328
  const result = this.getElementsDisplayStatus({
329
+ rootCategoryIds,
277
330
  elementIds,
278
331
  modelId,
279
332
  categoryId,
333
+ parentElementIdsPath: getParentElementsIdsPath({ parentKeys, modelId }),
334
+ childrenCount: totalChildrenCount,
280
335
  });
281
336
  return createVisibilityHandlerResult(this, { elementId: elementIds[0], modelId, categoryId }, result, this.#props.overrides?.getElementDisplayStatus);
282
337
  }
283
338
  getElementsDisplayStatus(props) {
284
339
  return defer(() => {
285
- const { modelId, categoryId, elementIds } = props;
340
+ const { modelId, categoryId, elementIds, parentElementIdsPath, childrenCount, rootCategoryIds } = props;
286
341
  if (!this.#props.viewport.view.viewsModel(modelId)) {
287
342
  return of(elementIds).pipe(this.getSubModeledElementsVisibilityStatus({
288
343
  tooltips: {
@@ -296,6 +351,10 @@ class ModelsTreeVisibilityHandlerImpl {
296
351
  }
297
352
  return this.getVisibilityFromAlwaysAndNeverDrawnElements({
298
353
  elements: elementIds,
354
+ parentElementIdsPath,
355
+ modelIds: modelId,
356
+ rootCategoryIds,
357
+ childrenCount,
299
358
  defaultStatus: () => {
300
359
  const status = this.getDefaultCategoryVisibilityStatus({ categoryIds: categoryId, modelId, ignoreTooltip: true });
301
360
  return createVisibilityStatus(status.state, getTooltipOptions(`modelsTree.groupingNode.${status.state}ThroughCategory`));
@@ -331,7 +390,9 @@ class ModelsTreeVisibilityHandlerImpl {
331
390
  if (!HierarchyNode.isInstancesNode(node)) {
332
391
  return EMPTY;
333
392
  }
334
- if (node.filtering?.filteredChildrenIdentifierPaths?.length && !node.filtering.isFilterTarget) {
393
+ // Only call changeFilteredNodeVisibility when node is not a filter target, is not a child of filter target and has filtered children.
394
+ // Otherwise, it can be handled normally.
395
+ if (node.filtering?.filteredChildrenIdentifierPaths?.length && !node.filtering.isFilterTarget && !node.filtering.hasFilterTargetAncestor) {
335
396
  return this.changeFilteredNodeVisibility({ node, on });
336
397
  }
337
398
  if (ModelsTreeNode.isSubjectNode(node)) {
@@ -355,12 +416,18 @@ class ModelsTreeVisibilityHandlerImpl {
355
416
  if (!categoryId) {
356
417
  return EMPTY;
357
418
  }
358
- return this.changeElementsState({
359
- elementIds: new Set([...node.key.instanceKeys.map(({ id }) => id)]),
419
+ const elementIds = new Set(node.key.instanceKeys.map(({ id }) => id));
420
+ return this.#idsCache.getChildrenTree({ elementIds }).pipe(map((childrenTree) => {
421
+ // Children tree contains provided elementIds, they are at the root of this tree.
422
+ // We want to skip them and only get ids of children.
423
+ return getIdsFromChildrenTree({ tree: childrenTree, predicate: ({ depth }) => depth > 0 });
424
+ }), mergeMap((children) => this.changeElementsState({
425
+ elementIds,
360
426
  modelId,
427
+ children: children.size > 0 ? children : undefined,
361
428
  categoryId,
362
429
  on,
363
- });
430
+ })));
364
431
  });
365
432
  if (this.#props.viewport.isAlwaysDrawnExclusive) {
366
433
  return concat(this.removeAlwaysDrawnExclusive(), changeObs);
@@ -369,7 +436,7 @@ class ModelsTreeVisibilityHandlerImpl {
369
436
  }
370
437
  changeFilteredNodeVisibility({ on, ...props }) {
371
438
  assert(this.#filteredTree !== undefined);
372
- return from(this.#filteredTree).pipe(map((filteredTree) => filteredTree.getVisibilityChangeTargets(props.node)), mergeMap(({ subjects, models, categories, elements }) => {
439
+ return from(this.#filteredTree).pipe(map((filteredTree) => filteredTree.getVisibilityChangeTargets(props.node)), mergeMap(({ subjects, categories, elements, models }) => {
373
440
  const observables = new Array();
374
441
  if (subjects?.size) {
375
442
  observables.push(this.changeSubjectNodeState([...subjects], on));
@@ -384,10 +451,24 @@ class ModelsTreeVisibilityHandlerImpl {
384
451
  })));
385
452
  }
386
453
  if (elements?.size) {
387
- observables.push(from(elements).pipe(mergeMap(([categoryKey, elementsMap]) => {
454
+ const filterTargetElements = new Array();
455
+ const elementsSet = new Set();
456
+ elements.forEach((elementsMap) => elementsMap.forEach(({ isFilterTarget }, elementId) => {
457
+ elementsSet.add(elementId);
458
+ if (isFilterTarget) {
459
+ filterTargetElements.push(elementId);
460
+ }
461
+ }));
462
+ observables.push(this.#idsCache.getChildrenTree({ elementIds: filterTargetElements }).pipe(map((childrenTree) => getIdsFromChildrenTree({ tree: childrenTree })), map((childrenIds) => setDifference(childrenIds, elementsSet)), mergeMap((childElementIds) => from(elements).pipe(mergeMap(([categoryKey, elementsMap]) => {
388
463
  const { modelId, categoryId } = parseCategoryKey(categoryKey);
389
- return this.changeElementsState({ modelId, categoryId, elementIds: new Set([...elementsMap.keys()]), on });
390
- })));
464
+ return this.changeElementsState({
465
+ modelId,
466
+ categoryId,
467
+ elementIds: new Set([...elementsMap.keys()]),
468
+ on,
469
+ children: childElementIds.size > 0 ? childElementIds : undefined,
470
+ });
471
+ })))));
391
472
  }
392
473
  return merge(...observables);
393
474
  }));
@@ -422,9 +503,7 @@ class ModelsTreeVisibilityHandlerImpl {
422
503
  const idsObs = from(Id64.iterable(ids));
423
504
  if (!on) {
424
505
  viewport.changeModelDisplay(ids, false);
425
- return idsObs.pipe(mergeMap((modelId) => this.#idsCache.getModelCategories(modelId).pipe(map((categoryIds) => {
426
- return { modelId, categoryIds };
427
- }))), mergeMap(({ modelId, categoryIds }) => this.#idsCache.getCategoriesModeledElements(modelId, categoryIds)), mergeMap((modeledElementIds) => this.changeModelState({ ids: modeledElementIds, on })));
506
+ return idsObs.pipe(mergeMap((modelId) => forkJoin({ categoryIds: this.#idsCache.getModelCategories(modelId), modelId: of(modelId) })), mergeMap(({ modelId, categoryIds }) => this.#idsCache.getCategoriesModeledElements(modelId, categoryIds)), mergeMap((modeledElementIds) => this.changeModelState({ ids: modeledElementIds, on })));
428
507
  }
429
508
  return concat(defer(() => {
430
509
  viewport.perModelCategoryVisibility.clearOverrides(ids);
@@ -440,15 +519,12 @@ class ModelsTreeVisibilityHandlerImpl {
440
519
  return forkJoin({
441
520
  categories: this.#idsCache.getModelCategories(modelId),
442
521
  alwaysDrawnElements: this.getAlwaysOrNeverDrawnElements({ modelIds: modelId, setType: "always" }),
443
- }).pipe(mergeMap(async ({ categories, alwaysDrawnElements }) => {
522
+ }).pipe(mergeMap(({ categories, alwaysDrawnElements }) => {
444
523
  const alwaysDrawn = this.#props.viewport.alwaysDrawn;
445
524
  if (alwaysDrawn && alwaysDrawnElements) {
446
525
  viewport.setAlwaysDrawn(setDifference(alwaysDrawn, alwaysDrawnElements));
447
526
  }
448
- categories.forEach((categoryId) => {
449
- this.changeCategoryStateInViewportAccordingToModelVisibility(modelId, categoryId, false);
450
- });
451
- await viewport.addViewedModels(modelId);
527
+ return concat(from(Id64.iterable(categories)).pipe(releaseMainThreadOnItemsCount(300), map((categoryId) => this.changeCategoryStateInViewportAccordingToModelVisibility(modelId, categoryId, false))), defer(async () => viewport.addViewedModels(modelId)));
452
528
  }));
453
529
  }
454
530
  changeCategoryStateInViewportAccordingToModelVisibility(modelId, categoryId, on) {
@@ -470,12 +546,7 @@ class ModelsTreeVisibilityHandlerImpl {
470
546
  const result = defer(() => {
471
547
  const viewport = this.#props.viewport;
472
548
  const { modelId, categoryIds, on } = props;
473
- return concat(props.on && !viewport.view.viewsModel(modelId) ? this.showModelWithoutAnyCategoriesOrElements(modelId) : EMPTY, defer(() => {
474
- for (const categoryId of Id64.iterable(categoryIds)) {
475
- this.changeCategoryStateInViewportAccordingToModelVisibility(modelId, categoryId, on);
476
- }
477
- return this.clearAlwaysAndNeverDrawnElements(props);
478
- }), this.#idsCache
549
+ return concat(props.on && !viewport.view.viewsModel(modelId) ? this.showModelWithoutAnyCategoriesOrElements(modelId) : EMPTY, from(Id64.iterable(categoryIds)).pipe(releaseMainThreadOnItemsCount(300), map((categoryId) => this.changeCategoryStateInViewportAccordingToModelVisibility(modelId, categoryId, on))), this.clearAlwaysAndNeverDrawnElements(props), this.#idsCache
479
550
  .getCategoriesModeledElements(modelId, categoryIds)
480
551
  .pipe(mergeMap((modeledElementIds) => this.changeModelState({ ids: modeledElementIds, on }))));
481
552
  });
@@ -483,12 +554,25 @@ class ModelsTreeVisibilityHandlerImpl {
483
554
  }
484
555
  doChangeElementsState(props) {
485
556
  return defer(() => {
486
- const { modelId, categoryId, elementIds, on } = props;
557
+ const { modelId, categoryId, elementIds, on, children } = props;
487
558
  const viewport = this.#props.viewport;
488
559
  return concat(on && !viewport.view.viewsModel(modelId) ? this.showModelWithoutAnyCategoriesOrElements(modelId) : EMPTY, defer(() => {
489
560
  const categoryVisibility = this.getDefaultCategoryVisibilityStatus({ categoryIds: categoryId, modelId, ignoreTooltip: true });
490
- const isDisplayedByDefault = categoryVisibility.state === "visible";
491
- return this.queueElementsVisibilityChange(elementIds, on, isDisplayedByDefault);
561
+ const isCategoryVisible = categoryVisibility.state === "visible";
562
+ // Make sure to add all children to always/never drawn list.
563
+ const isDisplayedByDefault =
564
+ // When category is visible and elements need to be turned off, or when category is hidden and elements need to be turned on,
565
+ // We can set isDisplayedByDefault to isCategoryVisible. This allows to not check if each element is in the elementIds list or not.
566
+ isCategoryVisible === !on
567
+ ? () => isCategoryVisible
568
+ : (elementId) => {
569
+ if (elementIds.has(elementId)) {
570
+ return isCategoryVisible;
571
+ }
572
+ return !on;
573
+ };
574
+ const elementsToChange = children ? [...elementIds, ...(typeof children === "string" ? [children] : children)] : elementIds;
575
+ return this.queueElementsVisibilityChange(elementsToChange, on, isDisplayedByDefault);
492
576
  }), from(elementIds).pipe(mergeMap((elementId) => forkJoin({
493
577
  elementId: of(elementId),
494
578
  isSubModel: this.#idsCache.hasSubModel(elementId),
@@ -500,7 +584,14 @@ class ModelsTreeVisibilityHandlerImpl {
500
584
  * @see `changeElementState`
501
585
  */
502
586
  changeElementGroupingNodeState(node, on) {
503
- const result = this.doChangeElementsState({ ...this.getGroupingNodeInfo(node), on });
587
+ const { modelId, categoryId, elementIds } = this.getGroupingNodeInfo(node);
588
+ const result = this.#idsCache.getChildrenTree({ elementIds }).pipe(map((childrenTree) => getIdsFromChildrenTree({ tree: childrenTree, predicate: ({ depth }) => depth > 0 })), mergeMap((children) => this.doChangeElementsState({
589
+ modelId,
590
+ categoryId,
591
+ elementIds,
592
+ children: children.size > 0 ? children : undefined,
593
+ on,
594
+ })));
504
595
  return createVisibilityHandlerResult(this, { node, on }, result, this.#props.overrides?.changeElementGroupingNodeState);
505
596
  }
506
597
  /**
@@ -515,7 +606,7 @@ class ModelsTreeVisibilityHandlerImpl {
515
606
  const finishedSubject = new Subject();
516
607
  // observable to track if visibility change is finished/cancelled
517
608
  const changeFinished = finishedSubject.pipe(startWith(false), shareReplay(1), filter((finished) => finished));
518
- const changeObservable = from(elementIds).pipe(
609
+ const changeObservable = from(Id64.iterable(elementIds)).pipe(
519
610
  // check if visibility change is not finished (cancelled) due to change overall change request being cancelled
520
611
  takeUntil(changeFinished), this.changeElementStateNoChildrenOperator({ on, isDisplayedByDefault: visibleByDefault }), tap({
521
612
  next: () => {
@@ -546,7 +637,7 @@ class ModelsTreeVisibilityHandlerImpl {
546
637
  const wasRemoved = acc.neverDrawn.delete(elementId);
547
638
  acc.changedNeverDrawn ||= wasRemoved;
548
639
  // If exclusive mode is enabled, we must add the element to the always drawn list.
549
- if ((!isDisplayedByDefault || isAlwaysDrawnExclusive) && !acc.alwaysDrawn.has(elementId)) {
640
+ if ((!isDisplayedByDefault(elementId) || isAlwaysDrawnExclusive) && !acc.alwaysDrawn.has(elementId)) {
550
641
  acc.alwaysDrawn.add(elementId);
551
642
  acc.changedAlwaysDrawn = true;
552
643
  }
@@ -555,7 +646,7 @@ class ModelsTreeVisibilityHandlerImpl {
555
646
  const wasRemoved = acc.alwaysDrawn.delete(elementId);
556
647
  acc.changedAlwaysDrawn ||= wasRemoved;
557
648
  // If exclusive mode is not enabled, we have to add the element to the never drawn list.
558
- if (isDisplayedByDefault && !isAlwaysDrawnExclusive && !acc.neverDrawn.has(elementId)) {
649
+ if (isDisplayedByDefault(elementId) && !isAlwaysDrawnExclusive && !acc.neverDrawn.has(elementId)) {
559
650
  acc.neverDrawn.add(elementId);
560
651
  acc.changedNeverDrawn = true;
561
652
  }
@@ -577,20 +668,22 @@ class ModelsTreeVisibilityHandlerImpl {
577
668
  if (totalCount === 0) {
578
669
  return props.defaultStatus();
579
670
  }
580
- if (neverDrawn?.size === totalCount) {
671
+ const neverDrawnSize = neverDrawn ? Id64.sizeOf(neverDrawn) : undefined;
672
+ const alwaysDrawnSize = alwaysDrawn ? Id64.sizeOf(alwaysDrawn) : undefined;
673
+ if (neverDrawnSize === totalCount) {
581
674
  return createVisibilityStatus("hidden", getTooltipOptions(props.tooltips.allElementsInNeverDrawnList, ignoreTooltip));
582
675
  }
583
- if (alwaysDrawn?.size === totalCount) {
676
+ if (alwaysDrawnSize === totalCount) {
584
677
  return createVisibilityStatus("visible", getTooltipOptions(props.tooltips.allElementsInAlwaysDrawnList, ignoreTooltip));
585
678
  }
586
679
  const viewport = this.#props.viewport;
587
680
  if (viewport.isAlwaysDrawnExclusive && viewport.alwaysDrawn?.size) {
588
- return alwaysDrawn?.size
681
+ return alwaysDrawnSize
589
682
  ? createVisibilityStatus("partial", getTooltipOptions(props.tooltips.elementsInBothAlwaysAndNeverDrawn, ignoreTooltip))
590
683
  : createVisibilityStatus("hidden", getTooltipOptions(props.tooltips.noElementsInExclusiveAlwaysDrawnList, ignoreTooltip));
591
684
  }
592
685
  const status = props.defaultStatus();
593
- if ((status.state === "visible" && neverDrawn?.size) || (status.state === "hidden" && alwaysDrawn?.size)) {
686
+ if ((status.state === "visible" && neverDrawnSize) || (status.state === "hidden" && alwaysDrawnSize)) {
594
687
  return createVisibilityStatus("partial", getTooltipOptions(undefined, ignoreTooltip));
595
688
  }
596
689
  return status;
@@ -606,21 +699,41 @@ class ModelsTreeVisibilityHandlerImpl {
606
699
  return of(props.defaultStatus());
607
700
  }
608
701
  if ("elements" in props) {
609
- return of(this.getVisibilityFromAlwaysAndNeverDrawnElementsImpl({
610
- ...props,
611
- alwaysDrawn: viewport.alwaysDrawn?.size ? setIntersection(props.elements, viewport.alwaysDrawn) : undefined,
612
- neverDrawn: viewport.neverDrawn?.size ? setIntersection(props.elements, viewport.neverDrawn) : undefined,
613
- totalCount: Id64.sizeOf(props.elements),
614
- ignoreTooltip,
702
+ const parentElementIdsPath = [...props.parentElementIdsPath, props.elements];
703
+ return forkJoin({
704
+ childAlwaysDrawn: this.getAlwaysOrNeverDrawnElements({
705
+ modelIds: props.modelIds,
706
+ categoryIds: props.rootCategoryIds,
707
+ parentElementIdsPath,
708
+ setType: "always",
709
+ }),
710
+ childNeverDrawn: this.getAlwaysOrNeverDrawnElements({
711
+ modelIds: props.modelIds,
712
+ categoryIds: props.rootCategoryIds,
713
+ parentElementIdsPath,
714
+ setType: "never",
715
+ }),
716
+ }).pipe(map(({ childAlwaysDrawn, childNeverDrawn }) => {
717
+ const alwaysDrawn = new Set([...childAlwaysDrawn, ...(viewport.alwaysDrawn?.size ? setIntersection(props.elements, viewport.alwaysDrawn) : [])]);
718
+ const neverDrawn = new Set([...childNeverDrawn, ...(viewport.neverDrawn?.size ? setIntersection(props.elements, viewport.neverDrawn) : [])]);
719
+ return this.getVisibilityFromAlwaysAndNeverDrawnElementsImpl({
720
+ ...props,
721
+ alwaysDrawn: alwaysDrawn.size > 0 ? alwaysDrawn : undefined,
722
+ neverDrawn: neverDrawn.size > 0 ? neverDrawn : undefined,
723
+ totalCount: props.childrenCount + Id64.sizeOf(props.elements),
724
+ ignoreTooltip,
725
+ });
615
726
  }));
616
727
  }
617
728
  const { modelId, categoryIds } = props.categoryProps;
618
- return from(Id64.iterable(categoryIds)).pipe(mergeMap((categoryId) => forkJoin({
619
- categoryId: of(categoryId),
620
- totalCount: this.#idsCache.getCategoryElementsCount(modelId, categoryId),
621
- alwaysDrawn: this.getAlwaysOrNeverDrawnElements({ modelIds: modelId, categoryIds: categoryId, setType: "always" }),
622
- neverDrawn: this.getAlwaysOrNeverDrawnElements({ modelIds: modelId, categoryIds: categoryId, setType: "never" }),
623
- })),
729
+ return from(Id64.iterable(categoryIds)).pipe(releaseMainThreadOnItemsCount(100), mergeMap((categoryId) => {
730
+ return forkJoin({
731
+ categoryId: of(categoryId),
732
+ totalCount: this.#idsCache.getCategoryElementsCount(modelId, categoryId),
733
+ alwaysDrawn: this.getAlwaysOrNeverDrawnElements({ modelIds: modelId, categoryIds: categoryId, setType: "always" }),
734
+ neverDrawn: this.getAlwaysOrNeverDrawnElements({ modelIds: modelId, categoryIds: categoryId, setType: "never" }),
735
+ });
736
+ }),
624
737
  // There is a known bug:
625
738
  // Categories that don't have root elements will make visibility result incorrect
626
739
  // E.g.:
@@ -639,9 +752,31 @@ class ModelsTreeVisibilityHandlerImpl {
639
752
  }), mergeVisibilityStatuses());
640
753
  }
641
754
  getAlwaysOrNeverDrawnElements(props) {
642
- return this.#alwaysAndNeverDrawnElements
643
- .getElementsTree(props)
644
- .pipe(map((childrenTree) => getIdsFromChildrenTree({ tree: childrenTree, predicate: ({ treeEntry }) => treeEntry.isInAlwaysOrNeverDrawnSet })));
755
+ if (!this.#filteredTree) {
756
+ return this.#alwaysAndNeverDrawnElements
757
+ .getElementsTree(props)
758
+ .pipe(map((childrenTree) => getIdsFromChildrenTree({ tree: childrenTree, predicate: ({ treeEntry }) => treeEntry.isInAlwaysOrNeverDrawnSet })));
759
+ }
760
+ // When filtered tree is present, children tree retrieved from alwaysAndNeverDrawnElements may include elements that are not present in filtered tree.
761
+ // Need to filter out such elements.
762
+ return forkJoin({
763
+ filteredTree: from(this.#filteredTree),
764
+ childrenTree: this.#alwaysAndNeverDrawnElements.getElementsTree(props),
765
+ }).pipe(map(({ filteredTree, childrenTree }) => {
766
+ const parentIdsPath = [props.modelIds];
767
+ if ("categoryIds" in props) {
768
+ parentIdsPath.push(props.categoryIds);
769
+ if ("parentElementIdsPath" in props) {
770
+ props.parentElementIdsPath.forEach((parentElementIds) => parentIdsPath.push(parentElementIds));
771
+ }
772
+ }
773
+ // Get all ids that exist in filtered tree.
774
+ const elements = filteredTree.getElementsFromUnfilteredChildrenTree({ parentIdsPath, childrenTree });
775
+ return elements
776
+ ? // Need to filter elements that are in always/never drawn set.
777
+ setIntersection(elements, getIdsFromChildrenTree({ tree: childrenTree, predicate: ({ treeEntry }) => treeEntry.isInAlwaysOrNeverDrawnSet }))
778
+ : new Set();
779
+ }));
645
780
  }
646
781
  clearAlwaysAndNeverDrawnElements(props) {
647
782
  return forkJoin({
@@ -662,7 +797,9 @@ class ModelsTreeVisibilityHandlerImpl {
662
797
  const categoryId = ModelsTreeNode.getCategoryId(node);
663
798
  assert(!!modelId && !!categoryId);
664
799
  const elementIds = new Set(node.groupedInstanceKeys.map((key) => key.id));
665
- return { modelId, categoryId, elementIds };
800
+ const childrenCount = node.extendedData?.childrenCount;
801
+ const isFiltered = node.extendedData?.isFiltered;
802
+ return { modelId, categoryId, elementIds, childrenCount, isFiltered };
666
803
  }
667
804
  getSubModeledElementsVisibilityStatus({ parentNodeVisibilityStatus, haveSubModel, tooltips, ignoreTooltips, }) {
668
805
  return (obs) => {
@@ -672,7 +809,7 @@ class ModelsTreeVisibilityHandlerImpl {
672
809
  if (haveSubModel === "yes") {
673
810
  return of(modeledElementIds);
674
811
  }
675
- return from(Id64.iterable(modeledElementIds)).pipe(mergeMap((elementId) => forkJoin({
812
+ return from(Id64.iterable(modeledElementIds)).pipe(releaseMainThreadOnItemsCount(100), mergeMap((elementId) => forkJoin({
676
813
  hasSubModel: this.#idsCache.hasSubModel(elementId),
677
814
  elementId: of(elementId),
678
815
  })), filter(({ hasSubModel }) => hasSubModel), map(({ elementId }) => elementId), toArray());
@@ -682,51 +819,11 @@ class ModelsTreeVisibilityHandlerImpl {
682
819
  if (Id64.sizeOf(modeledElementIds) === 0) {
683
820
  return of(parentNodeVisibilityStatus);
684
821
  }
685
- return this.getModelVisibilityStatus({ modelIds: modeledElementIds }).pipe(startWith(parentNodeVisibilityStatus), mergeVisibilityStatuses(tooltips, ignoreTooltips));
822
+ return this.getModelVisibilityStatus({ modelIds: typeof modeledElementIds === "string" ? [modeledElementIds] : modeledElementIds }).pipe(startWith(parentNodeVisibilityStatus), mergeVisibilityStatuses(tooltips, ignoreTooltips));
686
823
  }));
687
824
  };
688
825
  }
689
826
  }
690
- function mergeVisibilities(obs) {
691
- return obs.pipe(reduceWhile((x) => x.allVisible || x.allHidden, (acc, val) => {
692
- acc.allVisible &&= val === "visible";
693
- acc.allHidden &&= val === "hidden";
694
- return acc;
695
- }, { allVisible: true, allHidden: true }), map((x) => {
696
- if (!x) {
697
- return "empty";
698
- }
699
- return x.allVisible ? "visible" : x.allHidden ? "hidden" : "partial";
700
- }));
701
- }
702
- function mergeVisibilityStatuses(tooltipMap, ignoreTooltip) {
703
- return (obs) => {
704
- return obs.pipe(map((visibilityStatus) => visibilityStatus.state), mergeVisibilities, map((visibility) => {
705
- if (visibility === "empty") {
706
- visibility = "visible";
707
- }
708
- return createVisibilityStatus(visibility, getTooltipOptions(tooltipMap?.[visibility], ignoreTooltip));
709
- }));
710
- };
711
- }
712
- function setDifference(lhs, rhs) {
713
- const result = new Set();
714
- for (const x of lhs) {
715
- if (!rhs.has(x)) {
716
- result.add(x);
717
- }
718
- }
719
- return result;
720
- }
721
- function setIntersection(lhs, rhs) {
722
- const result = new Set();
723
- for (const x of lhs) {
724
- if (rhs.has(x)) {
725
- result.add(x);
726
- }
727
- }
728
- return result;
729
- }
730
827
  /**
731
828
  * Enables display of all given models. Also enables display of all categories and clears always and
732
829
  * never drawn lists in the viewport.
@@ -793,9 +890,33 @@ export async function toggleModels(models, enable, viewport) {
793
890
  export function areAllModelsVisible(models, viewport) {
794
891
  return models.length !== 0 ? models.every((id) => viewport.viewsModel(id)) : false;
795
892
  }
796
- function getTooltipOptions(key, ignoreTooltip) {
797
- return {
798
- useTooltip: ignoreTooltip ? false : key,
799
- };
893
+ function getParentElementsIdsPath(props) {
894
+ const parentElementIdsPath = new Array();
895
+ const modelIndex = props.parentKeys.findIndex((parentKey) => HierarchyNodeKey.isInstances(parentKey) && parentKey.instanceKeys.some((instanceKey) => instanceKey.id === props.modelId));
896
+ if (modelIndex === -1) {
897
+ return parentElementIdsPath;
898
+ }
899
+ // Hierarchy is model -> category -> root elements.
900
+ // Root elements index is 2 more than models'.
901
+ const firstParentIndex = modelIndex + 2;
902
+ for (let i = firstParentIndex; i < props.parentKeys.length; ++i) {
903
+ const parentKey = props.parentKeys[i];
904
+ if (!HierarchyNodeKey.isInstances(parentKey)) {
905
+ continue;
906
+ }
907
+ parentElementIdsPath.push(parentKey.instanceKeys.map(({ id }) => id));
908
+ }
909
+ return parentElementIdsPath;
910
+ }
911
+ function getRootCategoryIds(props) {
912
+ const modelIndex = props.parentKeys.findIndex((parentKey) => HierarchyNodeKey.isInstances(parentKey) && parentKey.instanceKeys.some((instanceKey) => instanceKey.id === props.modelId));
913
+ if (modelIndex === -1 || modelIndex + 1 === props.parentKeys.length) {
914
+ return undefined;
915
+ }
916
+ const rootCategoryKeys = props.parentKeys[modelIndex + 1];
917
+ if (!HierarchyNodeKey.isInstances(rootCategoryKeys)) {
918
+ return undefined;
919
+ }
920
+ return rootCategoryKeys.instanceKeys.map(({ id }) => id);
800
921
  }
801
922
  //# sourceMappingURL=ModelsTreeVisibilityHandler.js.map