@itwin/presentation-hierarchies-react 0.5.3 → 0.7.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 +46 -0
- package/README.md +164 -79
- package/lib/cjs/presentation-hierarchies-react/TreeNode.js +1 -2
- package/lib/cjs/presentation-hierarchies-react/TreeNode.js.map +1 -1
- package/lib/cjs/presentation-hierarchies-react/UnifiedSelectionContext.js +2 -3
- package/lib/cjs/presentation-hierarchies-react/UnifiedSelectionContext.js.map +1 -1
- package/lib/cjs/presentation-hierarchies-react/UseSelectionHandler.d.ts +8 -2
- package/lib/cjs/presentation-hierarchies-react/UseSelectionHandler.d.ts.map +1 -1
- package/lib/cjs/presentation-hierarchies-react/UseSelectionHandler.js +1 -2
- package/lib/cjs/presentation-hierarchies-react/UseSelectionHandler.js.map +1 -1
- package/lib/cjs/presentation-hierarchies-react/UseTree.d.ts +48 -8
- package/lib/cjs/presentation-hierarchies-react/UseTree.d.ts.map +1 -1
- package/lib/cjs/presentation-hierarchies-react/UseTree.js +5 -5
- package/lib/cjs/presentation-hierarchies-react/UseTree.js.map +1 -1
- package/lib/cjs/presentation-hierarchies-react/internal/TreeActions.d.ts +5 -0
- package/lib/cjs/presentation-hierarchies-react/internal/TreeActions.d.ts.map +1 -1
- package/lib/cjs/presentation-hierarchies-react/internal/TreeActions.js +22 -4
- package/lib/cjs/presentation-hierarchies-react/internal/TreeActions.js.map +1 -1
- package/lib/cjs/presentation-hierarchies-react/internal/TreeLoader.d.ts +4 -0
- package/lib/cjs/presentation-hierarchies-react/internal/TreeLoader.d.ts.map +1 -1
- package/lib/cjs/presentation-hierarchies-react/internal/TreeLoader.js +7 -1
- package/lib/cjs/presentation-hierarchies-react/internal/TreeLoader.js.map +1 -1
- package/lib/cjs/presentation-hierarchies-react/internal/TreeModel.js +3 -3
- package/lib/cjs/presentation-hierarchies-react/internal/TreeModel.js.map +1 -1
- package/lib/cjs/presentation-hierarchies-react/internal/UseUnifiedSelection.d.ts +8 -3
- package/lib/cjs/presentation-hierarchies-react/internal/UseUnifiedSelection.d.ts.map +1 -1
- package/lib/cjs/presentation-hierarchies-react/internal/UseUnifiedSelection.js +2 -3
- package/lib/cjs/presentation-hierarchies-react/internal/UseUnifiedSelection.js.map +1 -1
- package/lib/cjs/presentation-hierarchies-react/internal/Utils.js +3 -3
- package/lib/cjs/presentation-hierarchies-react/internal/Utils.js.map +1 -1
- package/lib/cjs/presentation-hierarchies-react/itwinui/LocalizationContext.d.ts +13 -3
- package/lib/cjs/presentation-hierarchies-react/itwinui/LocalizationContext.d.ts.map +1 -1
- package/lib/cjs/presentation-hierarchies-react/itwinui/LocalizationContext.js +6 -4
- package/lib/cjs/presentation-hierarchies-react/itwinui/LocalizationContext.js.map +1 -1
- package/lib/cjs/presentation-hierarchies-react/itwinui/TreeNodeRenderer.d.ts +9 -0
- package/lib/cjs/presentation-hierarchies-react/itwinui/TreeNodeRenderer.d.ts.map +1 -1
- package/lib/cjs/presentation-hierarchies-react/itwinui/TreeNodeRenderer.js +15 -17
- package/lib/cjs/presentation-hierarchies-react/itwinui/TreeNodeRenderer.js.map +1 -1
- package/lib/cjs/presentation-hierarchies-react/itwinui/TreeRenderer.d.ts +4 -3
- package/lib/cjs/presentation-hierarchies-react/itwinui/TreeRenderer.d.ts.map +1 -1
- package/lib/cjs/presentation-hierarchies-react/itwinui/TreeRenderer.js +2 -3
- package/lib/cjs/presentation-hierarchies-react/itwinui/TreeRenderer.js.map +1 -1
- package/lib/esm/presentation-hierarchies-react/UseSelectionHandler.d.ts +8 -2
- package/lib/esm/presentation-hierarchies-react/UseSelectionHandler.d.ts.map +1 -1
- package/lib/esm/presentation-hierarchies-react/UseSelectionHandler.js.map +1 -1
- package/lib/esm/presentation-hierarchies-react/UseTree.d.ts +48 -8
- package/lib/esm/presentation-hierarchies-react/UseTree.d.ts.map +1 -1
- package/lib/esm/presentation-hierarchies-react/UseTree.js +3 -2
- package/lib/esm/presentation-hierarchies-react/UseTree.js.map +1 -1
- package/lib/esm/presentation-hierarchies-react/internal/TreeActions.d.ts +5 -0
- package/lib/esm/presentation-hierarchies-react/internal/TreeActions.d.ts.map +1 -1
- package/lib/esm/presentation-hierarchies-react/internal/TreeActions.js +22 -4
- package/lib/esm/presentation-hierarchies-react/internal/TreeActions.js.map +1 -1
- package/lib/esm/presentation-hierarchies-react/internal/TreeLoader.d.ts +4 -0
- package/lib/esm/presentation-hierarchies-react/internal/TreeLoader.d.ts.map +1 -1
- package/lib/esm/presentation-hierarchies-react/internal/TreeLoader.js +7 -1
- package/lib/esm/presentation-hierarchies-react/internal/TreeLoader.js.map +1 -1
- package/lib/esm/presentation-hierarchies-react/internal/UseUnifiedSelection.d.ts +8 -3
- package/lib/esm/presentation-hierarchies-react/internal/UseUnifiedSelection.d.ts.map +1 -1
- package/lib/esm/presentation-hierarchies-react/internal/UseUnifiedSelection.js +1 -1
- package/lib/esm/presentation-hierarchies-react/internal/UseUnifiedSelection.js.map +1 -1
- package/lib/esm/presentation-hierarchies-react/itwinui/LocalizationContext.d.ts +13 -3
- package/lib/esm/presentation-hierarchies-react/itwinui/LocalizationContext.d.ts.map +1 -1
- package/lib/esm/presentation-hierarchies-react/itwinui/LocalizationContext.js +4 -1
- package/lib/esm/presentation-hierarchies-react/itwinui/LocalizationContext.js.map +1 -1
- package/lib/esm/presentation-hierarchies-react/itwinui/TreeNodeRenderer.d.ts +9 -0
- package/lib/esm/presentation-hierarchies-react/itwinui/TreeNodeRenderer.d.ts.map +1 -1
- package/lib/esm/presentation-hierarchies-react/itwinui/TreeNodeRenderer.js +14 -15
- package/lib/esm/presentation-hierarchies-react/itwinui/TreeNodeRenderer.js.map +1 -1
- package/lib/esm/presentation-hierarchies-react/itwinui/TreeRenderer.d.ts +4 -3
- package/lib/esm/presentation-hierarchies-react/itwinui/TreeRenderer.d.ts.map +1 -1
- package/lib/esm/presentation-hierarchies-react/itwinui/TreeRenderer.js.map +1 -1
- package/package.json +24 -22
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# @itwin/presentation-hierarchies-react
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#676](https://github.com/iTwin/presentation/pull/676): `useTree` and `useUnifiedSelectionTree`: Extended return type of `getFilteredPaths` prop to allow specifying whether hierarchy should be expanded to filtering path target.
|
|
8
|
+
|
|
9
|
+
With this change, hierarchy is no longer expanded to filter targets by default. To achieve the same behavior, paths with `autoExpand` option should be returned:
|
|
10
|
+
|
|
11
|
+
_Before:_
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
async function getFilteredPaths(): Promise<HierarchyNodeIdentifiersPath[]> {
|
|
15
|
+
return paths;
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
_Now:_
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
async function getFilteredPaths(): Promise<Array<{ path: HierarchyNodeIdentifiersPath; options?: { autoExpand?: boolean }>> {
|
|
23
|
+
return paths.map((path) => ({ path, options: { autoExpand: true } }));
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- Updated dependencies:
|
|
30
|
+
- @itwin/presentation-hierarchies@0.4.0
|
|
31
|
+
- @itwin/presentation-shared@0.4.0
|
|
32
|
+
- @itwin/unified-selection@0.4.6
|
|
33
|
+
|
|
34
|
+
## 0.6.0
|
|
35
|
+
|
|
36
|
+
### Minor Changes
|
|
37
|
+
|
|
38
|
+
- [#661](https://github.com/iTwin/presentation/pull/661): Added `onHierarchyLoadError` callback to `useTree` and `useUnifiedSelectionTree` that is called when an error occurs while loading hierarchy.
|
|
39
|
+
|
|
40
|
+
### Patch Changes
|
|
41
|
+
|
|
42
|
+
- [#660](https://github.com/iTwin/presentation/pull/660): Switch to `onClick` and `onNodeKeyDown` callbacks provided by `TreeNode` in `TreeNodeRenderer`.
|
|
43
|
+
- [#655](https://github.com/iTwin/presentation/pull/655): Remove exposed internal APIs.
|
|
44
|
+
- Updated dependencies:
|
|
45
|
+
- @itwin/presentation-hierarchies@0.3.0
|
|
46
|
+
- @itwin/unified-selection@0.4.5
|
|
47
|
+
- @itwin/presentation-shared@0.3.2
|
|
48
|
+
|
|
3
49
|
## 0.5.3
|
|
4
50
|
|
|
5
51
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -14,11 +14,31 @@ It takes 2 required properties:
|
|
|
14
14
|
|
|
15
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
16
|
|
|
17
|
+
<!-- [[include: [Presentation.HierarchiesReact.iModelAccess.Imports, Presentation.HierarchiesReact.iModelAccess], tsx]] -->
|
|
18
|
+
<!-- BEGIN EXTRACTION -->
|
|
19
|
+
|
|
17
20
|
```tsx
|
|
18
21
|
import { IModelConnection } from "@itwin/core-frontend";
|
|
22
|
+
import { SchemaContext } from "@itwin/ecschema-metadata";
|
|
23
|
+
import { ECSchemaRpcLocater } from "@itwin/ecschema-rpcinterface-common";
|
|
19
24
|
import { createECSchemaProvider, createECSqlQueryExecutor } from "@itwin/presentation-core-interop";
|
|
20
|
-
import { createLimitingECSqlQueryExecutor } from "@itwin/presentation-hierarchies";
|
|
21
|
-
|
|
25
|
+
import { createLimitingECSqlQueryExecutor, createNodesQueryClauseFactory, HierarchyDefinition } from "@itwin/presentation-hierarchies";
|
|
26
|
+
|
|
27
|
+
// Not really part of the package, but we need SchemaContext to create the tree state. It's
|
|
28
|
+
// recommended to cache the schema context and reuse it across different application's components to
|
|
29
|
+
// avoid loading and storing same schemas multiple times.
|
|
30
|
+
const imodelSchemaContextsCache = new Map<string, SchemaContext>();
|
|
31
|
+
|
|
32
|
+
function getIModelSchemaContext(imodel: IModelConnection) {
|
|
33
|
+
let context = imodelSchemaContextsCache.get(imodel.key);
|
|
34
|
+
if (!context) {
|
|
35
|
+
context = new SchemaContext();
|
|
36
|
+
context.addLocater(new ECSchemaRpcLocater(imodel.getRpcProps()));
|
|
37
|
+
imodelSchemaContextsCache.set(imodel.key, context);
|
|
38
|
+
imodel.onClose.addListener(() => imodelSchemaContextsCache.delete(imodel.key));
|
|
39
|
+
}
|
|
40
|
+
return context;
|
|
41
|
+
}
|
|
22
42
|
|
|
23
43
|
function createIModelAccess(imodel: IModelConnection) {
|
|
24
44
|
const schemaProvider = createECSchemaProvider(getIModelSchemaContext(imodel));
|
|
@@ -33,6 +53,8 @@ It takes 2 required properties:
|
|
|
33
53
|
}
|
|
34
54
|
```
|
|
35
55
|
|
|
56
|
+
<!-- END EXTRACTION -->
|
|
57
|
+
|
|
36
58
|
- `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
59
|
|
|
38
60
|
The resulting state object contains the following properties:
|
|
@@ -60,7 +82,7 @@ The resulting state object contains the following properties:
|
|
|
60
82
|
|
|
61
83
|
### `useSelectionHandler`
|
|
62
84
|
|
|
63
|
-
This is React hook that helps implement different selection modes in tree, whose state is managed through the `useTree` or `useUnifiedSelectionTree` hooks.
|
|
85
|
+
This is a React hook that helps implement different selection modes in a tree, whose state is managed through the `useTree` or `useUnifiedSelectionTree` hooks.
|
|
64
86
|
|
|
65
87
|
It takes 3 required properties:
|
|
66
88
|
|
|
@@ -88,29 +110,64 @@ The returned result is identical to the one from [`useTree`](#usetree).
|
|
|
88
110
|
|
|
89
111
|
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
112
|
|
|
113
|
+
<!-- [[include: [Presentation.HierarchiesReact.SelectionStorage.Imports, Presentation.HierarchiesReact.SelectionStorage], tsx]] -->
|
|
114
|
+
<!-- BEGIN EXTRACTION -->
|
|
115
|
+
|
|
91
116
|
```tsx
|
|
92
|
-
import { UnifiedSelectionProvider, useUnifiedSelectionTree } from "@itwin/presentation-hierarchies-react";
|
|
117
|
+
import { TreeRenderer, UnifiedSelectionProvider, useUnifiedSelectionTree } from "@itwin/presentation-hierarchies-react";
|
|
93
118
|
import { createStorage } from "@itwin/unified-selection";
|
|
119
|
+
import { useEffect, useState } from "react";
|
|
94
120
|
|
|
95
|
-
//
|
|
121
|
+
// Not part of the package - this should be created once and reused across different components of the application.
|
|
96
122
|
const selectionStorage = createStorage();
|
|
97
123
|
|
|
98
|
-
/**
|
|
99
|
-
function
|
|
124
|
+
/** Component providing the selection storage and access to iModel. Usually this is done in a top-level component. */
|
|
125
|
+
function MyTreeComponent({ imodel }: { imodel: IModelConnection }) {
|
|
126
|
+
const [imodelAccess, setIModelAccess] = useState<IModelAccess>();
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
setIModelAccess(createIModelAccess(imodel));
|
|
129
|
+
}, [imodel]);
|
|
130
|
+
|
|
131
|
+
if (!imodelAccess) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
100
135
|
return (
|
|
101
136
|
<UnifiedSelectionProvider storage={selectionStorage}>
|
|
102
|
-
<
|
|
137
|
+
<MyTreeComponentInternal imodelKey={imodel.key} imodelAccess={imodelAccess} />
|
|
103
138
|
</UnifiedSelectionProvider>
|
|
104
139
|
);
|
|
105
140
|
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
<!-- END EXTRACTION -->
|
|
144
|
+
|
|
145
|
+
After providing the unified selection storage through the context, the `useUnifiedSelectionTree` hook can be used inside the component as follows:
|
|
146
|
+
|
|
147
|
+
<!-- [[include: Presentation.HierarchiesReact.UseUnifiedSelectionTree, tsx]] -->
|
|
148
|
+
<!-- BEGIN EXTRACTION -->
|
|
106
149
|
|
|
107
|
-
|
|
108
|
-
function
|
|
109
|
-
const state = useUnifiedSelectionTree(
|
|
110
|
-
|
|
150
|
+
```tsx
|
|
151
|
+
function MyTreeComponentInternal({ imodelAccess, imodelKey }: { imodelAccess: IModelAccess; imodelKey: string }) {
|
|
152
|
+
const { rootNodes, ...state } = useUnifiedSelectionTree({
|
|
153
|
+
// the source name is used to distinguish selection changes being made by different components
|
|
154
|
+
sourceName: "MyTreeComponent",
|
|
155
|
+
// the iModel key is required for unified selection system to distinguish selection changes between different iModels
|
|
156
|
+
imodelKey,
|
|
157
|
+
// iModel access is used to build the hierarchy
|
|
158
|
+
imodelAccess,
|
|
159
|
+
// the hierarchy definition describes the hierarchy using ECSQL queries
|
|
160
|
+
getHierarchyDefinition,
|
|
161
|
+
});
|
|
162
|
+
if (!rootNodes || !rootNodes.length) {
|
|
163
|
+
return "No data to display";
|
|
164
|
+
}
|
|
165
|
+
return <TreeRenderer {...state} rootNodes={rootNodes} />;
|
|
111
166
|
}
|
|
112
167
|
```
|
|
113
168
|
|
|
169
|
+
<!-- END EXTRACTION -->
|
|
170
|
+
|
|
114
171
|
## iTwinUI components
|
|
115
172
|
|
|
116
173
|
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.
|
|
@@ -132,27 +189,27 @@ The component is based on `TreeNode` component from iTwinUI library and supports
|
|
|
132
189
|
|
|
133
190
|
## Full example
|
|
134
191
|
|
|
192
|
+
<!-- [[include: [Presentation.HierarchiesReact.iModelAccess.Imports, Presentation.HierarchiesReact.SelectionStorage.Imports, Presentation.HierarchiesReact.CustomTreeExample.Imports, Presentation.HierarchiesReact.iModelAccess, Presentation.HierarchiesReact.SelectionStorage, Presentation.HierarchiesReact.CustomTreeExample], tsx]] -->
|
|
193
|
+
<!-- BEGIN EXTRACTION -->
|
|
194
|
+
|
|
135
195
|
```tsx
|
|
136
|
-
import { useEffect, useState } from "react";
|
|
137
196
|
import { IModelConnection } from "@itwin/core-frontend";
|
|
138
197
|
import { SchemaContext } from "@itwin/ecschema-metadata";
|
|
139
198
|
import { ECSchemaRpcLocater } from "@itwin/ecschema-rpcinterface-common";
|
|
140
199
|
import { createECSchemaProvider, createECSqlQueryExecutor } from "@itwin/presentation-core-interop";
|
|
141
|
-
import { createLimitingECSqlQueryExecutor, createNodesQueryClauseFactory } from "@itwin/presentation-hierarchies";
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
PresentationTreeNode,
|
|
145
|
-
UnifiedSelectionProvider,
|
|
146
|
-
useUnifiedSelectionTree,
|
|
147
|
-
TreeRenderer,
|
|
148
|
-
} from "@itwin/presentation-hierarchies-react";
|
|
149
|
-
import { createBisInstanceLabelSelectClauseFactory, createCachingECClassHierarchyInspector } from "@itwin/presentation-shared";
|
|
200
|
+
import { createLimitingECSqlQueryExecutor, createNodesQueryClauseFactory, HierarchyDefinition } from "@itwin/presentation-hierarchies";
|
|
201
|
+
|
|
202
|
+
import { TreeRenderer, UnifiedSelectionProvider, useUnifiedSelectionTree } from "@itwin/presentation-hierarchies-react";
|
|
150
203
|
import { createStorage } from "@itwin/unified-selection";
|
|
204
|
+
import { useEffect, useState } from "react";
|
|
205
|
+
|
|
206
|
+
import { createBisInstanceLabelSelectClauseFactory, createCachingECClassHierarchyInspector } from "@itwin/presentation-shared";
|
|
151
207
|
|
|
152
208
|
// Not really part of the package, but we need SchemaContext to create the tree state. It's
|
|
153
209
|
// recommended to cache the schema context and reuse it across different application's components to
|
|
154
210
|
// avoid loading and storing same schemas multiple times.
|
|
155
211
|
const imodelSchemaContextsCache = new Map<string, SchemaContext>();
|
|
212
|
+
|
|
156
213
|
function getIModelSchemaContext(imodel: IModelConnection) {
|
|
157
214
|
let context = imodelSchemaContextsCache.get(imodel.key);
|
|
158
215
|
if (!context) {
|
|
@@ -164,22 +221,26 @@ function getIModelSchemaContext(imodel: IModelConnection) {
|
|
|
164
221
|
return context;
|
|
165
222
|
}
|
|
166
223
|
|
|
224
|
+
function createIModelAccess(imodel: IModelConnection) {
|
|
225
|
+
const schemaProvider = createECSchemaProvider(getIModelSchemaContext(imodel));
|
|
226
|
+
return {
|
|
227
|
+
...schemaProvider,
|
|
228
|
+
// while caching for hierarchy inspector is not mandatory, it's recommended to use it to improve performance
|
|
229
|
+
...createCachingECClassHierarchyInspector({ schemaProvider, cacheSize: 100 }),
|
|
230
|
+
// the second argument is the maximum number of rows the executor will return - this allows us to
|
|
231
|
+
// avoid creating hierarchy levels of insane size (expensive to us and useless to users)
|
|
232
|
+
...createLimitingECSqlQueryExecutor(createECSqlQueryExecutor(imodel), 1000),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
167
236
|
// Not part of the package - this should be created once and reused across different components of the application.
|
|
168
237
|
const selectionStorage = createStorage();
|
|
169
238
|
|
|
170
|
-
/**
|
|
171
|
-
|
|
239
|
+
/** Component providing the selection storage and access to iModel. Usually this is done in a top-level component. */
|
|
240
|
+
function MyTreeComponent({ imodel }: { imodel: IModelConnection }) {
|
|
172
241
|
const [imodelAccess, setIModelAccess] = useState<IModelAccess>();
|
|
173
242
|
useEffect(() => {
|
|
174
|
-
|
|
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
|
-
});
|
|
243
|
+
setIModelAccess(createIModelAccess(imodel));
|
|
183
244
|
}, [imodel]);
|
|
184
245
|
|
|
185
246
|
if (!imodelAccess) {
|
|
@@ -192,64 +253,73 @@ export function MyTreeComponent({ imodel }: { imodel: IModelConnection }) {
|
|
|
192
253
|
</UnifiedSelectionProvider>
|
|
193
254
|
);
|
|
194
255
|
}
|
|
256
|
+
|
|
195
257
|
type IModelAccess = Parameters<typeof useUnifiedSelectionTree>[0]["imodelAccess"];
|
|
196
258
|
|
|
197
|
-
|
|
198
|
-
function
|
|
259
|
+
// The hierarchy definition describes the hierarchy using ECSQL queries; here it just returns all `BisCore.PhysicalModel` instances
|
|
260
|
+
function getHierarchyDefinition({ imodelAccess }: { imodelAccess: IModelAccess }): HierarchyDefinition {
|
|
199
261
|
// Create a factory for building nodes SELECT query clauses in a format understood by the provider
|
|
200
|
-
const
|
|
262
|
+
const nodesQueryFactory = createNodesQueryClauseFactory({ imodelAccess });
|
|
201
263
|
// Create a factory for building labels SELECT query clauses according to BIS conventions
|
|
202
|
-
const
|
|
264
|
+
const labelsQueryFactory = createBisInstanceLabelSelectClauseFactory({ classHierarchyInspector: imodelAccess });
|
|
265
|
+
return {
|
|
266
|
+
defineHierarchyLevel: async () => [
|
|
267
|
+
{
|
|
268
|
+
fullClassName: "BisCore.PhysicalModel",
|
|
269
|
+
query: {
|
|
270
|
+
ecsql: `
|
|
271
|
+
SELECT
|
|
272
|
+
${await nodesQueryFactory.createSelectClause({
|
|
273
|
+
ecClassId: { selector: "this.ECClassId" },
|
|
274
|
+
ecInstanceId: { selector: "this.ECInstanceId" },
|
|
275
|
+
nodeLabel: {
|
|
276
|
+
selector: await labelsQueryFactory.createSelectClause({ classAlias: "this", className: "BisCore.PhysicalModel" }),
|
|
277
|
+
},
|
|
278
|
+
hasChildren: false,
|
|
279
|
+
})}
|
|
280
|
+
FROM BisCore.PhysicalModel this
|
|
281
|
+
`,
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
};
|
|
286
|
+
}
|
|
203
287
|
|
|
204
|
-
|
|
288
|
+
/** Internal component that creates and renders tree state. */
|
|
289
|
+
function MyTreeComponentInternal({ imodelAccess, imodelKey }: { imodelAccess: IModelAccess; imodelKey: string }) {
|
|
290
|
+
const { rootNodes, setFormatter, isLoading, ...state } = useUnifiedSelectionTree({
|
|
205
291
|
// the source name is used to distinguish selection changes being made by different components
|
|
206
292
|
sourceName: "MyTreeComponent",
|
|
207
293
|
// the iModel key is required for unified selection system to distinguish selection changes between different iModels
|
|
208
294
|
imodelKey,
|
|
209
295
|
// iModel access is used to build the hierarchy
|
|
210
296
|
imodelAccess,
|
|
211
|
-
// the hierarchy definition
|
|
212
|
-
|
|
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
|
-
}),
|
|
297
|
+
// supply the hierarchy definition
|
|
298
|
+
getHierarchyDefinition,
|
|
238
299
|
});
|
|
239
300
|
if (!rootNodes) {
|
|
240
301
|
return "Loading...";
|
|
241
302
|
}
|
|
242
|
-
return <TreeRenderer rootNodes={rootNodes}
|
|
303
|
+
return <TreeRenderer {...state} rootNodes={rootNodes} />;
|
|
243
304
|
}
|
|
244
305
|
```
|
|
245
306
|
|
|
307
|
+
<!-- END EXTRACTION -->
|
|
308
|
+
|
|
246
309
|
## Localization
|
|
247
310
|
|
|
248
311
|
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
312
|
|
|
250
313
|
Example:
|
|
251
314
|
|
|
315
|
+
<!-- [[include: [Presentation.HierarchiesReact.Localization.Tree.Imports, Presentation.HierarchiesReact.Localization.Strings, Presentation.HierarchiesReact.Localization.Tree], tsx]] -->
|
|
316
|
+
<!-- BEGIN EXTRACTION -->
|
|
317
|
+
|
|
252
318
|
```tsx
|
|
319
|
+
import { useUnifiedSelectionTree } from "@itwin/presentation-hierarchies-react";
|
|
320
|
+
|
|
321
|
+
type IModelAccess = Parameters<typeof useUnifiedSelectionTree>[0]["imodelAccess"];
|
|
322
|
+
|
|
253
323
|
const localizedStrings = {
|
|
254
324
|
// strings for the `useUnifiedSelectionTree` hook
|
|
255
325
|
unspecified: "Unspecified",
|
|
@@ -257,7 +327,7 @@ const localizedStrings = {
|
|
|
257
327
|
|
|
258
328
|
// strings for `TreeRenderer` and `TreeNodeRenderer`
|
|
259
329
|
loading: "Loading...",
|
|
260
|
-
filterHierarchyLevel: "Apply filter",
|
|
330
|
+
filterHierarchyLevel: "Apply hierarchy filter",
|
|
261
331
|
clearHierarchyLevelFilter: "Clear active filter",
|
|
262
332
|
noFilteredChildren: "No child nodes match current filter",
|
|
263
333
|
resultLimitExceeded: "There are more items than allowed limit of {{limit}}.",
|
|
@@ -272,32 +342,47 @@ function MyTreeComponent({ imodelAccess, imodelKey }: { imodelAccess: IModelAcce
|
|
|
272
342
|
imodelKey,
|
|
273
343
|
imodelAccess,
|
|
274
344
|
localizedStrings,
|
|
275
|
-
getHierarchyDefinition
|
|
276
|
-
defineHierarchyLevel: async () => [],
|
|
277
|
-
}),
|
|
345
|
+
getHierarchyDefinition,
|
|
278
346
|
});
|
|
279
347
|
if (!rootNodes) {
|
|
280
348
|
return localizedStrings.loading;
|
|
281
349
|
}
|
|
282
|
-
return <TreeRenderer rootNodes={rootNodes} localizedStrings={localizedStrings} {
|
|
350
|
+
return <TreeRenderer {...state} rootNodes={rootNodes} localizedStrings={localizedStrings} onFilterClick={() => {}} />;
|
|
283
351
|
}
|
|
284
352
|
```
|
|
285
353
|
|
|
354
|
+
<!-- END EXTRACTION -->
|
|
355
|
+
|
|
286
356
|
In case the `TreeNodeRenderer` component is used within a custom tree renderer, the tree component should supply localized strings through `LocalizationContextProvider`:
|
|
287
357
|
|
|
358
|
+
<!-- [[include: [Presentation.HierarchiesReact.Localization.TreeRenderer.Imports, Presentation.HierarchiesReact.Localization.TreeRenderer], tsx]] -->
|
|
359
|
+
<!-- BEGIN EXTRACTION -->
|
|
360
|
+
|
|
288
361
|
```tsx
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
362
|
+
import {
|
|
363
|
+
createRenderedTreeNodeData,
|
|
364
|
+
LocalizationContextProvider,
|
|
365
|
+
RenderedTreeNode,
|
|
366
|
+
TreeNodeRenderer,
|
|
367
|
+
TreeRenderer,
|
|
368
|
+
} from "@itwin/presentation-hierarchies-react";
|
|
369
|
+
|
|
370
|
+
type TreeProps = ComponentPropsWithoutRef<typeof Tree<RenderedTreeNode>>;
|
|
371
|
+
type TreeRendererProps = Parameters<typeof TreeRenderer>[0];
|
|
372
|
+
|
|
373
|
+
function MyTreeRenderer(props: TreeRendererProps) {
|
|
374
|
+
const nodeRenderer = useCallback<TreeProps["nodeRenderer"]>((nodeProps) => {
|
|
375
|
+
return <TreeNodeRenderer {...nodeProps} onFilterClick={() => {}} expandNode={() => {}} />;
|
|
376
|
+
}, []);
|
|
377
|
+
|
|
378
|
+
const getNode = useCallback<TreeProps["getNode"]>((node) => createRenderedTreeNodeData(node, () => false), []);
|
|
296
379
|
|
|
297
380
|
return (
|
|
298
381
|
<LocalizationContextProvider localizedStrings={localizedStrings}>
|
|
299
|
-
<Tree<RenderedTreeNode> {...props} />
|
|
382
|
+
<Tree<RenderedTreeNode> {...props} data={props.rootNodes} nodeRenderer={nodeRenderer} getNode={getNode} />
|
|
300
383
|
</LocalizationContextProvider>
|
|
301
384
|
);
|
|
302
385
|
}
|
|
303
386
|
```
|
|
387
|
+
|
|
388
|
+
<!-- END EXTRACTION -->
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
5
5
|
*--------------------------------------------------------------------------------------------*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.isPresentationHierarchyNode =
|
|
7
|
+
exports.isPresentationHierarchyNode = isPresentationHierarchyNode;
|
|
8
8
|
/**
|
|
9
9
|
* An utility function to check if a node is a `PresentationHierarchyNode`.
|
|
10
10
|
* @beta
|
|
@@ -12,5 +12,4 @@ exports.isPresentationHierarchyNode = void 0;
|
|
|
12
12
|
function isPresentationHierarchyNode(node) {
|
|
13
13
|
return "children" in node;
|
|
14
14
|
}
|
|
15
|
-
exports.isPresentationHierarchyNode = isPresentationHierarchyNode;
|
|
16
15
|
//# sourceMappingURL=TreeNode.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TreeNode.js","sourceRoot":"","sources":["../../../src/presentation-hierarchies-react/TreeNode.ts"],"names":[],"mappings":";AAAA;;;gGAGgG;;;
|
|
1
|
+
{"version":3,"file":"TreeNode.js","sourceRoot":"","sources":["../../../src/presentation-hierarchies-react/TreeNode.ts"],"names":[],"mappings":";AAAA;;;gGAGgG;;AAgFhG,kEAEC;AAND;;;GAGG;AACH,SAAgB,2BAA2B,CAAC,IAA0B;IACpE,OAAO,UAAU,IAAI,IAAI,CAAC;AAC5B,CAAC","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"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.UnifiedSelectionProvider = UnifiedSelectionProvider;
|
|
4
|
+
exports.useUnifiedSelectionContext = useUnifiedSelectionContext;
|
|
4
5
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
6
|
/*---------------------------------------------------------------------------------------------
|
|
6
7
|
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
|
|
@@ -19,10 +20,8 @@ const unifiedSelectionContext = (0, react_1.createContext)(undefined);
|
|
|
19
20
|
function UnifiedSelectionProvider({ storage, children }) {
|
|
20
21
|
return (0, jsx_runtime_1.jsx)(unifiedSelectionContext.Provider, { value: storage, children: children });
|
|
21
22
|
}
|
|
22
|
-
exports.UnifiedSelectionProvider = UnifiedSelectionProvider;
|
|
23
23
|
/** @internal */
|
|
24
24
|
function useUnifiedSelectionContext() {
|
|
25
25
|
return (0, react_1.useContext)(unifiedSelectionContext);
|
|
26
26
|
}
|
|
27
|
-
exports.useUnifiedSelectionContext = useUnifiedSelectionContext;
|
|
28
27
|
//# sourceMappingURL=UnifiedSelectionContext.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UnifiedSelectionContext.js","sourceRoot":"","sources":["../../../src/presentation-hierarchies-react/UnifiedSelectionContext.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"UnifiedSelectionContext.js","sourceRoot":"","sources":["../../../src/presentation-hierarchies-react/UnifiedSelectionContext.tsx"],"names":[],"mappings":";;AAkBA,4DAEC;AAGD,gEAEC;;AAzBD;;;gGAGgG;AAEhG,iCAAqE;AAGrE,MAAM,uBAAuB,GAAG,IAAA,qBAAa,EAA+B,SAAS,CAAC,CAAC;AAEvF;;;;;;;GAOG;AACH,SAAgB,wBAAwB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAoD;IAC9G,OAAO,uBAAC,uBAAuB,CAAC,QAAQ,IAAC,KAAK,EAAE,OAAO,YAAG,QAAQ,GAAoC,CAAC;AACzG,CAAC;AAED,gBAAgB;AAChB,SAAgB,0BAA0B;IACxC,OAAO,IAAA,kBAAU,EAAC,uBAAuB,CAAC,CAAC;AAC7C,CAAC","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\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<{ storage: SelectionStorage }>) {\n return <unifiedSelectionContext.Provider value={storage}>{children}</unifiedSelectionContext.Provider>;\n}\n\n/** @internal */\nexport function useUnifiedSelectionContext() {\n return useContext(unifiedSelectionContext);\n}\n"]}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/// <reference types="react" />
|
|
2
1
|
import { PresentationHierarchyNode } from "./TreeNode";
|
|
3
2
|
import { useTree } from "./UseTree";
|
|
4
3
|
/**
|
|
@@ -11,7 +10,14 @@ import { useTree } from "./UseTree";
|
|
|
11
10
|
* @beta
|
|
12
11
|
*/
|
|
13
12
|
export type SelectionMode = "none" | "single" | "extended" | "multiple";
|
|
14
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Type of selection change.
|
|
15
|
+
* - `add` - a node was added to the selection,
|
|
16
|
+
* - `replace` - a selected node was replaced with a different one,
|
|
17
|
+
* - `remove` - a node was removed from the selection.
|
|
18
|
+
*
|
|
19
|
+
* @beta
|
|
20
|
+
*/
|
|
15
21
|
export type SelectionChangeType = "add" | "replace" | "remove";
|
|
16
22
|
/**
|
|
17
23
|
* Props for `useSelectionHandler` hook.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UseSelectionHandler.d.ts","sourceRoot":"","sources":["../../../src/presentation-hierarchies-react/UseSelectionHandler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"UseSelectionHandler.d.ts","sourceRoot":"","sources":["../../../src/presentation-hierarchies-react/UseSelectionHandler.ts"],"names":[],"mappings":"AAMA,OAAO,EAA+B,yBAAyB,EAAwB,MAAM,YAAY,CAAC;AAC1G,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAC;AAExE;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,KAAK,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE/D;;;GAGG;AACH,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;;;GAGG;AACH,UAAU,yBAAyB;IACjC,0EAA0E;IAC1E,WAAW,EAAE,CAAC,IAAI,EAAE,yBAAyB,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,WAAW,EAAE,UAAU,CAAC,KAAK,IAAI,CAAC;IAC9H,iFAAiF;IACjF,aAAa,EAAE,CAAC,IAAI,EAAE,yBAAyB,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;CACxH;AAOD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,GAAG,yBAAyB,CAiE9F"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
5
5
|
*--------------------------------------------------------------------------------------------*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.useSelectionHandler =
|
|
7
|
+
exports.useSelectionHandler = useSelectionHandler;
|
|
8
8
|
const react_1 = require("react");
|
|
9
9
|
const TreeNode_1 = require("./TreeNode");
|
|
10
10
|
/**
|
|
@@ -59,7 +59,6 @@ function useSelectionHandler(props) {
|
|
|
59
59
|
}, [onNodeSelect]);
|
|
60
60
|
return { onNodeClick, onNodeKeyDown };
|
|
61
61
|
}
|
|
62
|
-
exports.useSelectionHandler = useSelectionHandler;
|
|
63
62
|
function getSelectionAction(selectionMode, isSelected, shiftDown, ctrlDown) {
|
|
64
63
|
switch (selectionMode) {
|
|
65
64
|
case "none":
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UseSelectionHandler.js","sourceRoot":"","sources":["../../../src/presentation-hierarchies-react/UseSelectionHandler.ts"],"names":[],"mappings":";AAAA;;;gGAGgG;;;AAEhG,iCAAuD;AACvD,yCAA0G;AA0C1G;;;GAGG;AACH,SAAgB,mBAAmB,CAAC,KAA+B;IACjE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,GAAG,KAAK,CAAC;IACxD,MAAM,oBAAoB,GAAG,IAAA,cAAM,EAAqB,SAAS,CAAC,CAAC;IACnE,MAAM,KAAK,GAAG,IAAA,cAAM,EAAgB;QAClC,YAAY,EAAE,EAAE;QAChB,gBAAgB,EAAE,IAAI,GAAG,EAAE;KAC5B,CAAC,CAAC;IAEH,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO;QACT,CAAC;QACD,KAAK,CAAC,OAAO,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;IACjD,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IAEhB,MAAM,YAAY,GAAG,CAAC,OAAgB,EAAE,QAAiB,EAAE,EAAE;QAC3D,MAAM,QAAQ,GAAG,CAAC,MAAe,EAAE,EAAE;YACnC,OAAO,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjE,CAAC,CAAC;QACF,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;QACrC,MAAM,WAAW,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,UAAU,KAAK,SAAS,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC1D,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QACnD,OAAO,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,aAAa,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAC;IACvE,CAAC,CAAC;IAEF,MAAM,YAAY,GAAG,IAAA,mBAAW,EAC9B,CAAC,MAAc,EAAE,UAAmB,EAAE,SAAkB,EAAE,QAAiB,EAAE,EAAE;QAC7E,MAAM,SAAS,GAAG,kBAAkB,CAAC,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;QACrF,IAAI,SAAS,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YAClC,OAAO;QACT,CAAC;QAED,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAC3G,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QAED,SAAS,CAAC,MAAM,KAAK,OAAO,IAAI,CAAC,oBAAoB,CAAC,OAAO,GAAG,MAAM,CAAC,CAAC;QACxE,WAAW,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,EACD,CAAC,aAAa,EAAE,WAAW,CAAC,CAC7B,CAAC;IAEF,MAAM,WAAW,GAAG,IAAA,mBAAW,EAC7B,CAAC,IAA+B,EAAE,UAAmB,EAAE,KAAgD,EAAE,EAAE;QACzG,OAAO,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAC1E,CAAC,EACD,CAAC,YAAY,CAAC,CACf,CAAC;IAEF,MAAM,aAAa,GAAG,IAAA,mBAAW,EAC/B,CAAC,IAA+B,EAAE,UAAmB,EAAE,KAAuC,EAAE,EAAE;QAChG,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,IAAI,KAAK,CAAC,GAAG,KAAK,UAAU,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC;YAC3E,OAAO,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC,EACD,CAAC,YAAY,CAAC,CACf,CAAC;IAEF,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC;AACxC,CAAC;AAjED,kDAiEC;AAOD,SAAS,kBAAkB,CAAC,aAA4B,EAAE,UAAmB,EAAE,SAAkB,EAAE,QAAiB;IAClH,QAAQ,aAAa,EAAE,CAAC;QACtB,KAAK,MAAM;YACT,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;QAC9C,KAAK,QAAQ;YACX,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QACrE,KAAK,UAAU;YACb,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QACjE,KAAK,UAAU;YACb,OAAO,0BAA0B,CAAC,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IACvE,CAAC;AACH,CAAC;AAED,SAAS,0BAA0B,CAAC,UAAmB,EAAE,SAAkB,EAAE,QAAiB;IAC5F,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAC9C,CAAC;IACD,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACjE,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC;AACvE,CAAC;AAED,SAAS,mBAAmB,CAAC,SAAsC;IACjE,MAAM,YAAY,GAAkB,EAAE,CAAC;IACvC,MAAM,gBAAgB,GAAwB,IAAI,GAAG,EAAE,CAAC;IAExD,MAAM,sBAAsB,GAAG,CAAC,KAAkC,EAAE,EAAE;QACpE,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;YACrB,IAAI,CAAC,IAAA,sCAA2B,EAAC,IAAI,CAAC,EAAE,CAAC;gBACvC,OAAO;YACT,CAAC;YACD,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;YACnD,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC3B,IAAI,IAAI,CAAC,UAAU,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC1D,sBAAsB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACxC,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,sBAAsB,CAAC,SAAS,CAAC,CAAC;IAClC,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAC5C,CAAC","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 { useCallback, useEffect, useRef } from \"react\";\nimport { isPresentationHierarchyNode, PresentationHierarchyNode, PresentationTreeNode } from \"./TreeNode\";\nimport { useTree } from \"./UseTree\";\n\n/**\n * A union of different supported selection modes in a tree component:\n * - `none` - no selection is allowed,\n * - `single` - only one node can be selected at a time,\n * - `extended` - multiple nodes can be selected using shift and ctrl keys,\n * - `multiple` - multiple nodes can be selected without using shift or ctrl keys.\n *\n * @beta\n */\nexport type SelectionMode = \"none\" | \"single\" | \"extended\" | \"multiple\";\n\n/** @internal */\nexport type SelectionChangeType = \"add\" | \"replace\" | \"remove\";\n\n/**\n * Props for `useSelectionHandler` hook.\n * @beta\n */\ntype UseSelectionHandlerProps = Pick<ReturnType<typeof useTree>, \"rootNodes\" | \"selectNodes\"> & {\n /** Selection mode that the component is working in. */\n selectionMode: SelectionMode;\n};\n\n/**\n * Result of `useSelectionHandler` hook.\n * @beta\n */\ninterface UseSelectionHandlerResult {\n /** Should be called by node renderer when a node component is clicked. */\n onNodeClick: (node: PresentationHierarchyNode, isSelected: boolean, event: React.MouseEvent<HTMLElement, MouseEvent>) => void;\n /** Should be called by node renderer when a keyboard event happens on a node. */\n onNodeKeyDown: (node: PresentationHierarchyNode, isSelected: boolean, event: React.KeyboardEvent<HTMLElement>) => void;\n}\n\ninterface FlatTreeState {\n flatNodeList: Array<string>;\n nodeIdToIndexMap: Map<string, number>;\n}\n\n/**\n * A react hook that helps implement different selection modes in a tree component created using `useTree` hook.\n * @beta\n */\nexport function useSelectionHandler(props: UseSelectionHandlerProps): UseSelectionHandlerResult {\n const { rootNodes, selectionMode, selectNodes } = props;\n const previousSelectionRef = useRef<string | undefined>(undefined);\n const state = useRef<FlatTreeState>({\n flatNodeList: [],\n nodeIdToIndexMap: new Map(),\n });\n\n useEffect(() => {\n if (!rootNodes) {\n return;\n }\n state.current = computeFlatNodeList(rootNodes);\n }, [rootNodes]);\n\n const getNodeRange = (firstId?: string, secondId?: string) => {\n const getIndex = (nodeId?: string) => {\n return nodeId ? state.current.nodeIdToIndexMap.get(nodeId) : 0;\n };\n const firstIndex = getIndex(firstId);\n const secondIndex = getIndex(secondId);\n if (firstIndex === undefined || secondIndex === undefined) {\n return [];\n }\n\n const startingIndex = Math.min(firstIndex, secondIndex);\n const endIndex = Math.max(firstIndex, secondIndex);\n return state.current.flatNodeList.slice(startingIndex, endIndex + 1);\n };\n\n const onNodeSelect = useCallback(\n (nodeId: string, isSelected: boolean, shiftDown: boolean, ctrlDown: boolean) => {\n const selection = getSelectionAction(selectionMode, isSelected, shiftDown, ctrlDown);\n if (selection.type === \"disabled\") {\n return;\n }\n\n const nodes = selection.select === \"range\" ? getNodeRange(previousSelectionRef.current, nodeId) : [nodeId];\n if (!nodes.length) {\n return;\n }\n\n selection.select !== \"range\" && (previousSelectionRef.current = nodeId);\n selectNodes(nodes, selection.type);\n },\n [selectionMode, selectNodes],\n );\n\n const onNodeClick = useCallback(\n (node: PresentationHierarchyNode, isSelected: boolean, event: React.MouseEvent<HTMLElement, MouseEvent>) => {\n return onNodeSelect(node.id, isSelected, event.shiftKey, event.ctrlKey);\n },\n [onNodeSelect],\n );\n\n const onNodeKeyDown = useCallback(\n (node: PresentationHierarchyNode, isSelected: boolean, event: React.KeyboardEvent<HTMLElement>) => {\n if (event.key === \" \" || event.key === \"Spacebar\" || event.key === \"Enter\") {\n return onNodeSelect(node.id, isSelected, event.shiftKey, event.ctrlKey);\n }\n },\n [onNodeSelect],\n );\n\n return { onNodeClick, onNodeKeyDown };\n}\n\ninterface SelectionAction {\n select: \"node\" | \"range\";\n type: SelectionChangeType | \"disabled\";\n}\n\nfunction getSelectionAction(selectionMode: SelectionMode, isSelected: boolean, shiftDown: boolean, ctrlDown: boolean): SelectionAction {\n switch (selectionMode) {\n case \"none\":\n return { select: \"node\", type: \"disabled\" };\n case \"single\":\n return { select: \"node\", type: isSelected ? \"replace\" : \"remove\" };\n case \"multiple\":\n return { select: \"node\", type: isSelected ? \"add\" : \"remove\" };\n case \"extended\":\n return getExtendedSelectionAction(isSelected, shiftDown, ctrlDown);\n }\n}\n\nfunction getExtendedSelectionAction(isSelected: boolean, shiftDown: boolean, ctrlDown: boolean): SelectionAction {\n if (shiftDown) {\n return { select: \"range\", type: \"replace\" };\n }\n if (ctrlDown) {\n return { select: \"node\", type: isSelected ? \"add\" : \"remove\" };\n }\n return { select: \"node\", type: isSelected ? \"replace\" : \"disabled\" };\n}\n\nfunction computeFlatNodeList(rootNodes: Array<PresentationTreeNode>): FlatTreeState {\n const flatNodeList: Array<string> = [];\n const nodeIdToIndexMap: Map<string, number> = new Map();\n\n const flattenNodeRecursively = (nodes: Array<PresentationTreeNode>) => {\n nodes.forEach((node) => {\n if (!isPresentationHierarchyNode(node)) {\n return;\n }\n nodeIdToIndexMap.set(node.id, flatNodeList.length);\n flatNodeList.push(node.id);\n if (node.isExpanded && typeof node.children !== \"boolean\") {\n flattenNodeRecursively(node.children);\n }\n });\n };\n\n flattenNodeRecursively(rootNodes);\n return { flatNodeList, nodeIdToIndexMap };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"UseSelectionHandler.js","sourceRoot":"","sources":["../../../src/presentation-hierarchies-react/UseSelectionHandler.ts"],"names":[],"mappings":";AAAA;;;gGAGgG;;AAwDhG,kDAiEC;AAvHD,iCAAuD;AACvD,yCAA0G;AAiD1G;;;GAGG;AACH,SAAgB,mBAAmB,CAAC,KAA+B;IACjE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,GAAG,KAAK,CAAC;IACxD,MAAM,oBAAoB,GAAG,IAAA,cAAM,EAAqB,SAAS,CAAC,CAAC;IACnE,MAAM,KAAK,GAAG,IAAA,cAAM,EAAgB;QAClC,YAAY,EAAE,EAAE;QAChB,gBAAgB,EAAE,IAAI,GAAG,EAAE;KAC5B,CAAC,CAAC;IAEH,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO;QACT,CAAC;QACD,KAAK,CAAC,OAAO,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;IACjD,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IAEhB,MAAM,YAAY,GAAG,CAAC,OAAgB,EAAE,QAAiB,EAAE,EAAE;QAC3D,MAAM,QAAQ,GAAG,CAAC,MAAe,EAAE,EAAE;YACnC,OAAO,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjE,CAAC,CAAC;QACF,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;QACrC,MAAM,WAAW,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,UAAU,KAAK,SAAS,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC1D,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QACnD,OAAO,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,aAAa,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAC;IACvE,CAAC,CAAC;IAEF,MAAM,YAAY,GAAG,IAAA,mBAAW,EAC9B,CAAC,MAAc,EAAE,UAAmB,EAAE,SAAkB,EAAE,QAAiB,EAAE,EAAE;QAC7E,MAAM,SAAS,GAAG,kBAAkB,CAAC,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;QACrF,IAAI,SAAS,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YAClC,OAAO;QACT,CAAC;QAED,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAC3G,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QAED,SAAS,CAAC,MAAM,KAAK,OAAO,IAAI,CAAC,oBAAoB,CAAC,OAAO,GAAG,MAAM,CAAC,CAAC;QACxE,WAAW,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,EACD,CAAC,aAAa,EAAE,WAAW,CAAC,CAC7B,CAAC;IAEF,MAAM,WAAW,GAAG,IAAA,mBAAW,EAC7B,CAAC,IAA+B,EAAE,UAAmB,EAAE,KAAgD,EAAE,EAAE;QACzG,OAAO,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAC1E,CAAC,EACD,CAAC,YAAY,CAAC,CACf,CAAC;IAEF,MAAM,aAAa,GAAG,IAAA,mBAAW,EAC/B,CAAC,IAA+B,EAAE,UAAmB,EAAE,KAAuC,EAAE,EAAE;QAChG,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,IAAI,KAAK,CAAC,GAAG,KAAK,UAAU,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC;YAC3E,OAAO,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC,EACD,CAAC,YAAY,CAAC,CACf,CAAC;IAEF,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC;AACxC,CAAC;AAOD,SAAS,kBAAkB,CAAC,aAA4B,EAAE,UAAmB,EAAE,SAAkB,EAAE,QAAiB;IAClH,QAAQ,aAAa,EAAE,CAAC;QACtB,KAAK,MAAM;YACT,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;QAC9C,KAAK,QAAQ;YACX,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QACrE,KAAK,UAAU;YACb,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QACjE,KAAK,UAAU;YACb,OAAO,0BAA0B,CAAC,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IACvE,CAAC;AACH,CAAC;AAED,SAAS,0BAA0B,CAAC,UAAmB,EAAE,SAAkB,EAAE,QAAiB;IAC5F,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAC9C,CAAC;IACD,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACjE,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC;AACvE,CAAC;AAED,SAAS,mBAAmB,CAAC,SAAsC;IACjE,MAAM,YAAY,GAAkB,EAAE,CAAC;IACvC,MAAM,gBAAgB,GAAwB,IAAI,GAAG,EAAE,CAAC;IAExD,MAAM,sBAAsB,GAAG,CAAC,KAAkC,EAAE,EAAE;QACpE,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;YACrB,IAAI,CAAC,IAAA,sCAA2B,EAAC,IAAI,CAAC,EAAE,CAAC;gBACvC,OAAO;YACT,CAAC;YACD,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;YACnD,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC3B,IAAI,IAAI,CAAC,UAAU,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC1D,sBAAsB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACxC,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,sBAAsB,CAAC,SAAS,CAAC,CAAC;IAClC,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAC5C,CAAC","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 { useCallback, useEffect, useRef } from \"react\";\nimport { isPresentationHierarchyNode, PresentationHierarchyNode, PresentationTreeNode } from \"./TreeNode\";\nimport { useTree } from \"./UseTree\";\n\n/**\n * A union of different supported selection modes in a tree component:\n * - `none` - no selection is allowed,\n * - `single` - only one node can be selected at a time,\n * - `extended` - multiple nodes can be selected using shift and ctrl keys,\n * - `multiple` - multiple nodes can be selected without using shift or ctrl keys.\n *\n * @beta\n */\nexport type SelectionMode = \"none\" | \"single\" | \"extended\" | \"multiple\";\n\n/**\n * Type of selection change.\n * - `add` - a node was added to the selection,\n * - `replace` - a selected node was replaced with a different one,\n * - `remove` - a node was removed from the selection.\n *\n * @beta\n */\nexport type SelectionChangeType = \"add\" | \"replace\" | \"remove\";\n\n/**\n * Props for `useSelectionHandler` hook.\n * @beta\n */\ntype UseSelectionHandlerProps = Pick<ReturnType<typeof useTree>, \"rootNodes\" | \"selectNodes\"> & {\n /** Selection mode that the component is working in. */\n selectionMode: SelectionMode;\n};\n\n/**\n * Result of `useSelectionHandler` hook.\n * @beta\n */\ninterface UseSelectionHandlerResult {\n /** Should be called by node renderer when a node component is clicked. */\n onNodeClick: (node: PresentationHierarchyNode, isSelected: boolean, event: React.MouseEvent<HTMLElement, MouseEvent>) => void;\n /** Should be called by node renderer when a keyboard event happens on a node. */\n onNodeKeyDown: (node: PresentationHierarchyNode, isSelected: boolean, event: React.KeyboardEvent<HTMLElement>) => void;\n}\n\ninterface FlatTreeState {\n flatNodeList: Array<string>;\n nodeIdToIndexMap: Map<string, number>;\n}\n\n/**\n * A react hook that helps implement different selection modes in a tree component created using `useTree` hook.\n * @beta\n */\nexport function useSelectionHandler(props: UseSelectionHandlerProps): UseSelectionHandlerResult {\n const { rootNodes, selectionMode, selectNodes } = props;\n const previousSelectionRef = useRef<string | undefined>(undefined);\n const state = useRef<FlatTreeState>({\n flatNodeList: [],\n nodeIdToIndexMap: new Map(),\n });\n\n useEffect(() => {\n if (!rootNodes) {\n return;\n }\n state.current = computeFlatNodeList(rootNodes);\n }, [rootNodes]);\n\n const getNodeRange = (firstId?: string, secondId?: string) => {\n const getIndex = (nodeId?: string) => {\n return nodeId ? state.current.nodeIdToIndexMap.get(nodeId) : 0;\n };\n const firstIndex = getIndex(firstId);\n const secondIndex = getIndex(secondId);\n if (firstIndex === undefined || secondIndex === undefined) {\n return [];\n }\n\n const startingIndex = Math.min(firstIndex, secondIndex);\n const endIndex = Math.max(firstIndex, secondIndex);\n return state.current.flatNodeList.slice(startingIndex, endIndex + 1);\n };\n\n const onNodeSelect = useCallback(\n (nodeId: string, isSelected: boolean, shiftDown: boolean, ctrlDown: boolean) => {\n const selection = getSelectionAction(selectionMode, isSelected, shiftDown, ctrlDown);\n if (selection.type === \"disabled\") {\n return;\n }\n\n const nodes = selection.select === \"range\" ? getNodeRange(previousSelectionRef.current, nodeId) : [nodeId];\n if (!nodes.length) {\n return;\n }\n\n selection.select !== \"range\" && (previousSelectionRef.current = nodeId);\n selectNodes(nodes, selection.type);\n },\n [selectionMode, selectNodes],\n );\n\n const onNodeClick = useCallback(\n (node: PresentationHierarchyNode, isSelected: boolean, event: React.MouseEvent<HTMLElement, MouseEvent>) => {\n return onNodeSelect(node.id, isSelected, event.shiftKey, event.ctrlKey);\n },\n [onNodeSelect],\n );\n\n const onNodeKeyDown = useCallback(\n (node: PresentationHierarchyNode, isSelected: boolean, event: React.KeyboardEvent<HTMLElement>) => {\n if (event.key === \" \" || event.key === \"Spacebar\" || event.key === \"Enter\") {\n return onNodeSelect(node.id, isSelected, event.shiftKey, event.ctrlKey);\n }\n },\n [onNodeSelect],\n );\n\n return { onNodeClick, onNodeKeyDown };\n}\n\ninterface SelectionAction {\n select: \"node\" | \"range\";\n type: SelectionChangeType | \"disabled\";\n}\n\nfunction getSelectionAction(selectionMode: SelectionMode, isSelected: boolean, shiftDown: boolean, ctrlDown: boolean): SelectionAction {\n switch (selectionMode) {\n case \"none\":\n return { select: \"node\", type: \"disabled\" };\n case \"single\":\n return { select: \"node\", type: isSelected ? \"replace\" : \"remove\" };\n case \"multiple\":\n return { select: \"node\", type: isSelected ? \"add\" : \"remove\" };\n case \"extended\":\n return getExtendedSelectionAction(isSelected, shiftDown, ctrlDown);\n }\n}\n\nfunction getExtendedSelectionAction(isSelected: boolean, shiftDown: boolean, ctrlDown: boolean): SelectionAction {\n if (shiftDown) {\n return { select: \"range\", type: \"replace\" };\n }\n if (ctrlDown) {\n return { select: \"node\", type: isSelected ? \"add\" : \"remove\" };\n }\n return { select: \"node\", type: isSelected ? \"replace\" : \"disabled\" };\n}\n\nfunction computeFlatNodeList(rootNodes: Array<PresentationTreeNode>): FlatTreeState {\n const flatNodeList: Array<string> = [];\n const nodeIdToIndexMap: Map<string, number> = new Map();\n\n const flattenNodeRecursively = (nodes: Array<PresentationTreeNode>) => {\n nodes.forEach((node) => {\n if (!isPresentationHierarchyNode(node)) {\n return;\n }\n nodeIdToIndexMap.set(node.id, flatNodeList.length);\n flatNodeList.push(node.id);\n if (node.isExpanded && typeof node.children !== \"boolean\") {\n flattenNodeRecursively(node.children);\n }\n });\n };\n\n flattenNodeRecursively(rootNodes);\n return { flatNodeList, nodeIdToIndexMap };\n}\n"]}
|