@itwin/tree-widget-react 3.16.1 → 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 (142) hide show
  1. package/CHANGELOG.md +24 -2
  2. package/lib/cjs/tree-widget-react/components/SelectableTree.js.map +1 -1
  3. package/lib/cjs/tree-widget-react/components/TreeSelector.js.map +1 -1
  4. package/lib/cjs/tree-widget-react/components/TreeWidgetUiItemsProvider.js +1 -1
  5. package/lib/cjs/tree-widget-react/components/TreeWidgetUiItemsProvider.js.map +1 -1
  6. package/lib/cjs/tree-widget-react/components/tree-header/TreeHeader.js.map +1 -1
  7. package/lib/cjs/tree-widget-react/components/tree-header/TreeWithHeader.js.map +1 -1
  8. package/lib/cjs/tree-widget-react/components/trees/categories-tree/CategoriesTree.d.ts +2 -2
  9. package/lib/cjs/tree-widget-react/components/trees/categories-tree/CategoriesTree.js +2 -1
  10. package/lib/cjs/tree-widget-react/components/trees/categories-tree/CategoriesTree.js.map +1 -1
  11. package/lib/cjs/tree-widget-react/components/trees/categories-tree/CategoriesTreeButtons.js.map +1 -1
  12. package/lib/cjs/tree-widget-react/components/trees/categories-tree/CategoriesTreeComponent.d.ts +1 -1
  13. package/lib/cjs/tree-widget-react/components/trees/categories-tree/CategoriesTreeComponent.js.map +1 -1
  14. package/lib/cjs/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.d.ts +13 -0
  15. package/lib/cjs/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.js +92 -74
  16. package/lib/cjs/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.js.map +1 -1
  17. package/lib/cjs/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.d.ts +3 -1
  18. package/lib/cjs/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.js +21 -8
  19. package/lib/cjs/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.js.map +1 -1
  20. package/lib/cjs/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.d.ts +21 -9
  21. package/lib/cjs/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.js +300 -251
  22. package/lib/cjs/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.js.map +1 -1
  23. package/lib/cjs/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.d.ts +2 -0
  24. package/lib/cjs/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.js +66 -107
  25. package/lib/cjs/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.js.map +1 -1
  26. package/lib/cjs/tree-widget-react/components/trees/common/CategoriesVisibilityUtils.js +22 -12
  27. package/lib/cjs/tree-widget-react/components/trees/common/CategoriesVisibilityUtils.js.map +1 -1
  28. package/lib/cjs/tree-widget-react/components/trees/common/UseActiveViewport.js.map +1 -1
  29. package/lib/cjs/tree-widget-react/components/trees/common/UseHierarchyVisibility.js.map +1 -1
  30. package/lib/cjs/tree-widget-react/components/trees/common/components/ProgressOverlay.js.map +1 -1
  31. package/lib/cjs/tree-widget-react/components/trees/common/components/Tree.d.ts +2 -2
  32. package/lib/cjs/tree-widget-react/components/trees/common/components/Tree.js.map +1 -1
  33. package/lib/cjs/tree-widget-react/components/trees/common/components/TreeNodeCheckbox.d.ts +1 -1
  34. package/lib/cjs/tree-widget-react/components/trees/common/components/TreeNodeCheckbox.js.map +1 -1
  35. package/lib/cjs/tree-widget-react/components/trees/common/components/TreeRenderer.d.ts +1 -1
  36. package/lib/cjs/tree-widget-react/components/trees/common/components/TreeRenderer.js +2 -2
  37. package/lib/cjs/tree-widget-react/components/trees/common/components/TreeRenderer.js.map +1 -1
  38. package/lib/cjs/tree-widget-react/components/trees/common/components/VisibilityTree.d.ts +3 -3
  39. package/lib/cjs/tree-widget-react/components/trees/common/components/VisibilityTree.js.map +1 -1
  40. package/lib/cjs/tree-widget-react/components/trees/common/components/VisibilityTreeRenderer.d.ts +1 -1
  41. package/lib/cjs/tree-widget-react/components/trees/common/components/VisibilityTreeRenderer.js.map +1 -1
  42. package/lib/cjs/tree-widget-react/components/trees/common/internal/Utils.d.ts +25 -0
  43. package/lib/cjs/tree-widget-react/components/trees/common/internal/Utils.js +83 -0
  44. package/lib/cjs/tree-widget-react/components/trees/common/internal/Utils.js.map +1 -0
  45. package/lib/cjs/tree-widget-react/components/trees/models-tree/ModelsTree.d.ts +1 -1
  46. package/lib/cjs/tree-widget-react/components/trees/models-tree/ModelsTree.js.map +1 -1
  47. package/lib/cjs/tree-widget-react/components/trees/models-tree/ModelsTreeButtons.js.map +1 -1
  48. package/lib/cjs/tree-widget-react/components/trees/models-tree/ModelsTreeComponent.js.map +1 -1
  49. package/lib/cjs/tree-widget-react/components/trees/models-tree/ModelsTreeDefinition.d.ts +1 -0
  50. package/lib/cjs/tree-widget-react/components/trees/models-tree/ModelsTreeDefinition.js +86 -28
  51. package/lib/cjs/tree-widget-react/components/trees/models-tree/ModelsTreeDefinition.js.map +1 -1
  52. package/lib/cjs/tree-widget-react/components/trees/models-tree/UseModelsTree.js +2 -1
  53. package/lib/cjs/tree-widget-react/components/trees/models-tree/UseModelsTree.js.map +1 -1
  54. package/lib/cjs/tree-widget-react/components/trees/models-tree/Utils.d.ts +5 -6
  55. package/lib/cjs/tree-widget-react/components/trees/models-tree/Utils.js +5 -17
  56. package/lib/cjs/tree-widget-react/components/trees/models-tree/Utils.js.map +1 -1
  57. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/AlwaysAndNeverDrawnElementInfo.js +5 -4
  58. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/AlwaysAndNeverDrawnElementInfo.js.map +1 -1
  59. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/FilteredTree.d.ts +7 -1
  60. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/FilteredTree.js +52 -0
  61. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/FilteredTree.js.map +1 -1
  62. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/ModelsTreeIdsCache.d.ts +28 -12
  63. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/ModelsTreeIdsCache.js +382 -278
  64. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/ModelsTreeIdsCache.js.map +1 -1
  65. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/ModelsTreeNode.d.ts +1 -1
  66. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/ModelsTreeNode.js.map +1 -1
  67. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/ModelsTreeVisibilityHandler.js +272 -139
  68. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/ModelsTreeVisibilityHandler.js.map +1 -1
  69. package/lib/cjs/tree-widget-react-internal.d.ts +2 -1
  70. package/lib/cjs/tree-widget-react-internal.js +4 -1
  71. package/lib/cjs/tree-widget-react-internal.js.map +1 -1
  72. package/lib/esm/tree-widget-react/components/SelectableTree.js.map +1 -1
  73. package/lib/esm/tree-widget-react/components/TreeSelector.js.map +1 -1
  74. package/lib/esm/tree-widget-react/components/TreeWidgetUiItemsProvider.js +1 -1
  75. package/lib/esm/tree-widget-react/components/TreeWidgetUiItemsProvider.js.map +1 -1
  76. package/lib/esm/tree-widget-react/components/tree-header/TreeHeader.js.map +1 -1
  77. package/lib/esm/tree-widget-react/components/tree-header/TreeWithHeader.js.map +1 -1
  78. package/lib/esm/tree-widget-react/components/trees/categories-tree/CategoriesTree.d.ts +2 -2
  79. package/lib/esm/tree-widget-react/components/trees/categories-tree/CategoriesTree.js +2 -1
  80. package/lib/esm/tree-widget-react/components/trees/categories-tree/CategoriesTree.js.map +1 -1
  81. package/lib/esm/tree-widget-react/components/trees/categories-tree/CategoriesTreeButtons.js.map +1 -1
  82. package/lib/esm/tree-widget-react/components/trees/categories-tree/CategoriesTreeComponent.d.ts +1 -1
  83. package/lib/esm/tree-widget-react/components/trees/categories-tree/CategoriesTreeComponent.js.map +1 -1
  84. package/lib/esm/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.d.ts +13 -0
  85. package/lib/esm/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.js +92 -74
  86. package/lib/esm/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.js.map +1 -1
  87. package/lib/esm/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.d.ts +3 -1
  88. package/lib/esm/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.js +22 -9
  89. package/lib/esm/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.js.map +1 -1
  90. package/lib/esm/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.d.ts +21 -9
  91. package/lib/esm/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.js +301 -252
  92. package/lib/esm/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.js.map +1 -1
  93. package/lib/esm/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.d.ts +2 -0
  94. package/lib/esm/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.js +67 -108
  95. package/lib/esm/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.js.map +1 -1
  96. package/lib/esm/tree-widget-react/components/trees/common/CategoriesVisibilityUtils.js +23 -13
  97. package/lib/esm/tree-widget-react/components/trees/common/CategoriesVisibilityUtils.js.map +1 -1
  98. package/lib/esm/tree-widget-react/components/trees/common/UseActiveViewport.js.map +1 -1
  99. package/lib/esm/tree-widget-react/components/trees/common/UseHierarchyVisibility.js.map +1 -1
  100. package/lib/esm/tree-widget-react/components/trees/common/components/ProgressOverlay.js.map +1 -1
  101. package/lib/esm/tree-widget-react/components/trees/common/components/Tree.d.ts +2 -2
  102. package/lib/esm/tree-widget-react/components/trees/common/components/Tree.js.map +1 -1
  103. package/lib/esm/tree-widget-react/components/trees/common/components/TreeNodeCheckbox.d.ts +1 -1
  104. package/lib/esm/tree-widget-react/components/trees/common/components/TreeNodeCheckbox.js.map +1 -1
  105. package/lib/esm/tree-widget-react/components/trees/common/components/TreeRenderer.d.ts +1 -1
  106. package/lib/esm/tree-widget-react/components/trees/common/components/TreeRenderer.js +2 -2
  107. package/lib/esm/tree-widget-react/components/trees/common/components/TreeRenderer.js.map +1 -1
  108. package/lib/esm/tree-widget-react/components/trees/common/components/VisibilityTree.d.ts +3 -3
  109. package/lib/esm/tree-widget-react/components/trees/common/components/VisibilityTree.js.map +1 -1
  110. package/lib/esm/tree-widget-react/components/trees/common/components/VisibilityTreeRenderer.d.ts +1 -1
  111. package/lib/esm/tree-widget-react/components/trees/common/components/VisibilityTreeRenderer.js.map +1 -1
  112. package/lib/esm/tree-widget-react/components/trees/common/internal/Utils.d.ts +25 -0
  113. package/lib/esm/tree-widget-react/components/trees/common/internal/Utils.js +74 -0
  114. package/lib/esm/tree-widget-react/components/trees/common/internal/Utils.js.map +1 -0
  115. package/lib/esm/tree-widget-react/components/trees/models-tree/ModelsTree.d.ts +1 -1
  116. package/lib/esm/tree-widget-react/components/trees/models-tree/ModelsTree.js.map +1 -1
  117. package/lib/esm/tree-widget-react/components/trees/models-tree/ModelsTreeButtons.js.map +1 -1
  118. package/lib/esm/tree-widget-react/components/trees/models-tree/ModelsTreeComponent.js.map +1 -1
  119. package/lib/esm/tree-widget-react/components/trees/models-tree/ModelsTreeDefinition.d.ts +1 -0
  120. package/lib/esm/tree-widget-react/components/trees/models-tree/ModelsTreeDefinition.js +83 -25
  121. package/lib/esm/tree-widget-react/components/trees/models-tree/ModelsTreeDefinition.js.map +1 -1
  122. package/lib/esm/tree-widget-react/components/trees/models-tree/UseModelsTree.js +2 -1
  123. package/lib/esm/tree-widget-react/components/trees/models-tree/UseModelsTree.js.map +1 -1
  124. package/lib/esm/tree-widget-react/components/trees/models-tree/Utils.d.ts +5 -6
  125. package/lib/esm/tree-widget-react/components/trees/models-tree/Utils.js +4 -16
  126. package/lib/esm/tree-widget-react/components/trees/models-tree/Utils.js.map +1 -1
  127. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/AlwaysAndNeverDrawnElementInfo.js +4 -3
  128. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/AlwaysAndNeverDrawnElementInfo.js.map +1 -1
  129. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/FilteredTree.d.ts +7 -1
  130. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/FilteredTree.js +52 -0
  131. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/FilteredTree.js.map +1 -1
  132. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/ModelsTreeIdsCache.d.ts +28 -12
  133. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/ModelsTreeIdsCache.js +382 -278
  134. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/ModelsTreeIdsCache.js.map +1 -1
  135. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/ModelsTreeNode.d.ts +1 -1
  136. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/ModelsTreeNode.js.map +1 -1
  137. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/ModelsTreeVisibilityHandler.js +257 -124
  138. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/ModelsTreeVisibilityHandler.js.map +1 -1
  139. package/lib/esm/tree-widget-react-internal.d.ts +2 -1
  140. package/lib/esm/tree-widget-react-internal.js +2 -1
  141. package/lib/esm/tree-widget-react-internal.js.map +1 -1
  142. 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());
