@neovici/cosmoz-treenode-navigator 7.9.0 → 8.0.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 +1 -0
- package/dist/cosmoz-treenode-button-view.d.ts.map +1 -1
- package/dist/cosmoz-treenode-button-view.js +91 -135
- 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/dist/util/helpers.d.ts.map +1 -1
- package/dist/util/helpers.js +19 -10
- package/dist/util/helpers.js.map +1 -1
- package/package.json +35 -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;AAI9C,OAAO,6BAA6B,CAAC"}
|
|
@@ -1,93 +1,65 @@
|
|
|
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 { t } from 'i18next';
|
|
7
|
+
import { when } from 'lit-html/directives/when.js';
|
|
6
8
|
import './cosmoz-treenode-navigator';
|
|
7
9
|
import { useKeyDown } from './hooks/useKeyDown';
|
|
8
|
-
import { when } from 'lit-html/directives/when.js';
|
|
9
|
-
import { debounce$ } from '@neovici/cosmoz-utils/promise';
|
|
10
10
|
import { getTreePathParts } from './util/helpers';
|
|
11
|
-
const
|
|
11
|
+
const defaultIcon = html `<svg
|
|
12
|
+
class="default-icon"
|
|
13
|
+
width="20"
|
|
14
|
+
height="20"
|
|
15
|
+
viewBox="0 0 24 24"
|
|
16
|
+
fill="none"
|
|
17
|
+
stroke="currentColor"
|
|
18
|
+
stroke-width="2"
|
|
19
|
+
stroke-linecap="round"
|
|
20
|
+
stroke-linejoin="round"
|
|
21
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
22
|
+
>
|
|
23
|
+
<circle cx="7" cy="7" r="2" />
|
|
24
|
+
<circle cx="17" cy="17" r="2" />
|
|
25
|
+
<path d="M7 9v3c0 1.66 1.34 3 3 3h7" />
|
|
26
|
+
</svg>`;
|
|
27
|
+
const CosmozNodeButtonView = ({ tree, showReset = false, searchMinLength = 3, searchDebounceTimeout = 2000, }) => {
|
|
12
28
|
const dialogRef = useRef(null);
|
|
13
|
-
|
|
14
|
-
const [highlightedNode, setHighlightedNode] = useProperty('highlightedNode');
|
|
15
|
-
// legacy props
|
|
16
|
-
const [selectedNode, setSelectedNode] = useProperty('selectedNode');
|
|
29
|
+
// nodePath is the single source of truth - external two-way binding
|
|
17
30
|
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]);
|
|
31
|
+
// opened is controllable externally
|
|
32
|
+
const [opened, setOpened] = useProperty('opened', false);
|
|
33
|
+
// Track highlighted node path from navigator for Select button
|
|
34
|
+
const [highlightedNodePath, setHighlightedNodePath] = useState('');
|
|
35
|
+
// nodesOnNodePath derived from nodePath + tree
|
|
36
|
+
const nodesOnNodePath = useMemo(() => getTreePathParts(nodePath, tree), [nodePath, tree]);
|
|
37
|
+
// buttonLabel derived from nodesOnNodePath
|
|
46
38
|
const buttonLabel = useMemo(() => {
|
|
47
39
|
if (!Array.isArray(nodesOnNodePath) || nodesOnNodePath.length === 0) {
|
|
48
|
-
return
|
|
40
|
+
return t('Select a node');
|
|
49
41
|
}
|
|
50
42
|
return nodesOnNodePath
|
|
51
43
|
.filter((n) => n)
|
|
52
44
|
.map((part) => part[tree.searchProperty])
|
|
53
45
|
.join(' / ');
|
|
54
46
|
}, [nodesOnNodePath, tree]);
|
|
47
|
+
// Sync dialog DOM state with opened property
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (opened) {
|
|
50
|
+
dialogRef.current?.showModal();
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
dialogRef.current?.close();
|
|
54
|
+
}
|
|
55
|
+
}, [opened]);
|
|
55
56
|
const reset = () => {
|
|
56
|
-
setNodesOnNodePath([]);
|
|
57
|
-
setSelectedNodes([]);
|
|
58
|
-
setHighlightedNode(null);
|
|
59
|
-
setSelectedNode(null);
|
|
60
57
|
setNodePath('');
|
|
61
58
|
};
|
|
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
|
-
};
|
|
59
|
+
const onOpen = () => setOpened(true);
|
|
60
|
+
const onClose = () => setOpened(false);
|
|
90
61
|
useKeyDown('Escape', onClose);
|
|
62
|
+
// Dialog dragging support
|
|
91
63
|
const onHeaderMouseDown = (e) => {
|
|
92
64
|
const dialog = dialogRef.current;
|
|
93
65
|
if (!dialog)
|
|
@@ -111,41 +83,47 @@ const CosmozNodeButtonView = ({ tree, multiSelection = false, dialogText, button
|
|
|
111
83
|
document.addEventListener('mousemove', onMouseMove);
|
|
112
84
|
document.addEventListener('mouseup', onMouseUp);
|
|
113
85
|
};
|
|
114
|
-
//
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
86
|
+
// Handle node-path-changed from navigator - close dialog on selection
|
|
87
|
+
const onNodePathChanged = (e) => {
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
const newPath = e.detail.value;
|
|
90
|
+
if (newPath) {
|
|
91
|
+
setNodePath(newPath);
|
|
92
|
+
onClose();
|
|
118
93
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
94
|
+
};
|
|
95
|
+
// Handle highlighted-node-path-changed from navigator
|
|
96
|
+
const onHighlightedNodePathChanged = lift(setHighlightedNodePath);
|
|
97
|
+
// Handle Select button click
|
|
98
|
+
const onSelect = () => {
|
|
99
|
+
if (highlightedNodePath) {
|
|
100
|
+
setNodePath(highlightedNodePath);
|
|
101
|
+
onClose();
|
|
122
102
|
}
|
|
123
|
-
// update legacy props
|
|
124
|
-
setSelectedNode(highlightedNode);
|
|
125
|
-
setNodePath(highlightedNode.pathLocator);
|
|
126
|
-
setNodesOnNodePath(getTreePathParts(highlightedNode.pathLocator, tree));
|
|
127
|
-
onClose();
|
|
128
103
|
};
|
|
129
104
|
return html `
|
|
130
105
|
<div class="actions" part="actions">
|
|
131
|
-
<button
|
|
132
|
-
|
|
133
|
-
|
|
106
|
+
<cosmoz-button
|
|
107
|
+
variant="secondary"
|
|
108
|
+
full-width
|
|
109
|
+
data-testid="open-button"
|
|
134
110
|
@click=${onOpen}
|
|
135
111
|
part="action-open"
|
|
136
112
|
>
|
|
137
|
-
<slot name="
|
|
113
|
+
<slot name="prefix" slot="prefix">${defaultIcon}</slot>
|
|
138
114
|
<div class="path-text">
|
|
139
115
|
<span>${buttonLabel}</span>
|
|
140
116
|
</div>
|
|
141
|
-
<slot name="
|
|
142
|
-
</button>
|
|
143
|
-
${when(
|
|
117
|
+
<slot name="suffix" slot="suffix"></slot>
|
|
118
|
+
</cosmoz-button>
|
|
119
|
+
${when(showReset && !!nodePath, () => html `<cosmoz-button
|
|
120
|
+
variant="tertiary"
|
|
144
121
|
@click=${reset}
|
|
145
|
-
|
|
122
|
+
data-testid="reset-button"
|
|
146
123
|
part="action-reset"
|
|
147
124
|
>
|
|
148
125
|
<svg
|
|
126
|
+
slot="prefix"
|
|
149
127
|
width="10"
|
|
150
128
|
height="9"
|
|
151
129
|
viewBox="0 0 10 9"
|
|
@@ -167,28 +145,13 @@ const CosmozNodeButtonView = ({ tree, multiSelection = false, dialogText, button
|
|
|
167
145
|
stroke-width="1.5"
|
|
168
146
|
></line>
|
|
169
147
|
</svg>
|
|
170
|
-
</button>`)}
|
|
148
|
+
</cosmoz-button>`)}
|
|
171
149
|
</div>
|
|
172
|
-
${when(showSelectedNodes(multiSelection, selectedNodes.length), () => html `
|
|
173
|
-
<div id="chips" part="chips" class="row">
|
|
174
|
-
${selectedNodes.map((item) => html `
|
|
175
|
-
<div class="chip">
|
|
176
|
-
<span>${getChipText(item)}</span>
|
|
177
|
-
<button
|
|
178
|
-
class="chip__clear"
|
|
179
|
-
@click=${(ev) => clearItemSelection({ item, ev })}
|
|
180
|
-
type="button"
|
|
181
|
-
>
|
|
182
|
-
×
|
|
183
|
-
</button>
|
|
184
|
-
</div>
|
|
185
|
-
`)}
|
|
186
|
-
</div>
|
|
187
|
-
`)}
|
|
188
150
|
|
|
189
151
|
<dialog
|
|
190
152
|
class="dialog"
|
|
191
153
|
part="dialog"
|
|
154
|
+
data-testid="dialog"
|
|
192
155
|
${ref((el) => {
|
|
193
156
|
dialogRef.current = el;
|
|
194
157
|
})}
|
|
@@ -198,58 +161,51 @@ const CosmozNodeButtonView = ({ tree, multiSelection = false, dialogText, button
|
|
|
198
161
|
part="header"
|
|
199
162
|
@mousedown=${onHeaderMouseDown}
|
|
200
163
|
>
|
|
201
|
-
<h1 class="dialog-heading" part="heading"
|
|
164
|
+
<h1 class="dialog-heading" part="heading">
|
|
165
|
+
${t('Search or navigate to chosen destination')}
|
|
166
|
+
</h1>
|
|
202
167
|
</header>
|
|
203
168
|
<main class="dialog-main" part="main">
|
|
204
169
|
<cosmoz-treenode-navigator
|
|
205
170
|
id="treeNavigator"
|
|
206
171
|
class="dialog-treenode-navigator no-padding"
|
|
207
|
-
.
|
|
208
|
-
@
|
|
209
|
-
|
|
210
|
-
.searchGlobalPlaceholder=${searchGlobalPlaceholder}
|
|
172
|
+
.nodePath=${nodePath}
|
|
173
|
+
@node-path-changed=${onNodePathChanged}
|
|
174
|
+
@highlighted-node-path-changed=${onHighlightedNodePathChanged}
|
|
211
175
|
.searchMinLength=${searchMinLength}
|
|
212
176
|
.searchDebounceTimeout=${searchDebounceTimeout}
|
|
213
177
|
.tree=${tree}
|
|
214
178
|
.opened=${opened}
|
|
215
|
-
.nodesOnNodePath=${nodesOnNodePath}
|
|
216
|
-
@node-dblclicked=${selectNode}
|
|
217
|
-
@on-data-plane-changed=${refit}
|
|
218
179
|
>
|
|
219
180
|
<slot></slot>
|
|
220
181
|
</cosmoz-treenode-navigator>
|
|
221
182
|
</main>
|
|
222
183
|
<footer class="dialog-footer" part="footer">
|
|
223
|
-
<
|
|
224
|
-
<button
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
184
|
+
<div class="dialog-footer-button-container">
|
|
185
|
+
<cosmoz-button
|
|
186
|
+
variant="primary"
|
|
187
|
+
?disabled=${!highlightedNodePath}
|
|
188
|
+
@click=${onSelect}
|
|
189
|
+
data-testid="select-button"
|
|
228
190
|
part="select-button"
|
|
229
|
-
@click=${selectNode}
|
|
230
191
|
>
|
|
231
|
-
Select
|
|
232
|
-
</button>
|
|
233
|
-
<button
|
|
234
|
-
|
|
235
|
-
part="cancel-button"
|
|
192
|
+
${t('Select')}
|
|
193
|
+
</cosmoz-button>
|
|
194
|
+
<cosmoz-button
|
|
195
|
+
variant="secondary"
|
|
236
196
|
@click=${onClose}
|
|
197
|
+
data-testid="cancel-button"
|
|
198
|
+
part="cancel-button"
|
|
237
199
|
>
|
|
238
|
-
Cancel
|
|
239
|
-
</button>
|
|
240
|
-
</
|
|
200
|
+
${t('Cancel')}
|
|
201
|
+
</cosmoz-button>
|
|
202
|
+
</div>
|
|
241
203
|
</footer>
|
|
242
204
|
</dialog>
|
|
243
205
|
`;
|
|
244
206
|
};
|
|
245
207
|
CosmozNodeButtonView.observedAttributes = [
|
|
246
|
-
'
|
|
247
|
-
'dialog-text',
|
|
248
|
-
'search-placeholder',
|
|
249
|
-
'search-global-placeholder',
|
|
250
|
-
'node-path',
|
|
251
|
-
'selected-node',
|
|
252
|
-
'no-reset',
|
|
208
|
+
'show-reset',
|
|
253
209
|
'search-min-length',
|
|
254
210
|
];
|
|
255
211
|
customElements.define('cosmoz-treenode-button-view', component(CosmozNodeButtonView, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cosmoz-treenode-button-view.js","sourceRoot":"","sources":["../src/cosmoz-treenode-button-view.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"cosmoz-treenode-button-view.js","sourceRoot":"","sources":["../src/cosmoz-treenode-button-view.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cosmoz-treenode-button-view.styles.d.ts","sourceRoot":"","sources":["../src/cosmoz-treenode-button-view.styles.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"cosmoz-treenode-button-view.styles.d.ts","sourceRoot":"","sources":["../src/cosmoz-treenode-button-view.styles.ts"],"names":[],"mappings":";AAGA,wBA2FE"}
|