@neovici/cosmoz-treenode-navigator 4.0.0 → 5.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/.eslintrc +7 -0
- package/.github/workflows/workflow.yml +6 -11
- package/.prettierrc +1 -0
- package/CHANGELOG.md +33 -0
- package/cosmoz-treenode-navigator.js +162 -200
- package/cosmoz-treenode-navigator.styles.js +119 -0
- package/helpers.js +27 -113
- package/package.json +8 -13
- package/test/cosmoz-treenode-navigator.test.js +3 -3
- package/.eslintrc.json +0 -3
- package/test/.eslintrc.json +0 -3
package/.eslintrc
ADDED
|
@@ -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/.prettierrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"@neovici/cfg/prettier"
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
# [5.1.0](https://github.com/neovici/cosmoz-treenode-navigator/compare/v5.0.1...v5.1.0) (2022-07-28)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **ci:** worflow typo ([17b92aa](https://github.com/neovici/cosmoz-treenode-navigator/commit/17b92aa332f0411b6548729002fd9cf62a99d223))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **cosmoz-treenode-navigator:** add keyboard navigation ([26b6d42](https://github.com/neovici/cosmoz-treenode-navigator/commit/26b6d42d99737d0b48a7fbc21a8677117b1d8a9e))
|
|
12
|
+
* refactor with lit-virtualizer instead of listbox ([7b69ab4](https://github.com/neovici/cosmoz-treenode-navigator/commit/7b69ab4e0e049c9c7f8e03c3b781f02fab83eb18))
|
|
13
|
+
* support `Right` key ([28a4df6](https://github.com/neovici/cosmoz-treenode-navigator/commit/28a4df6d35bc62ac49aba81135891ce8fcacfa05))
|
|
14
|
+
|
|
15
|
+
## [5.0.1](https://github.com/neovici/cosmoz-treenode-navigator/compare/v5.0.0...v5.0.1) (2022-07-27)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* **search:** remove label ([950ed7c](https://github.com/neovici/cosmoz-treenode-navigator/commit/950ed7cef3ab282f5fea8925dae121c771723eab))
|
|
21
|
+
|
|
22
|
+
# [5.0.0](https://github.com/neovici/cosmoz-treenode-navigator/compare/v4.0.0...v5.0.0) (2022-07-19)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Features
|
|
26
|
+
|
|
27
|
+
* upgrade to cosmoz-utils@5 ([8c70d76](https://github.com/neovici/cosmoz-treenode-navigator/commit/8c70d76d8157e0793f10566a7f75e98f44d0a67f))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### BREAKING CHANGES
|
|
31
|
+
|
|
32
|
+
* Upgrade to cosmoz-utils@5
|
|
33
|
+
|
|
1
34
|
# [4.0.0](https://github.com/neovici/cosmoz-treenode-navigator/compare/v3.1.1...v4.0.0) (2022-07-05)
|
|
2
35
|
|
|
3
36
|
|
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
import { component, useEffect, useState, useMemo, useCallback } from 'haunted';
|
|
2
2
|
import { html } from 'lit-html';
|
|
3
|
-
import { when } from 'lit/directives/when.js';
|
|
4
|
-
|
|
3
|
+
import { when } from 'lit-html/directives/when.js';
|
|
4
|
+
import { virtualize } from '@lit-labs/virtualizer/virtualize.js';
|
|
5
5
|
import '@neovici/cosmoz-input';
|
|
6
|
-
import '@neovici/cosmoz-
|
|
7
|
-
import { notifyProperty } from '@neovici/cosmoz-utils/
|
|
6
|
+
import { useMeta } from '@neovici/cosmoz-utils/hooks/use-meta';
|
|
7
|
+
import { notifyProperty } from '@neovici/cosmoz-utils/hooks/use-notify-property';
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
10
|
computeDataPlane,
|
|
11
11
|
computeRowClass,
|
|
12
12
|
computeSearching,
|
|
13
|
-
getNodeName,
|
|
14
|
-
hasChildren,
|
|
15
|
-
nodeStyles,
|
|
16
13
|
normalizeNodes,
|
|
17
14
|
onNodeDblClicked,
|
|
18
15
|
renderLevel,
|
|
19
|
-
showGlobalSearchBtn
|
|
16
|
+
showGlobalSearchBtn,
|
|
20
17
|
} from './helpers';
|
|
18
|
+
import styles from './cosmoz-treenode-navigator.styles';
|
|
21
19
|
|
|
22
|
-
const TreenodeNavigator = host => {
|
|
20
|
+
const TreenodeNavigator = (host) => {
|
|
23
21
|
const {
|
|
24
22
|
/**
|
|
25
23
|
* The main node structure
|
|
@@ -38,7 +36,7 @@ const TreenodeNavigator = host => {
|
|
|
38
36
|
* Minimum length of searchValue to trigger a search
|
|
39
37
|
*/
|
|
40
38
|
searchMinLength,
|
|
41
|
-
nodePath
|
|
39
|
+
nodePath,
|
|
42
40
|
} = host,
|
|
43
41
|
/**
|
|
44
42
|
* The path of the opened node
|
|
@@ -47,7 +45,16 @@ const TreenodeNavigator = host => {
|
|
|
47
45
|
/**
|
|
48
46
|
* The nodes on the path of the opened node
|
|
49
47
|
*/
|
|
50
|
-
|
|
48
|
+
nodesOnOpenNodePath = useMemo(
|
|
49
|
+
() =>
|
|
50
|
+
openNodePath
|
|
51
|
+
? normalizeNodes(
|
|
52
|
+
tree,
|
|
53
|
+
tree.getPathNodes(openNodePath).filter((item) => item)
|
|
54
|
+
)
|
|
55
|
+
: [],
|
|
56
|
+
[tree, openNodePath]
|
|
57
|
+
),
|
|
51
58
|
/**
|
|
52
59
|
* The highlighted (focused) node
|
|
53
60
|
* This is the node which is currently selected in the list
|
|
@@ -57,168 +64,172 @@ const TreenodeNavigator = host => {
|
|
|
57
64
|
* The search string
|
|
58
65
|
*/
|
|
59
66
|
[searchValue, setSearchValue] = useState(''),
|
|
67
|
+
isSearching = useMemo(
|
|
68
|
+
() => computeSearching(searchValue, searchMinLength),
|
|
69
|
+
[searchValue, searchMinLength]
|
|
70
|
+
),
|
|
71
|
+
renderedLevel = useMemo(
|
|
72
|
+
() => renderLevel(openNodePath, tree),
|
|
73
|
+
[openNodePath, tree]
|
|
74
|
+
),
|
|
60
75
|
/**
|
|
61
76
|
* The currently displayed node list
|
|
62
77
|
*/
|
|
63
|
-
dataPlane = useMemo(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
renderLevel(openNodePath, tree),
|
|
68
|
-
tree
|
|
69
|
-
);
|
|
70
|
-
}, [tree, openNodePath, highlightedNode, searchValue]),
|
|
78
|
+
dataPlane = useMemo(
|
|
79
|
+
() => computeDataPlane(isSearching, searchValue, renderedLevel, tree),
|
|
80
|
+
[isSearching, searchValue, renderedLevel, tree]
|
|
81
|
+
),
|
|
71
82
|
/**
|
|
72
83
|
* Opens a node (renderLevel) based on a given path
|
|
73
84
|
* @param {object} clickedNode - The clicked node
|
|
74
85
|
* @return {undefined}
|
|
75
86
|
*/
|
|
76
|
-
openNode = useCallback(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
openNode = useCallback(
|
|
88
|
+
(clickedNode) => {
|
|
89
|
+
if (tree.hasChildren(clickedNode)) {
|
|
90
|
+
setOpenNodePath(clickedNode.path);
|
|
91
|
+
setSearchValue('');
|
|
92
|
+
}
|
|
93
|
+
if (!clickedNode) {
|
|
94
|
+
setOpenNodePath('');
|
|
95
|
+
setSearchValue('');
|
|
96
|
+
setHighlightedNode(null);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
[tree]
|
|
100
|
+
),
|
|
87
101
|
/**
|
|
88
102
|
* Returns true, if the path of a node should be visible in the view
|
|
89
103
|
* @param {Number} index - The node's current index in the list
|
|
90
104
|
* @param {Object} parentSectionName - The nodes current parent section name
|
|
91
105
|
* @return {Boolean} - If the path should be visible
|
|
92
106
|
*/
|
|
93
|
-
renderSection = (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
index
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
const prevItem = _dataPlane[index - 1];
|
|
113
|
-
return prevItem.parentSectionName !== parentSectionName;
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
useEffect(() => {
|
|
117
|
-
if (!openNodePath) {
|
|
118
|
-
notifyProperty(host, 'highlightedNodePath', '');
|
|
119
|
-
notifyProperty(host, 'nodesOnNodePath', []);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
setNodesOnOpenNodePath(
|
|
123
|
-
normalizeNodes(tree.getPathNodes(openNodePath).filter(item => item))
|
|
107
|
+
renderSection = useCallback(
|
|
108
|
+
(index, parentSectionName) => {
|
|
109
|
+
if (
|
|
110
|
+
!isSearching ||
|
|
111
|
+
index == null ||
|
|
112
|
+
dataPlane == null ||
|
|
113
|
+
index >= dataPlane.length ||
|
|
114
|
+
parentSectionName == null
|
|
115
|
+
) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (index === 0) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
const prevItem = dataPlane[index - 1];
|
|
122
|
+
return prevItem.parentSectionName !== parentSectionName;
|
|
123
|
+
},
|
|
124
|
+
[isSearching, dataPlane]
|
|
124
125
|
);
|
|
125
|
-
}, [openNodePath]);
|
|
126
126
|
|
|
127
127
|
useEffect(() => {
|
|
128
128
|
if (!openNodePath) {
|
|
129
129
|
notifyProperty(host, 'highlightedNodePath', '');
|
|
130
|
-
|
|
130
|
+
notifyProperty(host, 'nodesOnNodePath', []);
|
|
131
131
|
}
|
|
132
|
-
setNodesOnOpenNodePath(
|
|
133
|
-
normalizeNodes(tree.getPathNodes(openNodePath).filter(item => item))
|
|
134
|
-
);
|
|
135
132
|
}, [openNodePath]);
|
|
136
133
|
|
|
137
134
|
useEffect(() => {
|
|
138
135
|
if (nodePath !== undefined) {
|
|
139
136
|
host.dispatchEvent(
|
|
140
137
|
new CustomEvent('select-node', {
|
|
141
|
-
detail: {}
|
|
138
|
+
detail: {},
|
|
142
139
|
})
|
|
143
140
|
);
|
|
144
141
|
}
|
|
145
142
|
}, [nodePath]);
|
|
146
143
|
|
|
147
144
|
useEffect(() => {
|
|
148
|
-
notifyProperty(
|
|
149
|
-
host,
|
|
150
|
-
'highlightedNodePath',
|
|
151
|
-
!highlightedNode ? '' : highlightedNode.path
|
|
152
|
-
);
|
|
145
|
+
notifyProperty(host, 'highlightedNodePath', highlightedNode?.path || '');
|
|
153
146
|
}, [highlightedNode]);
|
|
154
147
|
|
|
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
|
-
}
|
|
164
|
-
|
|
165
|
-
#header {
|
|
166
|
-
margin: 0 16px;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
#header a {
|
|
170
|
-
text-decoration: none;
|
|
171
|
-
color: inherit;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
.icon {
|
|
175
|
-
display: inline-block;
|
|
176
|
-
position: relative;
|
|
177
|
-
padding: 8px;
|
|
178
|
-
outline: none;
|
|
179
|
-
user-select: none;
|
|
180
|
-
cursor: pointer;
|
|
181
|
-
z-index: 0;
|
|
182
|
-
line-height: 1;
|
|
183
|
-
width: 40px;
|
|
184
|
-
height: 40px;
|
|
185
|
-
box-sizing: border-box;
|
|
186
|
-
}
|
|
148
|
+
const meta = useMeta({ dataPlane, highlightedNode, openNode });
|
|
187
149
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
const handler = (e) => {
|
|
152
|
+
if ((e.ctrlKey && e.altKey) || e.defaultPrevented) {
|
|
153
|
+
return;
|
|
192
154
|
}
|
|
155
|
+
const { dataPlane: items, highlightedNode: node, openNode: open } = meta;
|
|
156
|
+
switch (e.key) {
|
|
157
|
+
case 'Up':
|
|
158
|
+
case 'ArrowUp':
|
|
159
|
+
{
|
|
160
|
+
const idx = items.findIndex((i) => i.path === node?.path);
|
|
161
|
+
e.preventDefault();
|
|
162
|
+
setHighlightedNode(items[Math.max(idx - 1, 0)]);
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
case 'Down':
|
|
166
|
+
case 'ArrowDown':
|
|
167
|
+
{
|
|
168
|
+
const idx = items.findIndex((i) => i.path === node?.path);
|
|
169
|
+
if (idx < items.length - 1) {
|
|
170
|
+
e.preventDefault();
|
|
171
|
+
setHighlightedNode(items[idx + 1]);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
break;
|
|
193
175
|
|
|
194
|
-
|
|
195
|
-
|
|
176
|
+
case 'Right':
|
|
177
|
+
case 'ArrowRight':
|
|
178
|
+
case 'Enter':
|
|
179
|
+
if (node) {
|
|
180
|
+
e.preventDefault();
|
|
181
|
+
open(node);
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
default:
|
|
185
|
+
break;
|
|
196
186
|
}
|
|
187
|
+
};
|
|
197
188
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
189
|
+
document.addEventListener('keydown', handler, true);
|
|
190
|
+
return () => document.removeEventListener('keydown', handler, true);
|
|
191
|
+
}, [meta]);
|
|
201
192
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
193
|
+
const renderItem = (node, index) => html` <div class="item">
|
|
194
|
+
${when(
|
|
195
|
+
renderSection(index, node.parentSectionName),
|
|
196
|
+
() => html` <div class="section">${node.parentSectionName}</div> `
|
|
197
|
+
)}
|
|
198
|
+
<div
|
|
199
|
+
class=${computeRowClass('node', node, highlightedNode)}
|
|
200
|
+
@click=${() => setHighlightedNode(node)}
|
|
201
|
+
@dblclick=${(e) => onNodeDblClicked(e, host)}
|
|
202
|
+
>
|
|
203
|
+
<div class="name">${node.name}</div>
|
|
204
|
+
${when(
|
|
205
|
+
tree.hasChildren(node),
|
|
206
|
+
() => html`
|
|
207
|
+
<span class="icon" @click=${() => openNode(node)}>
|
|
208
|
+
<svg
|
|
209
|
+
viewBox="0 0 24 24"
|
|
210
|
+
preserveAspectRatio="xMidYMid meet"
|
|
211
|
+
focusable="false"
|
|
212
|
+
style="pointer-events: none; display: block; width: 100%; height: 100%;"
|
|
213
|
+
>
|
|
214
|
+
<g>
|
|
215
|
+
<path
|
|
216
|
+
d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z"
|
|
217
|
+
></path>
|
|
218
|
+
</g>
|
|
219
|
+
</svg>
|
|
220
|
+
</span>
|
|
221
|
+
`
|
|
222
|
+
)}
|
|
223
|
+
</div>
|
|
224
|
+
</div>`;
|
|
209
225
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
cursor: pointer;
|
|
214
|
-
margin: 0 0.29em;
|
|
215
|
-
padding: 0.7em 0.57em;
|
|
216
|
-
text-transform: uppercase;
|
|
217
|
-
}
|
|
226
|
+
return html`
|
|
227
|
+
<style>
|
|
228
|
+
${styles}
|
|
218
229
|
</style>
|
|
219
|
-
<div
|
|
230
|
+
<div class="header">
|
|
220
231
|
<h3 class="path">
|
|
221
|
-
<span class="icon" @click=${
|
|
232
|
+
<span class="icon" @click=${() => openNode()}>
|
|
222
233
|
<svg
|
|
223
234
|
viewBox="0 0 24 24"
|
|
224
235
|
preserveAspectRatio="xMidYMid meet"
|
|
@@ -228,84 +239,35 @@ const TreenodeNavigator = host => {
|
|
|
228
239
|
<g><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"></path></g>
|
|
229
240
|
</svg>
|
|
230
241
|
</span>
|
|
231
|
-
${
|
|
232
|
-
|
|
233
|
-
<span class="pointer" tabindex="0" @click
|
|
234
|
-
>${
|
|
242
|
+
${nodesOnOpenNodePath.map((node) => {
|
|
243
|
+
return html` <span class="slash">/</span>
|
|
244
|
+
<span class="pointer" tabindex="0" @click=${() => openNode(node)}
|
|
245
|
+
>${node[tree.searchProperty]}</span
|
|
235
246
|
>`;
|
|
236
|
-
|
|
247
|
+
})}
|
|
237
248
|
</h3>
|
|
238
249
|
<cosmoz-input
|
|
239
250
|
tabindex="0"
|
|
240
|
-
.value=${
|
|
241
|
-
.placeholder
|
|
242
|
-
|
|
243
|
-
@input="${ ({ target }) => setSearchValue(target.value) }"
|
|
251
|
+
.value=${searchValue}
|
|
252
|
+
.placeholder=${searchPlaceholder}
|
|
253
|
+
@input=${(e) => setSearchValue(e.target.value)}
|
|
244
254
|
/>
|
|
245
255
|
</div>
|
|
246
|
-
${
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
${ when(
|
|
259
|
-
renderSection(index, node.parentSectionName),
|
|
260
|
-
() => html`
|
|
261
|
-
<div class="section">${ node.parentSectionName }</div>
|
|
262
|
-
`
|
|
263
|
-
) }
|
|
264
|
-
<div
|
|
265
|
-
class="${ computeRowClass(
|
|
266
|
-
'node-item pointer',
|
|
267
|
-
node,
|
|
268
|
-
highlightedNode
|
|
269
|
-
) }"
|
|
270
|
-
@click="${ () => setHighlightedNode(node) }"
|
|
271
|
-
@dblclick="${ e => onNodeDblClicked(e, host) }"
|
|
272
|
-
>
|
|
273
|
-
<div style="flex: auto">${ node.name }</div>
|
|
274
|
-
${ when(
|
|
275
|
-
hasChildren(node),
|
|
276
|
-
() => html`
|
|
277
|
-
<span class="icon" @click="${ () => openNode(node) }">
|
|
278
|
-
<svg
|
|
279
|
-
viewBox="0 0 24 24"
|
|
280
|
-
preserveAspectRatio="xMidYMid meet"
|
|
281
|
-
focusable="false"
|
|
282
|
-
style="pointer-events: none; display: block; width: 100%; height: 100%;"
|
|
283
|
-
>
|
|
284
|
-
<g>
|
|
285
|
-
<path
|
|
286
|
-
d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z"
|
|
287
|
-
></path>
|
|
288
|
-
</g>
|
|
289
|
-
</svg>
|
|
290
|
-
</span>
|
|
291
|
-
`
|
|
292
|
-
) }
|
|
293
|
-
</div>
|
|
294
|
-
</div>
|
|
295
|
-
` }
|
|
296
|
-
</cosmoz-listbox>`
|
|
297
|
-
) }
|
|
298
|
-
${ when(
|
|
299
|
-
showGlobalSearchBtn(
|
|
300
|
-
computeSearching(searchValue, searchMinLength),
|
|
301
|
-
openNodePath
|
|
302
|
-
),
|
|
303
|
-
() => html`
|
|
304
|
-
<button class="btn-ghost" @click="${ () => setOpenNodePath('') }">
|
|
305
|
-
${ searchGlobalPlaceholder }
|
|
256
|
+
${when(
|
|
257
|
+
tree,
|
|
258
|
+
() => html` <div class="items">
|
|
259
|
+
<div virtualizer-sizer></div>
|
|
260
|
+
${virtualize({ items: dataPlane, renderItem, scroller: true })}
|
|
261
|
+
</div>`
|
|
262
|
+
)}
|
|
263
|
+
${when(
|
|
264
|
+
showGlobalSearchBtn(isSearching, openNodePath),
|
|
265
|
+
() => html`
|
|
266
|
+
<button class="btn-ghost" @click=${() => setOpenNodePath('')}>
|
|
267
|
+
${searchGlobalPlaceholder}
|
|
306
268
|
</button>
|
|
307
269
|
`
|
|
308
|
-
|
|
270
|
+
)}
|
|
309
271
|
`;
|
|
310
272
|
};
|
|
311
273
|
|
|
@@ -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,16 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import basicTree from './test/data/basicTree';
|
|
3
|
-
import { tagged as css } from '@neovici/cosmoz-utils/lib/tagged';
|
|
4
|
-
|
|
5
|
-
const tree = new DefaultTree(basicTree),
|
|
6
|
-
/**
|
|
7
|
-
* Returns true if a given node has children
|
|
8
|
-
* @param {Object} node - The node
|
|
9
|
-
* @return {Boolean} - True if node has children
|
|
10
|
-
*/
|
|
11
|
-
hasChildren = node => {
|
|
12
|
-
return tree.hasChildren(node);
|
|
13
|
-
},
|
|
1
|
+
export const //
|
|
14
2
|
/**
|
|
15
3
|
* Returns true, if a search string is eligable to trigger a search
|
|
16
4
|
* @param {String} value - The search string
|
|
@@ -23,27 +11,30 @@ const tree = new DefaultTree(basicTree),
|
|
|
23
11
|
/**
|
|
24
12
|
* Normalizes and returns an Array of nodes
|
|
25
13
|
* with the properties name, path, parentSectionName, children
|
|
14
|
+
* @param {Object} tree - The cosmoz tree
|
|
26
15
|
* @param {Array} nodes - The input nodes
|
|
27
16
|
* @return {Array} - The normalized nodes
|
|
28
17
|
*/
|
|
29
|
-
normalizeNodes = nodes => {
|
|
18
|
+
normalizeNodes = (tree, nodes) => {
|
|
30
19
|
if (!Array.isArray(nodes)) {
|
|
31
20
|
return [];
|
|
32
21
|
}
|
|
33
|
-
return nodes.map(node => {
|
|
22
|
+
return nodes.map((node) => {
|
|
34
23
|
if (!node) {
|
|
35
24
|
return node;
|
|
36
25
|
}
|
|
37
26
|
const path = node.pathLocator || node.path,
|
|
38
27
|
pathLocatorSeparator = tree.pathLocatorSeparator,
|
|
39
|
-
parentPath = path.includes(pathLocatorSeparator)
|
|
28
|
+
parentPath = path.includes(pathLocatorSeparator)
|
|
29
|
+
? path.substring(0, path.lastIndexOf(pathLocatorSeparator))
|
|
30
|
+
: path;
|
|
40
31
|
return {
|
|
41
32
|
name: node[tree.searchProperty],
|
|
42
33
|
path,
|
|
43
34
|
children: node[tree.childProperty],
|
|
44
35
|
parentSectionName: tree.getPathString(parentPath, tree.searchProperty),
|
|
45
36
|
id: node.id,
|
|
46
|
-
pathLocator: node.pathLocator
|
|
37
|
+
pathLocator: node.pathLocator,
|
|
47
38
|
};
|
|
48
39
|
});
|
|
49
40
|
},
|
|
@@ -58,7 +49,7 @@ const tree = new DefaultTree(basicTree),
|
|
|
58
49
|
computeDataPlane = (searching, searchString, renderedLevel, tree) => {
|
|
59
50
|
if (searching && tree) {
|
|
60
51
|
const results = tree.searchNodes(searchString, renderedLevel, false);
|
|
61
|
-
return normalizeNodes(results);
|
|
52
|
+
return normalizeNodes(tree, results);
|
|
62
53
|
}
|
|
63
54
|
return renderedLevel;
|
|
64
55
|
},
|
|
@@ -78,11 +69,11 @@ const tree = new DefaultTree(basicTree),
|
|
|
78
69
|
level = tree.hasChildren(node) ? children : node,
|
|
79
70
|
sortFunc = (a, b) => {
|
|
80
71
|
// First sort based on "folder" status (containing children)
|
|
81
|
-
if (hasChildren(a)) {
|
|
82
|
-
if (!hasChildren(b)) {
|
|
72
|
+
if (tree.hasChildren(a)) {
|
|
73
|
+
if (!tree.hasChildren(b)) {
|
|
83
74
|
return -1;
|
|
84
75
|
}
|
|
85
|
-
} else if (hasChildren(b)) {
|
|
76
|
+
} else if (tree.hasChildren(b)) {
|
|
86
77
|
return 1;
|
|
87
78
|
}
|
|
88
79
|
// Then sort on searchProperty
|
|
@@ -97,8 +88,7 @@ const tree = new DefaultTree(basicTree),
|
|
|
97
88
|
}
|
|
98
89
|
return 0;
|
|
99
90
|
};
|
|
100
|
-
return normalizeNodes(level)
|
|
101
|
-
.sort(sortFunc);
|
|
91
|
+
return normalizeNodes(tree, level).sort(sortFunc);
|
|
102
92
|
},
|
|
103
93
|
/**
|
|
104
94
|
* Returns the classes of a row based its selection state
|
|
@@ -110,9 +100,10 @@ const tree = new DefaultTree(basicTree),
|
|
|
110
100
|
computeRowClass = (classes, node, highlightedNode) => {
|
|
111
101
|
let selected = false;
|
|
112
102
|
if (highlightedNode) {
|
|
113
|
-
selected =
|
|
114
|
-
|
|
115
|
-
|
|
103
|
+
selected =
|
|
104
|
+
node === highlightedNode ||
|
|
105
|
+
(node.id && node.id === highlightedNode.id) ||
|
|
106
|
+
node.pathLocator === highlightedNode.pathLocator;
|
|
116
107
|
}
|
|
117
108
|
return selected ? classes + ' selected' : classes;
|
|
118
109
|
},
|
|
@@ -123,19 +114,13 @@ const tree = new DefaultTree(basicTree),
|
|
|
123
114
|
* @return {undefined}
|
|
124
115
|
*/
|
|
125
116
|
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];
|
|
117
|
+
host.dispatchEvent(
|
|
118
|
+
new CustomEvent('node-dblclicked', {
|
|
119
|
+
detail: {
|
|
120
|
+
model: event.model,
|
|
121
|
+
},
|
|
122
|
+
})
|
|
123
|
+
);
|
|
139
124
|
},
|
|
140
125
|
/**
|
|
141
126
|
* Returns true, if the button should be visible
|
|
@@ -156,7 +141,7 @@ const tree = new DefaultTree(basicTree),
|
|
|
156
141
|
if (!tree || !pathLocator) {
|
|
157
142
|
return [];
|
|
158
143
|
}
|
|
159
|
-
return normalizeNodes(tree.getPathNodes(pathLocator));
|
|
144
|
+
return normalizeNodes(tree, tree.getPathNodes(pathLocator));
|
|
160
145
|
},
|
|
161
146
|
/**
|
|
162
147
|
* Returns a node based on a given path locator.
|
|
@@ -175,78 +160,7 @@ const tree = new DefaultTree(basicTree),
|
|
|
175
160
|
let nodes;
|
|
176
161
|
|
|
177
162
|
if (!node) {
|
|
178
|
-
nodes = tree.getPathNodes(pathLocator).filter(n => n != null);
|
|
163
|
+
nodes = tree.getPathNodes(pathLocator).filter((n) => n != null);
|
|
179
164
|
}
|
|
180
165
|
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
|
-
};
|
|
166
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neovici/cosmoz-treenode-navigator",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.1.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,30 +54,24 @@
|
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@
|
|
58
|
-
"@neovici/cosmoz-autocomplete": "^4.0.0",
|
|
57
|
+
"@lit-labs/virtualizer": "^0.7.1",
|
|
59
58
|
"@neovici/cosmoz-dialog": "^3.0.0",
|
|
60
59
|
"@neovici/cosmoz-i18next": "^3.2.0",
|
|
61
|
-
"@neovici/cosmoz-input": "^
|
|
60
|
+
"@neovici/cosmoz-input": "^3.0.0",
|
|
62
61
|
"@neovici/cosmoz-tree": "^3.0.0",
|
|
63
|
-
"@neovici/cosmoz-utils": "^
|
|
64
|
-
"@open-wc/lit-helpers": "^0.4.1",
|
|
62
|
+
"@neovici/cosmoz-utils": "^5.0.0",
|
|
65
63
|
"@polymer/iron-icons": "^3.0.0",
|
|
66
64
|
"@polymer/paper-button": "^3.0.0",
|
|
67
65
|
"@polymer/paper-icon-button": "^3.0.0",
|
|
68
66
|
"@polymer/paper-input": "^3.2.0",
|
|
69
67
|
"@polymer/polymer": "^3.4.0",
|
|
70
|
-
"@web/dev-server": "^0.1.25",
|
|
71
|
-
"@web/test-runner": "^0.13.19",
|
|
72
|
-
"@web/test-runner-selenium": "^0.5.2",
|
|
73
68
|
"haunted": "^5.0.0",
|
|
74
|
-
"lit": "^2.2.7",
|
|
75
69
|
"lit-html": "^2.0.0"
|
|
76
70
|
},
|
|
77
71
|
"devDependencies": {
|
|
78
72
|
"@commitlint/cli": "^17.0.0",
|
|
79
73
|
"@commitlint/config-conventional": "^17.0.0",
|
|
80
|
-
"@neovici/
|
|
74
|
+
"@neovici/cfg": "^1.15.2",
|
|
81
75
|
"@open-wc/testing": "^3.0.0",
|
|
82
76
|
"@polymer/iron-ajax": "^3.0.0",
|
|
83
77
|
"@polymer/iron-component-page": "^4.0.0",
|
|
@@ -86,9 +80,10 @@
|
|
|
86
80
|
"@polymer/test-fixture": "^4.0.2",
|
|
87
81
|
"@semantic-release/changelog": "^6.0.0",
|
|
88
82
|
"@semantic-release/git": "^10.0.0",
|
|
83
|
+
"@web/dev-server": "^0.1.25",
|
|
84
|
+
"@web/test-runner": "^0.13.19",
|
|
89
85
|
"@webcomponents/webcomponentsjs": "^2.0.0",
|
|
90
|
-
"
|
|
91
|
-
"husky": "^6.0.0",
|
|
86
|
+
"husky": "^8.0.0",
|
|
92
87
|
"semantic-release": "^19.0.3"
|
|
93
88
|
}
|
|
94
89
|
}
|
|
@@ -2,7 +2,7 @@ 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, computeSearching,
|
|
5
|
+
import { computeDataPlane, computeSearching, renderLevel } from '../helpers';
|
|
6
6
|
|
|
7
7
|
suite('cosmoz-treenode-navigator', () => {
|
|
8
8
|
let navigator;
|
|
@@ -60,9 +60,9 @@ suite('cosmoz-treenode-navigator', () => {
|
|
|
60
60
|
parentSectionName: 'D:',
|
|
61
61
|
pathLocator: '1000'
|
|
62
62
|
};
|
|
63
|
-
assert.equal(hasChildren(node), true);
|
|
63
|
+
assert.equal(navigator.tree.hasChildren(node), true);
|
|
64
64
|
const node2 = { name: 'Git', path: '1.5.7', parentSectionName: 'C:/Program Files', pathLocator: '1.5.7' };
|
|
65
|
-
assert.equal(hasChildren(node2), false);
|
|
65
|
+
assert.equal(navigator.tree.hasChildren(node2), false);
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
test('match search string', () => {
|
package/.eslintrc.json
DELETED
package/test/.eslintrc.json
DELETED