@@ -152,7 +199,7 @@ class ModelsTreeVisibilityHandlerImpl {
152
199
  if (!this.#props.viewport.view.isSpatialView()) {
153
200
  return of(createVisibilityStatus("disabled", getTooltipOptions("modelsTree.subject.nonSpatialView", ignoreTooltip)));
154
201
  }
155
- return from(this.#idsCache.getSubjectModelIds(subjectIds)).pipe(mergeMap((modelIds) => this.getModelVisibilityStatus({ modelIds, ignoreTooltip: true })), mergeVisibilityStatuses({
202
+ return this.#idsCache.getSubjectModelIds(subjectIds).pipe(mergeMap((modelIds) => this.getModelVisibilityStatus({ modelIds, ignoreTooltip: true })), mergeVisibilityStatuses({
156
203
  visible: "modelsTree.subject.allModelsVisible",
157
204
  hidden: "modelsTree.subject.allModelsHidden",
158
205
  partial: "modelsTree.subject.someModelsHidden",
@@ -168,14 +215,18 @@ class ModelsTreeVisibilityHandlerImpl {
168
215
  }
169
216
  return from(Id64.iterable(modelIds)).pipe(distinct(), mergeMap((modelId) => {
170
217
  if (!viewport.view.viewsModel(modelId)) {
171
- return from(this.#idsCache.getModelCategories(modelId)).pipe(mergeMap((categoryIds) => from(this.#idsCache.getCategoriesModeledElements(modelId, categoryIds))), this.getSubModeledElementsVisibilityStatus({
218
+ return this.#idsCache.getModelCategories(modelId).pipe(mergeMap((categoryIds) => this.#idsCache.getCategoriesModeledElements(modelId, categoryIds)), this.getSubModeledElementsVisibilityStatus({
172
219
  ignoreTooltips: ignoreTooltip,
173
220
  haveSubModel: "yes",
174
221
  tooltips: { visible: undefined, hidden: "modelsTree.model.hiddenThroughModelSelector", partial: "modelsTree.model.someSubModelsVisible" },
175
222
  parentNodeVisibilityStatus: createVisibilityStatus("hidden"),
176
223
  }));
177
224
  }
178
- return from(this.#idsCache.getModelCategories(modelId)).pipe(mergeMap((categoryIds) => categoryIds.length === 0 ? of(createVisibilityStatus("visible")) : this.getCategoryDisplayStatus({ modelId, categoryIds, ignoreTooltip: true })), mergeVisibilityStatuses({
225
+ return this.#idsCache.getModelCategories(modelId).pipe(mergeMap((categoryIds) => {
226
+ return categoryIds.length === 0
227
+ ? of(createVisibilityStatus("visible"))
228
+ : this.getCategoryDisplayStatus({ modelId, categoryIds, ignoreTooltip: true });
229
+ }), mergeVisibilityStatuses({
179
230
  visible: "modelsTree.model.allCategoriesVisible",
180
231
  partial: "modelsTree.model.someCategoriesHidden",
181
232
  hidden: "modelsTree.model.allCategoriesHidden",
@@ -221,7 +272,7 @@ class ModelsTreeVisibilityHandlerImpl {
221
272
  getCategoryDisplayStatus({ ignoreTooltip, ...props }) {
222
273
  const result = defer(() => {
223
274
  if (!this.#props.viewport.view.viewsModel(props.modelId)) {
224
- return from(this.#idsCache.getCategoriesModeledElements(props.modelId, props.categoryIds)).pipe(this.getSubModeledElementsVisibilityStatus({
275
+ return this.#idsCache.getCategoriesModeledElements(props.modelId, props.categoryIds).pipe(this.getSubModeledElementsVisibilityStatus({
225
276
  ignoreTooltips: ignoreTooltip,
226
277
  parentNodeVisibilityStatus: createVisibilityStatus("hidden"),
227
278
  tooltips: {
@@ -245,7 +296,7 @@ class ModelsTreeVisibilityHandlerImpl {
245
296
  : this.getDefaultCategoryVisibilityStatus({ modelId: props.modelId, categoryIds: props.categoryIds }),
246
297
  ignoreTooltip,
247
298
  }).pipe(mergeMap((visibilityStatusAlwaysAndNeverDraw) => {
248
- return from(this.#idsCache.getCategoriesModeledElements(props.modelId, props.categoryIds)).pipe(this.getSubModeledElementsVisibilityStatus({
299
+ return this.#idsCache.getCategoriesModeledElements(props.modelId, props.categoryIds).pipe(this.getSubModeledElementsVisibilityStatus({
249
300
  tooltips: {
250
301
  visible: undefined,
251
302
  hidden: "modelsTree.category.allElementsAndSubModelsHidden",
@@ -260,25 +311,33 @@ class ModelsTreeVisibilityHandlerImpl {
260
311
  return createVisibilityHandlerResult(this, props, result, this.#props.overrides?.getCategoryDisplayStatus);
261
312
  }
262
313
  getClassGroupingNodeDisplayStatus(node) {
263
- 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);
264
317
  const result = this.getElementsDisplayStatus({
265
318
  elementIds,
266
319
  modelId,
267
320
  categoryId,
321
+ rootCategoryIds,
322
+ parentElementIdsPath: getParentElementsIdsPath({ parentKeys: node.parentKeys, modelId }),
323
+ childrenCount: childrenCount ?? 0,
268
324
  });
269
325
  return createVisibilityHandlerResult(this, { node }, result, this.#props.overrides?.getElementGroupingNodeDisplayStatus);
270
326
  }
271
- getElementDisplayStatus({ elementIds, modelId, categoryId, }) {
327
+ getElementDisplayStatus({ parentKeys, totalChildrenCount, elementIds, modelId, rootCategoryIds, categoryId, }) {
272
328
  const result = this.getElementsDisplayStatus({
329
+ rootCategoryIds,
273
330
  elementIds,
274
331
  modelId,
275
332
  categoryId,
333
+ parentElementIdsPath: getParentElementsIdsPath({ parentKeys, modelId }),
334
+ childrenCount: totalChildrenCount,
276
335
  });
277
336
  return createVisibilityHandlerResult(this, { elementId: elementIds[0], modelId, categoryId }, result, this.#props.overrides?.getElementDisplayStatus);
278
337
  }
279
338
  getElementsDisplayStatus(props) {
280
339
  return defer(() => {
281
- const { modelId, categoryId, elementIds } = props;
340
+ const { modelId, categoryId, elementIds, parentElementIdsPath, childrenCount, rootCategoryIds } = props;
282
341
  if (!this.#props.viewport.view.viewsModel(modelId)) {
283
342
  return of(elementIds).pipe(this.getSubModeledElementsVisibilityStatus({
284
343
  tooltips: {
@@ -292,6 +351,10 @@ class ModelsTreeVisibilityHandlerImpl {
292
351
  }
293
352
  return this.getVisibilityFromAlwaysAndNeverDrawnElements({
294
353
  elements: elementIds,
354
+ parentElementIdsPath,
355
+ modelIds: modelId,
356
+ rootCategoryIds,
357
+ childrenCount,
295
358
  defaultStatus: () => {
296
359
  const status = this.getDefaultCategoryVisibilityStatus({ categoryIds: categoryId, modelId, ignoreTooltip: true });
297
360
  return createVisibilityStatus(status.state, getTooltipOptions(`modelsTree.groupingNode.${status.state}ThroughCategory`));
@@ -327,7 +390,9 @@ class ModelsTreeVisibilityHandlerImpl {
327
390
  if (!HierarchyNode.isInstancesNode(node)) {
328
391
  return EMPTY;
329
392
  }
330
- 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) {
331
396
  return this.changeFilteredNodeVisibility({ node, on });
332
397
  }
333
398
  if (ModelsTreeNode.isSubjectNode(node)) {
@@ -351,12 +416,18 @@ class ModelsTreeVisibilityHandlerImpl {
351
416
  if (!categoryId) {
352
417
  return EMPTY;
353
418
  }
354
- return this.changeElementsState({
355
- 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,
356
426
  modelId,
427
+ children: children.size > 0 ? children : undefined,
357
428
  categoryId,
358
429
  on,
359
- });
430
+ })));
360
431
  });
361
432
  if (this.#props.viewport.isAlwaysDrawnExclusive) {
362
433
  return concat(this.removeAlwaysDrawnExclusive(), changeObs);
@@ -365,7 +436,7 @@ class ModelsTreeVisibilityHandlerImpl {
365
436
  }
366
437
  changeFilteredNodeVisibility({ on, ...props }) {
367
438
  assert(this.#filteredTree !== undefined);
368
- 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 }) => {
369
440
  const observables = new Array();
370
441
  if (subjects?.size) {
371
442
  observables.push(this.changeSubjectNodeState([...subjects], on));
@@ -380,16 +451,30 @@ class ModelsTreeVisibilityHandlerImpl {
380
451
  })));
381
452
  }
382
453
  if (elements?.size) {
383
- 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]) => {
384
463
  const { modelId, categoryId } = parseCategoryKey(categoryKey);
385
- return this.changeElementsState({ modelId, categoryId, elementIds: new Set([...elementsMap.keys()]), on });
386
- })));
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
+ })))));
387
472
  }
388
473
  return merge(...observables);
389
474
  }));
390
475
  }
391
476
  removeAlwaysDrawnExclusive() {
392
- return from(this.#idsCache.getAllCategories()).pipe(map((categoryIds) => {
477
+ return this.#idsCache.getAllCategories().pipe(map((categoryIds) => {
393
478
  this.#props.viewport.changeCategoryDisplay(categoryIds, false, false);
394
479
  this.#props.viewport.clearNeverDrawn();
395
480
  this.#props.viewport.perModelCategoryVisibility.clearOverrides();
@@ -401,7 +486,7 @@ class ModelsTreeVisibilityHandlerImpl {
401
486
  if (!this.#props.viewport.view.isSpatialView()) {
402
487
  return EMPTY;
403
488
  }
404
- return from(this.#idsCache.getSubjectModelIds(ids)).pipe(mergeMap((modelIds) => this.changeModelState({ ids: modelIds, on })));
489
+ return this.#idsCache.getSubjectModelIds(ids).pipe(mergeMap((modelIds) => this.changeModelState({ ids: modelIds, on })));
405
490
  });
406
491
  return createVisibilityHandlerResult(this, { ids, on }, result, this.#props.overrides?.changeSubjectNodeState);
407
492
  }
@@ -418,13 +503,13 @@ class ModelsTreeVisibilityHandlerImpl {
418
503
  const idsObs = from(Id64.iterable(ids));
419
504
  if (!on) {
420
505
  viewport.changeModelDisplay(ids, false);
421
- return idsObs.pipe(mergeMap(async (modelId) => ({ modelId, categoryIds: await this.#idsCache.getModelCategories(modelId) })), mergeMap(({ modelId, categoryIds }) => from(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 })));
422
507
  }
423
508
  return concat(defer(() => {
424
509
  viewport.perModelCategoryVisibility.clearOverrides(ids);
425
510
  return from(viewport.addViewedModels(ids));
426
511
  }), idsObs.pipe(mergeMap((modelId) => {
427
- return from(this.#idsCache.getModelCategories(modelId)).pipe(mergeMap((categoryIds) => this.changeCategoryState({ categoryIds, modelId, on: true })));
512
+ return this.#idsCache.getModelCategories(modelId).pipe(mergeMap((categoryIds) => this.changeCategoryState({ categoryIds, modelId, on: true })));
428
513
  })));
429
514
  });
430
515
  return createVisibilityHandlerResult(this, props, result, this.#props.overrides?.changeModelState);
@@ -434,15 +519,12 @@ class ModelsTreeVisibilityHandlerImpl {
434
519
  return forkJoin({
435
520
  categories: this.#idsCache.getModelCategories(modelId),
436
521
  alwaysDrawnElements: this.getAlwaysOrNeverDrawnElements({ modelIds: modelId, setType: "always" }),
437
- }).pipe(mergeMap(async ({ categories, alwaysDrawnElements }) => {
522
+ }).pipe(mergeMap(({ categories, alwaysDrawnElements }) => {
438
523
  const alwaysDrawn = this.#props.viewport.alwaysDrawn;
439
524
  if (alwaysDrawn && alwaysDrawnElements) {
440
525
  viewport.setAlwaysDrawn(setDifference(alwaysDrawn, alwaysDrawnElements));
441
526
  }
442
- categories.forEach((categoryId) => {
443
- this.changeCategoryStateInViewportAccordingToModelVisibility(modelId, categoryId, false);
444
- });
445
- 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)));
446
528
  }));
447
529
  }
448
530
  changeCategoryStateInViewportAccordingToModelVisibility(modelId, categoryId, on) {
@@ -464,24 +546,37 @@ class ModelsTreeVisibilityHandlerImpl {
464
546
  const result = defer(() => {
465
547
  const viewport = this.#props.viewport;
466
548
  const { modelId, categoryIds, on } = props;
467
- return concat(props.on && !viewport.view.viewsModel(modelId) ? this.showModelWithoutAnyCategoriesOrElements(modelId) : EMPTY, defer(() => {
468
- for (const categoryId of Id64.iterable(categoryIds)) {
469
- this.changeCategoryStateInViewportAccordingToModelVisibility(modelId, categoryId, on);
470
- }
471
- return this.clearAlwaysAndNeverDrawnElements(props);
472
- }), from(this.#idsCache.getCategoriesModeledElements(modelId, categoryIds)).pipe(mergeMap((modeledElementIds) => this.changeModelState({ ids: modeledElementIds, on }))));
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
550
+ .getCategoriesModeledElements(modelId, categoryIds)
551
+ .pipe(mergeMap((modeledElementIds) => this.changeModelState({ ids: modeledElementIds, on }))));
473
552
  });
474
553
  return createVisibilityHandlerResult(this, props, result, this.#props.overrides?.changeCategoryState);
475
554
  }
476
555
  doChangeElementsState(props) {
477
556
  return defer(() => {
478
- const { modelId, categoryId, elementIds, on } = props;
557
+ const { modelId, categoryId, elementIds, on, children } = props;
479
558
  const viewport = this.#props.viewport;
480
559
  return concat(on && !viewport.view.viewsModel(modelId) ? this.showModelWithoutAnyCategoriesOrElements(modelId) : EMPTY, defer(() => {
481
560
  const categoryVisibility = this.getDefaultCategoryVisibilityStatus({ categoryIds: categoryId, modelId, ignoreTooltip: true });
482
- const isDisplayedByDefault = categoryVisibility.state === "visible";
483
- return this.queueElementsVisibilityChange(elementIds, on, isDisplayedByDefault);
484
- }), from(elementIds).pipe(mergeMap(async (elementId) => ({ elementId, isSubModel: await this.#idsCache.hasSubModel(elementId) })), filter(({ isSubModel }) => isSubModel), map(({ elementId }) => elementId), toArray(), mergeMap((subModelIds) => this.changeModelState({ ids: subModelIds, on }))));
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);
576
+ }), from(elementIds).pipe(mergeMap((elementId) => forkJoin({
577
+ elementId: of(elementId),
578
+ isSubModel: this.#idsCache.hasSubModel(elementId),
579
+ })), filter(({ isSubModel }) => isSubModel), map(({ elementId }) => elementId), toArray(), mergeMap((subModelIds) => this.changeModelState({ ids: subModelIds, on }))));
485
580
  });
486
581
  }
487
582
  /**
@@ -489,7 +584,14 @@ class ModelsTreeVisibilityHandlerImpl {
489
584
  * @see `changeElementState`
490
585
  */
491
586
  changeElementGroupingNodeState(node, on) {
492
- 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
+ })));
493
595
  return createVisibilityHandlerResult(this, { node, on }, result, this.#props.overrides?.changeElementGroupingNodeState);
494
596
  }
495
597
  /**
@@ -504,7 +606,7 @@ class ModelsTreeVisibilityHandlerImpl {
504
606
  const finishedSubject = new Subject();
505
607
  // observable to track if visibility change is finished/cancelled
506
608
  const changeFinished = finishedSubject.pipe(startWith(false), shareReplay(1), filter((finished) => finished));
507
- const changeObservable = from(elementIds).pipe(
609
+ const changeObservable = from(Id64.iterable(elementIds)).pipe(
508
610
  // check if visibility change is not finished (cancelled) due to change overall change request being cancelled
509
611
  takeUntil(changeFinished), this.changeElementStateNoChildrenOperator({ on, isDisplayedByDefault: visibleByDefault }), tap({
510
612
  next: () => {
@@ -535,7 +637,7 @@ class ModelsTreeVisibilityHandlerImpl {
535
637
  const wasRemoved = acc.neverDrawn.delete(elementId);
536
638
  acc.changedNeverDrawn ||= wasRemoved;
537
639
  // If exclusive mode is enabled, we must add the element to the always drawn list.
538
- if ((!isDisplayedByDefault || isAlwaysDrawnExclusive) && !acc.alwaysDrawn.has(elementId)) {
640
+ if ((!isDisplayedByDefault(elementId) || isAlwaysDrawnExclusive) && !acc.alwaysDrawn.has(elementId)) {
539
641
  acc.alwaysDrawn.add(elementId);
540
642
  acc.changedAlwaysDrawn = true;
541
643
  }
@@ -544,7 +646,7 @@ class ModelsTreeVisibilityHandlerImpl {
544
646
  const wasRemoved = acc.alwaysDrawn.delete(elementId);
545
647
  acc.changedAlwaysDrawn ||= wasRemoved;
546
648
  // If exclusive mode is not enabled, we have to add the element to the never drawn list.
547
- if (isDisplayedByDefault && !isAlwaysDrawnExclusive && !acc.neverDrawn.has(elementId)) {
649
+ if (isDisplayedByDefault(elementId) && !isAlwaysDrawnExclusive && !acc.neverDrawn.has(elementId)) {
548
650
  acc.neverDrawn.add(elementId);
549
651
  acc.changedNeverDrawn = true;
550
652
  }
@@ -566,20 +668,22 @@ class ModelsTreeVisibilityHandlerImpl {
566
668
  if (totalCount === 0) {
567
669
  return props.defaultStatus();
568
670
  }
569
- if (neverDrawn?.size === totalCount) {
671
+ const neverDrawnSize = neverDrawn ? Id64.sizeOf(neverDrawn) : undefined;
672
+ const alwaysDrawnSize = alwaysDrawn ? Id64.sizeOf(alwaysDrawn) : undefined;
673
+ if (neverDrawnSize === totalCount) {
570
674
  return createVisibilityStatus("hidden", getTooltipOptions(props.tooltips.allElementsInNeverDrawnList, ignoreTooltip));
571
675
  }
572
- if (alwaysDrawn?.size === totalCount) {
676
+ if (alwaysDrawnSize === totalCount) {
573
677
  return createVisibilityStatus("visible", getTooltipOptions(props.tooltips.allElementsInAlwaysDrawnList, ignoreTooltip));
574
678
  }
575
679
  const viewport = this.#props.viewport;
576
680
  if (viewport.isAlwaysDrawnExclusive && viewport.alwaysDrawn?.size) {
577
- return alwaysDrawn?.size
681
+ return alwaysDrawnSize
578
682
  ? createVisibilityStatus("partial", getTooltipOptions(props.tooltips.elementsInBothAlwaysAndNeverDrawn, ignoreTooltip))
579
683
  : createVisibilityStatus("hidden", getTooltipOptions(props.tooltips.noElementsInExclusiveAlwaysDrawnList, ignoreTooltip));
580
684
  }
581
685
  const status = props.defaultStatus();
582
- if ((status.state === "visible" && neverDrawn?.size) || (status.state === "hidden" && alwaysDrawn?.size)) {
686
+ if ((status.state === "visible" && neverDrawnSize) || (status.state === "hidden" && alwaysDrawnSize)) {
583
687
  return createVisibilityStatus("partial", getTooltipOptions(undefined, ignoreTooltip));
584
688
  }
585
689
  return status;
@@ -595,44 +699,84 @@ class ModelsTreeVisibilityHandlerImpl {
595
699
  return of(props.defaultStatus());
596
700
  }
597
701
  if ("elements" in props) {
598
- return of(this.getVisibilityFromAlwaysAndNeverDrawnElementsImpl({
599
- ...props,
600
- alwaysDrawn: viewport.alwaysDrawn?.size ? setIntersection(props.elements, viewport.alwaysDrawn) : undefined,
601
- neverDrawn: viewport.neverDrawn?.size ? setIntersection(props.elements, viewport.neverDrawn) : undefined,
602
- totalCount: Id64.sizeOf(props.elements),
603
- ignoreTooltip,
604
- }));
605
- }
606
- const { modelId, categoryIds } = props.categoryProps;
607
- return from(Id64.iterable(categoryIds)).pipe(mergeMap((categoryId) => {
608
- const totalCount = this.#idsCache.getCategoryElementsCount(modelId, categoryId);
702
+ const parentElementIdsPath = [...props.parentElementIdsPath, props.elements];
609
703
  return forkJoin({
610
- totalCount,
611
- alwaysDrawn: this.getAlwaysOrNeverDrawnElements({ modelIds: modelId, categoryIds: categoryId, setType: "always" }),
612
- neverDrawn: this.getAlwaysOrNeverDrawnElements({ modelIds: modelId, categoryIds: categoryId, setType: "never" }),
613
- }).pipe(
614
- // There is a known bug:
615
- // Categories that don't have root elements will make visibility result incorrect
616
- // E.g.:
617
- // - CategoryA
618
- // - ElementA (CategoryA is visible)
619
- // - ChildElementB (CategoryB is hidden) ChildElementB is in always drawn list
620
- // Result will be "partial" because CategoryB will return hidden visibility, even though all elements are visible
621
- // TODO fix with: https://github.com/iTwin/viewer-components-react/issues/1100
622
- map((state) => {
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) : [])]);
623
719
  return this.getVisibilityFromAlwaysAndNeverDrawnElementsImpl({
624
720
  ...props,
625
- ...state,
626
- defaultStatus: () => props.defaultStatus(categoryId),
721
+ alwaysDrawn: alwaysDrawn.size > 0 ? alwaysDrawn : undefined,
722
+ neverDrawn: neverDrawn.size > 0 ? neverDrawn : undefined,
723
+ totalCount: props.childrenCount + Id64.sizeOf(props.elements),
627
724
  ignoreTooltip,
628
725
  });
629
726
  }));
727
+ }
728
+ const { modelId, categoryIds } = props.categoryProps;
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
+ }),
737
+ // There is a known bug:
738
+ // Categories that don't have root elements will make visibility result incorrect
739
+ // E.g.:
740
+ // - CategoryA
741
+ // - ElementA (CategoryA is visible)
742
+ // - ChildElementB (CategoryB is hidden) ChildElementB is in always drawn list
743
+ // Result will be "partial" because CategoryB will return hidden visibility, even though all elements are visible
744
+ // TODO fix with: https://github.com/iTwin/viewer-components-react/issues/1100
745
+ map((state) => {
746
+ return this.getVisibilityFromAlwaysAndNeverDrawnElementsImpl({
747
+ ...props,
748
+ ...state,
749
+ defaultStatus: () => props.defaultStatus(state.categoryId),
750
+ ignoreTooltip,
751
+ });
630
752
  }), mergeVisibilityStatuses());
631
753
  }
632
754
  getAlwaysOrNeverDrawnElements(props) {
633
- return this.#alwaysAndNeverDrawnElements
634
- .getElementsTree(props)
635
- .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
+ }));
636
780
  }
637
781
  clearAlwaysAndNeverDrawnElements(props) {
638
782
  return forkJoin({
@@ -653,7 +797,9 @@ class ModelsTreeVisibilityHandlerImpl {
653
797
  const categoryId = ModelsTreeNode.getCategoryId(node);
654
798
  assert(!!modelId && !!categoryId);
655
799
  const elementIds = new Set(node.groupedInstanceKeys.map((key) => key.id));
656
- return { modelId, categoryId, elementIds };
800
+ const childrenCount = node.extendedData?.childrenCount;
801
+ const isFiltered = node.extendedData?.isFiltered;
802
+ return { modelId, categoryId, elementIds, childrenCount, isFiltered };
657
803
  }
658
804
  getSubModeledElementsVisibilityStatus({ parentNodeVisibilityStatus, haveSubModel, tooltips, ignoreTooltips, }) {
659
805
  return (obs) => {
@@ -663,58 +809,21 @@ class ModelsTreeVisibilityHandlerImpl {
663
809
  if (haveSubModel === "yes") {
664
810
  return of(modeledElementIds);
665
811
  }
666
- return from(Id64.iterable(modeledElementIds)).pipe(mergeMap(async (elementId) => ({ elementId, hasSubModel: await this.#idsCache.hasSubModel(elementId) })), filter(({ hasSubModel }) => hasSubModel), map(({ elementId }) => elementId), toArray());
812
+ return from(Id64.iterable(modeledElementIds)).pipe(releaseMainThreadOnItemsCount(100), mergeMap((elementId) => forkJoin({
813
+ hasSubModel: this.#idsCache.hasSubModel(elementId),
814
+ elementId: of(elementId),
815
+ })), filter(({ hasSubModel }) => hasSubModel), map(({ elementId }) => elementId), toArray());
667
816
  }),
668
817
  // combine visibility status of sub-models with visibility status of parent node
669
818
  mergeMap((modeledElementIds) => {
670
819
  if (Id64.sizeOf(modeledElementIds) === 0) {
671
820
  return of(parentNodeVisibilityStatus);
672
821
  }
673
- 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));
674
823
  }));
675
824
  };
676
825
  }
677
826
  }
678
- function mergeVisibilities(obs) {
679
- return obs.pipe(reduceWhile((x) => x.allVisible || x.allHidden, (acc, val) => {
680
- acc.allVisible &&= val === "visible";
681
- acc.allHidden &&= val === "hidden";
682
- return acc;
683
- }, { allVisible: true, allHidden: true }), map((x) => {
684
- if (!x) {
685
- return "empty";
686
- }
687
- return x.allVisible ? "visible" : x.allHidden ? "hidden" : "partial";
688
- }));
689
- }
690
- function mergeVisibilityStatuses(tooltipMap, ignoreTooltip) {
691
- return (obs) => {
692
- return obs.pipe(map((visibilityStatus) => visibilityStatus.state), mergeVisibilities, map((visibility) => {
693
- if (visibility === "empty") {
694
- visibility = "visible";
695
- }
696
- return createVisibilityStatus(visibility, getTooltipOptions(tooltipMap?.[visibility], ignoreTooltip));
697
- }));
698
- };
699
- }
700
- function setDifference(lhs, rhs) {
701
- const result = new Set();
702
- for (const x of lhs) {
703
- if (!rhs.has(x)) {
704
- result.add(x);
705
- }
706
- }
707
- return result;
708
- }
709
- function setIntersection(lhs, rhs) {
710
- const result = new Set();
711
- for (const x of lhs) {
712
- if (rhs.has(x)) {
713
- result.add(x);
714
- }
715
- }
716
- return result;
717
- }
718
827
  /**
719
828
  * Enables display of all given models. Also enables display of all categories and clears always and
720
829
  * never drawn lists in the viewport.
@@ -781,9 +890,33 @@ export async function toggleModels(models, enable, viewport) {
781
890
  export function areAllModelsVisible(models, viewport) {
782
891
  return models.length !== 0 ? models.every((id) => viewport.viewsModel(id)) : false;
783
892
  }
784
- function getTooltipOptions(key, ignoreTooltip) {
785
- return {
786
- useTooltip: ignoreTooltip ? false : key,
787
- };
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);
788
921
  }
789
922
  //# sourceMappingURL=ModelsTreeVisibilityHandler.js.map