@neovici/cosmoz-treenode-navigator 5.0.1 → 5.3.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/.github/workflows/workflow.yml +6 -11
- package/CHANGELOG.md +35 -0
- package/cosmoz-treenode-button-view.js +41 -20
- package/cosmoz-treenode-navigator.js +188 -208
- package/cosmoz-treenode-navigator.styles.js +119 -0
- package/helpers.js +44 -172
- package/package.json +3 -3
- package/test/cosmoz-treenode-navigator.test.js +33 -31
|
@@ -12,22 +12,17 @@ jobs:
|
|
|
12
12
|
runs-on: ubuntu-latest
|
|
13
13
|
steps:
|
|
14
14
|
- name: Checkout
|
|
15
|
-
uses: actions/checkout@
|
|
16
|
-
|
|
17
|
-
- name: Use Node.js 14.x
|
|
18
|
-
uses: actions/setup-node@v1
|
|
15
|
+
uses: actions/checkout@v2
|
|
19
16
|
with:
|
|
20
|
-
|
|
17
|
+
fetch-depth: 0
|
|
21
18
|
|
|
22
|
-
- name:
|
|
23
|
-
|
|
24
|
-
uses: actions/cache@v2
|
|
19
|
+
- name: Use Node.js
|
|
20
|
+
uses: actions/setup-node@v2
|
|
25
21
|
with:
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
node-version: 16.x
|
|
23
|
+
cache: 'npm'
|
|
28
24
|
|
|
29
25
|
- name: Install
|
|
30
|
-
if: steps.node-cache.outputs.cache-hit != 'true'
|
|
31
26
|
run: npm ci
|
|
32
27
|
|
|
33
28
|
- name: Lint commit messages
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,38 @@
|
|
|
1
|
+
# [5.3.0](https://github.com/neovici/cosmoz-treenode-navigator/compare/v5.2.0...v5.3.0) (2022-08-01)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* close on enter ([e7cd4d1](https://github.com/neovici/cosmoz-treenode-navigator/commit/e7cd4d191abdf1b9486a5d097154f530130d4a96))
|
|
7
|
+
* **cosmoz-treenode-button-view:** pass opened to treenode ([4f43ade](https://github.com/neovici/cosmoz-treenode-navigator/commit/4f43ade0844fe5168cf84f6f7833dc61a608e176))
|
|
8
|
+
* **renderLevel:** handle leaf nodes ([760f6bf](https://github.com/neovici/cosmoz-treenode-navigator/commit/760f6bf4888ccbec0184ca72ea21ab4781e66f46))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **treenode-navigator:** autoscroll to highlighed node ([5c55a38](https://github.com/neovici/cosmoz-treenode-navigator/commit/5c55a38ad406b3d711405b8173c388fb26d5eb6c))
|
|
14
|
+
|
|
15
|
+
# [5.2.0](https://github.com/neovici/cosmoz-treenode-navigator/compare/v5.1.0...v5.2.0) (2022-08-01)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* **cosmoz-treenode-navigator:** improve performance ([ac12d74](https://github.com/neovici/cosmoz-treenode-navigator/commit/ac12d74345501e34cdccde6962563a24d6efed4f))
|
|
21
|
+
|
|
22
|
+
# [5.1.0](https://github.com/neovici/cosmoz-treenode-navigator/compare/v5.0.1...v5.1.0) (2022-07-28)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Bug Fixes
|
|
26
|
+
|
|
27
|
+
* **ci:** worflow typo ([17b92aa](https://github.com/neovici/cosmoz-treenode-navigator/commit/17b92aa332f0411b6548729002fd9cf62a99d223))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Features
|
|
31
|
+
|
|
32
|
+
* **cosmoz-treenode-navigator:** add keyboard navigation ([26b6d42](https://github.com/neovici/cosmoz-treenode-navigator/commit/26b6d42d99737d0b48a7fbc21a8677117b1d8a9e))
|
|
33
|
+
* refactor with lit-virtualizer instead of listbox ([7b69ab4](https://github.com/neovici/cosmoz-treenode-navigator/commit/7b69ab4e0e049c9c7f8e03c3b781f02fab83eb18))
|
|
34
|
+
* support `Right` key ([28a4df6](https://github.com/neovici/cosmoz-treenode-navigator/commit/28a4df6d35bc62ac49aba81135891ce8fcacfa05))
|
|
35
|
+
|
|
1
36
|
## [5.0.1](https://github.com/neovici/cosmoz-treenode-navigator/compare/v5.0.0...v5.0.1) (2022-07-27)
|
|
2
37
|
|
|
3
38
|
|
|
@@ -127,7 +127,8 @@ class CosmozTreenodeButtonView extends translatable(PolymerElement) {
|
|
|
127
127
|
<cosmoz-dialog
|
|
128
128
|
id="dialogTree"
|
|
129
129
|
class="treeDialog"
|
|
130
|
-
on-iron-overlay-opened="
|
|
130
|
+
on-iron-overlay-opened="onOpened"
|
|
131
|
+
on-iron-overlay-closed="onClosed"
|
|
131
132
|
modal
|
|
132
133
|
prerender
|
|
133
134
|
>
|
|
@@ -147,6 +148,7 @@ class CosmozTreenodeButtonView extends translatable(PolymerElement) {
|
|
|
147
148
|
nodes-on-node-path="{{ nodesOnNodePath }}"
|
|
148
149
|
on-node-dblclicked="_selectNodeAndCloseDialog"
|
|
149
150
|
on-select-node="selectNode"
|
|
151
|
+
opened="[[ opened ]]"
|
|
150
152
|
>
|
|
151
153
|
<slot></slot>
|
|
152
154
|
</cosmoz-treenode-navigator>
|
|
@@ -174,13 +176,13 @@ class CosmozTreenodeButtonView extends translatable(PolymerElement) {
|
|
|
174
176
|
return {
|
|
175
177
|
multiSelection: {
|
|
176
178
|
type: Boolean,
|
|
177
|
-
value: false
|
|
179
|
+
value: false,
|
|
178
180
|
},
|
|
179
181
|
/*
|
|
180
182
|
* The main node structure
|
|
181
183
|
*/
|
|
182
184
|
tree: {
|
|
183
|
-
type: Tree
|
|
185
|
+
type: Tree,
|
|
184
186
|
},
|
|
185
187
|
/*
|
|
186
188
|
* Currently selected node object
|
|
@@ -190,7 +192,7 @@ class CosmozTreenodeButtonView extends translatable(PolymerElement) {
|
|
|
190
192
|
value() {
|
|
191
193
|
return {};
|
|
192
194
|
},
|
|
193
|
-
notify: true
|
|
195
|
+
notify: true,
|
|
194
196
|
},
|
|
195
197
|
/**
|
|
196
198
|
* Selected nodes
|
|
@@ -198,32 +200,40 @@ class CosmozTreenodeButtonView extends translatable(PolymerElement) {
|
|
|
198
200
|
selectedNodes: {
|
|
199
201
|
type: Array,
|
|
200
202
|
notify: true,
|
|
201
|
-
value: () => []
|
|
203
|
+
value: () => [],
|
|
202
204
|
},
|
|
203
205
|
/**
|
|
204
206
|
* If true, reset button gets hidden
|
|
205
207
|
*/
|
|
206
208
|
noReset: {
|
|
207
209
|
type: Boolean,
|
|
208
|
-
value: false
|
|
210
|
+
value: false,
|
|
211
|
+
},
|
|
212
|
+
/**
|
|
213
|
+
* If true opened
|
|
214
|
+
*/
|
|
215
|
+
opened: {
|
|
216
|
+
type: Boolean,
|
|
217
|
+
value: false,
|
|
209
218
|
},
|
|
219
|
+
|
|
210
220
|
/*
|
|
211
221
|
* Placeholder for the search field
|
|
212
222
|
*/
|
|
213
223
|
searchPlaceholder: {
|
|
214
|
-
type: String
|
|
224
|
+
type: String,
|
|
215
225
|
},
|
|
216
226
|
/*
|
|
217
227
|
* Placeholder for button text
|
|
218
228
|
*/
|
|
219
229
|
buttonTextPlaceholder: {
|
|
220
230
|
type: String,
|
|
221
|
-
computed: 'getButtonTextPlaceholder(multiSelection)'
|
|
231
|
+
computed: 'getButtonTextPlaceholder(multiSelection)',
|
|
222
232
|
},
|
|
223
233
|
|
|
224
234
|
buttonText: {
|
|
225
235
|
type: String,
|
|
226
|
-
computed: '_getButtonLabel(nodesOnNodePath, buttonTextPlaceholder)'
|
|
236
|
+
computed: '_getButtonLabel(nodesOnNodePath, buttonTextPlaceholder)',
|
|
227
237
|
},
|
|
228
238
|
|
|
229
239
|
/*
|
|
@@ -231,13 +241,13 @@ class CosmozTreenodeButtonView extends translatable(PolymerElement) {
|
|
|
231
241
|
*/
|
|
232
242
|
nodePath: {
|
|
233
243
|
type: String,
|
|
234
|
-
notify: true
|
|
244
|
+
notify: true,
|
|
235
245
|
},
|
|
236
246
|
/*
|
|
237
247
|
* The nodes on the path of the selected node
|
|
238
248
|
*/
|
|
239
249
|
nodesOnNodePath: {
|
|
240
|
-
type: Array
|
|
250
|
+
type: Array,
|
|
241
251
|
},
|
|
242
252
|
/*
|
|
243
253
|
* Text displayed when local search has finished
|
|
@@ -245,14 +255,14 @@ class CosmozTreenodeButtonView extends translatable(PolymerElement) {
|
|
|
245
255
|
*/
|
|
246
256
|
searchGlobalPlaceholder: {
|
|
247
257
|
type: String,
|
|
248
|
-
value: 'Click to search again but globally.'
|
|
258
|
+
value: 'Click to search again but globally.',
|
|
249
259
|
},
|
|
250
260
|
/*
|
|
251
261
|
* Settable text for dialog title.
|
|
252
262
|
*/
|
|
253
263
|
dialogText: {
|
|
254
264
|
type: String,
|
|
255
|
-
value: 'Search or navigate to chosen destination'
|
|
265
|
+
value: 'Search or navigate to chosen destination',
|
|
256
266
|
},
|
|
257
267
|
/*
|
|
258
268
|
* Minimum length before an search
|
|
@@ -260,14 +270,14 @@ class CosmozTreenodeButtonView extends translatable(PolymerElement) {
|
|
|
260
270
|
*/
|
|
261
271
|
searchMinLength: {
|
|
262
272
|
type: Number,
|
|
263
|
-
value: 1
|
|
273
|
+
value: 1,
|
|
264
274
|
},
|
|
265
275
|
/*
|
|
266
276
|
* Path string of highlighted (focused) node
|
|
267
277
|
*/
|
|
268
278
|
highlightedNodePath: {
|
|
269
|
-
type: String
|
|
270
|
-
}
|
|
279
|
+
type: String,
|
|
280
|
+
},
|
|
271
281
|
};
|
|
272
282
|
}
|
|
273
283
|
/**
|
|
@@ -324,8 +334,8 @@ class CosmozTreenodeButtonView extends translatable(PolymerElement) {
|
|
|
324
334
|
return placeholder;
|
|
325
335
|
}
|
|
326
336
|
return pathParts
|
|
327
|
-
.filter(n => n)
|
|
328
|
-
.map(part => part[this.tree.searchProperty])
|
|
337
|
+
.filter((n) => n)
|
|
338
|
+
.map((part) => part[this.tree.searchProperty])
|
|
329
339
|
.join(' / ');
|
|
330
340
|
}
|
|
331
341
|
/**
|
|
@@ -348,7 +358,10 @@ class CosmozTreenodeButtonView extends translatable(PolymerElement) {
|
|
|
348
358
|
* @returns {void}
|
|
349
359
|
*/
|
|
350
360
|
focusSearch() {
|
|
351
|
-
this.$.dialogTree.paperDialog
|
|
361
|
+
this.$.dialogTree.paperDialog
|
|
362
|
+
.querySelector('#treeNavigator')
|
|
363
|
+
.shadowRoot.querySelector('cosmoz-input')
|
|
364
|
+
.focus();
|
|
352
365
|
}
|
|
353
366
|
/**
|
|
354
367
|
* Reset the component to make it ready for reuse
|
|
@@ -383,7 +396,7 @@ class CosmozTreenodeButtonView extends translatable(PolymerElement) {
|
|
|
383
396
|
if (this.multiSelection) {
|
|
384
397
|
if (
|
|
385
398
|
!this.selectedNodes.some(
|
|
386
|
-
node => node.pathLocator === this.highlightedNodePath
|
|
399
|
+
(node) => node.pathLocator === this.highlightedNodePath
|
|
387
400
|
)
|
|
388
401
|
) {
|
|
389
402
|
this.push('selectedNodes', this.selectedNode);
|
|
@@ -423,5 +436,13 @@ class CosmozTreenodeButtonView extends translatable(PolymerElement) {
|
|
|
423
436
|
}
|
|
424
437
|
);
|
|
425
438
|
}
|
|
439
|
+
|
|
440
|
+
onOpened() {
|
|
441
|
+
this.opened = true;
|
|
442
|
+
this.focusSearch();
|
|
443
|
+
}
|
|
444
|
+
onClosed() {
|
|
445
|
+
this.opened = false;
|
|
446
|
+
}
|
|
426
447
|
}
|
|
427
448
|
customElements.define(CosmozTreenodeButtonView.is, CosmozTreenodeButtonView);
|
|
@@ -1,25 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
component,
|
|
3
|
+
useEffect,
|
|
4
|
+
useState,
|
|
5
|
+
useMemo,
|
|
6
|
+
useCallback,
|
|
7
|
+
useRef,
|
|
8
|
+
} from 'haunted';
|
|
2
9
|
import { html } from 'lit-html';
|
|
3
10
|
import { when } from 'lit-html/directives/when.js';
|
|
11
|
+
import { guard } from 'lit-html/directives/guard.js';
|
|
12
|
+
import { ref } from 'lit-html/directives/ref.js';
|
|
13
|
+
import { virtualize } from '@lit-labs/virtualizer/virtualize.js';
|
|
4
14
|
|
|
5
15
|
import '@neovici/cosmoz-input';
|
|
6
|
-
import '@neovici/cosmoz-
|
|
16
|
+
import { useMeta } from '@neovici/cosmoz-utils/hooks/use-meta';
|
|
7
17
|
import { notifyProperty } from '@neovici/cosmoz-utils/hooks/use-notify-property';
|
|
8
18
|
|
|
9
19
|
import {
|
|
20
|
+
getParentPath,
|
|
10
21
|
computeDataPlane,
|
|
11
22
|
computeRowClass,
|
|
12
|
-
computeSearching,
|
|
13
|
-
getNodeName,
|
|
14
|
-
hasChildren,
|
|
15
|
-
nodeStyles,
|
|
16
|
-
normalizeNodes,
|
|
17
23
|
onNodeDblClicked,
|
|
18
|
-
renderLevel,
|
|
19
|
-
showGlobalSearchBtn
|
|
20
24
|
} from './helpers';
|
|
25
|
+
import styles from './cosmoz-treenode-navigator.styles';
|
|
21
26
|
|
|
22
|
-
const TreenodeNavigator = host => {
|
|
27
|
+
const TreenodeNavigator = (host) => {
|
|
23
28
|
const {
|
|
24
29
|
/**
|
|
25
30
|
* The main node structure
|
|
@@ -38,16 +43,13 @@ const TreenodeNavigator = host => {
|
|
|
38
43
|
* Minimum length of searchValue to trigger a search
|
|
39
44
|
*/
|
|
40
45
|
searchMinLength,
|
|
41
|
-
nodePath
|
|
46
|
+
nodePath,
|
|
47
|
+
opened,
|
|
42
48
|
} = host,
|
|
43
49
|
/**
|
|
44
50
|
* The path of the opened node
|
|
45
51
|
*/
|
|
46
52
|
[openNodePath, setOpenNodePath] = useState(''),
|
|
47
|
-
/**
|
|
48
|
-
* The nodes on the path of the opened node
|
|
49
|
-
*/
|
|
50
|
-
[nodesOnOpenNodePath, setNodesOnOpenNodePath] = useState([]),
|
|
51
53
|
/**
|
|
52
54
|
* The highlighted (focused) node
|
|
53
55
|
* This is the node which is currently selected in the list
|
|
@@ -57,90 +59,42 @@ const TreenodeNavigator = host => {
|
|
|
57
59
|
* The search string
|
|
58
60
|
*/
|
|
59
61
|
[searchValue, setSearchValue] = useState(''),
|
|
62
|
+
search = useMemo(
|
|
63
|
+
() => (searchValue?.length > searchMinLength && searchValue) || undefined,
|
|
64
|
+
[searchValue, searchMinLength]
|
|
65
|
+
),
|
|
60
66
|
/**
|
|
61
67
|
* The currently displayed node list
|
|
62
68
|
*/
|
|
63
|
-
dataPlane = useMemo(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
renderLevel(openNodePath, tree),
|
|
68
|
-
tree
|
|
69
|
-
);
|
|
70
|
-
}, [tree, openNodePath, highlightedNode, searchValue]),
|
|
69
|
+
dataPlane = useMemo(
|
|
70
|
+
() => computeDataPlane(tree, search, openNodePath),
|
|
71
|
+
[tree, search, openNodePath]
|
|
72
|
+
),
|
|
71
73
|
/**
|
|
72
74
|
* Opens a node (renderLevel) based on a given path
|
|
73
75
|
* @param {object} clickedNode - The clicked node
|
|
74
76
|
* @return {undefined}
|
|
75
77
|
*/
|
|
76
|
-
openNode = useCallback(
|
|
77
|
-
|
|
78
|
-
setOpenNodePath(clickedNode
|
|
78
|
+
openNode = useCallback(
|
|
79
|
+
(clickedNode) => {
|
|
80
|
+
setOpenNodePath(clickedNode?.pathLocator || '');
|
|
79
81
|
setSearchValue('');
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
}, []),
|
|
87
|
-
/**
|
|
88
|
-
* Returns true, if the path of a node should be visible in the view
|
|
89
|
-
* @param {Number} index - The node's current index in the list
|
|
90
|
-
* @param {Object} parentSectionName - The nodes current parent section name
|
|
91
|
-
* @return {Boolean} - If the path should be visible
|
|
92
|
-
*/
|
|
93
|
-
renderSection = (index, parentSectionName) => {
|
|
94
|
-
const _dataPlane = computeDataPlane(
|
|
95
|
-
computeSearching(searchValue, searchMinLength),
|
|
96
|
-
searchValue,
|
|
97
|
-
renderLevel(openNodePath, tree),
|
|
98
|
-
tree
|
|
99
|
-
);
|
|
100
|
-
if (
|
|
101
|
-
!computeSearching(searchValue, searchMinLength) ||
|
|
102
|
-
index == null ||
|
|
103
|
-
_dataPlane == null ||
|
|
104
|
-
index >= _dataPlane.length ||
|
|
105
|
-
parentSectionName == null
|
|
106
|
-
) {
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
if (index === 0) {
|
|
110
|
-
return true;
|
|
111
|
-
}
|
|
112
|
-
const prevItem = _dataPlane[index - 1];
|
|
113
|
-
return prevItem.parentSectionName !== parentSectionName;
|
|
114
|
-
};
|
|
82
|
+
setHighlightedNode();
|
|
83
|
+
},
|
|
84
|
+
[tree]
|
|
85
|
+
),
|
|
86
|
+
listRef = useRef();
|
|
115
87
|
|
|
116
88
|
useEffect(() => {
|
|
117
89
|
if (!openNodePath) {
|
|
118
90
|
notifyProperty(host, 'highlightedNodePath', '');
|
|
119
91
|
notifyProperty(host, 'nodesOnNodePath', []);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
setNodesOnOpenNodePath(
|
|
123
|
-
normalizeNodes(tree.getPathNodes(openNodePath).filter(item => item))
|
|
124
|
-
);
|
|
125
|
-
}, [openNodePath]);
|
|
126
|
-
|
|
127
|
-
useEffect(() => {
|
|
128
|
-
if (!openNodePath) {
|
|
129
|
-
notifyProperty(host, 'highlightedNodePath', '');
|
|
130
|
-
return;
|
|
131
92
|
}
|
|
132
|
-
setNodesOnOpenNodePath(
|
|
133
|
-
normalizeNodes(tree.getPathNodes(openNodePath).filter(item => item))
|
|
134
|
-
);
|
|
135
93
|
}, [openNodePath]);
|
|
136
94
|
|
|
137
95
|
useEffect(() => {
|
|
138
96
|
if (nodePath !== undefined) {
|
|
139
|
-
host.dispatchEvent(
|
|
140
|
-
new CustomEvent('select-node', {
|
|
141
|
-
detail: {}
|
|
142
|
-
})
|
|
143
|
-
);
|
|
97
|
+
host.dispatchEvent(new CustomEvent('select-node'));
|
|
144
98
|
}
|
|
145
99
|
}, [nodePath]);
|
|
146
100
|
|
|
@@ -148,77 +102,143 @@ const TreenodeNavigator = host => {
|
|
|
148
102
|
notifyProperty(
|
|
149
103
|
host,
|
|
150
104
|
'highlightedNodePath',
|
|
151
|
-
|
|
105
|
+
highlightedNode?.pathLocator || ''
|
|
152
106
|
);
|
|
153
107
|
}, [highlightedNode]);
|
|
154
108
|
|
|
155
|
-
|
|
156
|
-
<style>
|
|
157
|
-
:host {
|
|
158
|
-
--cosmoz-treenode-navigator-select-node-icon-color: var(
|
|
159
|
-
--primary-color,
|
|
160
|
-
white
|
|
161
|
-
);
|
|
162
|
-
--cosmoz-treenode-navigator-list-item-focused-color: #f0f8ff;
|
|
163
|
-
}
|
|
109
|
+
const meta = useMeta({ dataPlane, highlightedNode, openNode });
|
|
164
110
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
if (!opened) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
168
115
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
116
|
+
const getVlist = () => {
|
|
117
|
+
const list = listRef.current,
|
|
118
|
+
vlist = list[Object.getOwnPropertySymbols(list)[0]];
|
|
119
|
+
return vlist;
|
|
120
|
+
},
|
|
121
|
+
// eslint-disable-next-line max-lines-per-function, max-statements
|
|
122
|
+
handler = (e) => {
|
|
123
|
+
if ((e.ctrlKey && e.altKey) || e.defaultPrevented) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const {
|
|
127
|
+
dataPlane: items,
|
|
128
|
+
highlightedNode: node,
|
|
129
|
+
openNode: open,
|
|
130
|
+
} = meta,
|
|
131
|
+
vlist = getVlist(),
|
|
132
|
+
getIndex = () =>
|
|
133
|
+
items.findIndex((i) => i.pathLocator === node?.pathLocator);
|
|
173
134
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
135
|
+
switch (e.key) {
|
|
136
|
+
case 'Up':
|
|
137
|
+
case 'ArrowUp':
|
|
138
|
+
{
|
|
139
|
+
const idx = getIndex(),
|
|
140
|
+
nidx = Math.max(idx - 1, 0);
|
|
141
|
+
e.preventDefault();
|
|
142
|
+
setHighlightedNode(items[nidx]);
|
|
143
|
+
if (nidx < vlist._firstVisible) {
|
|
144
|
+
vlist.scrollToIndex = { index: nidx, position: 'start' };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
break;
|
|
148
|
+
case 'Down':
|
|
149
|
+
case 'ArrowDown':
|
|
150
|
+
{
|
|
151
|
+
const idx = getIndex();
|
|
152
|
+
if (idx < items.length - 1) {
|
|
153
|
+
const nidx = idx + 1;
|
|
154
|
+
e.preventDefault();
|
|
155
|
+
setHighlightedNode(items[nidx]);
|
|
156
|
+
if (nidx > vlist._lastVisible) {
|
|
157
|
+
vlist.scrollToIndex = { index: nidx, position: 'end' };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
break;
|
|
187
162
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
163
|
+
case 'Enter':
|
|
164
|
+
if (node) {
|
|
165
|
+
e.preventDefault();
|
|
166
|
+
host.dispatchEvent(new CustomEvent('node-dblclicked'));
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
193
169
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
170
|
+
case 'Right':
|
|
171
|
+
case 'ArrowRight':
|
|
172
|
+
if (node && tree.hasChildren(node)) {
|
|
173
|
+
e.preventDefault();
|
|
174
|
+
open(node);
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
default:
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
197
181
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
182
|
+
// autoscroll on open
|
|
183
|
+
(() => {
|
|
184
|
+
getVlist().scrollToIndex = {
|
|
185
|
+
index: meta.dataPlane?.indexOf(meta.highlightedNode),
|
|
186
|
+
position: 'center',
|
|
187
|
+
};
|
|
188
|
+
})();
|
|
201
189
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
height: var(--cosmoz-treenode-navigator-list-height, 50vh);
|
|
206
|
-
position: static;
|
|
207
|
-
width: 100%;
|
|
208
|
-
}
|
|
190
|
+
document.addEventListener('keydown', handler, true);
|
|
191
|
+
return () => document.removeEventListener('keydown', handler, true);
|
|
192
|
+
}, [opened, meta]);
|
|
209
193
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
194
|
+
const renderItem = (node, index) => html` <div class="item">
|
|
195
|
+
${((parentPath) =>
|
|
196
|
+
when(
|
|
197
|
+
search &&
|
|
198
|
+
(index === 0 ||
|
|
199
|
+
parentPath !== getParentPath(tree, dataPlane[index - 1])),
|
|
200
|
+
() =>
|
|
201
|
+
html`
|
|
202
|
+
<div class="section">
|
|
203
|
+
${tree.getPathString(parentPath, tree.searchProperty)}
|
|
204
|
+
</div>
|
|
205
|
+
`
|
|
206
|
+
))(getParentPath(tree, node))}
|
|
207
|
+
<div
|
|
208
|
+
class=${computeRowClass('node', node, highlightedNode)}
|
|
209
|
+
@click=${() => setHighlightedNode(node)}
|
|
210
|
+
@dblclick=${(e) => onNodeDblClicked(e, host)}
|
|
211
|
+
>
|
|
212
|
+
<div class="name">${node[tree.searchProperty]}</div>
|
|
213
|
+
${when(
|
|
214
|
+
tree.hasChildren(node),
|
|
215
|
+
() => html`
|
|
216
|
+
<span class="icon" @click=${() => openNode(node)}>
|
|
217
|
+
<svg
|
|
218
|
+
viewBox="0 0 24 24"
|
|
219
|
+
preserveAspectRatio="xMidYMid meet"
|
|
220
|
+
focusable="false"
|
|
221
|
+
style="pointer-events: none; display: block; width: 100%; height: 100%;"
|
|
222
|
+
>
|
|
223
|
+
<g>
|
|
224
|
+
<path
|
|
225
|
+
d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z"
|
|
226
|
+
></path>
|
|
227
|
+
</g>
|
|
228
|
+
</svg>
|
|
229
|
+
</span>
|
|
230
|
+
`
|
|
231
|
+
)}
|
|
232
|
+
</div>
|
|
233
|
+
</div>`;
|
|
234
|
+
|
|
235
|
+
return html`
|
|
236
|
+
<style>
|
|
237
|
+
${styles}
|
|
218
238
|
</style>
|
|
219
|
-
<div
|
|
239
|
+
<div class="header">
|
|
220
240
|
<h3 class="path">
|
|
221
|
-
<span class="icon" @click=${
|
|
241
|
+
<span class="icon" @click=${() => openNode()}>
|
|
222
242
|
<svg
|
|
223
243
|
viewBox="0 0 24 24"
|
|
224
244
|
preserveAspectRatio="xMidYMid meet"
|
|
@@ -228,83 +248,43 @@ const TreenodeNavigator = host => {
|
|
|
228
248
|
<g><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"></path></g>
|
|
229
249
|
</svg>
|
|
230
250
|
</span>
|
|
231
|
-
${
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
251
|
+
${guard([tree, openNodePath], () =>
|
|
252
|
+
((openNodePath && tree.getPathNodes(openNodePath)) || []).map(
|
|
253
|
+
(node) => html`
|
|
254
|
+
<span class="slash">/</span>
|
|
255
|
+
<span class="pointer" tabindex="0" @click=${() => openNode(node)}
|
|
256
|
+
>${node[tree.searchProperty]}</span
|
|
257
|
+
>
|
|
258
|
+
`
|
|
259
|
+
)
|
|
260
|
+
)}
|
|
237
261
|
</h3>
|
|
238
262
|
<cosmoz-input
|
|
239
263
|
tabindex="0"
|
|
240
|
-
.value=${
|
|
241
|
-
.placeholder
|
|
242
|
-
@input
|
|
264
|
+
.value=${searchValue}
|
|
265
|
+
.placeholder=${searchPlaceholder}
|
|
266
|
+
@input=${(e) => setSearchValue(e.target.value)}
|
|
243
267
|
/>
|
|
244
268
|
</div>
|
|
245
|
-
${
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
`
|
|
262
|
-
) }
|
|
263
|
-
<div
|
|
264
|
-
class="${ computeRowClass(
|
|
265
|
-
'node-item pointer',
|
|
266
|
-
node,
|
|
267
|
-
highlightedNode
|
|
268
|
-
) }"
|
|
269
|
-
@click="${ () => setHighlightedNode(node) }"
|
|
270
|
-
@dblclick="${ e => onNodeDblClicked(e, host) }"
|
|
271
|
-
>
|
|
272
|
-
<div style="flex: auto">${ node.name }</div>
|
|
273
|
-
${ when(
|
|
274
|
-
hasChildren(node),
|
|
275
|
-
() => html`
|
|
276
|
-
<span class="icon" @click="${ () => openNode(node) }">
|
|
277
|
-
<svg
|
|
278
|
-
viewBox="0 0 24 24"
|
|
279
|
-
preserveAspectRatio="xMidYMid meet"
|
|
280
|
-
focusable="false"
|
|
281
|
-
style="pointer-events: none; display: block; width: 100%; height: 100%;"
|
|
282
|
-
>
|
|
283
|
-
<g>
|
|
284
|
-
<path
|
|
285
|
-
d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z"
|
|
286
|
-
></path>
|
|
287
|
-
</g>
|
|
288
|
-
</svg>
|
|
289
|
-
</span>
|
|
290
|
-
`
|
|
291
|
-
) }
|
|
292
|
-
</div>
|
|
293
|
-
</div>
|
|
294
|
-
` }
|
|
295
|
-
</cosmoz-listbox>`
|
|
296
|
-
) }
|
|
297
|
-
${ when(
|
|
298
|
-
showGlobalSearchBtn(
|
|
299
|
-
computeSearching(searchValue, searchMinLength),
|
|
300
|
-
openNodePath
|
|
301
|
-
),
|
|
302
|
-
() => html`
|
|
303
|
-
<button class="btn-ghost" @click="${ () => setOpenNodePath('') }">
|
|
304
|
-
${ searchGlobalPlaceholder }
|
|
269
|
+
${when(
|
|
270
|
+
tree,
|
|
271
|
+
() => html` <div class="items" ${ref((el) => (listRef.current = el))}>
|
|
272
|
+
<div virtualizer-sizer></div>
|
|
273
|
+
${virtualize({
|
|
274
|
+
items: dataPlane,
|
|
275
|
+
renderItem,
|
|
276
|
+
scroller: true,
|
|
277
|
+
})}
|
|
278
|
+
</div>`
|
|
279
|
+
)}
|
|
280
|
+
${when(
|
|
281
|
+
search && openNodePath,
|
|
282
|
+
() => html`
|
|
283
|
+
<button class="btn-ghost" @click=${() => setOpenNodePath('')}>
|
|
284
|
+
${searchGlobalPlaceholder}
|
|
305
285
|
</button>
|
|
306
286
|
`
|
|
307
|
-
|
|
287
|
+
)}
|
|
308
288
|
`;
|
|
309
289
|
};
|
|
310
290
|
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { tagged as css } from '@neovici/cosmoz-utils';
|
|
2
|
+
|
|
3
|
+
export default css`
|
|
4
|
+
:host {
|
|
5
|
+
--cosmoz-treenode-navigator-select-node-icon-color: var(
|
|
6
|
+
--primary-color,
|
|
7
|
+
white
|
|
8
|
+
);
|
|
9
|
+
--cosmoz-treenode-navigator-list-item-focused-color: #f0f8ff;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.header {
|
|
13
|
+
margin: 0 16px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.header a {
|
|
17
|
+
text-decoration: none;
|
|
18
|
+
color: inherit;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.icon {
|
|
22
|
+
display: inline-block;
|
|
23
|
+
position: relative;
|
|
24
|
+
padding: 8px;
|
|
25
|
+
outline: none;
|
|
26
|
+
user-select: none;
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
z-index: 0;
|
|
29
|
+
line-height: 1;
|
|
30
|
+
width: 40px;
|
|
31
|
+
height: 40px;
|
|
32
|
+
box-sizing: border-box;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.path {
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
flex-wrap: wrap;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.slash {
|
|
42
|
+
margin: 0 2px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.pointer {
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.items {
|
|
49
|
+
height: var(--cosmoz-treenode-navigator-list-height, 50vh);
|
|
50
|
+
width: 100%;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.section {
|
|
54
|
+
background-color: #f5f5f5;
|
|
55
|
+
padding: 5px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.pointer {
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.item {
|
|
63
|
+
width: 100%;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.node {
|
|
67
|
+
align-items: center;
|
|
68
|
+
display: flex;
|
|
69
|
+
font-family: 'Roboto', 'Noto', sans-serif;
|
|
70
|
+
font-size: 16px;
|
|
71
|
+
font-weight: 400;
|
|
72
|
+
height: 40px;
|
|
73
|
+
line-height: 24px;
|
|
74
|
+
padding: 6px 12px;
|
|
75
|
+
cursor: pointer;
|
|
76
|
+
}
|
|
77
|
+
.name {
|
|
78
|
+
flex: auto;
|
|
79
|
+
}
|
|
80
|
+
.icon {
|
|
81
|
+
box-sizing: border-box;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
display: inline-block;
|
|
84
|
+
height: 40px;
|
|
85
|
+
line-height: 1;
|
|
86
|
+
outline: none;
|
|
87
|
+
padding: 8px;
|
|
88
|
+
position: relative;
|
|
89
|
+
user-select: none;
|
|
90
|
+
width: 40px;
|
|
91
|
+
z-index: 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.node.selected {
|
|
95
|
+
background-color: var(
|
|
96
|
+
--cosmoz-listbox-active-color,
|
|
97
|
+
var(--cosmoz-selection-color, rgba(58, 145, 226, 0.1))
|
|
98
|
+
);
|
|
99
|
+
transition: background-color 0.2s ease-out;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.node.selected .icon svg {
|
|
103
|
+
fill: var(--cosmoz-treenode-navigator-select-node-icon-color);
|
|
104
|
+
transition: color 0.8s ease-out;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.btn-ghost {
|
|
108
|
+
background: transparent;
|
|
109
|
+
border: none;
|
|
110
|
+
cursor: pointer;
|
|
111
|
+
margin: 0 20px;
|
|
112
|
+
padding: 0.7em 0.57em;
|
|
113
|
+
width: calc(100% - 50px);
|
|
114
|
+
border: solid 1px #a4abae;
|
|
115
|
+
border-radius: 16px;
|
|
116
|
+
font-size: 14px;
|
|
117
|
+
font-family: inherit;
|
|
118
|
+
}
|
|
119
|
+
`;
|
package/helpers.js
CHANGED
|
@@ -1,93 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* @param {Object} node - The node
|
|
9
|
-
* @return {Boolean} - True if node has children
|
|
10
|
-
*/
|
|
11
|
-
hasChildren = node => {
|
|
12
|
-
return tree.hasChildren(node);
|
|
13
|
-
},
|
|
14
|
-
/**
|
|
15
|
-
* Returns true, if a search string is eligable to trigger a search
|
|
16
|
-
* @param {String} value - The search string
|
|
17
|
-
* @param {Number} searchMinLength - The minimum length of value to be valid
|
|
18
|
-
* @return {Boolean} - If a search should be triggered
|
|
19
|
-
*/
|
|
20
|
-
computeSearching = (value, searchMinLength) => {
|
|
21
|
-
return value && value.length >= searchMinLength && value !== '';
|
|
22
|
-
},
|
|
23
|
-
/**
|
|
24
|
-
* Normalizes and returns an Array of nodes
|
|
25
|
-
* with the properties name, path, parentSectionName, children
|
|
26
|
-
* @param {Array} nodes - The input nodes
|
|
27
|
-
* @return {Array} - The normalized nodes
|
|
28
|
-
*/
|
|
29
|
-
normalizeNodes = nodes => {
|
|
30
|
-
if (!Array.isArray(nodes)) {
|
|
31
|
-
return [];
|
|
32
|
-
}
|
|
33
|
-
return nodes.map(node => {
|
|
34
|
-
if (!node) {
|
|
35
|
-
return node;
|
|
36
|
-
}
|
|
37
|
-
const path = node.pathLocator || node.path,
|
|
38
|
-
pathLocatorSeparator = tree.pathLocatorSeparator,
|
|
39
|
-
parentPath = path.includes(pathLocatorSeparator) ? path.substring(0, path.lastIndexOf(pathLocatorSeparator)) : path;
|
|
40
|
-
return {
|
|
41
|
-
name: node[tree.searchProperty],
|
|
42
|
-
path,
|
|
43
|
-
children: node[tree.childProperty],
|
|
44
|
-
parentSectionName: tree.getPathString(parentPath, tree.searchProperty),
|
|
45
|
-
id: node.id,
|
|
46
|
-
pathLocator: node.pathLocator
|
|
47
|
-
};
|
|
48
|
-
});
|
|
49
|
-
},
|
|
50
|
-
/**
|
|
51
|
-
* Returns the found nodes based on a search string and a given tree to be searched
|
|
52
|
-
* @param {Boolean} searching - If true, a search should be executed
|
|
53
|
-
* @param {String} searchString - The search string
|
|
54
|
-
* @param {Array} renderedLevel - The node list on which the search should be executed
|
|
55
|
-
* @param {Tree} tree - The main tree
|
|
56
|
-
* @return {Array} - The found nodes
|
|
57
|
-
*/
|
|
58
|
-
computeDataPlane = (searching, searchString, renderedLevel, tree) => {
|
|
59
|
-
if (searching && tree) {
|
|
60
|
-
const results = tree.searchNodes(searchString, renderedLevel, false);
|
|
61
|
-
return normalizeNodes(results);
|
|
62
|
-
}
|
|
63
|
-
return renderedLevel;
|
|
1
|
+
export const //
|
|
2
|
+
getParentPath = (tree, node) => {
|
|
3
|
+
const path = node.pathLocator || node.path,
|
|
4
|
+
pathLocatorSeparator = tree.pathLocatorSeparator;
|
|
5
|
+
return path.includes(pathLocatorSeparator)
|
|
6
|
+
? path.substring(0, path.lastIndexOf(pathLocatorSeparator))
|
|
7
|
+
: path;
|
|
64
8
|
},
|
|
65
9
|
/**
|
|
66
10
|
* Returns a node array with the children of a node on the given path
|
|
67
11
|
* If the node doesn't have children, the node gets returned
|
|
68
|
-
* @param {String} pathLocator - The separated address parts of a node
|
|
69
12
|
* @param {Tree} tree - The main tree
|
|
13
|
+
* @param {String} pathLocator - The separated address parts of a node
|
|
70
14
|
* @return {Array} - Nodes
|
|
71
15
|
*/
|
|
72
|
-
renderLevel = (
|
|
16
|
+
renderLevel = (tree, pathLocator) => {
|
|
73
17
|
if (!tree) {
|
|
74
18
|
return;
|
|
75
19
|
}
|
|
76
20
|
const node = tree.getNodeByPathLocator(pathLocator),
|
|
77
21
|
children = tree.getChildren(node),
|
|
78
|
-
|
|
22
|
+
searchProperty = tree.searchProperty,
|
|
79
23
|
sortFunc = (a, b) => {
|
|
80
24
|
// First sort based on "folder" status (containing children)
|
|
81
|
-
if (hasChildren(a)) {
|
|
82
|
-
if (!hasChildren(b)) {
|
|
25
|
+
if (tree.hasChildren(a)) {
|
|
26
|
+
if (!tree.hasChildren(b)) {
|
|
83
27
|
return -1;
|
|
84
28
|
}
|
|
85
|
-
} else if (hasChildren(b)) {
|
|
29
|
+
} else if (tree.hasChildren(b)) {
|
|
86
30
|
return 1;
|
|
87
31
|
}
|
|
88
32
|
// Then sort on searchProperty
|
|
89
|
-
const val1 = a[
|
|
90
|
-
val2 = b[
|
|
33
|
+
const val1 = a[searchProperty],
|
|
34
|
+
val2 = b[searchProperty];
|
|
91
35
|
|
|
92
36
|
if (val1 > val2) {
|
|
93
37
|
return 1;
|
|
@@ -97,8 +41,21 @@ const tree = new DefaultTree(basicTree),
|
|
|
97
41
|
}
|
|
98
42
|
return 0;
|
|
99
43
|
};
|
|
100
|
-
return
|
|
101
|
-
|
|
44
|
+
return children.length > 0 ? children.sort(sortFunc) : node;
|
|
45
|
+
},
|
|
46
|
+
/**
|
|
47
|
+
* Returns the found nodes based on a search string and a given tree to be searched
|
|
48
|
+
* @param {Tree} tree - The main tree
|
|
49
|
+
* @param {String|undefined} search - The search string
|
|
50
|
+
* @param {String} pathLocator - The separated address parts of a node
|
|
51
|
+
* @return {Array} - The found nodes
|
|
52
|
+
*/
|
|
53
|
+
computeDataPlane = (tree, search, pathLocator) => {
|
|
54
|
+
if (!tree) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
const nodes = renderLevel(tree, pathLocator);
|
|
58
|
+
return search ? tree.searchNodes(search, nodes, false) : nodes;
|
|
102
59
|
},
|
|
103
60
|
/**
|
|
104
61
|
* Returns the classes of a row based its selection state
|
|
@@ -110,9 +67,10 @@ const tree = new DefaultTree(basicTree),
|
|
|
110
67
|
computeRowClass = (classes, node, highlightedNode) => {
|
|
111
68
|
let selected = false;
|
|
112
69
|
if (highlightedNode) {
|
|
113
|
-
selected =
|
|
114
|
-
|
|
115
|
-
|
|
70
|
+
selected =
|
|
71
|
+
node === highlightedNode ||
|
|
72
|
+
(node.id && node.id === highlightedNode.id) ||
|
|
73
|
+
node.pathLocator === highlightedNode.pathLocator;
|
|
116
74
|
}
|
|
117
75
|
return selected ? classes + ' selected' : classes;
|
|
118
76
|
},
|
|
@@ -123,28 +81,13 @@ const tree = new DefaultTree(basicTree),
|
|
|
123
81
|
* @return {undefined}
|
|
124
82
|
*/
|
|
125
83
|
onNodeDblClicked = (event, host) => {
|
|
126
|
-
host.dispatchEvent(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
* Returns the name of a given node
|
|
134
|
-
* @param {Object} node - The node
|
|
135
|
-
* @return {String} - The name
|
|
136
|
-
*/
|
|
137
|
-
getNodeName = node => {
|
|
138
|
-
return node[tree.searchProperty];
|
|
139
|
-
},
|
|
140
|
-
/**
|
|
141
|
-
* Returns true, if the button should be visible
|
|
142
|
-
* @param {Boolean} searching - If a search is currently executed
|
|
143
|
-
* @param {String} openNodeLevelPath - The open node level
|
|
144
|
-
* @return {Boolean} - The visibility of the button
|
|
145
|
-
*/
|
|
146
|
-
showGlobalSearchBtn = (searching, openNodeLevelPath) => {
|
|
147
|
-
return searching && openNodeLevelPath !== '';
|
|
84
|
+
host.dispatchEvent(
|
|
85
|
+
new CustomEvent('node-dblclicked', {
|
|
86
|
+
detail: {
|
|
87
|
+
model: event.model,
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
);
|
|
148
91
|
},
|
|
149
92
|
/**
|
|
150
93
|
* Returns the nodes on a path specified by a given path locator
|
|
@@ -156,7 +99,7 @@ const tree = new DefaultTree(basicTree),
|
|
|
156
99
|
if (!tree || !pathLocator) {
|
|
157
100
|
return [];
|
|
158
101
|
}
|
|
159
|
-
return
|
|
102
|
+
return tree.getPathNodes(pathLocator);
|
|
160
103
|
},
|
|
161
104
|
/**
|
|
162
105
|
* Returns a node based on a given path locator.
|
|
@@ -175,78 +118,7 @@ const tree = new DefaultTree(basicTree),
|
|
|
175
118
|
let nodes;
|
|
176
119
|
|
|
177
120
|
if (!node) {
|
|
178
|
-
nodes = tree.getPathNodes(pathLocator).filter(n => n != null);
|
|
121
|
+
nodes = tree.getPathNodes(pathLocator).filter((n) => n != null);
|
|
179
122
|
}
|
|
180
123
|
return nodes && nodes.length > 0 ? nodes.pop() : node;
|
|
181
|
-
}
|
|
182
|
-
nodeStyles = css`
|
|
183
|
-
.section {
|
|
184
|
-
background-color: #f5f5f5;
|
|
185
|
-
padding: 5px;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
.pointer {
|
|
189
|
-
cursor: pointer;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
.item {
|
|
193
|
-
padding: 0;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
.item[data-index] {
|
|
197
|
-
background: transparent;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
.node-item {
|
|
201
|
-
align-items: center;
|
|
202
|
-
display: flex;
|
|
203
|
-
font-family: 'Roboto', 'Noto', sans-serif;
|
|
204
|
-
font-size: 16px;
|
|
205
|
-
font-weight: 400;
|
|
206
|
-
height: 40px;
|
|
207
|
-
line-height: 24px;
|
|
208
|
-
padding: 6px 12px;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
.node-item-wrapper {
|
|
212
|
-
width: 100%;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
.icon {
|
|
216
|
-
box-sizing: border-box;
|
|
217
|
-
cursor: pointer;
|
|
218
|
-
display: inline-block;
|
|
219
|
-
height: 40px;
|
|
220
|
-
line-height: 1;
|
|
221
|
-
outline: none;
|
|
222
|
-
padding: 8px;
|
|
223
|
-
position: relative;
|
|
224
|
-
user-select: none;
|
|
225
|
-
width: 40px;
|
|
226
|
-
z-index: 0;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
.node-item.selected {
|
|
230
|
-
background-color: var(--cosmoz-listbox-active-color, var(--cosmoz-selection-color, rgba(58, 145, 226, 0.1)));
|
|
231
|
-
transition: background-color 0.2s ease-out;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
.node-item.selected .icon svg {
|
|
235
|
-
fill: var(--cosmoz-treenode-navigator-select-node-icon-color);
|
|
236
|
-
transition: color 0.8s ease-out;
|
|
237
|
-
}`;
|
|
238
|
-
|
|
239
|
-
export {
|
|
240
|
-
hasChildren,
|
|
241
|
-
computeSearching,
|
|
242
|
-
normalizeNodes,
|
|
243
|
-
renderLevel,
|
|
244
|
-
computeDataPlane,
|
|
245
|
-
computeRowClass,
|
|
246
|
-
onNodeDblClicked,
|
|
247
|
-
getNodeName,
|
|
248
|
-
showGlobalSearchBtn,
|
|
249
|
-
getTreePathParts,
|
|
250
|
-
getNode,
|
|
251
|
-
nodeStyles
|
|
252
|
-
};
|
|
124
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neovici/cosmoz-treenode-navigator",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"description": "A Polymer component that lets you navigate and search through hierarchically structured data-nodes and select one of them.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"polymer",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@
|
|
57
|
+
"@lit-labs/virtualizer": "^0.7.1",
|
|
58
58
|
"@neovici/cosmoz-dialog": "^3.0.0",
|
|
59
59
|
"@neovici/cosmoz-i18next": "^3.2.0",
|
|
60
60
|
"@neovici/cosmoz-input": "^3.0.0",
|
|
@@ -80,9 +80,9 @@
|
|
|
80
80
|
"@polymer/test-fixture": "^4.0.2",
|
|
81
81
|
"@semantic-release/changelog": "^6.0.0",
|
|
82
82
|
"@semantic-release/git": "^10.0.0",
|
|
83
|
-
"@webcomponents/webcomponentsjs": "^2.0.0",
|
|
84
83
|
"@web/dev-server": "^0.1.25",
|
|
85
84
|
"@web/test-runner": "^0.13.19",
|
|
85
|
+
"@webcomponents/webcomponentsjs": "^2.0.0",
|
|
86
86
|
"husky": "^8.0.0",
|
|
87
87
|
"semantic-release": "^19.0.3"
|
|
88
88
|
}
|
|
@@ -2,18 +2,17 @@ import { assert, fixture, html } from '@open-wc/testing';
|
|
|
2
2
|
import { DefaultTree } from '@neovici/cosmoz-tree/cosmoz-default-tree';
|
|
3
3
|
import basicTree from './data/basicTree.js';
|
|
4
4
|
import '../cosmoz-treenode-navigator';
|
|
5
|
-
import { computeDataPlane
|
|
5
|
+
import { computeDataPlane } from '../helpers';
|
|
6
6
|
|
|
7
7
|
suite('cosmoz-treenode-navigator', () => {
|
|
8
8
|
let navigator;
|
|
9
9
|
|
|
10
10
|
setup(async () => {
|
|
11
|
-
navigator = await fixture(html`
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
>`);
|
|
11
|
+
navigator = await fixture(html` <cosmoz-treenode-navigator
|
|
12
|
+
.tree=${new DefaultTree(basicTree)}
|
|
13
|
+
.searchPlaceholder="${'Custom search placeholder'}"
|
|
14
|
+
.searchMinLength="${2}"
|
|
15
|
+
></cosmoz-treenode-navigator>`);
|
|
17
16
|
});
|
|
18
17
|
|
|
19
18
|
test('instantiating the element', () => {
|
|
@@ -27,10 +26,9 @@ suite('cosmoz-treenode-navigator', () => {
|
|
|
27
26
|
navigator.openNodePath = '1000';
|
|
28
27
|
|
|
29
28
|
const dataPlane = computeDataPlane(
|
|
30
|
-
|
|
29
|
+
navigator.tree,
|
|
31
30
|
navigator.searchValue,
|
|
32
|
-
|
|
33
|
-
navigator.tree
|
|
31
|
+
navigator.openNodePath
|
|
34
32
|
);
|
|
35
33
|
// then I can see the contents of the node
|
|
36
34
|
// and it is ordered folder-first, then alphabetically
|
|
@@ -41,28 +39,35 @@ suite('cosmoz-treenode-navigator', () => {
|
|
|
41
39
|
|
|
42
40
|
test('set proper search placeholder', () => {
|
|
43
41
|
assert.equal(
|
|
44
|
-
navigator.shadowRoot
|
|
45
|
-
.
|
|
46
|
-
.querySelector('input').placeholder,
|
|
47
|
-
'Custom search placeholder'
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
test('compute search if value is bigger than searchMinLength', () => {
|
|
51
|
-
assert.equal(computeSearching('long string', navigator.searchMinLength), true);
|
|
52
|
-
assert.equal(computeSearching('a', navigator.searchMinLength), false);
|
|
42
|
+
navigator.shadowRoot
|
|
43
|
+
.querySelector('cosmoz-input')
|
|
44
|
+
.shadowRoot.querySelector('input').placeholder,
|
|
45
|
+
'Custom search placeholder'
|
|
46
|
+
);
|
|
53
47
|
});
|
|
54
48
|
|
|
55
49
|
test('should check if node has children', () => {
|
|
56
50
|
const node = {
|
|
57
51
|
name: 'D:',
|
|
58
52
|
path: '1000',
|
|
59
|
-
children: {
|
|
53
|
+
children: {
|
|
54
|
+
1001: {
|
|
55
|
+
name: 'Data',
|
|
56
|
+
pathLocator: '1000.1001',
|
|
57
|
+
children: { 1002: { name: 'John', pathLocator: '1000.1001.1002' } },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
60
|
parentSectionName: 'D:',
|
|
61
|
-
pathLocator: '1000'
|
|
61
|
+
pathLocator: '1000',
|
|
62
62
|
};
|
|
63
|
-
assert.equal(hasChildren(node), true);
|
|
64
|
-
const node2 = {
|
|
65
|
-
|
|
63
|
+
assert.equal(navigator.tree.hasChildren(node), true);
|
|
64
|
+
const node2 = {
|
|
65
|
+
name: 'Git',
|
|
66
|
+
path: '1.5.7',
|
|
67
|
+
parentSectionName: 'C:/Program Files',
|
|
68
|
+
pathLocator: '1.5.7',
|
|
69
|
+
};
|
|
70
|
+
assert.equal(navigator.tree.hasChildren(node2), false);
|
|
66
71
|
});
|
|
67
72
|
|
|
68
73
|
test('match search string', () => {
|
|
@@ -71,16 +76,14 @@ suite('cosmoz-treenode-navigator', () => {
|
|
|
71
76
|
navigator.searchValue = 'John';
|
|
72
77
|
|
|
73
78
|
const dataPlane = computeDataPlane(
|
|
74
|
-
|
|
79
|
+
navigator.tree,
|
|
75
80
|
navigator.searchValue,
|
|
76
|
-
|
|
77
|
-
navigator.tree
|
|
81
|
+
'1000'
|
|
78
82
|
);
|
|
79
83
|
|
|
80
84
|
//then I can see only 1 item matching 'John'
|
|
81
85
|
assert.lengthOf(dataPlane, 1);
|
|
82
86
|
assert.equal(dataPlane[0].name, 'John');
|
|
83
|
-
|
|
84
87
|
});
|
|
85
88
|
|
|
86
89
|
test('not match search string', () => {
|
|
@@ -88,10 +91,9 @@ suite('cosmoz-treenode-navigator', () => {
|
|
|
88
91
|
// when I search for 'NONEXISTING'
|
|
89
92
|
navigator.searchValue = 'NONEXISTING';
|
|
90
93
|
const dataPlane = computeDataPlane(
|
|
91
|
-
|
|
94
|
+
navigator.tree,
|
|
92
95
|
navigator.searchValue,
|
|
93
|
-
|
|
94
|
-
navigator.tree
|
|
96
|
+
'1000'
|
|
95
97
|
);
|
|
96
98
|
|
|
97
99
|
//then I can see that is no contents
|