@itwin/presentation-hierarchies-react 0.1.0
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.
- package/CHANGELOG.md +14 -0
- package/LICENSE.md +9 -0
- package/README.md +303 -0
- package/lib/cjs/presentation-hierarchies-react/TreeNode.d.ts +75 -0
- package/lib/cjs/presentation-hierarchies-react/TreeNode.d.ts.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/TreeNode.js +16 -0
- package/lib/cjs/presentation-hierarchies-react/TreeNode.js.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/UnifiedSelectionContext.d.ts +18 -0
- package/lib/cjs/presentation-hierarchies-react/UnifiedSelectionContext.d.ts.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/UnifiedSelectionContext.js +28 -0
- package/lib/cjs/presentation-hierarchies-react/UnifiedSelectionContext.js.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/UseSelectionHandler.d.ts +33 -0
- package/lib/cjs/presentation-hierarchies-react/UseSelectionHandler.d.ts.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/UseSelectionHandler.js +102 -0
- package/lib/cjs/presentation-hierarchies-react/UseSelectionHandler.js.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/UseTree.d.ts +85 -0
- package/lib/cjs/presentation-hierarchies-react/UseTree.d.ts.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/UseTree.js +214 -0
- package/lib/cjs/presentation-hierarchies-react/UseTree.js.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/internal/TreeActions.d.ts +27 -0
- package/lib/cjs/presentation-hierarchies-react/internal/TreeActions.d.ts.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/internal/TreeActions.js +236 -0
- package/lib/cjs/presentation-hierarchies-react/internal/TreeActions.js.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/internal/TreeLoader.d.ts +33 -0
- package/lib/cjs/presentation-hierarchies-react/internal/TreeLoader.d.ts.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/internal/TreeLoader.js +71 -0
- package/lib/cjs/presentation-hierarchies-react/internal/TreeLoader.js.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/internal/TreeModel.d.ts +70 -0
- package/lib/cjs/presentation-hierarchies-react/internal/TreeModel.d.ts.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/internal/TreeModel.js +154 -0
- package/lib/cjs/presentation-hierarchies-react/internal/TreeModel.js.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/internal/UseUnifiedSelection.d.ts +16 -0
- package/lib/cjs/presentation-hierarchies-react/internal/UseUnifiedSelection.d.ts.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/internal/UseUnifiedSelection.js +96 -0
- package/lib/cjs/presentation-hierarchies-react/internal/UseUnifiedSelection.js.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/internal/Utils.d.ts +8 -0
- package/lib/cjs/presentation-hierarchies-react/internal/Utils.d.ts.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/internal/Utils.js +45 -0
- package/lib/cjs/presentation-hierarchies-react/internal/Utils.js.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/itwinui/LocalizationContext.d.ts +57 -0
- package/lib/cjs/presentation-hierarchies-react/itwinui/LocalizationContext.d.ts.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/itwinui/LocalizationContext.js +35 -0
- package/lib/cjs/presentation-hierarchies-react/itwinui/LocalizationContext.js.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/itwinui/TreeNodeRenderer.css +17 -0
- package/lib/cjs/presentation-hierarchies-react/itwinui/TreeNodeRenderer.d.ts +27 -0
- package/lib/cjs/presentation-hierarchies-react/itwinui/TreeNodeRenderer.d.ts.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/itwinui/TreeNodeRenderer.js +101 -0
- package/lib/cjs/presentation-hierarchies-react/itwinui/TreeNodeRenderer.js.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/itwinui/TreeRenderer.d.ts +55 -0
- package/lib/cjs/presentation-hierarchies-react/itwinui/TreeRenderer.d.ts.map +1 -0
- package/lib/cjs/presentation-hierarchies-react/itwinui/TreeRenderer.js +83 -0
- package/lib/cjs/presentation-hierarchies-react/itwinui/TreeRenderer.js.map +1 -0
- package/lib/cjs/presentation-hierarchies-react.d.ts +10 -0
- package/lib/cjs/presentation-hierarchies-react.d.ts.map +1 -0
- package/lib/cjs/presentation-hierarchies-react.js +27 -0
- package/lib/cjs/presentation-hierarchies-react.js.map +1 -0
- package/lib/esm/presentation-hierarchies-react/TreeNode.d.ts +75 -0
- package/lib/esm/presentation-hierarchies-react/TreeNode.d.ts.map +1 -0
- package/lib/esm/presentation-hierarchies-react/TreeNode.js +12 -0
- package/lib/esm/presentation-hierarchies-react/TreeNode.js.map +1 -0
- package/lib/esm/presentation-hierarchies-react/UnifiedSelectionContext.d.ts +18 -0
- package/lib/esm/presentation-hierarchies-react/UnifiedSelectionContext.d.ts.map +1 -0
- package/lib/esm/presentation-hierarchies-react/UnifiedSelectionContext.js +23 -0
- package/lib/esm/presentation-hierarchies-react/UnifiedSelectionContext.js.map +1 -0
- package/lib/esm/presentation-hierarchies-react/UseSelectionHandler.d.ts +33 -0
- package/lib/esm/presentation-hierarchies-react/UseSelectionHandler.d.ts.map +1 -0
- package/lib/esm/presentation-hierarchies-react/UseSelectionHandler.js +98 -0
- package/lib/esm/presentation-hierarchies-react/UseSelectionHandler.js.map +1 -0
- package/lib/esm/presentation-hierarchies-react/UseTree.d.ts +85 -0
- package/lib/esm/presentation-hierarchies-react/UseTree.d.ts.map +1 -0
- package/lib/esm/presentation-hierarchies-react/UseTree.js +209 -0
- package/lib/esm/presentation-hierarchies-react/UseTree.js.map +1 -0
- package/lib/esm/presentation-hierarchies-react/internal/TreeActions.d.ts +27 -0
- package/lib/esm/presentation-hierarchies-react/internal/TreeActions.d.ts.map +1 -0
- package/lib/esm/presentation-hierarchies-react/internal/TreeActions.js +232 -0
- package/lib/esm/presentation-hierarchies-react/internal/TreeActions.js.map +1 -0
- package/lib/esm/presentation-hierarchies-react/internal/TreeLoader.d.ts +33 -0
- package/lib/esm/presentation-hierarchies-react/internal/TreeLoader.d.ts.map +1 -0
- package/lib/esm/presentation-hierarchies-react/internal/TreeLoader.js +67 -0
- package/lib/esm/presentation-hierarchies-react/internal/TreeLoader.js.map +1 -0
- package/lib/esm/presentation-hierarchies-react/internal/TreeModel.d.ts +70 -0
- package/lib/esm/presentation-hierarchies-react/internal/TreeModel.d.ts.map +1 -0
- package/lib/esm/presentation-hierarchies-react/internal/TreeModel.js +149 -0
- package/lib/esm/presentation-hierarchies-react/internal/TreeModel.js.map +1 -0
- package/lib/esm/presentation-hierarchies-react/internal/UseUnifiedSelection.d.ts +16 -0
- package/lib/esm/presentation-hierarchies-react/internal/UseUnifiedSelection.d.ts.map +1 -0
- package/lib/esm/presentation-hierarchies-react/internal/UseUnifiedSelection.js +92 -0
- package/lib/esm/presentation-hierarchies-react/internal/UseUnifiedSelection.js.map +1 -0
- package/lib/esm/presentation-hierarchies-react/internal/Utils.d.ts +8 -0
- package/lib/esm/presentation-hierarchies-react/internal/Utils.d.ts.map +1 -0
- package/lib/esm/presentation-hierarchies-react/internal/Utils.js +40 -0
- package/lib/esm/presentation-hierarchies-react/internal/Utils.js.map +1 -0
- package/lib/esm/presentation-hierarchies-react/itwinui/LocalizationContext.d.ts +57 -0
- package/lib/esm/presentation-hierarchies-react/itwinui/LocalizationContext.d.ts.map +1 -0
- package/lib/esm/presentation-hierarchies-react/itwinui/LocalizationContext.js +30 -0
- package/lib/esm/presentation-hierarchies-react/itwinui/LocalizationContext.js.map +1 -0
- package/lib/esm/presentation-hierarchies-react/itwinui/TreeNodeRenderer.css +17 -0
- package/lib/esm/presentation-hierarchies-react/itwinui/TreeNodeRenderer.d.ts +27 -0
- package/lib/esm/presentation-hierarchies-react/itwinui/TreeNodeRenderer.d.ts.map +1 -0
- package/lib/esm/presentation-hierarchies-react/itwinui/TreeNodeRenderer.js +94 -0
- package/lib/esm/presentation-hierarchies-react/itwinui/TreeNodeRenderer.js.map +1 -0
- package/lib/esm/presentation-hierarchies-react/itwinui/TreeRenderer.d.ts +55 -0
- package/lib/esm/presentation-hierarchies-react/itwinui/TreeRenderer.d.ts.map +1 -0
- package/lib/esm/presentation-hierarchies-react/itwinui/TreeRenderer.js +78 -0
- package/lib/esm/presentation-hierarchies-react/itwinui/TreeRenderer.js.map +1 -0
- package/lib/esm/presentation-hierarchies-react.d.ts +10 -0
- package/lib/esm/presentation-hierarchies-react.d.ts.map +1 -0
- package/lib/esm/presentation-hierarchies-react.js +13 -0
- package/lib/esm/presentation-hierarchies-react.js.map +1 -0
- package/package.json +103 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# @itwin/presentation-hierarchies-react
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#587](https://github.com/iTwin/presentation/pull/587): Initial release.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies:
|
|
12
|
+
- @itwin/presentation-shared@0.2.0
|
|
13
|
+
- @itwin/unified-selection@0.4.2
|
|
14
|
+
- @itwin/presentation-hierarchies@0.1.0
|
package/LICENSE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright © 2017-2024 Bentley Systems, Incorporated. All rights reserved.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# @itwin/presentation-hierarchies-react
|
|
2
|
+
|
|
3
|
+
Copyright © Bentley Systems, Incorporated. All rights reserved. See LICENSE.md for license terms and full copyright notice.
|
|
4
|
+
|
|
5
|
+
The `@itwin/presentation-hierarchies-react` package provides APIs for building a headless UI for rendering tree components based on data in an [iTwin.js iModel](https://www.itwinjs.org/learning/imodels/#imodel-overview). In addition, it delivers a set of [iTwinUI](https://itwinui.bentley.com/)-based components for rendering the tree.
|
|
6
|
+
|
|
7
|
+
## Headless UI
|
|
8
|
+
|
|
9
|
+
### `useTree`
|
|
10
|
+
|
|
11
|
+
This is a React hook that creates state for a tree component.
|
|
12
|
+
|
|
13
|
+
It takes 2 required properties:
|
|
14
|
+
|
|
15
|
+
- `imodelAccess` provides access to iModel's data and metadata, required to build the hierarchy. Generally, `@itwin/presentation-core-interop` and `@itwin/presentation-shared` packages are used to create this object:
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { IModelConnection } from "@itwin/core-frontend";
|
|
19
|
+
import { createECSchemaProvider, createECSqlQueryExecutor } from "@itwin/presentation-core-interop";
|
|
20
|
+
import { createLimitingECSqlQueryExecutor } from "@itwin/presentation-hierarchies";
|
|
21
|
+
import { createCachingECClassHierarchyInspector } from "@itwin/presentation-shared";
|
|
22
|
+
|
|
23
|
+
function createIModelAccess(imodel: IModelConnection) {
|
|
24
|
+
const schemaProvider = createECSchemaProvider(getIModelSchemaContext(imodel));
|
|
25
|
+
return {
|
|
26
|
+
...schemaProvider,
|
|
27
|
+
// while caching for hierarchy inspector is not mandatory, it's recommended to use it to improve performance
|
|
28
|
+
...createCachingECClassHierarchyInspector({ schemaProvider, cacheSize: 100 }),
|
|
29
|
+
// the second argument is the maximum number of rows the executor will return - this allows us to
|
|
30
|
+
// avoid creating hierarchy levels of insane size (expensive to us and useless to users)
|
|
31
|
+
...createLimitingECSqlQueryExecutor(createECSqlQueryExecutor(imodel), 1000),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- `getHierarchyDefinition` is a factory function that creates a hierarchy definition, describing the hierarchy the tree component will render. The `@itwin/presentation-hierarchies` package describes the concept of hierarchy definitions [in more detail](https://github.com/iTwin/presentation/blob/master/packages/hierarchies/README.md#hierarchy-definition).
|
|
37
|
+
|
|
38
|
+
The resulting state object contains the following properties:
|
|
39
|
+
|
|
40
|
+
- `isLoading` is a boolean indicating whether the root tree nodes are being loaded. Set to `true` on initial load and on reload (e.g. when iModel data changes).
|
|
41
|
+
|
|
42
|
+
- `rootNodes` is an array of root tree nodes and is what the component should render. There are several types of nodes:
|
|
43
|
+
|
|
44
|
+
- A `PresentationHierarchyNode` is the primary type of node, created based on the hierarchy definition. The `isPresentationHierarchyNode` type guard utility may be used to check if a node is of this type.
|
|
45
|
+
- A `PresentationInfoNode` is a non-expandable, non-selectable informational type of node, generally created when for some reason we don't have any real nodes to show. There may be different reasons like filtered-out nodes, too large result set, a network error, etc. The `type` attribute of the node indicates that.
|
|
46
|
+
|
|
47
|
+
- `expandNode` function to expand or collapse a node.
|
|
48
|
+
|
|
49
|
+
- `isNodeSelected` and `selectNodes` function to inspect and change tree selection.
|
|
50
|
+
|
|
51
|
+
- `getHierarchyLevelDetails` function to access details of a specific hierarchy level. The returned object provides access to:
|
|
52
|
+
|
|
53
|
+
- hierarchy level size limit,
|
|
54
|
+
- hierarchy level instance filter,
|
|
55
|
+
- instance keys of the nodes in the hierarchy level.
|
|
56
|
+
|
|
57
|
+
- `reloadTree` function to reload the tree, optionally keeping its state, after an iModel data change.
|
|
58
|
+
|
|
59
|
+
- `setFormatter` function to set active node label formatter.
|
|
60
|
+
|
|
61
|
+
### `useSelectionHandler`
|
|
62
|
+
|
|
63
|
+
This is React hook that helps implement different selection modes in tree, whose state is managed through the `useTree` or `useUnifiedSelectionTree` hooks.
|
|
64
|
+
|
|
65
|
+
It takes 3 required properties:
|
|
66
|
+
|
|
67
|
+
- `rootNodes` and `selectNodes` are the corresponding properties from the tree state object, created using `useTree` or `useUnifiedSelectionTree` hook.
|
|
68
|
+
|
|
69
|
+
- `selectionMode` is a string that defines the selection mode. It can be one of the following values:
|
|
70
|
+
- `none` - no selection is allowed,
|
|
71
|
+
- `single` - only one node can be selected at a time,
|
|
72
|
+
- `extended` - multiple nodes can be selected using shift and ctrl keys,
|
|
73
|
+
- `multiple` - multiple nodes can be selected without using shift or ctrl keys.
|
|
74
|
+
|
|
75
|
+
The returned object contains 2 functions, that should be called by the node renderer: `onNodeClick` and `onNodeKeyDown`.
|
|
76
|
+
|
|
77
|
+
Our [tree renderer implementation](#treerenderer) calls this hook and passes the callbacks to the [node renderer](#treenoderenderer), so there's no need to use it unless implementing a custom tree renderer.
|
|
78
|
+
|
|
79
|
+
### `useUnifiedSelectionTree` & `UnifiedSelectionProvider`
|
|
80
|
+
|
|
81
|
+
The package delivers a variation of [`useTree`](#usetree), that automatically hooks tree selection into the [Unified Selection](https://www.itwinjs.org/learning/unified-selection/) system. It takes the same properties as [`useTree`](#usetree), plus a couple of additional ones:
|
|
82
|
+
|
|
83
|
+
- `imodelKey` is a string that uniquely identifies the iModel the tree is associated with. It's used to distinguish selection changes between different iModels. Generally, the value is obtained using `IModelConnection.key` getter.
|
|
84
|
+
|
|
85
|
+
- `sourceName` is a string that distinguishes selection changes being made by different components. It's used to avoid conflicts between different components that use the same iModel and the same selection storage. The value should be unique for each component.
|
|
86
|
+
|
|
87
|
+
The returned result is identical to the one from [`useTree`](#usetree).
|
|
88
|
+
|
|
89
|
+
The hook also relies on unified selection storage being provided to it through a React context. For that, the package delivers the `UnifiedSelectionProvider` component, that should wrap the tree component, using the `useUnifiedSelectionTree` hook:
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
import { UnifiedSelectionProvider, useUnifiedSelectionTree } from "@itwin/presentation-hierarchies-react";
|
|
93
|
+
import { createStorage } from "@itwin/unified-selection";
|
|
94
|
+
|
|
95
|
+
// Unified selection storage should be created once per application and reused across components.
|
|
96
|
+
const selectionStorage = createStorage();
|
|
97
|
+
|
|
98
|
+
/** The top-level application component sets up the unified selection context. */
|
|
99
|
+
function MyApplication(props: MyApplicationProps) {
|
|
100
|
+
return (
|
|
101
|
+
<UnifiedSelectionProvider storage={selectionStorage}>
|
|
102
|
+
<MyTreeComponent {...props} />
|
|
103
|
+
</UnifiedSelectionProvider>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** The tree component calls `useUnifiedSelectionTree` that uses unified selection context. */
|
|
108
|
+
function MyTreeComponent(props: MyTreeComponentProps) {
|
|
109
|
+
const state = useUnifiedSelectionTree(props);
|
|
110
|
+
return <MyTreeRenderer {...state} />;
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## iTwinUI components
|
|
115
|
+
|
|
116
|
+
While the package provides a headless UI, it also delivers a set of [iTwinUI](https://itwinui.bentley.com/)-based components for rendering the tree, which should cover majority of use cases. Consumers using the below components are required to provide a compatible `@itwin/itwinui-react` package, which is an optional peer dependency to this package.
|
|
117
|
+
|
|
118
|
+
### `TreeRenderer`
|
|
119
|
+
|
|
120
|
+
The component is based on [iTwinUI Tree component](https://itwinui.bentley.com/docs/tree) and uses our [`TreeNodeRenderer`](#treenoderenderer) to render the nodes. In addition, it makes use of the [`useSelectionHandler` hook](#useselectionhandler) to add selection modes' support.
|
|
121
|
+
|
|
122
|
+
The iTwinUI Tree component requires a `getNode` function that maps nodes to `NodeData<TNode>` objects. Our `TreeRenderer` uses `createRenderedTreeNodeData` function for this purpose, and it's available for consumers as well, in case a custom iTwinUI Tree component implementation is being written.
|
|
123
|
+
|
|
124
|
+
### `TreeNodeRenderer`
|
|
125
|
+
|
|
126
|
+
The component is based on `TreeNode` component from iTwinUI library and supports the following features:
|
|
127
|
+
|
|
128
|
+
- Rendering informational type of nodes (e.g. "No filter matches", "Too many nodes in a hierarchy level", etc.).
|
|
129
|
+
- Reporting click and key down events for use with [`useSelectionHandler` hook](#useselectionhandler).
|
|
130
|
+
- Icons, selection, expand / collapse.
|
|
131
|
+
- Action buttons to clear / set hierarchy level instance filter.
|
|
132
|
+
|
|
133
|
+
## Full example
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
import { useEffect, useState } from "react";
|
|
137
|
+
import { IModelConnection } from "@itwin/core-frontend";
|
|
138
|
+
import { SchemaContext } from "@itwin/ecschema-metadata";
|
|
139
|
+
import { ECSchemaRpcLocater } from "@itwin/ecschema-rpcinterface-common";
|
|
140
|
+
import { createECSchemaProvider, createECSqlQueryExecutor } from "@itwin/presentation-core-interop";
|
|
141
|
+
import { createLimitingECSqlQueryExecutor, createNodesQueryClauseFactory } from "@itwin/presentation-hierarchies";
|
|
142
|
+
import {
|
|
143
|
+
isPresentationHierarchyNode,
|
|
144
|
+
PresentationTreeNode,
|
|
145
|
+
UnifiedSelectionProvider,
|
|
146
|
+
useUnifiedSelectionTree,
|
|
147
|
+
TreeRenderer,
|
|
148
|
+
} from "@itwin/presentation-hierarchies-react";
|
|
149
|
+
import { createBisInstanceLabelSelectClauseFactory, createCachingECClassHierarchyInspector } from "@itwin/presentation-shared";
|
|
150
|
+
import { createStorage } from "@itwin/unified-selection";
|
|
151
|
+
|
|
152
|
+
// Not really part of the package, but we need SchemaContext to create the tree state. It's
|
|
153
|
+
// recommended to cache the schema context and reuse it across different application's components to
|
|
154
|
+
// avoid loading and storing same schemas multiple times.
|
|
155
|
+
const imodelSchemaContextsCache = new Map<string, SchemaContext>();
|
|
156
|
+
function getIModelSchemaContext(imodel: IModelConnection) {
|
|
157
|
+
let context = imodelSchemaContextsCache.get(imodel.key);
|
|
158
|
+
if (!context) {
|
|
159
|
+
context = new SchemaContext();
|
|
160
|
+
context.addLocater(new ECSchemaRpcLocater(imodel.getRpcProps()));
|
|
161
|
+
imodelSchemaContextsCache.set(imodel.key, context);
|
|
162
|
+
imodel.onClose.addListener(() => imodelSchemaContextsCache.delete(imodel.key));
|
|
163
|
+
}
|
|
164
|
+
return context;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Not part of the package - this should be created once and reused across different components of the application.
|
|
168
|
+
const selectionStorage = createStorage();
|
|
169
|
+
|
|
170
|
+
/** The top level component sets up access to the iModel and unified selection. */
|
|
171
|
+
export function MyTreeComponent({ imodel }: { imodel: IModelConnection }) {
|
|
172
|
+
const [imodelAccess, setIModelAccess] = useState<IModelAccess>();
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
const schemaProvider = createECSchemaProvider(getIModelSchemaContext(imodel));
|
|
175
|
+
setIModelAccess({
|
|
176
|
+
...schemaProvider,
|
|
177
|
+
// while caching for hierarchy inspector is not mandatory, it's recommended to use it to improve performance
|
|
178
|
+
...createCachingECClassHierarchyInspector({ schemaProvider, cacheSize: 100 }),
|
|
179
|
+
// the second argument is the maximum number of rows the executor will return - this allows us to
|
|
180
|
+
// avoid creating hierarchy levels of insane size (expensive to us and useless to users)
|
|
181
|
+
...createLimitingECSqlQueryExecutor(createECSqlQueryExecutor(imodel), 1000),
|
|
182
|
+
});
|
|
183
|
+
}, [imodel]);
|
|
184
|
+
|
|
185
|
+
if (!imodelAccess) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<UnifiedSelectionProvider storage={selectionStorage}>
|
|
191
|
+
<MyTreeComponentInternal imodelKey={imodel.key} imodelAccess={imodelAccess} />
|
|
192
|
+
</UnifiedSelectionProvider>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
type IModelAccess = Parameters<typeof useUnifiedSelectionTree>[0]["imodelAccess"];
|
|
196
|
+
|
|
197
|
+
/** Internal component that defines the hierarchy and creates tree state. */
|
|
198
|
+
function MyTreeComponentInternal({ imodelAccess, imodelKey }: { imodelAccess: IModelAccess; imodelKey: string }) {
|
|
199
|
+
// Create a factory for building nodes SELECT query clauses in a format understood by the provider
|
|
200
|
+
const [nodesQueryFactory] = useState(createNodesQueryClauseFactory({ imodelAccess }));
|
|
201
|
+
// Create a factory for building labels SELECT query clauses according to BIS conventions
|
|
202
|
+
const [labelsQueryFactory] = useState(createBisInstanceLabelSelectClauseFactory({ classHierarchyInspector: imodelAccess }));
|
|
203
|
+
|
|
204
|
+
const { rootNodes, ...state } = useUnifiedSelectionTree({
|
|
205
|
+
// the source name is used to distinguish selection changes being made by different components
|
|
206
|
+
sourceName: "MyTreeComponent",
|
|
207
|
+
// the iModel key is required for unified selection system to distinguish selection changes between different iModels
|
|
208
|
+
imodelKey,
|
|
209
|
+
// iModel access is used to build the hierarchy
|
|
210
|
+
imodelAccess,
|
|
211
|
+
// the hierarchy definition describes the hierarchy using ECSQL queries; here it just returns all bis.Model instances
|
|
212
|
+
// grouped by class
|
|
213
|
+
getHierarchyDefinition: () => ({
|
|
214
|
+
defineHierarchyLevel: async () => [
|
|
215
|
+
{
|
|
216
|
+
fullClassName: "BisCore.Model",
|
|
217
|
+
query: {
|
|
218
|
+
ecsql: `
|
|
219
|
+
SELECT
|
|
220
|
+
${await nodesQueryFactory.createSelectClause({
|
|
221
|
+
ecClassId: { selector: "this.ECClassId" },
|
|
222
|
+
ecInstanceId: { selector: "this.ECInstanceId" },
|
|
223
|
+
nodeLabel: {
|
|
224
|
+
selector: await labelsQueryFactory.createSelectClause({ classAlias: "this", className: "BisCore.Model" }),
|
|
225
|
+
},
|
|
226
|
+
grouping: {
|
|
227
|
+
byClass: true,
|
|
228
|
+
},
|
|
229
|
+
hasChildren: false,
|
|
230
|
+
})}
|
|
231
|
+
FROM BisCore.Model this
|
|
232
|
+
WHERE this.ParentModel IS NULL
|
|
233
|
+
`,
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
}),
|
|
238
|
+
});
|
|
239
|
+
if (!rootNodes) {
|
|
240
|
+
return "Loading...";
|
|
241
|
+
}
|
|
242
|
+
return <TreeRenderer rootNodes={rootNodes} {...state} />;
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Localization
|
|
247
|
+
|
|
248
|
+
Localization can be enabled for `TreeRenderer` component and `useTree` and `useUnifiedSelectionTree` hooks by providing an object with localized strings that will be used instead of the default English ones.
|
|
249
|
+
|
|
250
|
+
Example:
|
|
251
|
+
|
|
252
|
+
```tsx
|
|
253
|
+
const localizedStrings = {
|
|
254
|
+
// strings for the `useUnifiedSelectionTree` hook
|
|
255
|
+
unspecified: "Unspecified",
|
|
256
|
+
other: "Other",
|
|
257
|
+
|
|
258
|
+
// strings for `TreeRenderer` and `TreeNodeRenderer`
|
|
259
|
+
loading: "Loading...",
|
|
260
|
+
filterHierarchyLevel: "Apply filter",
|
|
261
|
+
clearHierarchyLevelFilter: "Clear active filter",
|
|
262
|
+
noFilteredChildren: "No child nodes match current filter",
|
|
263
|
+
resultLimitExceeded: "There are more items than allowed limit of {{limit}}.",
|
|
264
|
+
resultLimitExceededWithFiltering: "Please provide <link>additional filtering</link> - there are more items than allowed limit of {{limit}}.",
|
|
265
|
+
increaseHierarchyLimit: "<link>Increase the hierarchy level size limit to {{limit}}.</link>",
|
|
266
|
+
increaseHierarchyLimitWithFiltering: "Or, <link>increase the hierarchy level size limit to {{limit}}.</link>",
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
function MyTreeComponent({ imodelAccess, imodelKey }: { imodelAccess: IModelAccess; imodelKey: string }) {
|
|
270
|
+
const { rootNodes, ...state } = useUnifiedSelectionTree({
|
|
271
|
+
sourceName: "MyTreeComponent",
|
|
272
|
+
imodelKey,
|
|
273
|
+
imodelAccess,
|
|
274
|
+
localizedStrings,
|
|
275
|
+
getHierarchyDefinition: () => ({
|
|
276
|
+
defineHierarchyLevel: async () => [],
|
|
277
|
+
}),
|
|
278
|
+
});
|
|
279
|
+
if (!rootNodes) {
|
|
280
|
+
return localizedStrings.loading;
|
|
281
|
+
}
|
|
282
|
+
return <TreeRenderer rootNodes={rootNodes} localizedStrings={localizedStrings} {...state} />;
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
In case the `TreeNodeRenderer` component is used within a custom tree renderer, the tree component should supply localized strings through `LocalizationContextProvider`:
|
|
287
|
+
|
|
288
|
+
```tsx
|
|
289
|
+
export function MyTreeRenderer(props: MyTreeRendererProps) {
|
|
290
|
+
const nodeRenderer = useCallback(
|
|
291
|
+
(nodeProps) => {
|
|
292
|
+
return <TreeNodeRenderer {...nodeProps} {...props} />;
|
|
293
|
+
},
|
|
294
|
+
[props],
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
return (
|
|
298
|
+
<LocalizationContextProvider localizedStrings={localizedStrings}>
|
|
299
|
+
<Tree<RenderedTreeNode> {...props} />
|
|
300
|
+
</LocalizationContextProvider>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
```
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { HierarchyNode } from "@itwin/presentation-hierarchies";
|
|
2
|
+
/**
|
|
3
|
+
* A type that defines an actual expandable node in a UI tree component, built with `useTree` hook.
|
|
4
|
+
* @beta
|
|
5
|
+
*/
|
|
6
|
+
export interface PresentationHierarchyNode {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
children: true | Array<PresentationTreeNode>;
|
|
10
|
+
isExpanded: boolean;
|
|
11
|
+
isLoading: boolean;
|
|
12
|
+
isFilterable: boolean;
|
|
13
|
+
isFiltered: boolean;
|
|
14
|
+
/** UI-agnostic source of this node object. */
|
|
15
|
+
nodeData: HierarchyNode;
|
|
16
|
+
/** Additional data that may be assigned to this node. */
|
|
17
|
+
extendedData?: {
|
|
18
|
+
[key: string]: any;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A type of `PresentationInfoNode` that is returned as the single child of a filtered parent node,
|
|
23
|
+
* when none of the child nodes match the filter.
|
|
24
|
+
*
|
|
25
|
+
* @beta
|
|
26
|
+
*/
|
|
27
|
+
export interface PresentationNoFilterMatchesInfoNode {
|
|
28
|
+
id: string;
|
|
29
|
+
parentNodeId: string | undefined;
|
|
30
|
+
type: "NoFilterMatches";
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A type of `PresentationInfoNode` that is returned as the single child of a parent node, when the
|
|
34
|
+
* number of child nodes exceeds the limit set on the tree nodes loader. The limit is also included
|
|
35
|
+
* on this node as `resultSetSizeLimit` attribute.
|
|
36
|
+
*
|
|
37
|
+
* @beta
|
|
38
|
+
*/
|
|
39
|
+
export interface PresentationResultSetTooLargeInfoNode {
|
|
40
|
+
id: string;
|
|
41
|
+
parentNodeId: string | undefined;
|
|
42
|
+
type: "ResultSetTooLarge";
|
|
43
|
+
resultSetSizeLimit: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A type of `PresentationInfoNode` that contains a user-friendly message to be displayed in the UI.
|
|
47
|
+
* Generally, this is the only child of a parent node, created in cases like an error loading children. The
|
|
48
|
+
* renderer may offer users to re-load the children or the whole component in such cases.
|
|
49
|
+
*
|
|
50
|
+
* @beta
|
|
51
|
+
*/
|
|
52
|
+
export interface PresentationGenericInfoNode {
|
|
53
|
+
id: string;
|
|
54
|
+
parentNodeId: string | undefined;
|
|
55
|
+
type: "Unknown";
|
|
56
|
+
message: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A type that defines a non-expandable, non-selectable informational node in a UI tree component, built
|
|
60
|
+
* with `useTree` hook.
|
|
61
|
+
*
|
|
62
|
+
* @beta
|
|
63
|
+
*/
|
|
64
|
+
export type PresentationInfoNode = PresentationGenericInfoNode | PresentationResultSetTooLargeInfoNode | PresentationNoFilterMatchesInfoNode;
|
|
65
|
+
/**
|
|
66
|
+
* A type that defines a node in a UI tree component, built with `useTree` hook.
|
|
67
|
+
* @beta
|
|
68
|
+
*/
|
|
69
|
+
export type PresentationTreeNode = PresentationHierarchyNode | PresentationInfoNode;
|
|
70
|
+
/**
|
|
71
|
+
* An utility function to check if a node is a `PresentationHierarchyNode`.
|
|
72
|
+
* @beta
|
|
73
|
+
*/
|
|
74
|
+
export declare function isPresentationHierarchyNode(node: PresentationTreeNode): node is PresentationHierarchyNode;
|
|
75
|
+
//# sourceMappingURL=TreeNode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TreeNode.d.ts","sourceRoot":"","sources":["../../../src/presentation-hierarchies-react/TreeNode.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAEhE;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,IAAI,GAAG,KAAK,CAAC,oBAAoB,CAAC,CAAC;IAC7C,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,8CAA8C;IAC9C,QAAQ,EAAE,aAAa,CAAC;IACxB,yDAAyD;IACzD,YAAY,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC;CACvC;AAED;;;;;GAKG;AACH,MAAM,WAAW,mCAAmC;IAClD,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,IAAI,EAAE,iBAAiB,CAAC;CACzB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,qCAAqC;IACpD,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,2BAA2B;IAC1C,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG,2BAA2B,GAAG,qCAAqC,GAAG,mCAAmC,CAAC;AAE7I;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,yBAAyB,GAAG,oBAAoB,CAAC;AAEpF;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,oBAAoB,GAAG,IAAI,IAAI,yBAAyB,CAEzG"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*---------------------------------------------------------------------------------------------
|
|
3
|
+
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
|
|
4
|
+
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
5
|
+
*--------------------------------------------------------------------------------------------*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.isPresentationHierarchyNode = void 0;
|
|
8
|
+
/**
|
|
9
|
+
* An utility function to check if a node is a `PresentationHierarchyNode`.
|
|
10
|
+
* @beta
|
|
11
|
+
*/
|
|
12
|
+
function isPresentationHierarchyNode(node) {
|
|
13
|
+
return "children" in node;
|
|
14
|
+
}
|
|
15
|
+
exports.isPresentationHierarchyNode = isPresentationHierarchyNode;
|
|
16
|
+
//# sourceMappingURL=TreeNode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TreeNode.js","sourceRoot":"","sources":["../../../src/presentation-hierarchies-react/TreeNode.ts"],"names":[],"mappings":";AAAA;;;gGAGgG;;;AA4EhG;;;GAGG;AACH,SAAgB,2BAA2B,CAAC,IAA0B;IACpE,OAAO,UAAU,IAAI,IAAI,CAAC;AAC5B,CAAC;AAFD,kEAEC","sourcesContent":["/*---------------------------------------------------------------------------------------------\n * Copyright (c) Bentley Systems, Incorporated. All rights reserved.\n * See LICENSE.md in the project root for license terms and full copyright notice.\n *--------------------------------------------------------------------------------------------*/\n\nimport { HierarchyNode } from \"@itwin/presentation-hierarchies\";\n\n/**\n * A type that defines an actual expandable node in a UI tree component, built with `useTree` hook.\n * @beta\n */\nexport interface PresentationHierarchyNode {\n id: string;\n label: string;\n children: true | Array<PresentationTreeNode>;\n isExpanded: boolean;\n isLoading: boolean;\n isFilterable: boolean;\n isFiltered: boolean;\n /** UI-agnostic source of this node object. */\n nodeData: HierarchyNode;\n /** Additional data that may be assigned to this node. */\n extendedData?: { [key: string]: any };\n}\n\n/**\n * A type of `PresentationInfoNode` that is returned as the single child of a filtered parent node,\n * when none of the child nodes match the filter.\n *\n * @beta\n */\nexport interface PresentationNoFilterMatchesInfoNode {\n id: string;\n parentNodeId: string | undefined;\n type: \"NoFilterMatches\";\n}\n\n/**\n * A type of `PresentationInfoNode` that is returned as the single child of a parent node, when the\n * number of child nodes exceeds the limit set on the tree nodes loader. The limit is also included\n * on this node as `resultSetSizeLimit` attribute.\n *\n * @beta\n */\nexport interface PresentationResultSetTooLargeInfoNode {\n id: string;\n parentNodeId: string | undefined;\n type: \"ResultSetTooLarge\";\n resultSetSizeLimit: number;\n}\n\n/**\n * A type of `PresentationInfoNode` that contains a user-friendly message to be displayed in the UI.\n * Generally, this is the only child of a parent node, created in cases like an error loading children. The\n * renderer may offer users to re-load the children or the whole component in such cases.\n *\n * @beta\n */\nexport interface PresentationGenericInfoNode {\n id: string;\n parentNodeId: string | undefined;\n type: \"Unknown\";\n message: string;\n}\n\n/**\n * A type that defines a non-expandable, non-selectable informational node in a UI tree component, built\n * with `useTree` hook.\n *\n * @beta\n */\nexport type PresentationInfoNode = PresentationGenericInfoNode | PresentationResultSetTooLargeInfoNode | PresentationNoFilterMatchesInfoNode;\n\n/**\n * A type that defines a node in a UI tree component, built with `useTree` hook.\n * @beta\n */\nexport type PresentationTreeNode = PresentationHierarchyNode | PresentationInfoNode;\n\n/**\n * An utility function to check if a node is a `PresentationHierarchyNode`.\n * @beta\n */\nexport function isPresentationHierarchyNode(node: PresentationTreeNode): node is PresentationHierarchyNode {\n return \"children\" in node;\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { PropsWithChildren } from "react";
|
|
2
|
+
import { SelectionStorage } from "@itwin/unified-selection";
|
|
3
|
+
interface Props {
|
|
4
|
+
storage: SelectionStorage;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* A React context provider that makes given selection storage available to all child components. This
|
|
8
|
+
* is a requirement for `useUnifiedSelectionTree` to work with unified selection.
|
|
9
|
+
*
|
|
10
|
+
* See `README.md` for a usage example.
|
|
11
|
+
*
|
|
12
|
+
* @beta
|
|
13
|
+
*/
|
|
14
|
+
export declare function UnifiedSelectionProvider({ storage, children }: PropsWithChildren<Props>): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
/** @internal */
|
|
16
|
+
export declare function useUnifiedSelectionContext(): SelectionStorage | undefined;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=UnifiedSelectionContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UnifiedSelectionContext.d.ts","sourceRoot":"","sources":["../../../src/presentation-hierarchies-react/UnifiedSelectionContext.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAiB,iBAAiB,EAAc,MAAM,OAAO,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAI5D,UAAU,KAAK;IACb,OAAO,EAAE,gBAAgB,CAAC;CAC3B;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,iBAAiB,CAAC,KAAK,CAAC,2CAEvF;AAED,gBAAgB;AAChB,wBAAgB,0BAA0B,iCAEzC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useUnifiedSelectionContext = exports.UnifiedSelectionProvider = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
/*---------------------------------------------------------------------------------------------
|
|
6
|
+
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
|
|
7
|
+
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
8
|
+
*--------------------------------------------------------------------------------------------*/
|
|
9
|
+
const react_1 = require("react");
|
|
10
|
+
const unifiedSelectionContext = (0, react_1.createContext)(undefined);
|
|
11
|
+
/**
|
|
12
|
+
* A React context provider that makes given selection storage available to all child components. This
|
|
13
|
+
* is a requirement for `useUnifiedSelectionTree` to work with unified selection.
|
|
14
|
+
*
|
|
15
|
+
* See `README.md` for a usage example.
|
|
16
|
+
*
|
|
17
|
+
* @beta
|
|
18
|
+
*/
|
|
19
|
+
function UnifiedSelectionProvider({ storage, children }) {
|
|
20
|
+
return (0, jsx_runtime_1.jsx)(unifiedSelectionContext.Provider, { value: storage, children: children });
|
|
21
|
+
}
|
|
22
|
+
exports.UnifiedSelectionProvider = UnifiedSelectionProvider;
|
|
23
|
+
/** @internal */
|
|
24
|
+
function useUnifiedSelectionContext() {
|
|
25
|
+
return (0, react_1.useContext)(unifiedSelectionContext);
|
|
26
|
+
}
|
|
27
|
+
exports.useUnifiedSelectionContext = useUnifiedSelectionContext;
|
|
28
|
+
//# sourceMappingURL=UnifiedSelectionContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UnifiedSelectionContext.js","sourceRoot":"","sources":["../../../src/presentation-hierarchies-react/UnifiedSelectionContext.tsx"],"names":[],"mappings":";;;;AAAA;;;gGAGgG;AAEhG,iCAAqE;AAGrE,MAAM,uBAAuB,GAAG,IAAA,qBAAa,EAA+B,SAAS,CAAC,CAAC;AAMvF;;;;;;;GAOG;AACH,SAAgB,wBAAwB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAA4B;IACtF,OAAO,uBAAC,uBAAuB,CAAC,QAAQ,IAAC,KAAK,EAAE,OAAO,YAAG,QAAQ,GAAoC,CAAC;AACzG,CAAC;AAFD,4DAEC;AAED,gBAAgB;AAChB,SAAgB,0BAA0B;IACxC,OAAO,IAAA,kBAAU,EAAC,uBAAuB,CAAC,CAAC;AAC7C,CAAC;AAFD,gEAEC","sourcesContent":["/*---------------------------------------------------------------------------------------------\n * Copyright (c) Bentley Systems, Incorporated. All rights reserved.\n * See LICENSE.md in the project root for license terms and full copyright notice.\n *--------------------------------------------------------------------------------------------*/\n\nimport { createContext, PropsWithChildren, useContext } from \"react\";\nimport { SelectionStorage } from \"@itwin/unified-selection\";\n\nconst unifiedSelectionContext = createContext<SelectionStorage | undefined>(undefined);\n\ninterface Props {\n storage: SelectionStorage;\n}\n\n/**\n * A React context provider that makes given selection storage available to all child components. This\n * is a requirement for `useUnifiedSelectionTree` to work with unified selection.\n *\n * See `README.md` for a usage example.\n *\n * @beta\n */\nexport function UnifiedSelectionProvider({ storage, children }: PropsWithChildren<Props>) {\n return <unifiedSelectionContext.Provider value={storage}>{children}</unifiedSelectionContext.Provider>;\n}\n\n/** @internal */\nexport function useUnifiedSelectionContext() {\n return useContext(unifiedSelectionContext);\n}\n"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { useTree } from "./UseTree";
|
|
3
|
+
/**
|
|
4
|
+
* A union of different supported selection modes in a tree component:
|
|
5
|
+
* - `none` - no selection is allowed,
|
|
6
|
+
* - `single` - only one node can be selected at a time,
|
|
7
|
+
* - `extended` - multiple nodes can be selected using shift and ctrl keys,
|
|
8
|
+
* - `multiple` - multiple nodes can be selected without using shift or ctrl keys.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export type SelectionMode = "none" | "single" | "extended" | "multiple";
|
|
13
|
+
/** @internal */
|
|
14
|
+
export type SelectionChangeType = "add" | "replace" | "remove";
|
|
15
|
+
/** Props for `useSelectionHandler` hook. */
|
|
16
|
+
type UseSelectionHandlerProps = Pick<ReturnType<typeof useTree>, "rootNodes" | "selectNodes"> & {
|
|
17
|
+
/** Selection mode that the component is working in. */
|
|
18
|
+
selectionMode: SelectionMode;
|
|
19
|
+
};
|
|
20
|
+
/** Result of `useSelectionHandler` hook. */
|
|
21
|
+
interface UseSelectionHandlerResult {
|
|
22
|
+
/** Should be called by node renderer when a node component is clicked. */
|
|
23
|
+
onNodeClick: (nodeId: string, isSelected: boolean, event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
|
|
24
|
+
/** Should be called by node renderer when a keyboard event happens on a node. */
|
|
25
|
+
onNodeKeyDown: (nodeId: string, isSelected: boolean, event: React.KeyboardEvent<HTMLElement>) => void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A react hook that helps implement different selection modes in a tree component created using `useTree` hook.
|
|
29
|
+
* @beta
|
|
30
|
+
*/
|
|
31
|
+
export declare function useSelectionHandler(props: UseSelectionHandlerProps): UseSelectionHandlerResult;
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=UseSelectionHandler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UseSelectionHandler.d.ts","sourceRoot":"","sources":["../../../src/presentation-hierarchies-react/UseSelectionHandler.ts"],"names":[],"mappings":";AAOA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAC;AAExE,gBAAgB;AAChB,MAAM,MAAM,mBAAmB,GAAG,KAAK,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE/D,4CAA4C;AAC5C,KAAK,wBAAwB,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,OAAO,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC,GAAG;IAC9F,uDAAuD;IACvD,aAAa,EAAE,aAAa,CAAC;CAC9B,CAAC;AAEF,4CAA4C;AAC5C,UAAU,yBAAyB;IACjC,0EAA0E;IAC1E,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,WAAW,EAAE,UAAU,CAAC,KAAK,IAAI,CAAC;IAC7G,iFAAiF;IACjF,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;CACvG;AAOD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,GAAG,yBAAyB,CAwD9F"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*---------------------------------------------------------------------------------------------
|
|
3
|
+
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
|
|
4
|
+
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
5
|
+
*--------------------------------------------------------------------------------------------*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.useSelectionHandler = void 0;
|
|
8
|
+
const react_1 = require("react");
|
|
9
|
+
const TreeNode_1 = require("./TreeNode");
|
|
10
|
+
/**
|
|
11
|
+
* A react hook that helps implement different selection modes in a tree component created using `useTree` hook.
|
|
12
|
+
* @beta
|
|
13
|
+
*/
|
|
14
|
+
function useSelectionHandler(props) {
|
|
15
|
+
const { rootNodes, selectionMode, selectNodes } = props;
|
|
16
|
+
const previousSelectionRef = (0, react_1.useRef)(undefined);
|
|
17
|
+
const state = (0, react_1.useRef)({
|
|
18
|
+
flatNodeList: [],
|
|
19
|
+
nodeIdToIndexMap: new Map(),
|
|
20
|
+
});
|
|
21
|
+
(0, react_1.useEffect)(() => {
|
|
22
|
+
if (!rootNodes) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
state.current = computeFlatNodeList(rootNodes);
|
|
26
|
+
}, [rootNodes]);
|
|
27
|
+
const getNodeRange = (firstId, secondId) => {
|
|
28
|
+
const getIndex = (nodeId) => {
|
|
29
|
+
return nodeId ? state.current.nodeIdToIndexMap.get(nodeId) : 0;
|
|
30
|
+
};
|
|
31
|
+
const firstIndex = getIndex(firstId);
|
|
32
|
+
const secondIndex = getIndex(secondId);
|
|
33
|
+
if (firstIndex === undefined || secondIndex === undefined) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
const startingIndex = Math.min(firstIndex, secondIndex);
|
|
37
|
+
const endIndex = Math.max(firstIndex, secondIndex);
|
|
38
|
+
return state.current.flatNodeList.slice(startingIndex, endIndex + 1);
|
|
39
|
+
};
|
|
40
|
+
const onNodeSelect = (nodeId, isSelected, shiftDown, ctrlDown) => {
|
|
41
|
+
const selection = getSelectionAction(selectionMode, isSelected, shiftDown, ctrlDown);
|
|
42
|
+
if (selection.type === "disabled") {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const nodes = selection.select === "range" ? getNodeRange(previousSelectionRef.current, nodeId) : [nodeId];
|
|
46
|
+
if (!nodes.length) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
selection.select !== "range" && (previousSelectionRef.current = nodeId);
|
|
50
|
+
selectNodes(nodes, selection.type);
|
|
51
|
+
};
|
|
52
|
+
const onNodeClick = (nodeId, isSelected, event) => {
|
|
53
|
+
return onNodeSelect(nodeId, isSelected, event.shiftKey, event.ctrlKey);
|
|
54
|
+
};
|
|
55
|
+
const onNodeKeyDown = (nodeId, isSelected, event) => {
|
|
56
|
+
if (event.key === " " || event.key === "Spacebar" || event.key === "Enter") {
|
|
57
|
+
return onNodeSelect(nodeId, isSelected, event.shiftKey, event.ctrlKey);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
return { onNodeClick, onNodeKeyDown };
|
|
61
|
+
}
|
|
62
|
+
exports.useSelectionHandler = useSelectionHandler;
|
|
63
|
+
function getSelectionAction(selectionMode, isSelected, shiftDown, ctrlDown) {
|
|
64
|
+
switch (selectionMode) {
|
|
65
|
+
case "none":
|
|
66
|
+
return { select: "node", type: "disabled" };
|
|
67
|
+
case "single":
|
|
68
|
+
return { select: "node", type: isSelected ? "replace" : "remove" };
|
|
69
|
+
case "multiple":
|
|
70
|
+
return { select: "node", type: isSelected ? "add" : "remove" };
|
|
71
|
+
case "extended":
|
|
72
|
+
return getExtendedSelectionAction(isSelected, shiftDown, ctrlDown);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function getExtendedSelectionAction(isSelected, shiftDown, ctrlDown) {
|
|
76
|
+
if (shiftDown) {
|
|
77
|
+
return { select: "range", type: "replace" };
|
|
78
|
+
}
|
|
79
|
+
if (!isSelected) {
|
|
80
|
+
return { select: "node", type: "remove" };
|
|
81
|
+
}
|
|
82
|
+
return { select: "node", type: ctrlDown ? "add" : "replace" };
|
|
83
|
+
}
|
|
84
|
+
function computeFlatNodeList(rootNodes) {
|
|
85
|
+
const flatNodeList = [];
|
|
86
|
+
const nodeIdToIndexMap = new Map();
|
|
87
|
+
const flattenNodeRecursively = (nodes) => {
|
|
88
|
+
nodes.forEach((node) => {
|
|
89
|
+
if (!(0, TreeNode_1.isPresentationHierarchyNode)(node)) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
nodeIdToIndexMap.set(node.id, flatNodeList.length);
|
|
93
|
+
flatNodeList.push(node.id);
|
|
94
|
+
if (node.isExpanded && typeof node.children !== "boolean") {
|
|
95
|
+
flattenNodeRecursively(node.children);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
flattenNodeRecursively(rootNodes);
|
|
100
|
+
return { flatNodeList, nodeIdToIndexMap };
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=UseSelectionHandler.js.map
|