@neovici/cosmoz-treenode-navigator 7.9.1 → 8.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/README.md +257 -4
- package/dist/cosmoz-treenode-button-view.d.ts +2 -0
- package/dist/cosmoz-treenode-button-view.d.ts.map +1 -1
- package/dist/cosmoz-treenode-button-view.js +130 -167
- package/dist/cosmoz-treenode-button-view.js.map +1 -1
- package/dist/cosmoz-treenode-button-view.styles.d.ts.map +1 -1
- package/dist/cosmoz-treenode-button-view.styles.js +27 -196
- package/dist/cosmoz-treenode-button-view.styles.js.map +1 -1
- package/dist/cosmoz-treenode-navigator.d.ts +1 -0
- package/dist/cosmoz-treenode-navigator.d.ts.map +1 -1
- package/dist/cosmoz-treenode-navigator.js +58 -70
- package/dist/cosmoz-treenode-navigator.js.map +1 -1
- package/dist/cosmoz-treenode-navigator.styles.d.ts.map +1 -1
- package/dist/cosmoz-treenode-navigator.styles.js +0 -13
- package/dist/cosmoz-treenode-navigator.styles.js.map +1 -1
- package/package.json +36 -38
package/README.md
CHANGED
|
@@ -3,24 +3,79 @@
|
|
|
3
3
|
[](https://github.com/Neovici/cosmoz-treenode-navigator/actions?workflow=Github+CI)
|
|
4
4
|
[](https://github.com/semantic-release/semantic-release)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
A [PionJS](https://github.com/nicholaspmccracken/pion)-based web component for navigating, searching, and selecting nodes in a hierarchical tree structure.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
npm install @neovici/cosmoz-treenode-navigator
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
import { component, html } from '@pionjs/pion';
|
|
18
|
+
import '@neovici/cosmoz-treenode-navigator/cosmoz-treenode-button-view';
|
|
19
|
+
import { DefaultTree } from '@neovici/cosmoz-tree';
|
|
20
|
+
|
|
21
|
+
const treeData = {
|
|
22
|
+
1: {
|
|
23
|
+
name: 'Root',
|
|
24
|
+
pathLocator: '1',
|
|
25
|
+
children: {
|
|
26
|
+
2: {
|
|
27
|
+
name: 'Child A',
|
|
28
|
+
pathLocator: '1.2',
|
|
29
|
+
children: {},
|
|
30
|
+
},
|
|
31
|
+
3: {
|
|
32
|
+
name: 'Child B',
|
|
33
|
+
pathLocator: '1.3',
|
|
34
|
+
children: {
|
|
35
|
+
4: { name: 'Grandchild', pathLocator: '1.3.4', children: {} },
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const tree = new DefaultTree(treeData);
|
|
43
|
+
|
|
44
|
+
const MyApp = () => html`
|
|
45
|
+
<cosmoz-treenode-button-view
|
|
46
|
+
.tree=${tree}
|
|
47
|
+
show-reset
|
|
48
|
+
@node-path-changed=${(e) => console.log('Selected:', e.detail.value)}
|
|
49
|
+
></cosmoz-treenode-button-view>
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
customElements.define('my-app', component(MyApp));
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Tree Data Structure
|
|
56
|
+
|
|
57
|
+
The component expects a `Tree` object from [`@neovici/cosmoz-tree`](https://github.com/Neovici/cosmoz-tree), typically instantiated with `new DefaultTree(data)`.
|
|
58
|
+
|
|
59
|
+
The data is a nested object keyed by node IDs:
|
|
9
60
|
|
|
10
61
|
```json
|
|
11
62
|
{
|
|
12
63
|
"1": {
|
|
13
64
|
"name": "Root",
|
|
65
|
+
"pathLocator": "1",
|
|
14
66
|
"children": {
|
|
15
67
|
"7": {
|
|
16
68
|
"name": "child seven",
|
|
69
|
+
"pathLocator": "1.7",
|
|
17
70
|
"children": {}
|
|
18
71
|
},
|
|
19
72
|
"8": {
|
|
20
73
|
"name": "child eight",
|
|
74
|
+
"pathLocator": "1.8",
|
|
21
75
|
"children": {
|
|
22
76
|
"9": {
|
|
23
77
|
"name": "child nine",
|
|
78
|
+
"pathLocator": "1.8.9",
|
|
24
79
|
"children": {}
|
|
25
80
|
}
|
|
26
81
|
}
|
|
@@ -30,6 +85,204 @@ It needs an object structure which looks, for example, like this in JSON:
|
|
|
30
85
|
}
|
|
31
86
|
```
|
|
32
87
|
|
|
33
|
-
The property names
|
|
88
|
+
The property names `name` and `children` are configurable via `DefaultTree` options (`searchProperty`, `childProperty`). The `pathLocatorSeparator` defaults to `"."`.
|
|
89
|
+
|
|
90
|
+
## Components
|
|
91
|
+
|
|
92
|
+
### `<cosmoz-treenode-button-view>`
|
|
93
|
+
|
|
94
|
+
A trigger button that opens a dialog containing the tree navigator. This is the main component most consumers will use.
|
|
95
|
+
|
|
96
|
+
#### Properties / Attributes
|
|
97
|
+
|
|
98
|
+
| Property | Attribute | Type | Default | Description |
|
|
99
|
+
| ----------------------- | ------------------- | --------- | ------- | ----------------------------------------------- |
|
|
100
|
+
| `tree` | -- | `Tree` | -- | The tree data structure (set via JS) |
|
|
101
|
+
| `nodePath` | -- | `string` | `''` | Selected node's path locator (two-way bindable) |
|
|
102
|
+
| `opened` | -- | `boolean` | `false` | Whether the dialog is open (two-way bindable) |
|
|
103
|
+
| `showReset` | `show-reset` | `boolean` | `false` | Show the reset/clear button |
|
|
104
|
+
| `searchMinLength` | `search-min-length` | `number` | `3` | Minimum characters to trigger search |
|
|
105
|
+
| `searchDebounceTimeout` | -- | `number` | `2000` | Debounce timeout (ms) before search triggers |
|
|
106
|
+
|
|
107
|
+
#### Events
|
|
108
|
+
|
|
109
|
+
| Event | Detail | Description |
|
|
110
|
+
| ------------------- | -------------------- | ----------------------------------------- |
|
|
111
|
+
| `node-path-changed` | `{ value: string }` | Fired when the selected node path changes |
|
|
112
|
+
| `opened-changed` | `{ value: boolean }` | Fired when the dialog opens or closes |
|
|
113
|
+
|
|
114
|
+
#### Slots
|
|
115
|
+
|
|
116
|
+
| Slot | Description |
|
|
117
|
+
| ----------- | ------------------------------------------------------------ |
|
|
118
|
+
| `prefix` | Button icon (defaults to folder icon, override to customize) |
|
|
119
|
+
| `suffix` | Content after the button label |
|
|
120
|
+
| _(default)_ | Passed through to the inner `<cosmoz-treenode-navigator>` |
|
|
121
|
+
|
|
122
|
+
#### CSS Parts
|
|
123
|
+
|
|
124
|
+
| Part | Description |
|
|
125
|
+
| --------------- | ------------------------------------------------------------------------------ |
|
|
126
|
+
| `actions` | Container for the open button and optional reset button |
|
|
127
|
+
| `action-open` | The main trigger button (`cosmoz-button`) |
|
|
128
|
+
| `action-reset` | The reset/clear button (`cosmoz-button`, visible when `showReset && nodePath`) |
|
|
129
|
+
| `dialog` | The `<dialog>` element |
|
|
130
|
+
| `header` | Dialog header |
|
|
131
|
+
| `heading` | Dialog heading (`<h1>`) |
|
|
132
|
+
| `main` | Dialog main content area |
|
|
133
|
+
| `footer` | Dialog footer |
|
|
134
|
+
| `select-button` | The "Select" confirmation button |
|
|
135
|
+
| `cancel-button` | The "Cancel" button |
|
|
136
|
+
|
|
137
|
+
#### Container Query Support
|
|
138
|
+
|
|
139
|
+
The host element is declared as a CSS container (`container-type: inline-size`). When the component's width shrinks to 80px or less, the selected node path text is automatically hidden, leaving only the icon. This allows graceful degradation in narrow layouts without media queries.
|
|
140
|
+
|
|
141
|
+
```css
|
|
142
|
+
/* Built-in behavior */
|
|
143
|
+
:host {
|
|
144
|
+
container-type: inline-size;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
@container (max-width: 80px) {
|
|
148
|
+
.path-text {
|
|
149
|
+
display: none;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
### `<cosmoz-treenode-navigator>`
|
|
157
|
+
|
|
158
|
+
The inner navigator component that provides tree browsing, search, and keyboard navigation. Typically used inside `<cosmoz-treenode-button-view>`, but can be used standalone.
|
|
159
|
+
|
|
160
|
+
#### Properties
|
|
161
|
+
|
|
162
|
+
| Property | Type | Default | Description |
|
|
163
|
+
| ----------------------- | --------- | ----------- | ----------------------------------------------- |
|
|
164
|
+
| `tree` | `Tree` | -- | The tree data structure (set via JS) |
|
|
165
|
+
| `nodePath` | `string` | `''` | Selected node's path locator (two-way bindable) |
|
|
166
|
+
| `highlightedNodePath` | `string` | `''` | Currently highlighted node's path (notify only) |
|
|
167
|
+
| `opened` | `boolean` | `undefined` | Controls keyboard listeners and scroll behavior |
|
|
168
|
+
| `searchMinLength` | `number` | `3` | Minimum characters to trigger search |
|
|
169
|
+
| `searchDebounceTimeout` | `number` | `2000` | Debounce timeout (ms) before search triggers |
|
|
170
|
+
|
|
171
|
+
#### Events
|
|
172
|
+
|
|
173
|
+
| Event | Detail | Description |
|
|
174
|
+
| ------------------------------- | ------------------- | ----------------------------------------- |
|
|
175
|
+
| `node-path-changed` | `{ value: string }` | Fired when the selected node path changes |
|
|
176
|
+
| `highlighted-node-path-changed` | `{ value: string }` | Fired when the highlighted node changes |
|
|
177
|
+
|
|
178
|
+
#### CSS Custom Properties
|
|
179
|
+
|
|
180
|
+
| Property | Default | Description |
|
|
181
|
+
| ----------------------------------------------------- | ------------------------------- | ------------------------------------------- |
|
|
182
|
+
| `--cosmoz-treenode-navigator-icon-color` | `currentColor` | Color of navigation icons (home, arrow) |
|
|
183
|
+
| `--cosmoz-treenode-navigator-select-node-icon-color` | `var(--primary-color, #3a91e2)` | Color of the selected/highlighted node icon |
|
|
184
|
+
| `--cosmoz-treenode-navigator-list-item-focused-color` | `#f0f8ff` | Background color of focused list items |
|
|
185
|
+
| `--cosmoz-treenode-navigator-list-height` | `50vh` | Height of the scrollable node list |
|
|
186
|
+
| `--cz-text-color` | `inherit` | General text color |
|
|
187
|
+
| `--cz-bg-color` | `#f5f5f5` | Section header background color |
|
|
188
|
+
|
|
189
|
+
## Internationalization
|
|
190
|
+
|
|
191
|
+
All UI text is managed via [`i18next`](https://www.i18next.com/). The English text is used as the translation key, so it works out of the box when no translations are loaded (i18next returns the key as-is).
|
|
192
|
+
|
|
193
|
+
Consumers must initialize i18next before using the components:
|
|
194
|
+
|
|
195
|
+
```js
|
|
196
|
+
import i18next from 'i18next';
|
|
197
|
+
|
|
198
|
+
i18next.init({ lng: 'en', resources: {} });
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
To provide translations, use `i18next.addResourceBundle()` or any i18next backend:
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
i18next.addResourceBundle('sv', 'translation', {
|
|
205
|
+
'Select a node': 'Valj en nod',
|
|
206
|
+
'Search or navigate to chosen destination':
|
|
207
|
+
'Sok eller navigera till vald destination',
|
|
208
|
+
'Search...': 'Sok...',
|
|
209
|
+
'Click to search again but globally': 'Klicka for att soka igen men globalt',
|
|
210
|
+
Select: 'Valj',
|
|
211
|
+
Cancel: 'Avbryt',
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Translation Keys
|
|
216
|
+
|
|
217
|
+
| Key | Used in |
|
|
218
|
+
| ------------------------------------------ | ------------------------------------------- |
|
|
219
|
+
| `Select a node` | Button placeholder when no node is selected |
|
|
220
|
+
| `Search or navigate to chosen destination` | Dialog heading |
|
|
221
|
+
| `Search...` | Search input placeholder |
|
|
222
|
+
| `Click to search again but globally` | Global search button label |
|
|
223
|
+
| `Select` | Dialog confirm button |
|
|
224
|
+
| `Cancel` | Dialog cancel button |
|
|
225
|
+
|
|
226
|
+
## Migrating from v7
|
|
227
|
+
|
|
228
|
+
This version includes breaking changes to the component API.
|
|
229
|
+
|
|
230
|
+
### Property Changes
|
|
231
|
+
|
|
232
|
+
| Before | After | Notes |
|
|
233
|
+
| ------------------------- | -------------------- | ------------------------------------------------------------ |
|
|
234
|
+
| `noReset` (opt-out) | `showReset` (opt-in) | Inverted logic: set `show-reset` to display the reset button |
|
|
235
|
+
| `selectedNode` | _removed_ | Use `nodePath` instead |
|
|
236
|
+
| `highlightedNode` | _removed_ | Use `highlightedNodePath` (on the navigator) instead |
|
|
237
|
+
| `dialogText` | _removed_ | Now managed via `i18next.t()` |
|
|
238
|
+
| `buttonTextPlaceholder` | _removed_ | Now managed via `i18next.t()` |
|
|
239
|
+
| `searchPlaceholder` | _removed_ | Now managed via `i18next.t()` |
|
|
240
|
+
| `searchGlobalPlaceholder` | _removed_ | Now managed via `i18next.t()` |
|
|
241
|
+
|
|
242
|
+
`nodePath` is now the single source of truth for the selected node. Dialog navigation no longer affects the selected node until explicitly confirmed via the Select button.
|
|
243
|
+
|
|
244
|
+
### Dependency Changes
|
|
245
|
+
|
|
246
|
+
| Before | After |
|
|
247
|
+
| ------------------------- | ----------------------------- |
|
|
248
|
+
| `@neovici/cosmoz-i18next` | `i18next` (direct dependency) |
|
|
249
|
+
|
|
250
|
+
All UI text is now translated via `i18next.t()` instead of being passed as component properties. See [Internationalization](#internationalization) for details.
|
|
251
|
+
|
|
252
|
+
### Selector Changes
|
|
253
|
+
|
|
254
|
+
All buttons have been converted to `cosmoz-button` components. Update any CSS selectors accordingly:
|
|
255
|
+
|
|
256
|
+
| Before | After |
|
|
257
|
+
| ------------------------------ | ------------------------------------- |
|
|
258
|
+
| `button.action-open` | `cosmoz-button[part="action-open"]` |
|
|
259
|
+
| `button.action-reset` | `cosmoz-button[part="action-reset"]` |
|
|
260
|
+
| `button[part="select-button"]` | `cosmoz-button[part="select-button"]` |
|
|
261
|
+
| `button[part="cancel-button"]` | `cosmoz-button[part="cancel-button"]` |
|
|
262
|
+
| `button.btn-ghost` | `cosmoz-button[variant="link"]` |
|
|
263
|
+
|
|
264
|
+
### Slot Changes
|
|
265
|
+
|
|
266
|
+
| Before | After | Notes |
|
|
267
|
+
| --------------- | -------- | ---------------------------------------------------------- |
|
|
268
|
+
| `button-before` | `prefix` | Now uses default slot content instead of JS-based toggling |
|
|
269
|
+
| `button-after` | `suffix` | Renamed for consistency |
|
|
270
|
+
|
|
271
|
+
## Development
|
|
272
|
+
|
|
273
|
+
| Command | Description |
|
|
274
|
+
| ------------------------- | -------------------------------------------------- |
|
|
275
|
+
| `npm start` | Start Storybook dev server on port 8000 |
|
|
276
|
+
| `npm test` | Run all tests (unit + storybook interaction tests) |
|
|
277
|
+
| `npm run test:unit` | Run unit tests only |
|
|
278
|
+
| `npm run test:storybook` | Run storybook interaction tests only |
|
|
279
|
+
| `npm run test:watch` | Run tests in watch mode |
|
|
280
|
+
| `npm run lint` | Lint with ESLint |
|
|
281
|
+
| `npm run build` | Compile TypeScript |
|
|
282
|
+
| `npm run storybook:build` | Build static Storybook |
|
|
283
|
+
|
|
284
|
+
Tests use [Vitest](https://vitest.dev/) with two projects: `unit` (jsdom) for helper function tests, and `storybook` (Playwright browser mode) for component interaction tests via Storybook play functions.
|
|
285
|
+
|
|
286
|
+
## License
|
|
34
287
|
|
|
35
|
-
|
|
288
|
+
[Apache-2.0](LICENSE)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cosmoz-treenode-button-view.d.ts","sourceRoot":"","sources":["../src/cosmoz-treenode-button-view.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cosmoz-treenode-button-view.d.ts","sourceRoot":"","sources":["../src/cosmoz-treenode-button-view.ts"],"names":[],"mappings":"AAaA,OAAO,sCAAsC,CAAC;AAC9C,OAAO,yBAAyB,CAAC;AAIjC,OAAO,6BAA6B,CAAC"}
|
|
@@ -1,93 +1,66 @@
|
|
|
1
|
-
/*eslint-disable max-statements */
|
|
2
1
|
import { component, lift, useEffect, useMemo, useProperty, useRef, useState, } from '@pionjs/pion';
|
|
3
2
|
import { html } from 'lit-html';
|
|
4
3
|
import { ref } from 'lit/directives/ref.js';
|
|
5
4
|
import style from './cosmoz-treenode-button-view.styles';
|
|
5
|
+
import '@neovici/cosmoz-button/cosmoz-button';
|
|
6
|
+
import '@neovici/cosmoz-tooltip';
|
|
7
|
+
import { t } from 'i18next';
|
|
8
|
+
import { when } from 'lit-html/directives/when.js';
|
|
6
9
|
import './cosmoz-treenode-navigator';
|
|
7
10
|
import { useKeyDown } from './hooks/useKeyDown';
|
|
8
|
-
import { when } from 'lit-html/directives/when.js';
|
|
9
|
-
import { debounce$ } from '@neovici/cosmoz-utils/promise';
|
|
10
11
|
import { getTreePathParts } from './util/helpers';
|
|
11
|
-
const
|
|
12
|
+
const defaultIcon = html `<svg
|
|
13
|
+
class="default-icon"
|
|
14
|
+
width="20"
|
|
15
|
+
height="20"
|
|
16
|
+
viewBox="0 0 24 24"
|
|
17
|
+
fill="none"
|
|
18
|
+
stroke="currentColor"
|
|
19
|
+
stroke-width="2"
|
|
20
|
+
stroke-linecap="round"
|
|
21
|
+
stroke-linejoin="round"
|
|
22
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
23
|
+
>
|
|
24
|
+
<circle cx="7" cy="7" r="2" />
|
|
25
|
+
<circle cx="17" cy="17" r="2" />
|
|
26
|
+
<path d="M7 9v3c0 1.66 1.34 3 3 3h7" />
|
|
27
|
+
</svg>`;
|
|
28
|
+
const CosmozNodeButtonView = ({ tree, showReset = false, searchMinLength = 3, searchDebounceTimeout = 2000, }) => {
|
|
12
29
|
const dialogRef = useRef(null);
|
|
13
|
-
|
|
14
|
-
const [highlightedNode, setHighlightedNode] = useProperty('highlightedNode');
|
|
15
|
-
// legacy props
|
|
16
|
-
const [selectedNode, setSelectedNode] = useProperty('selectedNode');
|
|
30
|
+
// nodePath is the single source of truth - external two-way binding
|
|
17
31
|
const [nodePath, setNodePath] = useProperty('nodePath', '');
|
|
18
|
-
|
|
19
|
-
const [opened, setOpened] =
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
?.shadowRoot?.querySelector('cosmoz-input');
|
|
26
|
-
}
|
|
27
|
-
}, [dialogRef.current]);
|
|
28
|
-
// external updates to "selectedNode"
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
if (selectedNode && selectedNode !== highlightedNode) {
|
|
31
|
-
setHighlightedNode(selectedNode);
|
|
32
|
-
}
|
|
33
|
-
}, [selectedNode]);
|
|
34
|
-
// external updates to "nodePath"
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
if (!(nodePath || tree)) {
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
const parts = getTreePathParts(nodePath, tree);
|
|
40
|
-
setNodesOnNodePath(parts);
|
|
41
|
-
const last = parts.length > 0 ? parts[parts.length - 1] : null;
|
|
42
|
-
if (last && last !== highlightedNode) {
|
|
43
|
-
setHighlightedNode(last);
|
|
44
|
-
}
|
|
45
|
-
}, [nodePath, tree]);
|
|
32
|
+
// opened is controllable externally
|
|
33
|
+
const [opened, setOpened] = useProperty('opened', false);
|
|
34
|
+
// Track highlighted node path from navigator for Select button
|
|
35
|
+
const [highlightedNodePath, setHighlightedNodePath] = useState('');
|
|
36
|
+
// nodesOnNodePath derived from nodePath + tree
|
|
37
|
+
const nodesOnNodePath = useMemo(() => getTreePathParts(nodePath, tree), [nodePath, tree]);
|
|
38
|
+
// buttonLabel derived from nodesOnNodePath
|
|
46
39
|
const buttonLabel = useMemo(() => {
|
|
47
40
|
if (!Array.isArray(nodesOnNodePath) || nodesOnNodePath.length === 0) {
|
|
48
|
-
return
|
|
41
|
+
return t('Select a node');
|
|
49
42
|
}
|
|
50
43
|
return nodesOnNodePath
|
|
51
44
|
.filter((n) => n)
|
|
52
45
|
.map((part) => part[tree.searchProperty])
|
|
53
46
|
.join(' / ');
|
|
54
47
|
}, [nodesOnNodePath, tree]);
|
|
48
|
+
// Sync dialog DOM state with opened property
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (opened) {
|
|
51
|
+
dialogRef.current?.showModal();
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
dialogRef.current?.close();
|
|
55
|
+
}
|
|
56
|
+
}, [opened]);
|
|
55
57
|
const reset = () => {
|
|
56
|
-
setNodesOnNodePath([]);
|
|
57
|
-
setSelectedNodes([]);
|
|
58
|
-
setHighlightedNode(null);
|
|
59
|
-
setSelectedNode(null);
|
|
60
58
|
setNodePath('');
|
|
61
59
|
};
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
ev.preventDefault();
|
|
65
|
-
ev.stopPropagation();
|
|
66
|
-
};
|
|
67
|
-
const getChipText = (node) => {
|
|
68
|
-
return node.name;
|
|
69
|
-
};
|
|
70
|
-
const showSelectedNodes = (multiSelection, selectedNodesLength) => {
|
|
71
|
-
return multiSelection && selectedNodesLength > 0;
|
|
72
|
-
};
|
|
73
|
-
const refit = debounce$(() => {
|
|
74
|
-
dialogRef.current?.fit?.();
|
|
75
|
-
}, 50);
|
|
76
|
-
const focusSearch = () => {
|
|
77
|
-
if (searchInputRef.current) {
|
|
78
|
-
searchInputRef.current.focus();
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
const onOpen = () => {
|
|
82
|
-
dialogRef.current?.showModal();
|
|
83
|
-
setOpened(true);
|
|
84
|
-
setTimeout(focusSearch, 0);
|
|
85
|
-
};
|
|
86
|
-
const onClose = () => {
|
|
87
|
-
setOpened(false);
|
|
88
|
-
dialogRef.current?.close();
|
|
89
|
-
};
|
|
60
|
+
const onOpen = () => setOpened(true);
|
|
61
|
+
const onClose = () => setOpened(false);
|
|
90
62
|
useKeyDown('Escape', onClose);
|
|
63
|
+
// Dialog dragging support
|
|
91
64
|
const onHeaderMouseDown = (e) => {
|
|
92
65
|
const dialog = dialogRef.current;
|
|
93
66
|
if (!dialog)
|
|
@@ -111,84 +84,81 @@ const CosmozNodeButtonView = ({ tree, multiSelection = false, dialogText, button
|
|
|
111
84
|
document.addEventListener('mousemove', onMouseMove);
|
|
112
85
|
document.addEventListener('mouseup', onMouseUp);
|
|
113
86
|
};
|
|
114
|
-
//
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
87
|
+
// Handle node-path-changed from navigator - close dialog on selection
|
|
88
|
+
const onNodePathChanged = (e) => {
|
|
89
|
+
e.preventDefault();
|
|
90
|
+
const newPath = e.detail.value;
|
|
91
|
+
if (newPath) {
|
|
92
|
+
setNodePath(newPath);
|
|
93
|
+
onClose();
|
|
118
94
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
95
|
+
};
|
|
96
|
+
// Handle highlighted-node-path-changed from navigator
|
|
97
|
+
const onHighlightedNodePathChanged = lift(setHighlightedNodePath);
|
|
98
|
+
// Handle Select button click
|
|
99
|
+
const onSelect = () => {
|
|
100
|
+
if (highlightedNodePath) {
|
|
101
|
+
setNodePath(highlightedNodePath);
|
|
102
|
+
onClose();
|
|
122
103
|
}
|
|
123
|
-
// update legacy props
|
|
124
|
-
setSelectedNode(highlightedNode);
|
|
125
|
-
setNodePath(highlightedNode.pathLocator);
|
|
126
|
-
setNodesOnNodePath(getTreePathParts(highlightedNode.pathLocator, tree));
|
|
127
|
-
onClose();
|
|
128
104
|
};
|
|
129
105
|
return html `
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
>
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
106
|
+
<cosmoz-tooltip
|
|
107
|
+
placement="right"
|
|
108
|
+
.description=${buttonLabel}
|
|
109
|
+
.delay=${1000}
|
|
110
|
+
>
|
|
111
|
+
<div class="actions" part="actions">
|
|
112
|
+
<cosmoz-button
|
|
113
|
+
variant="secondary"
|
|
114
|
+
full-width
|
|
115
|
+
data-testid="open-button"
|
|
116
|
+
@click=${onOpen}
|
|
117
|
+
part="action-open"
|
|
118
|
+
>
|
|
119
|
+
<slot name="prefix" slot="prefix">${defaultIcon}</slot>
|
|
120
|
+
<div class="path-text">
|
|
121
|
+
<span>${buttonLabel}</span>
|
|
122
|
+
</div>
|
|
123
|
+
<slot name="suffix" slot="suffix"></slot>
|
|
124
|
+
</cosmoz-button>
|
|
125
|
+
${when(showReset && !!nodePath, () => html `<cosmoz-button
|
|
126
|
+
variant="tertiary"
|
|
127
|
+
@click=${reset}
|
|
128
|
+
data-testid="reset-button"
|
|
129
|
+
part="action-reset"
|
|
154
130
|
>
|
|
155
|
-
<
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
stroke
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
>
|
|
182
|
-
×
|
|
183
|
-
</button>
|
|
184
|
-
</div>
|
|
185
|
-
`)}
|
|
186
|
-
</div>
|
|
187
|
-
`)}
|
|
131
|
+
<svg
|
|
132
|
+
slot="prefix"
|
|
133
|
+
width="10"
|
|
134
|
+
height="9"
|
|
135
|
+
viewBox="0 0 10 9"
|
|
136
|
+
stroke="currentColor"
|
|
137
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
138
|
+
>
|
|
139
|
+
<line
|
|
140
|
+
x1="8.53033"
|
|
141
|
+
y1="0.53033"
|
|
142
|
+
x2="1.53033"
|
|
143
|
+
y2="7.53033"
|
|
144
|
+
stroke-width="1.5"
|
|
145
|
+
></line>
|
|
146
|
+
<line
|
|
147
|
+
x1="8.46967"
|
|
148
|
+
y1="7.53033"
|
|
149
|
+
x2="1.46967"
|
|
150
|
+
y2="0.530331"
|
|
151
|
+
stroke-width="1.5"
|
|
152
|
+
></line>
|
|
153
|
+
</svg>
|
|
154
|
+
</cosmoz-button>`)}
|
|
155
|
+
</div>
|
|
156
|
+
</cosmoz-tooltip>
|
|
188
157
|
|
|
189
158
|
<dialog
|
|
190
159
|
class="dialog"
|
|
191
160
|
part="dialog"
|
|
161
|
+
data-testid="dialog"
|
|
192
162
|
${ref((el) => {
|
|
193
163
|
dialogRef.current = el;
|
|
194
164
|
})}
|
|
@@ -198,58 +168,51 @@ const CosmozNodeButtonView = ({ tree, multiSelection = false, dialogText, button
|
|
|
198
168
|
part="header"
|
|
199
169
|
@mousedown=${onHeaderMouseDown}
|
|
200
170
|
>
|
|
201
|
-
<h1 class="dialog-heading" part="heading"
|
|
171
|
+
<h1 class="dialog-heading" part="heading">
|
|
172
|
+
${t('Search or navigate to chosen destination')}
|
|
173
|
+
</h1>
|
|
202
174
|
</header>
|
|
203
175
|
<main class="dialog-main" part="main">
|
|
204
176
|
<cosmoz-treenode-navigator
|
|
205
177
|
id="treeNavigator"
|
|
206
178
|
class="dialog-treenode-navigator no-padding"
|
|
207
|
-
.
|
|
208
|
-
@
|
|
209
|
-
|
|
210
|
-
.searchGlobalPlaceholder=${searchGlobalPlaceholder}
|
|
179
|
+
.nodePath=${nodePath}
|
|
180
|
+
@node-path-changed=${onNodePathChanged}
|
|
181
|
+
@highlighted-node-path-changed=${onHighlightedNodePathChanged}
|
|
211
182
|
.searchMinLength=${searchMinLength}
|
|
212
183
|
.searchDebounceTimeout=${searchDebounceTimeout}
|
|
213
184
|
.tree=${tree}
|
|
214
185
|
.opened=${opened}
|
|
215
|
-
.nodesOnNodePath=${nodesOnNodePath}
|
|
216
|
-
@node-dblclicked=${selectNode}
|
|
217
|
-
@on-data-plane-changed=${refit}
|
|
218
186
|
>
|
|
219
187
|
<slot></slot>
|
|
220
188
|
</cosmoz-treenode-navigator>
|
|
221
189
|
</main>
|
|
222
190
|
<footer class="dialog-footer" part="footer">
|
|
223
|
-
<
|
|
224
|
-
<button
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
191
|
+
<div class="dialog-footer-button-container">
|
|
192
|
+
<cosmoz-button
|
|
193
|
+
variant="primary"
|
|
194
|
+
?disabled=${!highlightedNodePath}
|
|
195
|
+
@click=${onSelect}
|
|
196
|
+
data-testid="select-button"
|
|
228
197
|
part="select-button"
|
|
229
|
-
@click=${selectNode}
|
|
230
198
|
>
|
|
231
|
-
Select
|
|
232
|
-
</button>
|
|
233
|
-
<button
|
|
234
|
-
|
|
235
|
-
part="cancel-button"
|
|
199
|
+
${t('Select')}
|
|
200
|
+
</cosmoz-button>
|
|
201
|
+
<cosmoz-button
|
|
202
|
+
variant="secondary"
|
|
236
203
|
@click=${onClose}
|
|
204
|
+
data-testid="cancel-button"
|
|
205
|
+
part="cancel-button"
|
|
237
206
|
>
|
|
238
|
-
Cancel
|
|
239
|
-
</button>
|
|
240
|
-
</
|
|
207
|
+
${t('Cancel')}
|
|
208
|
+
</cosmoz-button>
|
|
209
|
+
</div>
|
|
241
210
|
</footer>
|
|
242
211
|
</dialog>
|
|
243
212
|
`;
|
|
244
213
|
};
|
|
245
214
|
CosmozNodeButtonView.observedAttributes = [
|
|
246
|
-
'
|
|
247
|
-
'dialog-text',
|
|
248
|
-
'search-placeholder',
|
|
249
|
-
'search-global-placeholder',
|
|
250
|
-
'node-path',
|
|
251
|
-
'selected-node',
|
|
252
|
-
'no-reset',
|
|
215
|
+
'show-reset',
|
|
253
216
|
'search-min-length',
|
|
254
217
|
];
|
|
255
218
|
customElements.define('cosmoz-treenode-button-view', component(CosmozNodeButtonView, {
|