diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index bd072c293..aaeeb6311 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -3,5 +3,6 @@
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"streetsidesoftware.code-spell-checker",
- ],
+ "mermaidchart.vscode-mermaid-chart"
+ ]
}
diff --git a/packages/itwin/tree-widget/docs/CategoriesTreeVisibilityHandling.md b/packages/itwin/tree-widget/docs/CategoriesTreeVisibilityHandling.md
new file mode 100644
index 000000000..fd91663ca
--- /dev/null
+++ b/packages/itwin/tree-widget/docs/CategoriesTreeVisibilityHandling.md
@@ -0,0 +1,48 @@
+
+
+# Categories tree specific visibility handling
+
+This document explains visibility handling for categories tree specific cases.
+
+## Table of contents
+
+- [Getting visibility status](#getting-visibility-status)
+ - [getDefinitionContainersVisibilityStatus](#getdefinitioncontainersvisibilitystatus)
+ - [getCategoriesVisibilityStatus](./SharedVisibilityHandling.md#getcategoriesvisibilitystatus)
+ - [getSubCategoriesVisibilityStatus](./SharedVisibilityHandling.md#getsubcategoriesvisibilitystatus)
+ - [getElementsVisibilityStatus](./SharedVisibilityHandling.md#getelementsvisibilitystatus)
+
+## Getting visibility status
+
+### getDefinitionContainersVisibilityStatus
+
+To determine definition containers' visibility status, get their child categories from cache and call [getCategoriesVisibilityStatus](./SharedVisibilityHandling.md#getcategoriesvisibilitystatus).
+
+```mermaid
+---
+config:
+ flowchart:
+ wrappingWidth: 750
+ useMaxWidth: false
+---
+
+flowchart TD
+ RESULT_Partial[/partial/]
+ RESULT_Visible[/visible/]
+ RESULT_Hidden[/hidden/]
+
+ %% Start
+ TITLE(["getDefinitionContainersVisibilityStatus"]) --> A["Get categories under props.definitionContainerIds from cache. These are categories whose modelId is the same as definition container or categories of child definition containers (can be nested)"]
+
+ PROPS[\"
+ props
+
- definitionContainerIds: **Id64Arg**
+ "\]
+
+ A -- categoryIds --> B["getCategoriesVisibilityStatus({ categoryIds, modelId: undefined })"]
+
+ %% Results
+ B -- partial --> RESULT_Partial
+ B -- visible --> RESULT_Visible
+ B -- hidden --> RESULT_Hidden
+```
diff --git a/packages/itwin/tree-widget/docs/ClassificationsTreeVisibilityHandling.md b/packages/itwin/tree-widget/docs/ClassificationsTreeVisibilityHandling.md
new file mode 100644
index 000000000..1fe340cfb
--- /dev/null
+++ b/packages/itwin/tree-widget/docs/ClassificationsTreeVisibilityHandling.md
@@ -0,0 +1,82 @@
+
+
+# Classifications tree specific visibility handling
+
+This document explains visibility handling for classifications tree specific cases.
+
+## Table of contents
+
+- [Getting visibility status](#getting-visibility-status)
+ - [getClassificationTablesVisibilityStatus](#getclassificationtablesvisibilitystatus)
+ - [getClassificationsVisibilityStatus](#getclassificationsvisibilitystatus)
+ - [getCategoriesVisibilityStatus](./SharedVisibilityHandling.md#getcategoriesvisibilitystatus)
+ - [getElementsVisibilityStatus](./SharedVisibilityHandling.md#getelementsvisibilitystatus)
+
+## Getting visibility status
+
+### getClassificationTablesVisibilityStatus
+
+To determine classification tables' visibility status, get their child categories from cache and call [getCategoriesVisibilityStatus](./SharedVisibilityHandling.md#getcategoriesvisibilitystatus).
+
+```mermaid
+---
+config:
+ flowchart:
+ wrappingWidth: 750
+ useMaxWidth: false
+---
+
+flowchart TD
+ RESULT_Partial[/partial/]
+ RESULT_Visible[/visible/]
+ RESULT_Hidden[/hidden/]
+
+
+ %% Start
+ TITLE(["getClassificationTablesVisibilityStatus"]) --> A["Get categories under props.classificationTableIds from cache. These are categories of child classifications (can be nested)"]
+
+ PROPS[\"
+ props
+ - classificationTableIds: **Id64Arg**
+ "\]
+
+ A -- categoryIds --> B["getCategoriesVisibilityStatus({ categoryIds, modelId: undefined })"]
+
+ %% Results
+ B -- partial --> RESULT_Partial
+ B -- visible --> RESULT_Visible
+ B -- hidden --> RESULT_Hidden
+```
+
+### getClassificationsVisibilityStatus
+
+To determine classifications' visibility status, get their child categories from cache and call [getCategoriesVisibilityStatus](./SharedVisibilityHandling.md#getcategoriesvisibilitystatus).
+
+```mermaid
+---
+config:
+ flowchart:
+ wrappingWidth: 750
+ useMaxWidth: false
+---
+
+flowchart TD
+ RESULT_Partial[/partial/]
+ RESULT_Visible[/visible/]
+ RESULT_Hidden[/hidden/]
+
+ %% Start
+ TITLE(["getClassificationsVisibilityStatus"]) --> A["Get categories under props.classificationIds from cache. These are related categories and categories of child classifications (can be nested)"]
+
+ PROPS[\"
+ props
+ - classificationIds: **Id64Arg**
+ "\]
+
+ A -- categoryIds --> B["getCategoriesVisibilityStatus({ categoryIds, modelId: undefined })"]
+
+ %% Results
+ B -- partial --> RESULT_Partial
+ B -- visible --> RESULT_Visible
+ B -- hidden --> RESULT_Hidden
+```
diff --git a/packages/itwin/tree-widget/docs/ModelsTreeVisibilityHandling.md b/packages/itwin/tree-widget/docs/ModelsTreeVisibilityHandling.md
new file mode 100644
index 000000000..d11b440ab
--- /dev/null
+++ b/packages/itwin/tree-widget/docs/ModelsTreeVisibilityHandling.md
@@ -0,0 +1,47 @@
+
+
+# Models tree specific visibility handling
+
+This document explains visibility handling for models tree specific cases.
+
+## Table of contents
+
+- [Getting visibility status](#getting-visibility-status)
+ - [getSubjectsVisibilityStatus](#getsubjectsvisibilitystatus)
+ - [getModelsVisibilityStatus](./SharedVisibilityHandling.md#getmodelsvisibilitystatus)
+ - [getCategoriesVisibilityStatus](./SharedVisibilityHandling.md#getcategoriesvisibilitystatus)
+ - [getElementsVisibilityStatus](./SharedVisibilityHandling.md#getelementsvisibilitystatus)
+
+## Getting visibility status
+
+### getSubjectsVisibilityStatus
+
+To determine subjects' visibility status, get their child models from cache and call [getModelsVisibilityStatus](./SharedVisibilityHandling.md#getmodelsvisibilitystatus).
+
+```mermaid
+---
+config:
+ flowchart:
+ wrappingWidth: 750
+ useMaxWidth: false
+---
+
+flowchart TD
+ RESULT_Partial[/partial/]
+ RESULT_Visible[/visible/]
+ RESULT_Hidden[/hidden/]
+
+ %% Start
+ TITLE("getSubjectsVisibilityStatus") --> A["Get models under props.subjectIds from cache. These are related models and models of child subjects (can be nested)"]
+
+ PROPS[\"props
+ - subjectIds: **Id64Arg**
+ "\]
+
+ A -- modelIds --> B["getModelsVisibilityStatus({ modelIds })"]
+
+ %% Results
+ B -- partial --> RESULT_Partial
+ B -- visible --> RESULT_Visible
+ B -- hidden --> RESULT_Hidden
+```
diff --git a/packages/itwin/tree-widget/docs/SharedVisibilityHandling.md b/packages/itwin/tree-widget/docs/SharedVisibilityHandling.md
new file mode 100644
index 000000000..9acf37590
--- /dev/null
+++ b/packages/itwin/tree-widget/docs/SharedVisibilityHandling.md
@@ -0,0 +1,442 @@
+
+
+# Shared visibility handling
+
+This document explains the shared parts of visibility handling in models, categories and classifications trees. Please read [how visibility is determined in the viewport](./Visibility.md#how-visibility-is-determined-in-the-viewport).
+
+## Table of contents
+
+- [Getting visibility status](#getting-visibility-status)
+ - [getSubCategoriesVisibilityStatus](#getsubcategoriesvisibilitystatus)
+ - [getModelsVisibilityStatus](#getmodelsvisibilitystatus)
+ - [getCategoriesVisibilityStatus](#getcategoriesvisibilitystatus)
+ - [getModelWithCategoryVisibilityStatus](#getmodelwithcategoryvisibilitystatus)
+ - [getElementsVisibilityStatus](#getelementsvisibilitystatus)
+ - [getAlwaysOrNeverDrawnVisibilityStatus](#getalwaysorneverdrawnvisibilitystatus)
+
+## Getting visibility status
+
+### getSubCategoriesVisibilityStatus
+
+Visibility of sub-category is `hidden` if its category is `hidden` **Or** the sub-category itself is hidden, otherwise it is `visible`. When determining visibility of multiple sub-categories, need to check if some are `visible` and some are `hidden`, in such case `partial` visibility is returned.
+
+```mermaid
+---
+config:
+ flowchart:
+ wrappingWidth: 750
+ useMaxWidth: false
+---
+
+flowchart TD
+ RESULT_Partial[/partial/]
+ RESULT_Visible[/visible/]
+ RESULT_Hidden[/hidden/]
+
+ %% Start
+ TITLE(["getSubCategoriesVisibilityStatus"]) --> A{"viewport.viewsCategory(props.categoryId)"}
+
+ PROPS[\"
+ props
+ - categoryId: **Id64String**
- subCategoryIds: **Id64Arg**
+ "\]
+
+ %% Branch No
+ A -- No --> RESULT_Hidden
+
+ %% Branch Yes
+ A -- Yes --> B["Iterate through sub-categories"]
+ B -- subCategoryId --> C{"viewport.viewsSubCategory(subCategoryId)"}
+ C -- Yes --> D1[visible]
+ C -- No --> D2[hidden]
+
+ %% Merge
+ D1 --> M[Merge visibility statuses]
+ D2 --> M
+
+ M --> N[Some 'visible' && Some 'hidden'
**OR**
at least one is 'partial']
+
+ %% Results
+ N -- Yes --> RESULT_Partial
+
+ N -- No --> O[All are 'visible']
+
+ O -- Yes --> RESULT_Visible
+ O -- No --> RESULT_Hidden
+```
+
+### getModelsVisibilityStatus
+
+Visibility of model is determined by merging visibility status of two parts:
+
+1. Model selector. If model is not hidden in selector, need to check categories of child elements (they are retrieved from cache) by calling [getCategoriesVisibilityStatus](#getcategoriesvisibilitystatus).
+2. Child elements' which are sub-models (retrieved from cache). For such elements call [getModelsVisibilityStatus](#getmodelsvisibilitystatus).
+
+```mermaid
+---
+config:
+ flowchart:
+ wrappingWidth: 750
+ useMaxWidth: false
+---
+
+flowchart TD
+ RESULT_Partial[/partial/]
+ RESULT_Visible[/visible/]
+ RESULT_Hidden[/hidden/]
+
+ %% Start
+ TITLE(["getModelsVisibilityStatus"]) --> A[Iterate through props.modelIds]
+
+ PROPS[\"
+ props
+ - modelIds: **Id64Arg**
+ "\]
+
+ A -- modelId --> B{"viewport.viewsModel(modelId)"}
+
+ %% Branch Yes
+ B -- Yes --> C1[Get categories of elements which exist under modelId]
+ C1 -- categoryIds --> D1["getCategoriesVisibilityStatus({ modelId, categoryIds })"]
+
+ %% Branch No
+ B -- No --> C2[Get modelled elements under modelId]
+ C2 -- modelIds --> D2{"getModelsVisibilityStatus({ modelIds })
=== 'hidden'/empty"}
+ D2 -- Yes --> E1[hidden]
+ D2 -- No --> E2[partial]
+
+ %% Merge
+ D1 --> M[Merge visibility statuses]
+ E1 --> M
+ E2 --> M
+
+ M --> N[Some 'visible' && Some 'hidden'
**OR**
at least one is 'partial']
+
+ N -- Yes --> RESULT_Partial
+
+ N -- No --> O[All are 'visible']
+
+ O -- Yes --> RESULT_Visible
+ O -- No --> RESULT_Hidden
+```
+
+### getCategoriesVisibilityStatus
+
+Allows getting category visibility under specific model (when modelId is defined in props) or to get generic category visibility.
+
+1. For category visibility under specific model, [getModelWithCategoryVisibilityStatus](#getmodelwithcategoryvisibilitystatus) is used.
+2. For generic category visibility status, merge statuses from:
+ - Get sub-categories related to category (from cache), and call [getSubCategoriesVisibilityStatus](#getsubcategoriesvisibilitystatus).
+ - Get models of category elements (from cache), for each model call [getModelWithCategoryVisibilityStatus](#getmodelwithcategoryvisibilitystatus).
+
+```mermaid
+---
+config:
+ flowchart:
+ wrappingWidth: 750
+ useMaxWidth: false
+---
+
+flowchart TD
+ RESULT_Partial[/partial/]
+ RESULT_Visible[/visible/]
+ RESULT_Hidden[/hidden/]
+
+ %% Start
+ TITLE(["getCategoriesVisibilityStatus"]) --> A{props.modelId
=== undefined}
+
+ PROPS[\"
+ props
+ - modelId: **Id64String | undefined**
- categoryIds: **Id64Arg**
+ "\]
+
+ %% Branch Yes
+ A -- Yes --> B[Iterate through categories]
+ B -- categoryId --> C1[Get sub-categories for specified category from cache]
+ B -- categoryId --> C2[Get models for specified category from cache]
+
+ C1 -- subCategoryIds --> D1["getSubCategoriesVisibilityStatus({ subCategoryIds, categoryId })"]
+
+ C2 --> D2[Iterate through models]
+ D2 -- modelId --> F["getModelWithCategoryVisibilityStatus({ modelId, categoryId })"]
+
+ %% Branch No
+ A -- No --> B2[Iterate through categories]
+ B2 -- categoryId --> F
+
+ %% Merge
+ D1 --> M[Merge visibility statuses]
+ F --> M
+
+ M --> N[Some 'visible' && Some 'hidden'
**OR**
at least one is 'partial']
+
+ %% Results
+ N -- Yes --> RESULT_Partial
+
+ N -- No --> O[All are 'visible']
+
+ O -- Yes --> RESULT_Visible
+ O -- No --> RESULT_Hidden
+```
+
+### getModelWithCategoryVisibilityStatus
+
+Determines visibility status of category under model. It is done by merging visibility statuses of:
+
+- **Sub-models**: Model category elements which are sub-models (retrieved from cache) and calling [getModelsVisibilityStatus](#getmodelsvisibilitystatus).
+- **Child elements**: determining child elements visibility is done by:
+ 1. Getting total count of elements under the category with model.
+ 2. Getting default child elements status based on per-model category override and category selector.
+ 3. Get `opposite set` to default status: default status === `visible` -> `alwaysDrawn`, `neverDrawn` otherwise.
+ 4. The `opposite set` can contain elements from any categories and models, need to query data of these elements and find the ones which are related to the desired category and model.
+ 5. Once all the above data (1-4) is known, visibility can be determined by comparing the total count, number of elements (related to specific model and category) in the opposite set, and default status.
+
+ **Note**: All the checks are done only when [visibility rules](./Visibility.md#how-visibility-is-determined-in-the-viewport) that have higher priority do not interfere (e.g. if model is hidden in selector, then always/never drawn elements are **not checked** and `hidden` is returned for `Child Elements` visibility).
+
+```mermaid
+---
+config:
+ flowchart:
+ wrappingWidth: 750
+ useMaxWidth: false
+---
+
+flowchart TD
+ RESULT_Partial[/partial/]
+ RESULT_Visible[/visible/]
+ RESULT_Hidden[/hidden/]
+
+ %% Start
+ TITLE(["getModelWithCategoryVisibilityStatus"]) --> A1[Get modelled elements under category with model]
+ TITLE(["getModelWithCategoryVisibilityStatus"]) --> A2{"viewport.viewsModel(props.modelId)"}
+
+ PROPS[\"
+ props
+ - modelId: **Id64String**
- categoryId: **Id64String**
+ "\]
+
+ %% Branch A1
+ A1 -- modelIds --> B["getModelsVisibilityStatus({ modelIds })"]
+
+ %% Branch A2
+
+ %% Branch No
+ A2 -- No --> C[hidden]
+
+ %% Branch Yes
+
+ A2 -- Yes --> D{Is always drawn exclusive}
+
+ %% Branch Yes
+ D -- Yes --> E1["**defaultStatus**: 'hidden'
**oppositeSet**: alwaysDrawn"]
+
+ %% Branch No
+ D -- No --> E2{"
+
+ Per model category override === 'show'
+ OR
+
+ Per model category override === 'none'
&& viewport.viewsCategory(props.categoryId)
+
+ "}
+
+
+ %% Branch No
+ E2 -- No --> E1
+
+ %% Branch Yes
+ E2 -- Yes --> E3["**defaultStatus**: 'visible'
**oppositeSet**: neverDrawn"]
+
+ E1 -- Pass down --> F{"**oppositeSet**.size > 0"}
+ E3 -- Pass down --> F
+
+ %% Branch No
+ F -- No --> G1[defaultStatus]
+
+ %% Branch Yes
+ F -- Yes --> G2[From cache get total count of elements under category with model]
+
+ F -- Yes --> G3["Props
+ - For **oppositeSet** elements execute query (if set changed after last execution), to get their models, categories and parent elements path.
- Find always/never drawn child elements (nested as well) where queried data matches props.modelId & props.categoryId.
- Get count of elements under model with category in **oppositeSet**: numberOfElementsInOppositeSet
+ "]
+
+ G2 -- totalCount --> H["getAlwaysOrNeverDrawnVisibilityStatus({ totalCount, numberOfElementsInOppositeSet, defaultStatus })"]
+ G3 -- Pass down --> H
+
+
+ %% Merge
+ B --> M[Merge visibility statuses]
+ C --> M
+ H --> M
+ G1 --> M
+
+ M --> N[Some 'visible' && Some 'hidden'
**OR**
at least one is 'partial']
+
+ %% Results
+ N -- Yes --> RESULT_Partial
+
+ N -- No --> O[All are 'visible']
+
+ O -- Yes --> RESULT_Visible
+ O -- No --> RESULT_Hidden
+```
+
+### getElementsVisibilityStatus
+
+Determines visibility status of elements. Structure is very similar to [getModelWithCategoryVisibilityStatus](#getmodelwithcategoryvisibilitystatus), except everything is done based on elements instead of model + category.
+
+```mermaid
+---
+config:
+ flowchart:
+ wrappingWidth: 750
+ useMaxWidth: false
+---
+
+flowchart TD
+ RESULT_Partial[/partial/]
+ RESULT_Visible[/visible/]
+ RESULT_Hidden[/hidden/]
+
+ %% Start
+ TITLE(["getElementsVisibilityStatus"]) --> A1[" Get modelIds from cache:
1. props.elementIds which are sub-models
2. Children which are sub-models (nested as well)
"]
+ TITLE(["getElementsVisibilityStatus"]) --> A2{"viewport.viewsModel(props.modelId)"}
+
+ PROPS[\"
+ props
+ - elementIds: **Id64Arg**
- modelId: **Id64String**
- categoryId: **Id64String**
- categoryOfTopMostParentElement: **Id64String**
- parentElementIdsPath: **Array**
- childrenCount: **number | undefined**
+ "\]
+
+ %% Branch A1
+ A1 -- modelIds --> B["getModelsVisibilityStatus({ modelIds })"]
+
+ %% Branch A2
+
+ %% Branch No
+ A2 -- No --> C[hidden]
+
+ %% Branch Yes
+
+ A2 -- Yes --> D{Is always drawn exclusive}
+
+ %% Branch Yes
+ D -- Yes --> E1["**defaultStatus**: 'hidden'
**oppositeSet**: alwaysDrawn"]
+
+ %% Branch No
+ D -- No --> E2{"
+
+ Per model category override === 'show'
+ OR
+
+ Per model category override === 'none'
&& viewport.viewsCategory(props.categoryId)
+
+ "}
+
+ %% Branch No
+ E2 -- No --> E1
+
+ %% Branch Yes
+ E2 -- Yes --> E3["**defaultStatus**: 'visible'
**oppositeSet**: neverDrawn"]
+
+ E1 -- Pass down --> F{"**oppositeSet**.size > 0"}
+ E3 -- Pass down --> F
+
+ %% Branch No
+ F -- No --> G1[defaultStatus]
+
+ %% Branch Yes
+ F -- Yes --> G2{"props.childrenCount
=== 0 / undefined"}
+
+ %% Branch Yes
+ G2 -- Yes --> H1[Children count in oppositeSet === 0]
+
+ %% Branch No
+ G2 -- No --> H2["Props
+ - For **oppositeSet** elements execute query (if set changed after last execution), to get their models, categories and parent elements path.
- Find always/never drawn child elements (nested as well) where queried data matches props.modelId & props.categoryId & props.parentElementIdsPath.
- Get count of children in **oppositeSet**: numberOfElementsInOppositeSet
+ "]
+
+
+
+ H1 -- Pass down --> I["**numberOfElementsInOppositeSet**: props.elementIds in oppositeSet and children count in oppositeSet
**totalCount**: props.elementIds + props.childrenCount"]
+ H2 -- Pass down --> I
+
+ I -- Pass down --> J["getAlwaysOrNeverDrawnVisibilityStatus({ totalCount, numberOfElementsInOppositeSet, defaultStatus })"]
+
+
+ %% Merge
+ B --> M[Merge visibility statuses]
+ C --> M
+ G1 --> M
+ J --> M
+
+ M --> N[Some 'visible' && Some 'hidden'
**OR**
at least one is 'partial']
+
+ %% Results
+ N -- Yes --> RESULT_Partial
+
+ N -- No --> O[All are 'visible']
+
+ O -- Yes --> RESULT_Visible
+ O -- No --> RESULT_Hidden
+```
+
+### getAlwaysOrNeverDrawnVisibilityStatus
+
+Helper function that is used by [getModelWithCategoryVisibilityStatus](#getmodelwithcategoryvisibilitystatus) and [getElementsVisibilityStatus](#getelementsvisibilitystatus). It determines visibility status of elements based on `totalCount`, `numberOfElementsInOppositeSet` and `defaultStatus`.
+
+```mermaid
+---
+config:
+ flowchart:
+ wrappingWidth: 750
+ useMaxWidth: false
+---
+
+flowchart TD
+ RESULT_Partial[/partial/]
+ RESULT_Visible[/visible/]
+ RESULT_Hidden[/hidden/]
+
+ %% Start
+ TITLE(["getAlwaysOrNeverDrawnVisibilityStatus"]) --> A{"props.totalCount
=== 0
**OR**
props.numberOfElementsInOppositeSet
=== 0"}
+
+ PROPS[\"
+ props
+
+ - totalCount: **number**
+ %% need !important on color since to not take the config color
+ Number of elements that are under node
(includes node itself and its nested child elements)
+
+ - numberOfElementsInOppositeSet: **number**
+ Number of elements in the set that is opposite
to default status. If default status 'visible', it's
always drawn, otherwise it's never drawn set
+
+ - defaultStatus: **'visible' | 'hidden'**
+ Elements visibility status when they are not
in always/never drawn list
+
+
+ "\]
+
+ %% Branch Yes
+ A -- Yes --> B1{"props.defaultStatus
=== 'visible'"}
+
+ %% Branch Yes
+ B1 -- Yes --> RESULT_Visible
+
+ %% Branch No
+ B1 -- No --> RESULT_Hidden
+
+ %% Branch No
+ A -- No --> B2{"props.numberOfElementsInOppositeSet
=== props.totalCount"}
+
+ %% Branch No
+ B2 -- No --> RESULT_Partial
+
+ %% Branch Yes
+ B2 -- Yes --> C{"props.defaultStatus
=== 'visible'"}
+
+ %% Branch Yes
+ C -- Yes --> RESULT_Hidden
+
+ %% Branch No
+ C -- No --> RESULT_Visible
+```
diff --git a/packages/itwin/tree-widget/docs/Visibility.md b/packages/itwin/tree-widget/docs/Visibility.md
new file mode 100644
index 000000000..a5316ed76
--- /dev/null
+++ b/packages/itwin/tree-widget/docs/Visibility.md
@@ -0,0 +1,79 @@
+
+
+# Visibility Handling in Tree Widget
+
+This document explains visibility handling across tree types (Models, Categories, and Classifications) and node types (models, categories, geometric elements, sub-categories, sub-models, classifications, classification tables and definition containers).
+
+## Key Internal APIs
+
+- [`useCachedVisibility`](../src/tree-widget-react/components/trees/common/internal/useTreeHooks/UseCachedVisibility.ts) — React hook that returns a tree-specific visibility handler.
+ - Uses [`VisibilityChangeEventListener`](../src/tree-widget-react/components/trees/common/internal/VisibilityChangeEventListener.ts) to allow `getVisibilityStatus()` calls to be cancelled and re-requested by [`useHierarchyVisibility`](../src/tree-widget-react/components/trees/common/UseHierarchyVisibility.ts) via `onVisibilityChange()`.
+ - Pauses event notifications while `changeVisibility()` is in progress to avoid re-requesting `getVisibilityStatus` before change finishes.
+ - Applies special handling when search paths are present (for nodes that are not search targets or have no search-target ancestors).
+
+- [`BaseVisibilityHelper`](../src/tree-widget-react/components/trees/common/internal/visibility/BaseVisibilityHelper.ts) — shared get/change operations for visibility status based on element/model/category ids.
+ - Uses [`BaseIdsCache`](../src/tree-widget-react/components/trees/common/internal/caches/BaseIdsCache.ts) to retrieve information about nodes.
+ - Examples: `getModelsVisibilityStatus()`, `getCategoriesVisibilityStatus()`, `changeModelsVisibilityStatus()`, `changeCategoriesVisibilityStatus()`.
+
+- Tree-specific visibility handlers [`CategoriesTreeVisibilityHandler`](../src/tree-widget-react/components/trees/categories-tree/internal/visibility/CategoriesTreeVisibilityHandler.ts), [`ClassificationsTreeVisibilityHandler`](../src/tree-widget-react/components/trees/classifications-tree/internal/visibility/ClassificationsTreeVisibilityHandler.ts), [`ModelsTreeVisibilityHandler`](../src/tree-widget-react/components/trees/models-tree/internal/visibility/ModelsTreeVisibilityHandler.ts):
+ - These handlers are aware of tree-specific hierarchy structure.
+ - Take tree nodes as input, determine node type via nodes' `extendedData` property, and use appropriate methods from visibility helpers.
+ - Expose get/change visibility status logic for search-target nodes.
+
+- Tree-specific visibility helpers ([`CategoriesTreeVisibilityHelper`](../src/tree-widget-react/components/trees/categories-tree/internal/visibility/CategoriesTreeVisibilityHelper.ts), [`ClassificationsTreeVisibilityHelper`](../src/tree-widget-react/components/trees/classifications-tree/internal/visibility/ClassificationsTreeVisibilityHelper.ts), [`ModelsTreeVisibilityHelper`](../src/tree-widget-react/components/trees/models-tree/internal/visibility/ModelsTreeVisibilityHelper.ts)):
+ - Cover tree-specific cases (e.g. definition containers exist only in the Categories tree, so `CategoriesTreeVisibilityHelper` implements get/change visibility methods for definition containers).
+ - All of them use [`BaseVisibilityHelper`](../src/tree-widget-react/components/trees/common/internal/visibility/BaseVisibilityHelper.ts) to get/change visibility for those tree-specific cases.
+
+- Search-results trees ([`BaseSearchResultsTree`](../src/tree-widget-react/components/trees/common/internal/visibility/BaseSearchResultsTree.ts) and tree-specific implementations: [Categories](../src/tree-widget-react/components/trees/categories-tree/internal/visibility/SearchResultsTree.ts), [Classifications](../src/tree-widget-react/components/trees/classifications-tree/internal/visibility/SearchResultsTree.ts), [Models](../src/tree-widget-react/components/trees/models-tree/internal/visibility/SearchResultsTree.ts)):
+ - Help get/change visibility of nodes which are not search targets and don't have search-target ancestors (since these nodes might have some children missing). They allow retrieving child search targets for such nodes and then getting/changing visibility is done based on search targets instead.
+
+- Caching:
+ - [`BaseIdsCache`](../src/tree-widget-react/components/trees/common/internal/caches/BaseIdsCache.ts) - stores data that is relevant to models/categories/classifications trees (e.g. model <-> category relationship).
+ - This cache is composed of other caches ([`ElementChildrenCache`](../src/tree-widget-react/components/trees/common/internal/caches/ElementChildrenCache.ts), [`SubCategoriesCache`](../src/tree-widget-react/components/trees/common/internal/caches/SubCategoriesCache.ts) and others).
+ - Data stored in this cache is requested only once, because it does not change.
+ - Tree-specific id caches ([`CategoriesTreeIdsCache`](../src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts), [`ClassificationsTreeIdsCache`](../src/tree-widget-react/components/trees/classifications-tree/internal/ClassificationsTreeIdsCache.ts), [`ModelsTreeIdsCache`](../src/tree-widget-react/components/trees/models-tree/internal/ModelsTreeIdsCache.ts)):
+ - Store various tree-specific relationships, (e.g. models tree ids cache stores element's model <-> subject relationship).
+ - Extend `BaseIdsCacheImpl` so each tree-specific cache can be used in [`BaseVisibilityHelper`](../src/tree-widget-react/components/trees/common/internal/visibility/BaseVisibilityHelper.ts).
+
+ - [`AlwaysAndNeverDrawnElementInfoCache`](../src/tree-widget-react/components/trees/common/internal/caches/AlwaysAndNeverDrawnElementInfoCache.ts) — caches extra data (like category) for always/never drawn elements.
+ - Always and never drawn caches are reset when always and never drawn sets change respectively.
+ - Child always and never drawn elements can be retrieved for models, categories and parent elements.
+
+ - [`ElementChildrenCache`](../src/tree-widget-react/components/trees/common/internal/caches/ElementChildrenCache.ts) — cache for retrieving elements' children.
+ - When changing element or element grouping nodes' visibility, need to put all children (nested as well) into always/never drawn list. This cache is used to retrieve child nodes' ids in such cases.
+ - It is not used (and should not be used) when getting visibility status:
+ - Only total children counts (this is stored on nodes `extendedData` property) and child always/never drawn elements (these are retrieved from [`AlwaysAndNeverDrawnElementInfoCache`](../src/tree-widget-react/components/trees/common/internal/caches/AlwaysAndNeverDrawnElementInfoCache.ts)) are needed for determining visibility.
+ - Element might have hundreds of thousands of child elements. And retrieving this information for each element in the hierarchy would be very slow.
+
+## How visibility is determined in the viewport
+
+The viewport only renders elements. Element visibility is resolved in the following order (highest priority first):
+
+1. **Model selector**: if a model is hidden, its elements are never visible.
+2. **Always/Never drawn sets**: elements in these sets are forced to be visible/hidden.
+3. **Always drawn exclusive flag**: If flag is on, then only elements in the `alwaysDrawn` set are visible, otherwise rules below apply.
+4. **Per model-category overrides**: a category can be overridden per model with `hide`, `show`, or `none`.
+ - `hide`: hides all elements of that category within the model.
+ - `show`: shows all elements of that category within the model.
+5. **Category selector**: hidden categories hide their elements.
+6. **Sub-categories**: hidden sub-categories hide their elements.
+ - **Note**: Determining element -> sub-category relationship is not supported at the moment. So sub-category checks are only performed when the Categories tree calls `getVisibilityStatus()` for categories or sub-categories.
+
+## Visibility logic
+
+- Getting visibility status
+ - [Models tree](./ModelsTreeVisibilityHandling.md)
+ - [getSubjectsVisibilityStatus](./ModelsTreeVisibilityHandling.md#getsubjectsvisibilitystatus)
+ - [getModelsVisibilityStatus](./SharedVisibilityHandling.md#getmodelsvisibilitystatus)
+ - [getCategoriesVisibilityStatus](./SharedVisibilityHandling.md#getcategoriesvisibilitystatus)
+ - [getElementsVisibilityStatus](./SharedVisibilityHandling.md#getelementsvisibilitystatus)
+ - [Categories tree](./CategoriesTreeVisibilityHandling.md)
+ - [getDefinitionContainersVisibilityStatus](./CategoriesTreeVisibilityHandling.md#getdefinitioncontainersvisibilitystatus)
+ - [getCategoriesVisibilityStatus](./SharedVisibilityHandling.md#getcategoriesvisibilitystatus)
+ - [getSubCategoriesVisibilityStatus](./SharedVisibilityHandling.md#getsubcategoriesvisibilitystatus)
+ - [getElementsVisibilityStatus](./SharedVisibilityHandling.md#getelementsvisibilitystatus)
+ - [Classifications tree](./ClassificationsTreeVisibilityHandling.md)
+ - [getClassificationTablesVisibilityStatus](./ClassificationsTreeVisibilityHandling.md#getclassificationtablesvisibilitystatus)
+ - [getClassificationsVisibilityStatus](./ClassificationsTreeVisibilityHandling.md#getclassificationsvisibilitystatus)
+ - [getCategoriesVisibilityStatus](./SharedVisibilityHandling.md#getcategoriesvisibilitystatus)
+ - [getElementsVisibilityStatus](./SharedVisibilityHandling.md#getelementsvisibilitystatus)
diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/common/TreeWidgetViewport.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/common/TreeWidgetViewport.ts
index 65121008e..9f100be7a 100644
--- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/common/TreeWidgetViewport.ts
+++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/common/TreeWidgetViewport.ts
@@ -95,10 +95,11 @@ export function createTreeWidgetViewport(viewport: Viewport): TreeWidgetViewport
* 1. Model visibility - if model is not visible, elements from that model should never be displayed.
* 2. `neverDrawn` set - elements in that set should never be displayed.
* 3. `alwaysDrawn` set - elements in that set should always be displayed.
- * 4. Per-model category visibility overrides:
+ * 4. Always drawn exclusive flag: if this flag is set to true, only elements in the `alwaysDrawn` set should be displayed.
+ * 5. Per-model category visibility overrides:
* - if a per-model-category has `Hide` override, elements which have that category and model should not be displayed.
* - if a per-model-category has `Show` override, elements which have that category and model should be displayed.
- * 5. Category and sub-category visibility - if element's category or sub-category is turned off, it should not be displayed.
+ * 6. Category and sub-category visibility - if element's category or sub-category is turned off, it should not be displayed.
*
* Based on this order of precedence, element can only be displayed in these scenarios:
* - Model is visible *AND* element is in `alwaysDrawn` set.