@neovici/cosmoz-treenode-navigator 3.0.8 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/cc-test-reporter +0 -0
- package/cosmoz-treenode-button-view.js +199 -113
- package/cosmoz-treenode-navigator.js +295 -496
- package/helpers.js +252 -0
- package/package.json +22 -8
- package/test/cosmoz-treenode-navigator.test.js +62 -28
- package/test/data/basicTree.js +98 -41
- package/web-test-runner.config.mjs +1 -28
|
@@ -1,516 +1,315 @@
|
|
|
1
|
-
import '
|
|
2
|
-
import '
|
|
3
|
-
import '
|
|
4
|
-
import '@polymer/paper-icon-button/paper-icon-button';
|
|
5
|
-
import '@polymer/iron-icons/iron-icons';
|
|
6
|
-
import '@polymer/iron-list/iron-list';
|
|
1
|
+
import { component, useEffect, useState, useMemo, useCallback } from 'haunted';
|
|
2
|
+
import { html } from 'lit-html';
|
|
3
|
+
import { when } from 'lit/directives/when.js';
|
|
7
4
|
|
|
8
|
-
import
|
|
9
|
-
import
|
|
5
|
+
import '@neovici/cosmoz-input';
|
|
6
|
+
import '@neovici/cosmoz-autocomplete';
|
|
7
|
+
import { notifyProperty } from '@neovici/cosmoz-utils/lib/hooks/use-notify-property';
|
|
10
8
|
|
|
11
|
-
import {
|
|
12
|
-
|
|
9
|
+
import {
|
|
10
|
+
computeDataPlane,
|
|
11
|
+
computeRowClass,
|
|
12
|
+
computeSearching,
|
|
13
|
+
getNodeName,
|
|
14
|
+
hasChildren,
|
|
15
|
+
nodeStyles,
|
|
16
|
+
normalizeNodes,
|
|
17
|
+
onNodeDblClicked,
|
|
18
|
+
renderLevel,
|
|
19
|
+
showGlobalSearchBtn
|
|
20
|
+
} from './helpers';
|
|
13
21
|
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
const TreenodeNavigator = host => {
|
|
23
|
+
const {
|
|
24
|
+
/**
|
|
25
|
+
* The main node structure
|
|
26
|
+
*/
|
|
27
|
+
tree,
|
|
28
|
+
/**
|
|
29
|
+
* Placeholder for search field.
|
|
30
|
+
*/
|
|
31
|
+
searchPlaceholder,
|
|
32
|
+
/**
|
|
33
|
+
* Text displayed when local search has finished
|
|
34
|
+
* to suggest a search on the entire tree
|
|
35
|
+
*/
|
|
36
|
+
searchGlobalPlaceholder,
|
|
37
|
+
/**
|
|
38
|
+
* Minimum length of searchValue to trigger a search
|
|
39
|
+
*/
|
|
40
|
+
searchMinLength,
|
|
41
|
+
nodePath
|
|
42
|
+
} = host,
|
|
43
|
+
/**
|
|
44
|
+
* The path of the opened node
|
|
45
|
+
*/
|
|
46
|
+
[openNodePath, setOpenNodePath] = useState(''),
|
|
47
|
+
/**
|
|
48
|
+
* The nodes on the path of the opened node
|
|
49
|
+
*/
|
|
50
|
+
[nodesOnOpenNodePath, setNodesOnOpenNodePath] = useState([]),
|
|
51
|
+
/**
|
|
52
|
+
* The highlighted (focused) node
|
|
53
|
+
* This is the node which is currently selected in the list
|
|
54
|
+
*/
|
|
55
|
+
[highlightedNode, setHighlightedNode] = useState(),
|
|
56
|
+
/**
|
|
57
|
+
* The search string
|
|
58
|
+
*/
|
|
59
|
+
[searchValue, setSearchValue] = useState(''),
|
|
60
|
+
/**
|
|
61
|
+
* The currently displayed node list
|
|
62
|
+
*/
|
|
63
|
+
dataPlane = useMemo(() => {
|
|
64
|
+
return computeDataPlane(
|
|
65
|
+
computeSearching(searchValue, searchMinLength),
|
|
66
|
+
searchValue,
|
|
67
|
+
renderLevel(openNodePath, tree),
|
|
68
|
+
tree
|
|
69
|
+
);
|
|
70
|
+
}, [tree, openNodePath, highlightedNode, searchValue]),
|
|
71
|
+
/**
|
|
72
|
+
* Opens a node (renderLevel) based on a given path
|
|
73
|
+
* @param {object} clickedNode - The clicked node
|
|
74
|
+
* @return {undefined}
|
|
75
|
+
*/
|
|
76
|
+
openNode = useCallback(clickedNode => {
|
|
77
|
+
if (hasChildren(clickedNode)) {
|
|
78
|
+
setOpenNodePath(clickedNode.path);
|
|
79
|
+
setSearchValue('');
|
|
80
|
+
}
|
|
81
|
+
if (!clickedNode) {
|
|
82
|
+
setOpenNodePath('');
|
|
83
|
+
setSearchValue('');
|
|
84
|
+
setHighlightedNode(null);
|
|
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
|
+
};
|
|
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))
|
|
124
|
+
);
|
|
125
|
+
}, [openNodePath]);
|
|
16
126
|
|
|
17
|
-
|
|
18
|
-
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (!openNodePath) {
|
|
129
|
+
notifyProperty(host, 'highlightedNodePath', '');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
setNodesOnOpenNodePath(
|
|
133
|
+
normalizeNodes(tree.getPathNodes(openNodePath).filter(item => item))
|
|
134
|
+
);
|
|
135
|
+
}, [openNodePath]);
|
|
19
136
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
--cosmoz-treenode-navigator-list-item-focused-color: #f0f8ff;
|
|
30
|
-
}
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
if (nodePath !== undefined) {
|
|
139
|
+
host.dispatchEvent(
|
|
140
|
+
new CustomEvent('select-node', {
|
|
141
|
+
detail: {}
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}, [nodePath]);
|
|
31
146
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
notifyProperty(
|
|
149
|
+
host,
|
|
150
|
+
'highlightedNodePath',
|
|
151
|
+
!highlightedNode ? '' : highlightedNode.path
|
|
152
|
+
);
|
|
153
|
+
}, [highlightedNode]);
|
|
35
154
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
155
|
+
return html`
|
|
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
|
+
}
|
|
40
164
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
flex-wrap: wrap;
|
|
45
|
-
}
|
|
165
|
+
#header {
|
|
166
|
+
margin: 0 16px;
|
|
167
|
+
}
|
|
46
168
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
169
|
+
#header a {
|
|
170
|
+
text-decoration: none;
|
|
171
|
+
color: inherit;
|
|
172
|
+
}
|
|
51
173
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
+
}
|
|
55
187
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
188
|
+
.path {
|
|
189
|
+
display: flex;
|
|
190
|
+
align-items: center;
|
|
191
|
+
flex-wrap: wrap;
|
|
192
|
+
}
|
|
59
193
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
194
|
+
.slash {
|
|
195
|
+
margin: 0 2px;
|
|
196
|
+
}
|
|
63
197
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
-webkit-font-smoothing: antialiased;
|
|
68
|
-
padding: 6px 12px;
|
|
69
|
-
font-size: 16px;
|
|
70
|
-
font-weight: 400;
|
|
71
|
-
line-height: 24px;
|
|
72
|
-
height: 40px;
|
|
73
|
-
display: flex;
|
|
74
|
-
align-items: center;
|
|
75
|
-
}
|
|
198
|
+
.pointer {
|
|
199
|
+
cursor: pointer;
|
|
200
|
+
}
|
|
76
201
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
202
|
+
cosmoz-listbox {
|
|
203
|
+
box-shadow: none;
|
|
204
|
+
display: block;
|
|
205
|
+
height: var(--cosmoz-treenode-navigator-list-height, 50vh);
|
|
206
|
+
position: static;
|
|
207
|
+
width: 100%;
|
|
208
|
+
}
|
|
83
209
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
210
|
+
.btn-ghost {
|
|
211
|
+
background: transparent;
|
|
212
|
+
border: none;
|
|
213
|
+
cursor: pointer;
|
|
214
|
+
margin: 0 0.29em;
|
|
215
|
+
padding: 0.7em 0.57em;
|
|
216
|
+
text-transform: uppercase;
|
|
217
|
+
}
|
|
218
|
+
</style>
|
|
91
219
|
<div id="header">
|
|
92
220
|
<h3 class="path">
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
221
|
+
<span class="icon" @click=${ () => openNode() }>
|
|
222
|
+
<svg
|
|
223
|
+
viewBox="0 0 24 24"
|
|
224
|
+
preserveAspectRatio="xMidYMid meet"
|
|
225
|
+
focusable="false"
|
|
226
|
+
style="pointer-events: none; display: block; width: 100%; height: 100%;"
|
|
227
|
+
>
|
|
228
|
+
<g><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"></path></g>
|
|
229
|
+
</svg>
|
|
230
|
+
</span>
|
|
231
|
+
${ nodesOnOpenNodePath.map(node => {
|
|
232
|
+
return html` <span class="slash">/</span>
|
|
233
|
+
<span class="pointer" tabindex="0" @click="${ () => openNode(node) }"
|
|
234
|
+
>${ getNodeName(node) }</span
|
|
235
|
+
>`;
|
|
236
|
+
}) }
|
|
99
237
|
</h3>
|
|
100
|
-
<
|
|
101
|
-
|
|
102
|
-
|
|
238
|
+
<cosmoz-input
|
|
239
|
+
tabindex="0"
|
|
240
|
+
.value=${ searchValue }
|
|
241
|
+
.placeholder="${ searchPlaceholder }"
|
|
242
|
+
.label="${ searchPlaceholder }"
|
|
243
|
+
@input="${ ({ target }) => setSearchValue(target.value) }"
|
|
244
|
+
/>
|
|
103
245
|
</div>
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
246
|
+
${ when(
|
|
247
|
+
tree,
|
|
248
|
+
() => html`
|
|
249
|
+
<cosmoz-listbox
|
|
250
|
+
.query="${ searchValue }"
|
|
251
|
+
.items="${ dataPlane }"
|
|
252
|
+
.textual="${ item => item.name }"
|
|
253
|
+
.itemRenderer=${ (node, index) => html`
|
|
254
|
+
<div class="node-item-wrapper">
|
|
255
|
+
<style>
|
|
256
|
+
${ nodeStyles }
|
|
257
|
+
</style>
|
|
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 }
|
|
306
|
+
</button>
|
|
307
|
+
`
|
|
308
|
+
) }
|
|
309
|
+
`;
|
|
310
|
+
};
|
|
125
311
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
* The main node structure
|
|
131
|
-
*/
|
|
132
|
-
tree: {
|
|
133
|
-
type: Tree
|
|
134
|
-
},
|
|
135
|
-
/*
|
|
136
|
-
* The currently displayed node list
|
|
137
|
-
*/
|
|
138
|
-
dataPlane: {
|
|
139
|
-
type: Array,
|
|
140
|
-
notify: true,
|
|
141
|
-
computed: '_computeDataPlane(_search, searchValue, _renderedLevel, tree)'
|
|
142
|
-
},
|
|
143
|
-
/*
|
|
144
|
-
* The selected node
|
|
145
|
-
*/
|
|
146
|
-
selectedNode: {
|
|
147
|
-
type: Object,
|
|
148
|
-
computed: '_getNode(nodePath, tree)',
|
|
149
|
-
notify: true
|
|
150
|
-
},
|
|
151
|
-
/*
|
|
152
|
-
* The path of the selected node
|
|
153
|
-
* This is the node which was highlighted and after the user tapped the select button
|
|
154
|
-
*/
|
|
155
|
-
nodePath: {
|
|
156
|
-
type: String,
|
|
157
|
-
value: '',
|
|
158
|
-
notify: true,
|
|
159
|
-
observer: '_nodePathChanged'
|
|
160
|
-
},
|
|
161
|
-
/*
|
|
162
|
-
* The nodes on the path of the selected node
|
|
163
|
-
*/
|
|
164
|
-
nodesOnNodePath: {
|
|
165
|
-
type: Array,
|
|
166
|
-
computed: '_getTreePathParts(nodePath, tree)',
|
|
167
|
-
notify: true
|
|
168
|
-
},
|
|
169
|
-
/*
|
|
170
|
-
* The highlighted (focused) node
|
|
171
|
-
* This is the node which is currently selected in the list
|
|
172
|
-
*/
|
|
173
|
-
highlightedNode: {
|
|
174
|
-
type: Object,
|
|
175
|
-
observer: '_highlightedNodeChanged'
|
|
176
|
-
},
|
|
177
|
-
/*
|
|
178
|
-
* The path string of highlighted (focused) node
|
|
179
|
-
*/
|
|
180
|
-
highlightedNodePath: {
|
|
181
|
-
type: String,
|
|
182
|
-
notify: true
|
|
183
|
-
},
|
|
184
|
-
/*
|
|
185
|
-
* The search string
|
|
186
|
-
*/
|
|
187
|
-
searchValue: {
|
|
188
|
-
type: String,
|
|
189
|
-
value: ''
|
|
190
|
-
},
|
|
191
|
-
/*
|
|
192
|
-
* Placeholder for search field.
|
|
193
|
-
*/
|
|
194
|
-
searchPlaceholder: {
|
|
195
|
-
type: String,
|
|
196
|
-
value: 'Search'
|
|
197
|
-
},
|
|
198
|
-
/*
|
|
199
|
-
* Text displayed when local search has finished
|
|
200
|
-
* to suggest a search on the entire tree
|
|
201
|
-
*/
|
|
202
|
-
searchGlobalPlaceholder: {
|
|
203
|
-
type: String,
|
|
204
|
-
value: 'Click to search again but globally.'
|
|
205
|
-
},
|
|
206
|
-
/*
|
|
207
|
-
* Minimum length of searchValue to trigger a search
|
|
208
|
-
*/
|
|
209
|
-
searchMinLength: {
|
|
210
|
-
type: Number,
|
|
211
|
-
value: 1
|
|
212
|
-
},
|
|
213
|
-
|
|
214
|
-
/** PRIVATE */
|
|
215
|
-
|
|
216
|
-
/*
|
|
217
|
-
* Nodes (children) to be displayed when opening a node
|
|
218
|
-
*/
|
|
219
|
-
_renderedLevel: {
|
|
220
|
-
type: Array,
|
|
221
|
-
computed: '_renderLevel(_openNodePath, tree)'
|
|
222
|
-
},
|
|
223
|
-
/*
|
|
224
|
-
* The path of the opened node
|
|
225
|
-
*/
|
|
226
|
-
_openNodePath: {
|
|
227
|
-
type: String,
|
|
228
|
-
value: ''
|
|
229
|
-
},
|
|
230
|
-
/*
|
|
231
|
-
* The nodes on the path of the opened node
|
|
232
|
-
*/
|
|
233
|
-
_nodesOnOpenNodePath: {
|
|
234
|
-
type: Array,
|
|
235
|
-
computed: '_getTreePathParts(_openNodePath, tree)'
|
|
236
|
-
},
|
|
237
|
-
/*
|
|
238
|
-
* Whether a search should be executed
|
|
239
|
-
*/
|
|
240
|
-
_search: {
|
|
241
|
-
type: Boolean,
|
|
242
|
-
computed: '_computeSearching(searchValue, searchMinLength)'
|
|
243
|
-
}
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Selects the doubled clicked node and dispatches an node-dblclicked event.
|
|
248
|
-
* @param {Event} event The triggering event
|
|
249
|
-
* @return {undefined}
|
|
250
|
-
*/
|
|
251
|
-
_onNodeDblClicked(event) {
|
|
252
|
-
this.$.ironList.selectIndex(event.model.index);
|
|
253
|
-
this.dispatchEvent(new CustomEvent('node-dblclicked', {
|
|
254
|
-
composed: true,
|
|
255
|
-
bubbles: false,
|
|
256
|
-
detail: {
|
|
257
|
-
model: event.model
|
|
258
|
-
}
|
|
259
|
-
}));
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Focusses the search input.
|
|
263
|
-
* @return {undefined}
|
|
264
|
-
*/
|
|
265
|
-
focus() {
|
|
266
|
-
this.$.searchInput.focus();
|
|
267
|
-
}
|
|
268
|
-
/**
|
|
269
|
-
* Returns the found nodes based on a search string and a given tree to be searched
|
|
270
|
-
* @param {Boolean} searching - If true, a search should be executed
|
|
271
|
-
* @param {String} searchString - The search string
|
|
272
|
-
* @param {Array} renderedLevel - The node list on which the search should be executed
|
|
273
|
-
* @param {Tree} tree - The main tree
|
|
274
|
-
* @return {Array} - The found nodes
|
|
275
|
-
*/
|
|
276
|
-
_computeDataPlane(searching, searchString, renderedLevel, tree) {
|
|
277
|
-
if (searching && tree) {
|
|
278
|
-
const results = tree.searchNodes(searchString, renderedLevel, false);
|
|
279
|
-
return this._normalizeNodes(results);
|
|
280
|
-
}
|
|
281
|
-
return renderedLevel;
|
|
282
|
-
}
|
|
283
|
-
/**
|
|
284
|
-
* Returns a node array with the children of a node on the given path
|
|
285
|
-
* If the node doesn't have children, the node gets returned
|
|
286
|
-
* @param {String} pathLocator - The separated address parts of a node
|
|
287
|
-
* @param {Tree} tree - The main tree
|
|
288
|
-
* @return {Array} - Nodes
|
|
289
|
-
*/
|
|
290
|
-
_renderLevel(pathLocator, tree) {
|
|
291
|
-
if (!tree) {
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
const node = tree.getNodeByPathLocator(pathLocator),
|
|
295
|
-
children = tree.getChildren(node),
|
|
296
|
-
level = tree.hasChildren(node) ? children : node,
|
|
297
|
-
sortFunc = (a, b) => {
|
|
298
|
-
// First sort based on "folder" status (containing children)
|
|
299
|
-
if (this.hasChildren(a)) {
|
|
300
|
-
if (!this.hasChildren(b)) {
|
|
301
|
-
return -1;
|
|
302
|
-
}
|
|
303
|
-
} else if (this.hasChildren(b)) {
|
|
304
|
-
return 1;
|
|
305
|
-
}
|
|
306
|
-
// Then sort on searchProperty
|
|
307
|
-
const val1 = a[this.tree.searchProperty],
|
|
308
|
-
val2 = b[this.tree.searchProperty];
|
|
309
|
-
|
|
310
|
-
if (val1 > val2) {
|
|
311
|
-
return 1;
|
|
312
|
-
}
|
|
313
|
-
if (val1 < val2) {
|
|
314
|
-
return -1;
|
|
315
|
-
}
|
|
316
|
-
return 0;
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
return this._normalizeNodes(level).sort(sortFunc);
|
|
320
|
-
}
|
|
321
|
-
/**
|
|
322
|
-
* Normalizes and returns an Array of nodes
|
|
323
|
-
* with the properties name, path, parentSectionName, children
|
|
324
|
-
* @param {Array} nodes - The input nodes
|
|
325
|
-
* @return {Array} - The normalized nodes
|
|
326
|
-
*/
|
|
327
|
-
_normalizeNodes(nodes) {
|
|
328
|
-
if (!Array.isArray(nodes)) {
|
|
329
|
-
return [];
|
|
330
|
-
}
|
|
331
|
-
return nodes.map(node => {
|
|
332
|
-
if (!node) {
|
|
333
|
-
return node;
|
|
334
|
-
}
|
|
335
|
-
const path = node.pathLocator || node.path,
|
|
336
|
-
pathLocatorSeparator = this.tree.pathLocatorSeparator,
|
|
337
|
-
parentPath = path.includes(pathLocatorSeparator) ? path.substring(0, path.lastIndexOf(pathLocatorSeparator)) : path;
|
|
338
|
-
return {
|
|
339
|
-
name: node[this.tree.searchProperty],
|
|
340
|
-
path,
|
|
341
|
-
children: node[this.tree.childProperty],
|
|
342
|
-
parentSectionName: this.tree.getPathString(parentPath, this.tree.searchProperty)
|
|
343
|
-
};
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* Returns a node based on a given path locator.
|
|
348
|
-
* If pathLocator is empty or not defined, null gets returned.
|
|
349
|
-
* If pathLocator is only partly valid, the last valid node gets returned.
|
|
350
|
-
* @param {String} pathLocator - The separated address parts of a node
|
|
351
|
-
* @param {Tree} tree - The main tree
|
|
352
|
-
* @return {Object} - The found node
|
|
353
|
-
*/
|
|
354
|
-
_getNode(pathLocator, tree) {
|
|
355
|
-
if (!tree || !pathLocator) {
|
|
356
|
-
return null;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
const node = this.tree.getNodeByPathLocator(pathLocator);
|
|
360
|
-
let nodes;
|
|
361
|
-
|
|
362
|
-
if (!node) {
|
|
363
|
-
nodes = tree.getPathNodes(pathLocator).filter(n => n != null);
|
|
364
|
-
}
|
|
365
|
-
return nodes && nodes.length > 0 ? nodes.pop() : node;
|
|
366
|
-
}
|
|
367
|
-
/**
|
|
368
|
-
* Returns the nodes on a path specified by a given path locator
|
|
369
|
-
* @param {String} pathLocator - The separated address parts of a node
|
|
370
|
-
* @param {Tree} tree - The main tree
|
|
371
|
-
* @return {Array} - The found nodes or empty array
|
|
372
|
-
*/
|
|
373
|
-
_getTreePathParts(pathLocator, tree) {
|
|
374
|
-
if (!tree || !pathLocator) {
|
|
375
|
-
return [];
|
|
376
|
-
}
|
|
377
|
-
return this._normalizeNodes(tree.getPathNodes(pathLocator));
|
|
378
|
-
}
|
|
379
|
-
/**
|
|
380
|
-
* Clears the search input
|
|
381
|
-
* @param {Event} e - The trigger event
|
|
382
|
-
* @return {undefined}
|
|
383
|
-
*/
|
|
384
|
-
_clearSearch(e) {
|
|
385
|
-
e.preventDefault();
|
|
386
|
-
e.stopPropagation();
|
|
387
|
-
this.searchValue = '';
|
|
388
|
-
}
|
|
389
|
-
/**
|
|
390
|
-
* Returns the name of a given node
|
|
391
|
-
* @param {Object} node - The node
|
|
392
|
-
* @return {String} - The name
|
|
393
|
-
*/
|
|
394
|
-
_getNodeName(node) {
|
|
395
|
-
return node[this.tree.searchProperty];
|
|
396
|
-
}
|
|
397
|
-
/**
|
|
398
|
-
* Sets the highlightedNodePath when highlightedNode changed
|
|
399
|
-
* @param {Object} node - The highlighted node
|
|
400
|
-
* @return {undefined}
|
|
401
|
-
*/
|
|
402
|
-
_highlightedNodeChanged(node) {
|
|
403
|
-
if (!node) {
|
|
404
|
-
this.highlightedNodePath = '';
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
this.highlightedNodePath = node.path;
|
|
408
|
-
}
|
|
409
|
-
/**
|
|
410
|
-
* Returns true if a given node has children
|
|
411
|
-
* @param {Object} node - The node
|
|
412
|
-
* @return {Boolean} - True if node has children
|
|
413
|
-
*/
|
|
414
|
-
hasChildren(node) {
|
|
415
|
-
return this.tree.hasChildren(node);
|
|
416
|
-
}
|
|
417
|
-
/**
|
|
418
|
-
* Opens a node (renderLevel) based on a given path
|
|
419
|
-
* @param {Event} e - The triggering event
|
|
420
|
-
* @param {Event} e.currentTarget.dataset.path - The path locator attribute
|
|
421
|
-
* @return {undefined}
|
|
422
|
-
*/
|
|
423
|
-
openNode(e) {
|
|
424
|
-
this._openNodePath = e.currentTarget.dataset.path;
|
|
425
|
-
this.searchValue = '';
|
|
426
|
-
e.currentTarget.parentElement.blur();
|
|
427
|
-
|
|
428
|
-
// stop event propagation to prevent iron-list trying to select the current item while it will be removed
|
|
429
|
-
e.preventDefault();
|
|
430
|
-
e.stopPropagation();
|
|
431
|
-
}
|
|
432
|
-
/**
|
|
433
|
-
* Gets called if the selected node (path) has changed
|
|
434
|
-
* @param {String} path - The path of the newly selected node
|
|
435
|
-
* @return {undefined}
|
|
436
|
-
*/
|
|
437
|
-
_nodePathChanged(path) {
|
|
438
|
-
if (!path) {
|
|
439
|
-
this.highlightedNodePath = '';
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
this.highlightedNodePath = path;
|
|
443
|
-
}
|
|
444
|
-
/**
|
|
445
|
-
* Returns true, if the button should be visible
|
|
446
|
-
* @param {Boolean} searching - If a search is currently executed
|
|
447
|
-
* @param {String} openNodeLevelPath - The open node level
|
|
448
|
-
* @return {Boolean} - The visibility of the button
|
|
449
|
-
*/
|
|
450
|
-
_showGlobalSearchBtn(searching, openNodeLevelPath) {
|
|
451
|
-
return searching && openNodeLevelPath !== '';
|
|
452
|
-
}
|
|
453
|
-
/**
|
|
454
|
-
* Triggers a global search
|
|
455
|
-
* @return {undefined}
|
|
456
|
-
*/
|
|
457
|
-
tryGlobalSearch() {
|
|
458
|
-
this._openNodePath = '';
|
|
459
|
-
}
|
|
460
|
-
/**
|
|
461
|
-
* Returns true, if a search string is eligable to trigger a search
|
|
462
|
-
* @param {String} value - The search string
|
|
463
|
-
* @param {Number} searchMinLength - The minimum length of value to be valid
|
|
464
|
-
* @return {Boolean} - If a search should be triggered
|
|
465
|
-
*/
|
|
466
|
-
_computeSearching(value, searchMinLength) {
|
|
467
|
-
return value && value.length >= searchMinLength && value !== '';
|
|
468
|
-
}
|
|
469
|
-
/**
|
|
470
|
-
* Returns true, if the path of a node should be visible in the view
|
|
471
|
-
* @param {Boolean} searching - If a search is currently executed
|
|
472
|
-
* @param {Number} index - The node's current index in the list
|
|
473
|
-
* @param {Array} dataPlane - The node list
|
|
474
|
-
* @param {Object} parentSectionName - The nodes current parent section name
|
|
475
|
-
* @return {Boolean} - If the path should be visible
|
|
476
|
-
*/
|
|
477
|
-
_renderSection(searching, index, dataPlane, parentSectionName) {
|
|
478
|
-
if (!searching || index == null || dataPlane == null || index >= dataPlane.length || parentSectionName == null) {
|
|
479
|
-
return false;
|
|
480
|
-
}
|
|
481
|
-
if (index === 0) {
|
|
482
|
-
return true;
|
|
483
|
-
}
|
|
484
|
-
const prevItem = dataPlane[index - 1];
|
|
485
|
-
if (prevItem.parentSectionName === parentSectionName) {
|
|
486
|
-
return false;
|
|
487
|
-
}
|
|
488
|
-
return true;
|
|
489
|
-
}
|
|
490
|
-
/**
|
|
491
|
-
* Triggers a click event on the currentTarget
|
|
492
|
-
* if space or enter key was pressed
|
|
493
|
-
* @param {Event} e - The event
|
|
494
|
-
* @return {undefined}
|
|
495
|
-
*/
|
|
496
|
-
_clickOnEnterOrSpace(e) {
|
|
497
|
-
if (e.keyCode === 13 || e.keyCode === 32) {
|
|
498
|
-
// enter or space pressed!
|
|
499
|
-
const fnName = 'click',
|
|
500
|
-
target = e.currentTarget,
|
|
501
|
-
// eslint-disable-next-line no-new-func
|
|
502
|
-
fn = new Function('target', 'fnName', 'return target.' + fnName + '()');
|
|
503
|
-
fn(target, fnName);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
/**
|
|
507
|
-
* Returns the classes of a row based its selection state
|
|
508
|
-
* @param {String} classes - The default classes
|
|
509
|
-
* @param {Boolean} selected - If the row is currently selected
|
|
510
|
-
* @return {String} - The CSS classes
|
|
511
|
-
*/
|
|
512
|
-
_computeRowClass(classes, selected) {
|
|
513
|
-
return selected ? classes + ' selected' : classes;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
customElements.define(CosmozTreenodeNavigator.is, CosmozTreenodeNavigator);
|
|
312
|
+
customElements.define(
|
|
313
|
+
'cosmoz-treenode-navigator',
|
|
314
|
+
component(TreenodeNavigator)
|
|
315
|
+
);
|