@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 CHANGED
@@ -3,24 +3,79 @@
3
3
  [![Build Status](https://github.com/Neovici/cosmoz-treenode-navigator/workflows/Github%20CI/badge.svg)](https://github.com/Neovici/cosmoz-treenode-navigator/actions?workflow=Github+CI)
4
4
  [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
5
5
 
6
- cosmoz-treenode-navigator is a PionJS-based component that lets you navigate and search through hierarchically structured data-nodes and select one of them.
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
- It needs an object structure which looks, for example, like this in JSON:
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 "name" and "children" are configurable in the component through the setting of "childProperty" and "comparisonProperty".
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
- ### Backwards compatibility added
288
+ [Apache-2.0](LICENSE)
@@ -1,2 +1,4 @@
1
+ import '@neovici/cosmoz-button/cosmoz-button';
2
+ import '@neovici/cosmoz-tooltip';
1
3
  import './cosmoz-treenode-navigator';
2
4
  //# sourceMappingURL=cosmoz-treenode-button-view.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cosmoz-treenode-button-view.d.ts","sourceRoot":"","sources":["../src/cosmoz-treenode-button-view.ts"],"names":[],"mappings":"AAcA,OAAO,6BAA6B,CAAC"}
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 CosmozNodeButtonView = ({ tree, multiSelection = false, dialogText, buttonTextPlaceholder, searchPlaceholder, noReset = false, searchGlobalPlaceholder, searchMinLength = 3, searchDebounceTimeout = 2000, }) => {
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
- const searchInputRef = useRef(null);
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
- const [nodesOnNodePath, setNodesOnNodePath] = useProperty('nodesOnNodePath', []);
19
- const [opened, setOpened] = useState(false);
20
- const [selectedNodes, setSelectedNodes] = useState([]);
21
- useEffect(() => {
22
- if (dialogRef.current) {
23
- searchInputRef.current = dialogRef.current
24
- .querySelector('cosmoz-treenode-navigator')
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 buttonTextPlaceholder || '';
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 clearItemSelection = ({ item, ev }) => {
63
- setSelectedNodes(selectedNodes.filter((node) => node !== item));
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
- // `cosmoz-treenode-navigator` handles updating nodesOnNodePath
115
- const selectNode = () => {
116
- if (!highlightedNode?.pathLocator) {
117
- return;
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
- if (multiSelection &&
120
- !selectedNodes.some((n) => n.pathLocator === highlightedNode.pathLocator)) {
121
- setSelectedNodes([...selectedNodes, highlightedNode]);
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
- <div class="actions" part="actions">
131
- <button
132
- class="action-open"
133
- type="button"
134
- @click=${onOpen}
135
- part="action-open"
136
- >
137
- <slot name="button-before"></slot>
138
- <div class="path-text">
139
- <span>${buttonLabel}</span>
140
- </div>
141
- <slot name="button-after"></slot>
142
- </button>
143
- ${when(!noReset && !!selectedNode, () => html ` <button
144
- @click=${reset}
145
- class="action-reset"
146
- part="action-reset"
147
- >
148
- <svg
149
- width="10"
150
- height="9"
151
- viewBox="0 0 10 9"
152
- stroke="currentColor"
153
- xmlns="http://www.w3.org/2000/svg"
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
- <line
156
- x1="8.53033"
157
- y1="0.53033"
158
- x2="1.53033"
159
- y2="7.53033"
160
- stroke-width="1.5"
161
- ></line>
162
- <line
163
- x1="8.46967"
164
- y1="7.53033"
165
- x2="1.46967"
166
- y2="0.530331"
167
- stroke-width="1.5"
168
- ></line>
169
- </svg>
170
- </button>`)}
171
- </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
- &times;
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">${dialogText}</h1>
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
- .highlightedNode=${highlightedNode}
208
- @highlighted-node-changed=${lift(setHighlightedNode)}
209
- .searchPlaceholder=${searchPlaceholder}
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
- <p class="dialog-footer-button-container">
224
- <button
225
- ?disabled=${!highlightedNode?.pathLocator}
226
- autofocus
227
- class="dialog-footer-button"
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
- class="dialog-footer-button"
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
- </p>
207
+ ${t('Cancel')}
208
+ </cosmoz-button>
209
+ </div>
241
210
  </footer>
242
211
  </dialog>
243
212
  `;
244
213
  };
245
214
  CosmozNodeButtonView.observedAttributes = [
246
- 'button-text-placeholder',
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, {