tree-element 0.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.
Files changed (182) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +221 -0
  3. package/lib/animation.d.ts +5 -0
  4. package/lib/animation.d.ts.map +1 -0
  5. package/lib/animation.js +39 -0
  6. package/lib/animation.js.map +1 -0
  7. package/lib/classNames.d.ts +34 -0
  8. package/lib/classNames.d.ts.map +1 -0
  9. package/lib/classNames.js +39 -0
  10. package/lib/classNames.js.map +1 -0
  11. package/lib/dataLoader.d.ts +25 -0
  12. package/lib/dataLoader.d.ts.map +1 -0
  13. package/lib/dataLoader.js +69 -0
  14. package/lib/dataLoader.js.map +1 -0
  15. package/lib/dragAndDropHandler/binarySearch.d.ts +3 -0
  16. package/lib/dragAndDropHandler/binarySearch.d.ts.map +1 -0
  17. package/lib/dragAndDropHandler/binarySearch.js +23 -0
  18. package/lib/dragAndDropHandler/binarySearch.js.map +1 -0
  19. package/lib/dragAndDropHandler/dragElement.d.ts +20 -0
  20. package/lib/dragAndDropHandler/dragElement.d.ts.map +1 -0
  21. package/lib/dragAndDropHandler/dragElement.js +39 -0
  22. package/lib/dragAndDropHandler/dragElement.js.map +1 -0
  23. package/lib/dragAndDropHandler/generateHitAreas.d.ts +13 -0
  24. package/lib/dragAndDropHandler/generateHitAreas.d.ts.map +1 -0
  25. package/lib/dragAndDropHandler/generateHitAreas.js +130 -0
  26. package/lib/dragAndDropHandler/generateHitAreas.js.map +1 -0
  27. package/lib/dragAndDropHandler/index.d.ts +76 -0
  28. package/lib/dragAndDropHandler/index.d.ts.map +1 -0
  29. package/lib/dragAndDropHandler/index.js +311 -0
  30. package/lib/dragAndDropHandler/index.js.map +1 -0
  31. package/lib/dragAndDropHandler/iterateVisibleNodes.d.ts +11 -0
  32. package/lib/dragAndDropHandler/iterateVisibleNodes.d.ts.map +1 -0
  33. package/lib/dragAndDropHandler/iterateVisibleNodes.js +54 -0
  34. package/lib/dragAndDropHandler/iterateVisibleNodes.js.map +1 -0
  35. package/lib/dragAndDropHandler/types.d.ts +11 -0
  36. package/lib/dragAndDropHandler/types.d.ts.map +1 -0
  37. package/lib/elementsRenderer.d.ts +53 -0
  38. package/lib/elementsRenderer.d.ts.map +1 -0
  39. package/lib/elementsRenderer.js +251 -0
  40. package/lib/elementsRenderer.js.map +1 -0
  41. package/lib/events.d.ts +224 -0
  42. package/lib/events.d.ts.map +1 -0
  43. package/lib/index.d.ts +367 -0
  44. package/lib/index.d.ts.map +1 -0
  45. package/lib/index.js +1098 -0
  46. package/lib/index.js.map +1 -0
  47. package/lib/keyHandler.d.ts +29 -0
  48. package/lib/keyHandler.d.ts.map +1 -0
  49. package/lib/keyHandler.js +107 -0
  50. package/lib/keyHandler.js.map +1 -0
  51. package/lib/methodTypes.d.ts +21 -0
  52. package/lib/methodTypes.d.ts.map +1 -0
  53. package/lib/mouseHandler.d.ts +60 -0
  54. package/lib/mouseHandler.d.ts.map +1 -0
  55. package/lib/mouseHandler.js +283 -0
  56. package/lib/mouseHandler.js.map +1 -0
  57. package/lib/mouseUtils.d.ts +9 -0
  58. package/lib/mouseUtils.d.ts.map +1 -0
  59. package/lib/mouseUtils.js +15 -0
  60. package/lib/mouseUtils.js.map +1 -0
  61. package/lib/node.d.ts +320 -0
  62. package/lib/node.d.ts.map +1 -0
  63. package/lib/node.js +737 -0
  64. package/lib/node.js.map +1 -0
  65. package/lib/nodeElement/borderDropHint.d.ts +9 -0
  66. package/lib/nodeElement/borderDropHint.d.ts.map +1 -0
  67. package/lib/nodeElement/borderDropHint.js +24 -0
  68. package/lib/nodeElement/borderDropHint.js.map +1 -0
  69. package/lib/nodeElement/folderElement.d.ts +22 -0
  70. package/lib/nodeElement/folderElement.d.ts.map +1 -0
  71. package/lib/nodeElement/folderElement.js +100 -0
  72. package/lib/nodeElement/folderElement.js.map +1 -0
  73. package/lib/nodeElement/ghostDropHint.d.ts +18 -0
  74. package/lib/nodeElement/ghostDropHint.d.ts.map +1 -0
  75. package/lib/nodeElement/ghostDropHint.js +67 -0
  76. package/lib/nodeElement/ghostDropHint.js.map +1 -0
  77. package/lib/nodeElement/index.d.ts +28 -0
  78. package/lib/nodeElement/index.d.ts.map +1 -0
  79. package/lib/nodeElement/index.js +66 -0
  80. package/lib/nodeElement/index.js.map +1 -0
  81. package/lib/nodeUtils.d.ts +7 -0
  82. package/lib/nodeUtils.d.ts.map +1 -0
  83. package/lib/nodeUtils.js +4 -0
  84. package/lib/nodeUtils.js.map +1 -0
  85. package/lib/options.d.ts +310 -0
  86. package/lib/options.d.ts.map +1 -0
  87. package/lib/package.json +3 -0
  88. package/lib/positionUtils.d.ts +8 -0
  89. package/lib/positionUtils.d.ts.map +1 -0
  90. package/lib/positionUtils.js +14 -0
  91. package/lib/positionUtils.js.map +1 -0
  92. package/lib/requestUrl.d.ts +9 -0
  93. package/lib/requestUrl.d.ts.map +1 -0
  94. package/lib/requestUrl.js +37 -0
  95. package/lib/requestUrl.js.map +1 -0
  96. package/lib/saveStateHandler.d.ts +46 -0
  97. package/lib/saveStateHandler.d.ts.map +1 -0
  98. package/lib/saveStateHandler.js +204 -0
  99. package/lib/saveStateHandler.js.map +1 -0
  100. package/lib/scrollHandler/containerScrollParent.d.ts +12 -0
  101. package/lib/scrollHandler/containerScrollParent.d.ts.map +1 -0
  102. package/lib/scrollHandler/containerScrollParent.js +49 -0
  103. package/lib/scrollHandler/containerScrollParent.js.map +1 -0
  104. package/lib/scrollHandler/createScrollParent.d.ts +4 -0
  105. package/lib/scrollHandler/createScrollParent.d.ts.map +1 -0
  106. package/lib/scrollHandler/createScrollParent.js +38 -0
  107. package/lib/scrollHandler/createScrollParent.js.map +1 -0
  108. package/lib/scrollHandler/documentScrollParent.d.ts +22 -0
  109. package/lib/scrollHandler/documentScrollParent.d.ts.map +1 -0
  110. package/lib/scrollHandler/documentScrollParent.js +71 -0
  111. package/lib/scrollHandler/documentScrollParent.js.map +1 -0
  112. package/lib/scrollHandler/scrollParent.d.ts +26 -0
  113. package/lib/scrollHandler/scrollParent.d.ts.map +1 -0
  114. package/lib/scrollHandler/scrollParent.js +79 -0
  115. package/lib/scrollHandler/scrollParent.js.map +1 -0
  116. package/lib/scrollHandler.d.ts +20 -0
  117. package/lib/scrollHandler.d.ts.map +1 -0
  118. package/lib/scrollHandler.js +41 -0
  119. package/lib/scrollHandler.js.map +1 -0
  120. package/lib/selectNodeHandler.d.ts +38 -0
  121. package/lib/selectNodeHandler.d.ts.map +1 -0
  122. package/lib/selectNodeHandler.js +166 -0
  123. package/lib/selectNodeHandler.js.map +1 -0
  124. package/lib/setDefaultOptions.d.ts +4 -0
  125. package/lib/setDefaultOptions.d.ts.map +1 -0
  126. package/lib/setDefaultOptions.js +86 -0
  127. package/lib/setDefaultOptions.js.map +1 -0
  128. package/lib/triggerCustomEvent.d.ts +3 -0
  129. package/lib/triggerCustomEvent.d.ts.map +1 -0
  130. package/lib/triggerCustomEvent.js +13 -0
  131. package/lib/triggerCustomEvent.js.map +1 -0
  132. package/lib/util.d.ts +3 -0
  133. package/lib/util.d.ts.map +1 -0
  134. package/lib/util.js +5 -0
  135. package/lib/util.js.map +1 -0
  136. package/lib/version.d.ts +3 -0
  137. package/lib/version.d.ts.map +1 -0
  138. package/lib/version.js +5 -0
  139. package/lib/version.js.map +1 -0
  140. package/package.json +110 -0
  141. package/src/animation.ts +48 -0
  142. package/src/classNames.ts +70 -0
  143. package/src/dataLoader.ts +92 -0
  144. package/src/dragAndDropHandler/binarySearch.ts +27 -0
  145. package/src/dragAndDropHandler/dragElement.ts +62 -0
  146. package/src/dragAndDropHandler/generateHitAreas.ts +195 -0
  147. package/src/dragAndDropHandler/index.ts +442 -0
  148. package/src/dragAndDropHandler/iterateVisibleNodes.ts +91 -0
  149. package/src/dragAndDropHandler/types.ts +12 -0
  150. package/src/elementsRenderer.ts +367 -0
  151. package/src/events.ts +237 -0
  152. package/src/header.txt +16 -0
  153. package/src/index.ts +1290 -0
  154. package/src/keyHandler.ts +139 -0
  155. package/src/methodTypes.ts +44 -0
  156. package/src/mouseHandler.ts +391 -0
  157. package/src/mouseUtils.ts +23 -0
  158. package/src/node.ts +831 -0
  159. package/src/nodeElement/borderDropHint.ts +37 -0
  160. package/src/nodeElement/folderElement.ts +140 -0
  161. package/src/nodeElement/ghostDropHint.ts +84 -0
  162. package/src/nodeElement/index.ts +103 -0
  163. package/src/nodeUtils.ts +12 -0
  164. package/src/options.ts +325 -0
  165. package/src/positionUtils.ts +18 -0
  166. package/src/requestUrl.ts +41 -0
  167. package/src/saveStateHandler.ts +279 -0
  168. package/src/scrollHandler/containerScrollParent.ts +71 -0
  169. package/src/scrollHandler/createScrollParent.ts +51 -0
  170. package/src/scrollHandler/documentScrollParent.ts +104 -0
  171. package/src/scrollHandler/scrollParent.ts +117 -0
  172. package/src/scrollHandler.ts +55 -0
  173. package/src/selectNodeHandler.ts +192 -0
  174. package/src/setDefaultOptions.ts +79 -0
  175. package/src/triggerCustomEvent.ts +14 -0
  176. package/src/util.ts +5 -0
  177. package/src/version.ts +4 -0
  178. package/tree_element.css +195 -0
  179. package/tree_element.debug.js +4153 -0
  180. package/tree_element.debug.js.map +1 -0
  181. package/tree_element.js +21 -0
  182. package/tree_element.js.map +1 -0
@@ -0,0 +1,4153 @@
1
+ /*
2
+ Html-tree 0.1.0
3
+
4
+ Copyright 2026 Marco Braak
5
+
6
+ Licensed under the Apache License, Version 2.0 (the "License");
7
+ you may not use this file except in compliance with the License.
8
+ You may obtain a copy of the License at
9
+
10
+ http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+ Unless required by applicable law or agreed to in writing, software
13
+ distributed under the License is distributed on an "AS IS" BASIS,
14
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ See the License for the specific language governing permissions and
16
+ limitations under the License.
17
+ @license
18
+
19
+ */
20
+ var TreeElement = (function () {
21
+ 'use strict';
22
+
23
+ const DEFAULT_CLASS_PREFIX = "tree-element";
24
+
25
+ /* Create the class names that the widget puts on the elements it creates.
26
+ * They are all derived from classPrefix, except for the class of the root
27
+ * element and the class that every element gets, which have an option of
28
+ * their own.
29
+ */
30
+ const createClassNames = ({
31
+ classPrefix,
32
+ commonClassName,
33
+ treeClassName
34
+ }) => ({
35
+ border: `${classPrefix}-border`,
36
+ circle: `${classPrefix}-circle`,
37
+ closed: `${classPrefix}-closed`,
38
+ common: commonClassName ?? `${classPrefix}-common`,
39
+ dnd: `${classPrefix}-dnd`,
40
+ dragging: `${classPrefix}-dragging`,
41
+ element: `${classPrefix}-element`,
42
+ folder: `${classPrefix}-folder`,
43
+ ghost: `${classPrefix}-ghost`,
44
+ inside: `${classPrefix}-inside`,
45
+ line: `${classPrefix}-line`,
46
+ loading: `${classPrefix}-loading`,
47
+ moving: `${classPrefix}-moving`,
48
+ rtl: `${classPrefix}-rtl`,
49
+ selected: `${classPrefix}-selected`,
50
+ title: `${classPrefix}-title`,
51
+ titleButtonLeft: `${classPrefix}-title-button-left`,
52
+ titleButtonRight: `${classPrefix}-title-button-right`,
53
+ titleFolder: `${classPrefix}-title-folder`,
54
+ toggler: `${classPrefix}-toggler`,
55
+ togglerLeft: `${classPrefix}-toggler-left`,
56
+ togglerRight: `${classPrefix}-toggler-right`,
57
+ tree: treeClassName ?? classPrefix
58
+ });
59
+
60
+ class DataLoader {
61
+ _abortController;
62
+ _classNames;
63
+ _dataFilter;
64
+ _loadData;
65
+ _treeElement;
66
+ _triggerEvent;
67
+ constructor({
68
+ classNames,
69
+ dataFilter,
70
+ loadData,
71
+ treeElement,
72
+ triggerEvent
73
+ }) {
74
+ this._abortController = new AbortController();
75
+ this._classNames = classNames;
76
+ this._dataFilter = dataFilter;
77
+ this._loadData = loadData;
78
+ this._treeElement = treeElement;
79
+ this._triggerEvent = triggerEvent;
80
+ }
81
+ deinit() {
82
+ this._abortController.abort();
83
+ }
84
+ async loadFromUrl(url, node) {
85
+ const element = node?.element ?? this._treeElement;
86
+ element.classList.add(this._classNames.loading);
87
+ this._triggerEvent("tree.loading_data", {
88
+ element,
89
+ node
90
+ });
91
+ const stopLoading = () => {
92
+ element.classList.remove(this._classNames.loading);
93
+ this._triggerEvent("tree.loaded_data", {
94
+ element,
95
+ node
96
+ });
97
+ };
98
+ const handleResponse = async response => {
99
+ if (response.ok) {
100
+ const data = await response.json();
101
+ stopLoading();
102
+ this._loadData(this._dataFilter ? this._dataFilter(data) : data, node);
103
+ } else {
104
+ stopLoading();
105
+ this._triggerEvent("tree.load_failed", {
106
+ response
107
+ });
108
+ }
109
+ };
110
+ const signal = this._abortController.signal;
111
+ url.setSearchParam("_", Date.now().toString());
112
+ return fetch(url.toString(), {
113
+ headers: {
114
+ "Content-Type": "application/json"
115
+ },
116
+ signal
117
+ }).then(handleResponse).catch(error => {
118
+ if (this._abortController.signal.aborted) {
119
+ // The request was aborted by deinit.
120
+ return;
121
+ }
122
+ throw error;
123
+ });
124
+ }
125
+ }
126
+
127
+ // Get the top position of the HTML element.
128
+ const getOffsetTop = element => getElementPosition(element).top;
129
+
130
+ // Get the top left position of the HTML element.
131
+ const getElementPosition = element => {
132
+ const rect = element.getBoundingClientRect();
133
+ return {
134
+ left: rect.x + window.scrollX,
135
+ top: rect.y + window.scrollY
136
+ };
137
+ };
138
+
139
+ function binarySearch(items, compareFn) {
140
+ let low = 0;
141
+ let high = items.length;
142
+ while (low < high) {
143
+ const mid = low + high >> 1;
144
+ const item = items[mid];
145
+ if (item === undefined) {
146
+ return null;
147
+ }
148
+ const compareResult = compareFn(item);
149
+ if (compareResult > 0) {
150
+ high = mid;
151
+ } else if (compareResult < 0) {
152
+ low = mid + 1;
153
+ } else {
154
+ return item;
155
+ }
156
+ }
157
+ return null;
158
+ }
159
+
160
+ class DragElement {
161
+ _element;
162
+ _offsetX;
163
+ _offsetY;
164
+ constructor({
165
+ autoEscape,
166
+ classNames,
167
+ nodeName,
168
+ offsetX,
169
+ offsetY,
170
+ treeElement
171
+ }) {
172
+ this._offsetX = offsetX;
173
+ this._offsetY = offsetY;
174
+ this._element = this._createElement(nodeName, autoEscape, classNames);
175
+ treeElement.appendChild(this._element);
176
+ }
177
+ move(pageX, pageY) {
178
+ this._element.style.left = `${pageX - this._offsetX}px`;
179
+ this._element.style.top = `${pageY - this._offsetY}px`;
180
+ }
181
+ remove() {
182
+ this._element.remove();
183
+ }
184
+ _createElement(nodeName, autoEscape, classNames) {
185
+ const element = document.createElement("span");
186
+ element.classList.add(classNames.title, classNames.dragging);
187
+ if (autoEscape) {
188
+ element.textContent = nodeName;
189
+ } else {
190
+ element.innerHTML = nodeName;
191
+ }
192
+ element.style.position = "absolute";
193
+ return element;
194
+ }
195
+ }
196
+
197
+ const iterateVisibleNodes = (tree, {
198
+ handleAfterOpenFolder,
199
+ handleClosedFolder,
200
+ handleFirstNode,
201
+ handleNode,
202
+ handleOpenFolder
203
+ }) => {
204
+ let isFirstNode = true;
205
+ const iterate = (node, nextNode) => {
206
+ let mustIterateInside = (node.is_open === true || !node.element) && node.hasChildren();
207
+ let element = null;
208
+
209
+ // Is the element visible?
210
+ if (node.element?.offsetParent) {
211
+ element = node.element;
212
+ if (isFirstNode) {
213
+ handleFirstNode(node);
214
+ isFirstNode = false;
215
+ }
216
+ if (!node.hasChildren()) {
217
+ handleNode(node, nextNode, node.element);
218
+ } else if (node.is_open) {
219
+ if (!handleOpenFolder(node, node.element)) {
220
+ mustIterateInside = false;
221
+ }
222
+ } else {
223
+ handleClosedFolder(node, nextNode, element);
224
+ }
225
+ }
226
+ if (mustIterateInside) {
227
+ const childrenLength = node.children.length;
228
+ node.children.forEach((_, i) => {
229
+ const child = node.children[i];
230
+ if (child) {
231
+ if (i === childrenLength - 1) {
232
+ iterate(child, null);
233
+ } else {
234
+ const nextChild = node.children[i + 1];
235
+ if (nextChild) {
236
+ iterate(child, nextChild);
237
+ }
238
+ }
239
+ }
240
+ });
241
+ if (node.is_open && element) {
242
+ handleAfterOpenFolder(node, nextNode);
243
+ }
244
+ }
245
+ };
246
+ iterate(tree, null);
247
+ };
248
+
249
+ const generateHitPositions = (tree, currentNode) => {
250
+ const hitPositions = [];
251
+ let lastTop = 0;
252
+ const addHitPosition = (node, position, top) => {
253
+ hitPositions.push({
254
+ node,
255
+ position,
256
+ top
257
+ });
258
+ lastTop = top;
259
+ };
260
+ const handleAfterOpenFolder = (node, nextNode) => {
261
+ if (node === currentNode || nextNode === currentNode) {
262
+ // Cannot move before or after current item
263
+ addHitPosition(node, null, lastTop);
264
+ } else {
265
+ addHitPosition(node, "after", lastTop);
266
+ }
267
+ };
268
+ const handleClosedFolder = (node, nextNode, element) => {
269
+ const top = getOffsetTop(element);
270
+ if (node === currentNode) {
271
+ // Cannot move after current item
272
+ addHitPosition(node, null, top);
273
+ } else {
274
+ addHitPosition(node, "inside", top);
275
+
276
+ // Cannot move before current item
277
+ if (nextNode !== currentNode) {
278
+ addHitPosition(node, "after", top);
279
+ }
280
+ }
281
+ };
282
+ const handleFirstNode = node => {
283
+ if (node !== currentNode && node.element) {
284
+ addHitPosition(node, "before", getOffsetTop(node.element));
285
+ }
286
+ };
287
+ const handleNode = (node, nextNode, element) => {
288
+ const top = getOffsetTop(element);
289
+ if (node === currentNode) {
290
+ // Cannot move inside current item
291
+ addHitPosition(node, null, top);
292
+ } else {
293
+ addHitPosition(node, "inside", top);
294
+ }
295
+ if (nextNode === currentNode || node === currentNode) {
296
+ // Cannot move before or after current item
297
+ addHitPosition(node, null, top);
298
+ } else {
299
+ addHitPosition(node, "after", top);
300
+ }
301
+ };
302
+ const handleOpenFolder = (node, element) => {
303
+ if (node === currentNode) {
304
+ // Cannot move inside current item
305
+
306
+ // Dnd over the current element is not possible: add a position with type None for the top and the bottom.
307
+ const top = getOffsetTop(element);
308
+ const height = element.clientHeight;
309
+ addHitPosition(node, null, top);
310
+ if (height > 5) {
311
+ // Subtract 5 pixels to allow more space for the next element.
312
+ addHitPosition(node, null, top + height - 5);
313
+ }
314
+
315
+ // Stop iterating
316
+ return false;
317
+ }
318
+
319
+ // Cannot move before current item
320
+ if (node.children[0] !== currentNode) {
321
+ addHitPosition(node, "inside", getOffsetTop(element));
322
+ }
323
+
324
+ // Continue iterating
325
+ return true;
326
+ };
327
+ iterateVisibleNodes(tree, {
328
+ handleAfterOpenFolder,
329
+ handleClosedFolder,
330
+ handleFirstNode,
331
+ handleNode,
332
+ handleOpenFolder
333
+ });
334
+ return hitPositions;
335
+ };
336
+ const generateHitAreasForGroup = (hitAreas, positionsInGroup, top, bottom) => {
337
+ // limit positions in group
338
+ const positionCount = Math.min(positionsInGroup.length, 4);
339
+ const areaHeight = Math.round((bottom - top) / positionCount);
340
+ let areaTop = top;
341
+ for (let i = 0; i < positionCount; i++) {
342
+ const position = positionsInGroup[i];
343
+ if (position.position) {
344
+ hitAreas.push({
345
+ bottom: areaTop + areaHeight,
346
+ node: position.node,
347
+ position: position.position,
348
+ top: areaTop
349
+ });
350
+ }
351
+ areaTop += areaHeight;
352
+ }
353
+ };
354
+ const generateHitAreasFromPositions = (hitPositions, treeBottom) => {
355
+ if (!hitPositions.length) {
356
+ return [];
357
+ }
358
+ let previousTop = hitPositions[0].top;
359
+ let group = [];
360
+ const hitAreas = [];
361
+ for (const position of hitPositions) {
362
+ if (position.top !== previousTop && group.length) {
363
+ generateHitAreasForGroup(hitAreas, group, previousTop, position.top);
364
+ previousTop = position.top;
365
+ group = [];
366
+ }
367
+ group.push(position);
368
+ }
369
+ generateHitAreasForGroup(hitAreas, group, previousTop, treeBottom);
370
+ return hitAreas;
371
+ };
372
+ const generateHitAreas = (tree, currentNode, treeBottom) => generateHitAreasFromPositions(generateHitPositions(tree, currentNode), treeBottom);
373
+
374
+ class DragAndDropHandler {
375
+ currentItem;
376
+ hitAreas;
377
+ hoveredArea;
378
+ isDragging;
379
+ _autoEscape;
380
+ _classNames;
381
+ _dragElement;
382
+ _getNodeElement;
383
+ _getNodeElementForNode;
384
+ _getScrollLeft;
385
+ _getTree;
386
+ _onCanMove;
387
+ _onCanMoveTo;
388
+ _onDragMove;
389
+ _onDragStop;
390
+ _onIsMoveHandle;
391
+ _openFolderDelay;
392
+ _openFolderTimer;
393
+ _openNode;
394
+ _previousGhost;
395
+ _refreshElements;
396
+ _slide;
397
+ _treeElement;
398
+ _triggerEvent;
399
+ constructor({
400
+ autoEscape,
401
+ classNames,
402
+ getNodeElement,
403
+ getNodeElementForNode,
404
+ getScrollLeft,
405
+ getTree,
406
+ onCanMove,
407
+ onCanMoveTo,
408
+ onDragMove,
409
+ onDragStop,
410
+ onIsMoveHandle,
411
+ openFolderDelay,
412
+ openNode,
413
+ refreshElements,
414
+ slide,
415
+ treeElement,
416
+ triggerEvent
417
+ }) {
418
+ this._autoEscape = autoEscape;
419
+ this._classNames = classNames;
420
+ this._getNodeElement = getNodeElement;
421
+ this._getNodeElementForNode = getNodeElementForNode;
422
+ this._getScrollLeft = getScrollLeft;
423
+ this._getTree = getTree;
424
+ this._onCanMove = onCanMove;
425
+ this._onCanMoveTo = onCanMoveTo;
426
+ this._onDragMove = onDragMove;
427
+ this._onDragStop = onDragStop;
428
+ this._onIsMoveHandle = onIsMoveHandle;
429
+ this._openFolderDelay = openFolderDelay;
430
+ this._openNode = openNode;
431
+ this._refreshElements = refreshElements;
432
+ this._slide = slide;
433
+ this._treeElement = treeElement;
434
+ this._triggerEvent = triggerEvent;
435
+ this.hoveredArea = null;
436
+ this.hitAreas = [];
437
+ this.isDragging = false;
438
+ this.currentItem = null;
439
+ this._dragElement = null;
440
+ this._openFolderTimer = null;
441
+ this._previousGhost = null;
442
+ }
443
+ mouseCapture(positionInfo) {
444
+ const element = positionInfo.target;
445
+ if (!this._mustCaptureElement(element)) {
446
+ return null;
447
+ }
448
+ if (this._onIsMoveHandle && !this._onIsMoveHandle(element)) {
449
+ return null;
450
+ }
451
+ let nodeElement = this._getNodeElement(element);
452
+ if (nodeElement && this._onCanMove) {
453
+ if (!this._onCanMove(nodeElement.node)) {
454
+ nodeElement = null;
455
+ }
456
+ }
457
+ this.currentItem = nodeElement;
458
+ return this.currentItem != null;
459
+ }
460
+ mouseDrag(positionInfo) {
461
+ if (!this.currentItem || !this._dragElement) {
462
+ return false;
463
+ }
464
+ this._dragElement.move(positionInfo.pageX, positionInfo.pageY);
465
+ const area = this._findHoveredArea(positionInfo.pageX, positionInfo.pageY);
466
+ if (area && this._canMoveToArea(area, this.currentItem)) {
467
+ if (!area.node.isFolder()) {
468
+ this._stopOpenFolderTimer();
469
+ }
470
+ if (this.hoveredArea !== area) {
471
+ this.hoveredArea = area;
472
+
473
+ // If this is a closed folder, start timer to open it
474
+ if (this._mustOpenFolderTimer(area)) {
475
+ this._startOpenFolderTimer(area.node);
476
+ } else {
477
+ this._stopOpenFolderTimer();
478
+ }
479
+ this._updateDropHint();
480
+ }
481
+ } else {
482
+ this._removeDropHint();
483
+ this._stopOpenFolderTimer();
484
+ this.hoveredArea = area;
485
+ }
486
+ if (!area) {
487
+ if (this._onDragMove) {
488
+ this._onDragMove(this.currentItem.node, positionInfo.originalEvent);
489
+ }
490
+ }
491
+ return true;
492
+ }
493
+ mouseStart(positionInfo) {
494
+ if (!this.currentItem) {
495
+ return false;
496
+ }
497
+ this.refresh();
498
+ const {
499
+ left,
500
+ top
501
+ } = getElementPosition(positionInfo.target);
502
+ const node = this.currentItem.node;
503
+ this._dragElement = new DragElement({
504
+ autoEscape: this._autoEscape ?? true,
505
+ classNames: this._classNames,
506
+ nodeName: node.name,
507
+ offsetX: positionInfo.pageX - left,
508
+ offsetY: positionInfo.pageY - top,
509
+ treeElement: this._treeElement
510
+ });
511
+ this.isDragging = true;
512
+ this.currentItem.element.classList.add(this._classNames.moving);
513
+ return true;
514
+ }
515
+ mouseStop(positionInfo) {
516
+ this._moveItem(positionInfo);
517
+ this._clear();
518
+ this._removeHover();
519
+ this._removeDropHint();
520
+ this._removeHitAreas();
521
+ const currentItem = this.currentItem;
522
+ if (this.currentItem) {
523
+ this.currentItem.element.classList.remove(this._classNames.moving);
524
+ this.currentItem = null;
525
+ }
526
+ this.isDragging = false;
527
+ if (!this.hoveredArea && currentItem) {
528
+ if (this._onDragStop) {
529
+ this._onDragStop(currentItem.node, positionInfo.originalEvent);
530
+ }
531
+ }
532
+ return false;
533
+ }
534
+ refresh() {
535
+ this._removeHitAreas();
536
+ if (this.currentItem) {
537
+ const currentNode = this.currentItem.node;
538
+ this._generateHitAreas(currentNode);
539
+ this.currentItem = this._getNodeElementForNode(currentNode);
540
+ if (this.isDragging) {
541
+ this.currentItem.element.classList.add(this._classNames.moving);
542
+ }
543
+ }
544
+ }
545
+ _canMoveToArea(area, currentItem) {
546
+ if (!this._onCanMoveTo) {
547
+ return true;
548
+ }
549
+ return this._onCanMoveTo(currentItem.node, area.node, area.position);
550
+ }
551
+ _clear() {
552
+ if (this._dragElement) {
553
+ this._dragElement.remove();
554
+ this._dragElement = null;
555
+ }
556
+ }
557
+ _findHoveredArea(x, y) {
558
+ const dimensions = this._getTreeDimensions();
559
+ if (x < dimensions.left || y < dimensions.top || x > dimensions.right || y > dimensions.bottom) {
560
+ return null;
561
+ }
562
+ return binarySearch(this.hitAreas, area => {
563
+ if (y < area.top) {
564
+ return 1;
565
+ } else if (y > area.bottom) {
566
+ return -1;
567
+ } else {
568
+ return 0;
569
+ }
570
+ });
571
+ }
572
+ _generateHitAreas(currentNode) {
573
+ const tree = this._getTree();
574
+ if (!tree) {
575
+ this.hitAreas = [];
576
+ } else {
577
+ this.hitAreas = generateHitAreas(tree, currentNode, this._getTreeDimensions().bottom);
578
+ }
579
+ }
580
+ _getTreeDimensions() {
581
+ // Return the dimensions of the tree. Add a margin to the bottom to allow
582
+ // to drag-and-drop after the last element.
583
+ const treePosition = getElementPosition(this._treeElement);
584
+ const left = treePosition.left + this._getScrollLeft();
585
+ const top = treePosition.top;
586
+ return {
587
+ bottom: top + this._treeElement.clientHeight + 16,
588
+ left,
589
+ right: left + this._treeElement.clientWidth,
590
+ top
591
+ };
592
+ }
593
+
594
+ /* Move the dragged node to the selected position in the tree. */
595
+ _moveItem(positionInfo) {
596
+ if (this.currentItem && this.hoveredArea?.position && this._canMoveToArea(this.hoveredArea, this.currentItem)) {
597
+ const movedNode = this.currentItem.node;
598
+ const targetNode = this.hoveredArea.node;
599
+ const position = this.hoveredArea.position;
600
+ const previousParent = movedNode.parent;
601
+ if (position === "inside") {
602
+ this.hoveredArea.node.is_open = true;
603
+ }
604
+ const doMove = () => {
605
+ const tree = this._getTree();
606
+ if (tree) {
607
+ tree.moveNode(movedNode, targetNode, position);
608
+ this._treeElement.textContent = "";
609
+ this._refreshElements(null);
610
+ }
611
+ };
612
+ if (this._triggerEvent("tree.move", {
613
+ moveInfo: {
614
+ doMove,
615
+ movedNode,
616
+ originalEvent: positionInfo.originalEvent,
617
+ position,
618
+ previousParent,
619
+ targetNode
620
+ }
621
+ })) {
622
+ doMove();
623
+ }
624
+ }
625
+ }
626
+ _mustCaptureElement(element) {
627
+ const nodeName = element.nodeName;
628
+ return nodeName !== "INPUT" && nodeName !== "SELECT" && nodeName !== "TEXTAREA";
629
+ }
630
+ _mustOpenFolderTimer(area) {
631
+ const node = area.node;
632
+ return node.isFolder() && !node.is_open && area.position === "inside";
633
+ }
634
+ _removeDropHint() {
635
+ if (this._previousGhost) {
636
+ this._previousGhost.remove();
637
+ }
638
+ }
639
+ _removeHitAreas() {
640
+ this.hitAreas = [];
641
+ }
642
+ _removeHover() {
643
+ this.hoveredArea = null;
644
+ }
645
+ _startOpenFolderTimer(folder) {
646
+ const openFolder = () => {
647
+ void this._openNode(folder, this._slide).then(() => {
648
+ this.refresh();
649
+ this._updateDropHint();
650
+ });
651
+ };
652
+ this._stopOpenFolderTimer();
653
+ const openFolderDelay = this._openFolderDelay;
654
+ if (openFolderDelay !== false) {
655
+ this._openFolderTimer = window.setTimeout(openFolder, openFolderDelay);
656
+ }
657
+ }
658
+ _stopOpenFolderTimer() {
659
+ if (this._openFolderTimer) {
660
+ clearTimeout(this._openFolderTimer);
661
+ this._openFolderTimer = null;
662
+ }
663
+ }
664
+ _updateDropHint() {
665
+ if (!this.hoveredArea) {
666
+ return;
667
+ }
668
+
669
+ // remove previous drop hint
670
+ this._removeDropHint();
671
+
672
+ // add new drop hint
673
+ const nodeElement = this._getNodeElementForNode(this.hoveredArea.node);
674
+ this._previousGhost = nodeElement.addDropHint(this.hoveredArea.position);
675
+ }
676
+ }
677
+
678
+ const isInt = n => typeof n === "number" && n % 1 === 0;
679
+ const getBoolString = value => value ? "true" : "false";
680
+
681
+ class ElementsRenderer {
682
+ closedIconElement;
683
+ openedIconElement;
684
+ _autoEscape;
685
+ _buttonLeft;
686
+ _classNames;
687
+ _dragAndDrop;
688
+ _getTree;
689
+ _htmlElement;
690
+ _isNodeSelected;
691
+ _onCreateLi;
692
+ _rtl;
693
+ _setNodeElement;
694
+ _showEmptyFolder;
695
+ _tabIndex;
696
+ constructor({
697
+ autoEscape,
698
+ buttonLeft,
699
+ classNames,
700
+ closedIcon,
701
+ dragAndDrop,
702
+ getTree,
703
+ htmlElement,
704
+ isNodeSelected,
705
+ onCreateLi,
706
+ openedIcon,
707
+ rtl,
708
+ setNodeElement,
709
+ showEmptyFolder,
710
+ tabIndex
711
+ }) {
712
+ this._autoEscape = autoEscape;
713
+ this._buttonLeft = buttonLeft;
714
+ this._classNames = classNames;
715
+ this._dragAndDrop = dragAndDrop;
716
+ this._getTree = getTree;
717
+ this._htmlElement = htmlElement;
718
+ this._isNodeSelected = isNodeSelected;
719
+ this._onCreateLi = onCreateLi;
720
+ this._rtl = rtl;
721
+ this._setNodeElement = setNodeElement;
722
+ this._showEmptyFolder = showEmptyFolder;
723
+ this._tabIndex = tabIndex;
724
+ this.openedIconElement = this._createButtonElement(openedIcon ?? "+");
725
+ this.closedIconElement = this._createButtonElement(closedIcon ?? "-");
726
+ }
727
+ render(fromNode) {
728
+ if (fromNode?.parent) {
729
+ this.renderFromNode(fromNode);
730
+ } else {
731
+ this.renderFromRoot();
732
+ }
733
+ }
734
+ renderFromNode(node) {
735
+ if (!node.element) {
736
+ return;
737
+ }
738
+ const currentLi = node.element;
739
+ const newLi = this._createLi(node, node.getLevel());
740
+ currentLi.replaceWith(newLi);
741
+
742
+ // create children
743
+ this._createDomElements(newLi, node.children, false, node.getLevel() + 1);
744
+ }
745
+ renderFromRoot() {
746
+ this._htmlElement.textContent = '';
747
+ const tree = this._getTree();
748
+ if (tree) {
749
+ this._createDomElements(this._htmlElement, tree.children, true, 1);
750
+ }
751
+ }
752
+ _attachNodeData(node, li) {
753
+ node.element = li;
754
+ this._setNodeElement(li, node);
755
+ }
756
+ _createButtonElement(value) {
757
+ if (typeof value === "string") {
758
+ // convert value to html
759
+ const div = document.createElement("div");
760
+ div.innerHTML = value;
761
+ return document.createTextNode(div.innerHTML);
762
+ } else if (value.nodeType) {
763
+ return value;
764
+ } else {
765
+ return undefined;
766
+ }
767
+ }
768
+ _createDomElements(element, children, isRootNode, level) {
769
+ const ul = this._createUl(isRootNode);
770
+ element.appendChild(ul);
771
+ for (const child of children) {
772
+ const li = this._createLi(child, level);
773
+ ul.appendChild(li);
774
+ if (child.hasChildren()) {
775
+ this._createDomElements(li, child.children, false, level + 1);
776
+ }
777
+ }
778
+ }
779
+ _createFolderLi(node, level, isSelected) {
780
+ const buttonClasses = this._getButtonClasses(node);
781
+ const folderClasses = this._getFolderClasses(node, isSelected);
782
+ const iconElement = node.is_open ? this.openedIconElement : this.closedIconElement;
783
+
784
+ // li
785
+ const li = document.createElement("li");
786
+ li.className = `${this._classNames.common} ${folderClasses}`;
787
+ li.setAttribute("role", "none");
788
+
789
+ // div
790
+ const div = document.createElement("div");
791
+ div.className = `${this._classNames.element} ${this._classNames.common}`;
792
+ div.setAttribute("role", "none");
793
+ li.appendChild(div);
794
+
795
+ // button link
796
+ const buttonLink = document.createElement("a");
797
+ buttonLink.className = buttonClasses;
798
+ if (iconElement) {
799
+ buttonLink.appendChild(iconElement.cloneNode(true));
800
+ }
801
+ if (this._buttonLeft) {
802
+ div.appendChild(buttonLink);
803
+ }
804
+
805
+ // title span
806
+ const titleSpan = this._createTitleSpan(node.name, isSelected, true, level);
807
+ titleSpan.setAttribute("aria-expanded", getBoolString(node.is_open));
808
+ div.appendChild(titleSpan);
809
+ if (!this._buttonLeft) {
810
+ div.appendChild(buttonLink);
811
+ }
812
+ return li;
813
+ }
814
+
815
+ /* Create the <li> element
816
+ * Attach it to node.element.
817
+ * Call onCreateLi
818
+ */
819
+ _createLi(node, level) {
820
+ const isSelected = this._isNodeSelected(node);
821
+ const mustShowFolder = node.isFolder() || node.isEmptyFolder && this._showEmptyFolder;
822
+ const li = mustShowFolder ? this._createFolderLi(node, level, isSelected) : this._createNodeLi(node, level, isSelected);
823
+ this._attachNodeData(node, li);
824
+ if (this._onCreateLi) {
825
+ this._onCreateLi(node, li, isSelected);
826
+ }
827
+ return li;
828
+ }
829
+ _createNodeLi(node, level, isSelected) {
830
+ const liClasses = [this._classNames.common];
831
+ if (isSelected) {
832
+ liClasses.push(this._classNames.selected);
833
+ }
834
+ const classString = liClasses.join(" ");
835
+
836
+ // li
837
+ const li = document.createElement("li");
838
+ li.className = classString;
839
+ li.setAttribute("role", "none");
840
+
841
+ // div
842
+ const div = document.createElement("div");
843
+ div.className = `${this._classNames.element} ${this._classNames.common}`;
844
+ div.setAttribute("role", "none");
845
+ li.appendChild(div);
846
+
847
+ // title span
848
+ const titleSpan = this._createTitleSpan(node.name, isSelected, false, level);
849
+ div.appendChild(titleSpan);
850
+ return li;
851
+ }
852
+ _createTitleSpan(nodeName, isSelected, isFolder, level) {
853
+ const titleSpan = document.createElement("span");
854
+ let classes = `${this._classNames.title} ${this._classNames.common}`;
855
+ if (isFolder) {
856
+ classes += ` ${this._classNames.titleFolder}`;
857
+ }
858
+ classes += ` ${this._buttonLeft ? this._classNames.titleButtonLeft : this._classNames.titleButtonRight}`;
859
+ titleSpan.className = classes;
860
+ if (isSelected) {
861
+ const tabIndex = this._tabIndex;
862
+ if (tabIndex !== undefined) {
863
+ titleSpan.setAttribute("tabindex", `${tabIndex}`);
864
+ }
865
+ }
866
+ this._setTreeItemAriaAttributes(titleSpan, nodeName, level, isSelected);
867
+ if (this._autoEscape) {
868
+ titleSpan.textContent = nodeName;
869
+ } else {
870
+ titleSpan.innerHTML = nodeName;
871
+ }
872
+ return titleSpan;
873
+ }
874
+ _createUl(isRootNode) {
875
+ let classString;
876
+ let role;
877
+ if (!isRootNode) {
878
+ classString = "";
879
+ role = "group";
880
+ } else {
881
+ classString = this._classNames.tree;
882
+ role = "tree";
883
+ if (this._rtl) {
884
+ classString += ` ${this._classNames.rtl}`;
885
+ }
886
+ }
887
+ if (this._dragAndDrop) {
888
+ classString += ` ${this._classNames.dnd}`;
889
+ }
890
+ const ul = document.createElement("ul");
891
+ ul.className = `${this._classNames.common} ${classString}`;
892
+ ul.setAttribute("role", role);
893
+ return ul;
894
+ }
895
+ _getButtonClasses(node) {
896
+ const classes = [this._classNames.toggler, this._classNames.common];
897
+ if (!node.is_open) {
898
+ classes.push(this._classNames.closed);
899
+ }
900
+ if (this._buttonLeft) {
901
+ classes.push(this._classNames.togglerLeft);
902
+ } else {
903
+ classes.push(this._classNames.togglerRight);
904
+ }
905
+ return classes.join(" ");
906
+ }
907
+ _getFolderClasses(node, isSelected) {
908
+ const classes = [this._classNames.folder];
909
+ if (!node.is_open) {
910
+ classes.push(this._classNames.closed);
911
+ }
912
+ if (isSelected) {
913
+ classes.push(this._classNames.selected);
914
+ }
915
+ if (node.is_loading) {
916
+ classes.push(this._classNames.loading);
917
+ }
918
+ return classes.join(" ");
919
+ }
920
+ _setTreeItemAriaAttributes(element, name, level, isSelected) {
921
+ element.setAttribute("aria-label", name);
922
+ element.setAttribute("aria-level", `${level}`);
923
+ element.setAttribute("aria-selected", getBoolString(isSelected));
924
+ element.setAttribute("role", "treeitem");
925
+ }
926
+ }
927
+
928
+ class KeyHandler {
929
+ _closeNode;
930
+ _getSelectedNode;
931
+ _isFocusOnTree;
932
+ _keyboardSupport;
933
+ _openNode;
934
+ _originalSelectNode;
935
+ constructor({
936
+ closeNode,
937
+ getSelectedNode,
938
+ isFocusOnTree,
939
+ keyboardSupport,
940
+ openNode,
941
+ selectNode
942
+ }) {
943
+ this._closeNode = closeNode;
944
+ this._getSelectedNode = getSelectedNode;
945
+ this._isFocusOnTree = isFocusOnTree;
946
+ this._keyboardSupport = keyboardSupport;
947
+ this._openNode = openNode;
948
+ this._originalSelectNode = selectNode;
949
+ if (keyboardSupport) {
950
+ document.addEventListener("keydown", this._handleKeyDown);
951
+ }
952
+ }
953
+ deinit() {
954
+ if (this._keyboardSupport) {
955
+ document.removeEventListener("keydown", this._handleKeyDown);
956
+ }
957
+ }
958
+ moveDown(selectedNode) {
959
+ return this._selectNode(selectedNode.getNextVisibleNode());
960
+ }
961
+ moveUp(selectedNode) {
962
+ return this._selectNode(selectedNode.getPreviousVisibleNode());
963
+ }
964
+ _canHandleKeyboard() {
965
+ return this._keyboardSupport && this._isFocusOnTree();
966
+ }
967
+ _handleKeyDown = e => {
968
+ if (!this._canHandleKeyboard()) {
969
+ return;
970
+ }
971
+ let isKeyHandled = false;
972
+ const selectedNode = this._getSelectedNode();
973
+ if (selectedNode) {
974
+ switch (e.key) {
975
+ case "ArrowDown":
976
+ isKeyHandled = this.moveDown(selectedNode);
977
+ break;
978
+ case "ArrowLeft":
979
+ isKeyHandled = this._moveLeft(selectedNode);
980
+ break;
981
+ case "ArrowRight":
982
+ isKeyHandled = this._moveRight(selectedNode);
983
+ break;
984
+ case "ArrowUp":
985
+ isKeyHandled = this.moveUp(selectedNode);
986
+ break;
987
+ }
988
+ }
989
+ if (isKeyHandled) {
990
+ e.preventDefault();
991
+ }
992
+ };
993
+ _moveLeft(selectedNode) {
994
+ if (selectedNode.isFolder() && selectedNode.is_open) {
995
+ // Left on an open node closes the node
996
+ this._closeNode(selectedNode);
997
+ return true;
998
+ } else {
999
+ // Left on a closed or end node moves focus to the node's parent
1000
+ return this._selectNode(selectedNode.getParent());
1001
+ }
1002
+ }
1003
+ _moveRight(selectedNode) {
1004
+ if (!selectedNode.isFolder()) {
1005
+ return false;
1006
+ } else {
1007
+ // folder node
1008
+ if (selectedNode.is_open) {
1009
+ // Right moves to the first child of an open node
1010
+ return this._selectNode(selectedNode.getNextVisibleNode());
1011
+ } else {
1012
+ // Right expands a closed node
1013
+ void this._openNode(selectedNode);
1014
+ return true;
1015
+ }
1016
+ }
1017
+ }
1018
+
1019
+ /* Select the node.
1020
+ * Don't do anything if the node is null.
1021
+ * Result: a different node was selected.
1022
+ */
1023
+ _selectNode(node) {
1024
+ if (!node) {
1025
+ return false;
1026
+ } else {
1027
+ this._originalSelectNode(node);
1028
+ return true;
1029
+ }
1030
+ }
1031
+ }
1032
+
1033
+ const getPositionInfoFromMouseEvent = e => ({
1034
+ originalEvent: e,
1035
+ pageX: e.pageX,
1036
+ pageY: e.pageY,
1037
+ target: e.target
1038
+ });
1039
+ const getPositionInfoFromTouch = (touch, e) => ({
1040
+ originalEvent: e,
1041
+ pageX: touch.pageX,
1042
+ pageY: touch.pageY,
1043
+ target: touch.target
1044
+ });
1045
+
1046
+ class MouseHandler {
1047
+ _classNames;
1048
+ _element;
1049
+ _getMouseDelay;
1050
+ _getNode;
1051
+ _isMouseDelayMet;
1052
+ _isMouseStarted;
1053
+ _mouseDelayTimer;
1054
+ _mouseDownInfo;
1055
+ _onClickButton;
1056
+ _onClickTitle;
1057
+ _onMouseCapture;
1058
+ _onMouseDrag;
1059
+ _onMouseStart;
1060
+ _onMouseStop;
1061
+ _triggerEvent;
1062
+ _useContextMenu;
1063
+ constructor({
1064
+ classNames,
1065
+ element,
1066
+ getMouseDelay,
1067
+ getNode,
1068
+ onClickButton,
1069
+ onClickTitle,
1070
+ onMouseCapture,
1071
+ onMouseDrag,
1072
+ onMouseStart,
1073
+ onMouseStop,
1074
+ triggerEvent,
1075
+ useContextMenu
1076
+ }) {
1077
+ this._classNames = classNames;
1078
+ this._element = element;
1079
+ this._getMouseDelay = getMouseDelay;
1080
+ this._getNode = getNode;
1081
+ this._onClickButton = onClickButton;
1082
+ this._onClickTitle = onClickTitle;
1083
+ this._onMouseCapture = onMouseCapture;
1084
+ this._onMouseDrag = onMouseDrag;
1085
+ this._onMouseStart = onMouseStart;
1086
+ this._onMouseStop = onMouseStop;
1087
+ this._triggerEvent = triggerEvent;
1088
+ this._useContextMenu = useContextMenu;
1089
+ element.addEventListener("click", this._handleClick);
1090
+ element.addEventListener("dblclick", this._handleDblclick);
1091
+ element.addEventListener("mousedown", this._mouseDown, {
1092
+ passive: false
1093
+ });
1094
+ element.addEventListener("touchstart", this._touchStart, {
1095
+ passive: false
1096
+ });
1097
+ if (useContextMenu) {
1098
+ element.addEventListener("contextmenu", this._handleContextmenu);
1099
+ }
1100
+ this._isMouseStarted = false;
1101
+ this._mouseDelayTimer = null;
1102
+ this._isMouseDelayMet = false;
1103
+ this._mouseDownInfo = null;
1104
+ }
1105
+ deinit() {
1106
+ this._element.removeEventListener("click", this._handleClick);
1107
+ this._element.removeEventListener("dblclick", this._handleDblclick);
1108
+ if (this._useContextMenu) {
1109
+ this._element.removeEventListener("contextmenu", this._handleContextmenu);
1110
+ }
1111
+ this._element.removeEventListener("mousedown", this._mouseDown);
1112
+ this._element.removeEventListener("touchstart", this._touchStart);
1113
+ this._removeMouseMoveEventListeners();
1114
+ }
1115
+ _getClickTarget(element) {
1116
+ const button = element.closest(`.${this._classNames.toggler}`);
1117
+ if (button) {
1118
+ const node = this._getNode(button);
1119
+ if (node) {
1120
+ return {
1121
+ node,
1122
+ type: "button"
1123
+ };
1124
+ }
1125
+ } else {
1126
+ const treeElement = element.closest(`.${this._classNames.element}`);
1127
+ if (treeElement) {
1128
+ const node = this._getNode(treeElement);
1129
+ if (node) {
1130
+ return {
1131
+ node,
1132
+ type: "label"
1133
+ };
1134
+ }
1135
+ }
1136
+ }
1137
+ return null;
1138
+ }
1139
+ _handleClick = e => {
1140
+ if (!e.target) {
1141
+ return;
1142
+ }
1143
+ const clickTarget = this._getClickTarget(e.target);
1144
+ if (!clickTarget) {
1145
+ return;
1146
+ }
1147
+ switch (clickTarget.type) {
1148
+ case "button":
1149
+ this._onClickButton(clickTarget.node);
1150
+ e.preventDefault();
1151
+ e.stopPropagation();
1152
+ break;
1153
+ case "label":
1154
+ {
1155
+ if (this._triggerEvent("tree.click", {
1156
+ node: clickTarget.node,
1157
+ originalEvent: e
1158
+ })) {
1159
+ this._onClickTitle(clickTarget.node);
1160
+ }
1161
+ break;
1162
+ }
1163
+ }
1164
+ };
1165
+ _handleContextmenu = e => {
1166
+ if (!e.target) {
1167
+ return;
1168
+ }
1169
+ const div = e.target.closest(`ul.${this._classNames.tree} .${this._classNames.element}`);
1170
+ if (div) {
1171
+ const node = this._getNode(div);
1172
+ if (node) {
1173
+ e.preventDefault();
1174
+ e.stopPropagation();
1175
+ this._triggerEvent("tree.contextmenu", {
1176
+ node,
1177
+ originalEvent: e
1178
+ });
1179
+ return false;
1180
+ }
1181
+ }
1182
+ return null;
1183
+ };
1184
+ _handleDblclick = e => {
1185
+ if (!e.target) {
1186
+ return;
1187
+ }
1188
+ const clickTarget = this._getClickTarget(e.target);
1189
+ if (clickTarget?.type === "label") {
1190
+ this._triggerEvent("tree.dblclick", {
1191
+ node: clickTarget.node,
1192
+ originalEvent: e
1193
+ });
1194
+ }
1195
+ };
1196
+ _handleMouseDown(positionInfo) {
1197
+ // We may have missed mouseup (out of window)
1198
+ if (this._isMouseStarted) {
1199
+ this._handleMouseUp(positionInfo);
1200
+ }
1201
+ this._mouseDownInfo = positionInfo;
1202
+ if (!this._onMouseCapture(positionInfo)) {
1203
+ return false;
1204
+ }
1205
+ this._handleStartMouse();
1206
+ return true;
1207
+ }
1208
+ _handleMouseMove(e, positionInfo) {
1209
+ if (this._isMouseStarted) {
1210
+ this._onMouseDrag(positionInfo);
1211
+ if (e.cancelable) {
1212
+ e.preventDefault();
1213
+ }
1214
+ return;
1215
+ }
1216
+ if (!this._isMouseDelayMet) {
1217
+ return;
1218
+ }
1219
+ if (this._mouseDownInfo) {
1220
+ this._isMouseStarted = this._onMouseStart(this._mouseDownInfo);
1221
+ }
1222
+ if (this._isMouseStarted) {
1223
+ this._onMouseDrag(positionInfo);
1224
+ if (e.cancelable) {
1225
+ e.preventDefault();
1226
+ }
1227
+ } else {
1228
+ this._handleMouseUp(positionInfo);
1229
+ }
1230
+ }
1231
+ _handleMouseUp(positionInfo) {
1232
+ this._removeMouseMoveEventListeners();
1233
+ this._isMouseDelayMet = false;
1234
+ this._mouseDownInfo = null;
1235
+ if (this._isMouseStarted) {
1236
+ this._isMouseStarted = false;
1237
+ this._onMouseStop(positionInfo);
1238
+ }
1239
+ }
1240
+ _handleStartMouse() {
1241
+ document.addEventListener("mousemove", this._mouseMove, {
1242
+ passive: false
1243
+ });
1244
+ document.addEventListener("touchmove", this._touchMove, {
1245
+ passive: false
1246
+ });
1247
+ document.addEventListener("mouseup", this._mouseUp, {
1248
+ passive: false
1249
+ });
1250
+ document.addEventListener("touchend", this._touchEnd, {
1251
+ passive: false
1252
+ });
1253
+ const mouseDelay = this._getMouseDelay();
1254
+ if (mouseDelay) {
1255
+ this._startMouseDelayTimer(mouseDelay);
1256
+ } else {
1257
+ this._isMouseDelayMet = true;
1258
+ }
1259
+ }
1260
+ _mouseDown = e => {
1261
+ // Left mouse button?
1262
+ if (e.button !== 0) {
1263
+ return;
1264
+ }
1265
+ const result = this._handleMouseDown(getPositionInfoFromMouseEvent(e));
1266
+ if (result && e.cancelable) {
1267
+ e.preventDefault();
1268
+ }
1269
+ };
1270
+ _mouseMove = e => {
1271
+ this._handleMouseMove(e, getPositionInfoFromMouseEvent(e));
1272
+ };
1273
+ _mouseUp = e => {
1274
+ this._handleMouseUp(getPositionInfoFromMouseEvent(e));
1275
+ };
1276
+ _removeMouseMoveEventListeners() {
1277
+ document.removeEventListener("mousemove", this._mouseMove);
1278
+ document.removeEventListener("touchmove", this._touchMove);
1279
+ document.removeEventListener("mouseup", this._mouseUp);
1280
+ document.removeEventListener("touchend", this._touchEnd);
1281
+ }
1282
+ _startMouseDelayTimer(mouseDelay) {
1283
+ if (this._mouseDelayTimer) {
1284
+ clearTimeout(this._mouseDelayTimer);
1285
+ }
1286
+ this._mouseDelayTimer = window.setTimeout(() => {
1287
+ if (this._mouseDownInfo) {
1288
+ this._isMouseDelayMet = true;
1289
+ }
1290
+ }, mouseDelay);
1291
+ this._isMouseDelayMet = false;
1292
+ }
1293
+ _touchEnd = e => {
1294
+ if (e.touches.length > 1) {
1295
+ return;
1296
+ }
1297
+ const touch = e.touches[0];
1298
+ if (!touch) {
1299
+ return;
1300
+ }
1301
+ this._handleMouseUp(getPositionInfoFromTouch(touch, e));
1302
+ };
1303
+ _touchMove = e => {
1304
+ if (e.touches.length > 1) {
1305
+ return;
1306
+ }
1307
+ const touch = e.touches[0];
1308
+ if (!touch) {
1309
+ return;
1310
+ }
1311
+ this._handleMouseMove(e, getPositionInfoFromTouch(touch, e));
1312
+ };
1313
+ _touchStart = e => {
1314
+ if (e.touches.length > 1) {
1315
+ return;
1316
+ }
1317
+ const touch = e.touches[0];
1318
+ if (!touch) {
1319
+ return;
1320
+ }
1321
+ this._handleMouseDown(getPositionInfoFromTouch(touch, e));
1322
+ };
1323
+ }
1324
+
1325
+ const isNodeRecordWithChildren = data => typeof data === "object" && "children" in data && data.children instanceof Array;
1326
+
1327
+ /*
1328
+ A node reads and writes the private members of other nodes (node.setParent),
1329
+ so the `_` prefix that the build adds cannot be limited to `this.`:
1330
+
1331
+ prefix-private-members: all
1332
+ */
1333
+
1334
+ /**
1335
+ * @groupDescription Properties
1336
+ * Any other key in the node data is copied onto the node, so
1337
+ * `{ name: "node1", color: "green" }` gives you `node.color`.
1338
+ *
1339
+ * @groupDescription Searching
1340
+ * These work on any node, and search that node's subtree. Called on the root
1341
+ * node, they search the whole tree — which is what the tree's own methods of
1342
+ * the same name do.
1343
+ *
1344
+ * @groupDescription Changing the tree
1345
+ * ::: warning
1346
+ * These methods change the data without re-rendering. The tree's
1347
+ * [methods](/reference/methods) — `appendNode`, `removeNode`, `moveNode` and
1348
+ * friends — do the same and refresh the display, so prefer those. If you do
1349
+ * use these, call `tree.refresh()` afterwards.
1350
+ * :::
1351
+ */
1352
+ class Node {
1353
+ /** @hidden */
1354
+
1355
+ /** The child nodes. */
1356
+ children;
1357
+ /** The `li` element, once the node is rendered. */
1358
+ element;
1359
+ /** The id from the node data. */
1360
+ id;
1361
+ /** @hidden */
1362
+ idMapping;
1363
+ /** Whether the node's children are being fetched. */
1364
+ is_loading;
1365
+ /** Whether the folder is open. */
1366
+ is_open;
1367
+ /** Whether the node data had an empty `children` array. */
1368
+ isEmptyFolder;
1369
+ /** Whether the children still have to be fetched. */
1370
+ load_on_demand;
1371
+ /** The label. Also settable from the `label` key in node data. */
1372
+ name;
1373
+ /** @hidden */
1374
+ nodeClass;
1375
+ /** The parent; `null` for the root node. */
1376
+ parent;
1377
+ /** The root node. */
1378
+ tree;
1379
+
1380
+ /** @hidden */
1381
+ constructor(nodeData = null, isRoot = false, nodeClass = Node) {
1382
+ this.name = "";
1383
+ this.load_on_demand = false;
1384
+ this.isEmptyFolder = nodeData != null && isNodeRecordWithChildren(nodeData) && nodeData.children.length === 0;
1385
+ this.setData(nodeData);
1386
+ this.children = [];
1387
+ this.parent = null;
1388
+ this.is_loading = undefined;
1389
+ this.is_open ??= undefined;
1390
+ if (isRoot) {
1391
+ this.idMapping = new Map();
1392
+ this.tree = this;
1393
+ this.nodeClass = nodeClass;
1394
+ } else {
1395
+ this.idMapping = undefined;
1396
+ }
1397
+ }
1398
+
1399
+ /**
1400
+ * Adds a sibling after this node.
1401
+ *
1402
+ * @returns The new node, or `null` when the node has no parent.
1403
+ * @group Changing the tree
1404
+ */
1405
+ addAfter(nodeInfo) {
1406
+ if (!this.parent) {
1407
+ return null;
1408
+ } else {
1409
+ const node = this._createNode(nodeInfo);
1410
+ const childIndex = this.parent.getChildIndex(this);
1411
+ this.parent.addChildAtPosition(node, childIndex + 1);
1412
+ node._loadChildrenFromData(nodeInfo);
1413
+ return node;
1414
+ }
1415
+ }
1416
+
1417
+ /**
1418
+ * Adds a sibling before this node.
1419
+ *
1420
+ * @returns The new node, or `null` when the node has no parent.
1421
+ * @group Changing the tree
1422
+ */
1423
+ addBefore(nodeInfo) {
1424
+ if (!this.parent) {
1425
+ return null;
1426
+ } else {
1427
+ const node = this._createNode(nodeInfo);
1428
+ const childIndex = this.parent.getChildIndex(this);
1429
+ this.parent.addChildAtPosition(node, childIndex);
1430
+ node._loadChildrenFromData(nodeInfo);
1431
+ return node;
1432
+ }
1433
+ }
1434
+
1435
+ /**
1436
+ * Adds an existing `Node` object as the last child.
1437
+ *
1438
+ * @group Changing the tree
1439
+ */
1440
+ addChild(node) {
1441
+ this.children.push(node);
1442
+ node._setParent(this);
1443
+ }
1444
+
1445
+ /**
1446
+ * Adds an existing `Node` object as a child, at this position in
1447
+ * `children`. The index starts at `0`.
1448
+ *
1449
+ * @group Changing the tree
1450
+ */
1451
+ addChildAtPosition(node, index) {
1452
+ this.children.splice(index, 0, node);
1453
+ node._setParent(this);
1454
+ }
1455
+
1456
+ /** @hidden */
1457
+ addNodeToIndex(node) {
1458
+ if (node.id != null) {
1459
+ this.idMapping?.set(node.id, node);
1460
+ }
1461
+ }
1462
+
1463
+ /**
1464
+ * Inserts a new parent between this node and its current parent.
1465
+ *
1466
+ * @returns The new node, or `null` when the node has no parent.
1467
+ * @group Changing the tree
1468
+ */
1469
+ addParent(nodeInfo) {
1470
+ if (!this.parent) {
1471
+ return null;
1472
+ } else {
1473
+ const newParent = this._createNode(nodeInfo);
1474
+ if (this.tree) {
1475
+ newParent._setParent(this.tree);
1476
+ }
1477
+ const originalParent = this.parent;
1478
+ for (const child of originalParent.children) {
1479
+ newParent.addChild(child);
1480
+ }
1481
+ originalParent.children = [];
1482
+ originalParent.addChild(newParent);
1483
+ return newParent;
1484
+ }
1485
+ }
1486
+
1487
+ /**
1488
+ * Adds a child at the end.
1489
+ *
1490
+ * @returns The new node.
1491
+ * @group Changing the tree
1492
+ */
1493
+ append(nodeInfo) {
1494
+ const node = this._createNode(nodeInfo);
1495
+ this.addChild(node);
1496
+ node._loadChildrenFromData(nodeInfo);
1497
+ return node;
1498
+ }
1499
+
1500
+ /**
1501
+ * Returns all nodes in the subtree for which the callback returns
1502
+ * `true`.
1503
+ *
1504
+ * @example
1505
+ * ```js
1506
+ * const folders = tree.getTree().filter((node) => node.isFolder());
1507
+ * ```
1508
+ *
1509
+ * @group Searching
1510
+ */
1511
+ filter(f) {
1512
+ const result = [];
1513
+ this.iterate(node => {
1514
+ if (f(node)) {
1515
+ result.push(node);
1516
+ }
1517
+ return true;
1518
+ });
1519
+ return result;
1520
+ }
1521
+
1522
+ /**
1523
+ * Returns the position of a child in `children`, or `-1`.
1524
+ *
1525
+ * @group Inspecting a node
1526
+ */
1527
+ getChildIndex(node) {
1528
+ return this.children.indexOf(node);
1529
+ }
1530
+
1531
+ /**
1532
+ * Returns the subtree as plain data, ready for `JSON.stringify`.
1533
+ * Internal properties (`parent`, `children`, `element`, `tree`,
1534
+ * `idMapping`, `nodeClass`, `load_on_demand`, `isEmptyFolder`) are left
1535
+ * out; your own properties are kept.
1536
+ *
1537
+ * ```js
1538
+ * tree.getTree().getData();
1539
+ * // [{ name: "node1", id: 1, children: [{ name: "child1", id: 2 }] }]
1540
+ * ```
1541
+ *
1542
+ * @param includeParent - Make the result the node itself rather than its
1543
+ * children. Default `false`.
1544
+ * @group Reading data back
1545
+ */
1546
+ getData(includeParent = false) {
1547
+ const getDataFromNodes = nodes => {
1548
+ return nodes.map(node => {
1549
+ const tmpNode = {};
1550
+ for (const k in node) {
1551
+ if (["parent", "children", "element", "idMapping", "load_on_demand", "nodeClass", "tree", "isEmptyFolder"].indexOf(k) === -1 && Object.prototype.hasOwnProperty.call(node, k)) {
1552
+ const v = node[k];
1553
+ tmpNode[k] = v;
1554
+ }
1555
+ }
1556
+ if (node.hasChildren()) {
1557
+ tmpNode.children = getDataFromNodes(node.children);
1558
+ }
1559
+ return tmpNode;
1560
+ });
1561
+ };
1562
+ if (includeParent) {
1563
+ return getDataFromNodes([this]);
1564
+ } else {
1565
+ return getDataFromNodes(this.children);
1566
+ }
1567
+ }
1568
+
1569
+ /**
1570
+ * Returns the last child, or `null`. When that child is an open folder,
1571
+ * its own last child, and so on.
1572
+ *
1573
+ * @group Moving around the tree
1574
+ */
1575
+ getLastChild() {
1576
+ if (!this.hasChildren()) {
1577
+ return null;
1578
+ } else {
1579
+ const lastChild = this.children[this.children.length - 1];
1580
+ if (!(lastChild.hasChildren() && lastChild.is_open)) {
1581
+ return lastChild;
1582
+ } else {
1583
+ return lastChild.getLastChild();
1584
+ }
1585
+ }
1586
+ }
1587
+
1588
+ /**
1589
+ * Returns the depth of the node, counting the top level as `1`.
1590
+ *
1591
+ * @group Inspecting a node
1592
+ */
1593
+ getLevel() {
1594
+ let level = 0;
1595
+ let node = this; // eslint-disable-line @typescript-eslint/no-this-alias
1596
+
1597
+ while (node.parent) {
1598
+ level += 1;
1599
+ node = node.parent;
1600
+ }
1601
+ return level;
1602
+ }
1603
+
1604
+ /**
1605
+ * Returns the next node in the tree, regardless of whether it is
1606
+ * visible. `getNextNode(false)` skips the node's own children.
1607
+ *
1608
+ * @group Moving around the tree
1609
+ */
1610
+ getNextNode(includeChildren = true) {
1611
+ if (includeChildren && this.hasChildren()) {
1612
+ return this.children[0] ?? null;
1613
+ } else if (!this.parent) {
1614
+ return null;
1615
+ } else {
1616
+ const nextSibling = this.getNextSibling();
1617
+ if (nextSibling) {
1618
+ return nextSibling;
1619
+ } else {
1620
+ return this.parent.getNextNode(false);
1621
+ }
1622
+ }
1623
+ }
1624
+
1625
+ /**
1626
+ * Returns the next sibling, or `null`.
1627
+ *
1628
+ * @group Moving around the tree
1629
+ */
1630
+ getNextSibling() {
1631
+ if (!this.parent) {
1632
+ return null;
1633
+ } else {
1634
+ const nextIndex = this.parent.getChildIndex(this) + 1;
1635
+ if (nextIndex < this.parent.children.length) {
1636
+ return this.parent.children[nextIndex] ?? null;
1637
+ } else {
1638
+ return null;
1639
+ }
1640
+ }
1641
+ }
1642
+
1643
+ /**
1644
+ * Like `getNextNode`, but skipping nodes inside closed folders — this is
1645
+ * what the arrow keys use.
1646
+ *
1647
+ * @group Moving around the tree
1648
+ */
1649
+ getNextVisibleNode() {
1650
+ if (this.hasChildren() && this.is_open) {
1651
+ // First child
1652
+ return this.children[0] ?? null;
1653
+ } else {
1654
+ if (!this.parent) {
1655
+ return null;
1656
+ } else {
1657
+ const nextSibling = this.getNextSibling();
1658
+ if (nextSibling) {
1659
+ // Next sibling
1660
+ return nextSibling;
1661
+ } else {
1662
+ // Next node of parent
1663
+ return this.parent.getNextNode(false);
1664
+ }
1665
+ }
1666
+ }
1667
+ }
1668
+
1669
+ /**
1670
+ * Returns the first node for which the callback returns `true`.
1671
+ *
1672
+ * @group Searching
1673
+ */
1674
+ getNodeByCallback(callback) {
1675
+ let result = null;
1676
+ this.iterate(node => {
1677
+ if (result) {
1678
+ return false;
1679
+ } else if (callback(node)) {
1680
+ result = node;
1681
+ return false;
1682
+ } else {
1683
+ return true;
1684
+ }
1685
+ });
1686
+ return result;
1687
+ }
1688
+
1689
+ /**
1690
+ * Returns the node with this id. Only available on the root node, which
1691
+ * keeps the id index.
1692
+ *
1693
+ * @group Searching
1694
+ */
1695
+ getNodeById(nodeId) {
1696
+ return this.idMapping?.get(nodeId) ?? null;
1697
+ }
1698
+
1699
+ /**
1700
+ * Returns the first node with this name.
1701
+ *
1702
+ * @group Searching
1703
+ */
1704
+ getNodeByName(name) {
1705
+ return this.getNodeByCallback(node => node.name === name);
1706
+ }
1707
+
1708
+ /**
1709
+ * Like `getNodeByName`, but throws when there is no such node.
1710
+ *
1711
+ * @group Searching
1712
+ */
1713
+ getNodeByNameMustExist(name) {
1714
+ const node = this.getNodeByCallback(n => n.name === name);
1715
+ if (!node) {
1716
+ throw new Error(`Node with name ${name} not found`);
1717
+ }
1718
+ return node;
1719
+ }
1720
+
1721
+ /**
1722
+ * Returns all nodes with this property value.
1723
+ *
1724
+ * @group Searching
1725
+ */
1726
+ getNodesByProperty(key, value) {
1727
+ return this.filter(node => node[key] === value);
1728
+ }
1729
+
1730
+ /**
1731
+ * Returns the parent: `null` for a top-level node — the root node is not
1732
+ * returned.
1733
+ *
1734
+ * @group Moving around the tree
1735
+ */
1736
+ getParent() {
1737
+ // Return parent except if it is the root node
1738
+ if (!this.parent) {
1739
+ return null;
1740
+ } else if (!this.parent.parent) {
1741
+ // Root node -> null
1742
+ return null;
1743
+ } else {
1744
+ return this.parent;
1745
+ }
1746
+ }
1747
+
1748
+ /**
1749
+ * Returns the previous node in the tree, regardless of whether it is
1750
+ * visible.
1751
+ *
1752
+ * @group Moving around the tree
1753
+ */
1754
+ getPreviousNode() {
1755
+ if (!this.parent) {
1756
+ return null;
1757
+ } else {
1758
+ const previousSibling = this.getPreviousSibling();
1759
+ if (!previousSibling) {
1760
+ return this.getParent();
1761
+ } else if (previousSibling.hasChildren()) {
1762
+ return previousSibling.getLastChild();
1763
+ } else {
1764
+ return previousSibling;
1765
+ }
1766
+ }
1767
+ }
1768
+
1769
+ /**
1770
+ * Returns the previous sibling, or `null`.
1771
+ *
1772
+ * @group Moving around the tree
1773
+ */
1774
+ getPreviousSibling() {
1775
+ if (!this.parent) {
1776
+ return null;
1777
+ } else {
1778
+ const previousIndex = this.parent.getChildIndex(this) - 1;
1779
+ if (previousIndex >= 0) {
1780
+ return this.parent.children[previousIndex] ?? null;
1781
+ } else {
1782
+ return null;
1783
+ }
1784
+ }
1785
+ }
1786
+
1787
+ /**
1788
+ * Like `getPreviousNode`, but skipping nodes inside closed folders —
1789
+ * this is what the arrow keys use.
1790
+ *
1791
+ * @group Moving around the tree
1792
+ */
1793
+ getPreviousVisibleNode() {
1794
+ if (!this.parent) {
1795
+ return null;
1796
+ } else {
1797
+ const previousSibling = this.getPreviousSibling();
1798
+ if (!previousSibling) {
1799
+ return this.getParent();
1800
+ } else if (!previousSibling.hasChildren() || !previousSibling.is_open) {
1801
+ // Previous sibling
1802
+ return previousSibling;
1803
+ } else {
1804
+ // Last child of previous sibling
1805
+ return previousSibling.getLastChild();
1806
+ }
1807
+ }
1808
+ }
1809
+
1810
+ /**
1811
+ * Whether the node has children.
1812
+ *
1813
+ * @group Inspecting a node
1814
+ */
1815
+ hasChildren() {
1816
+ return this.children.length !== 0;
1817
+ }
1818
+
1819
+ /**
1820
+ * Init Node from data without making it the root of the tree.
1821
+ *
1822
+ * @hidden
1823
+ */
1824
+ initFromData(data) {
1825
+ const addNode = nodeData => {
1826
+ this.setData(nodeData);
1827
+ if (isNodeRecordWithChildren(nodeData) && nodeData.children.length) {
1828
+ addChildren(nodeData.children);
1829
+ }
1830
+ };
1831
+ const addChildren = childrenData => {
1832
+ for (const child of childrenData) {
1833
+ const node = this._createNode();
1834
+ node.initFromData(child);
1835
+ this.addChild(node);
1836
+ }
1837
+ };
1838
+ addNode(data);
1839
+ }
1840
+
1841
+ /**
1842
+ * `true` when the node has children, or is marked `load_on_demand`.
1843
+ *
1844
+ * @group Inspecting a node
1845
+ */
1846
+ isFolder() {
1847
+ return this.hasChildren() || this.load_on_demand;
1848
+ }
1849
+
1850
+ /**
1851
+ * Whether this node is an ancestor of the other node.
1852
+ *
1853
+ * @group Inspecting a node
1854
+ */
1855
+ isParentOf(node) {
1856
+ let parent = node.parent;
1857
+ while (parent) {
1858
+ if (parent === this) {
1859
+ return true;
1860
+ }
1861
+ parent = parent.parent;
1862
+ }
1863
+ return false;
1864
+ }
1865
+
1866
+ /**
1867
+ * Walks the subtree, calling the callback with `(node, level)`. Return
1868
+ * `false` from the callback to stop descending into that node:
1869
+ *
1870
+ * ```js
1871
+ * tree.getTree().iterate((node, level) => {
1872
+ * console.log(" ".repeat(level) + node.name);
1873
+ * return level <= 2; // don't go deeper than level 2
1874
+ * });
1875
+ * ```
1876
+ *
1877
+ * @group Searching
1878
+ */
1879
+ iterate(callback) {
1880
+ const _iterate = (node, level) => {
1881
+ for (const child of node.children) {
1882
+ const result = callback(child, level);
1883
+ if (result && child.hasChildren()) {
1884
+ _iterate(child, level + 1);
1885
+ }
1886
+ }
1887
+ };
1888
+ _iterate(this, 0);
1889
+ }
1890
+
1891
+ /**
1892
+ * Replaces the children with new node data.
1893
+ *
1894
+ * @group Changing the tree
1895
+ */
1896
+ loadFromData(data) {
1897
+ this.removeChildren();
1898
+ for (const childData of data) {
1899
+ const node = this._createNode(childData);
1900
+ this.addChild(node);
1901
+ if (isNodeRecordWithChildren(childData)) {
1902
+ node.loadFromData(childData.children);
1903
+ }
1904
+ }
1905
+ return this;
1906
+ }
1907
+
1908
+ /**
1909
+ * Moves a node relative to another node. Called on the root node.
1910
+ *
1911
+ * @param position - `"before"`, `"after"` or `"inside"`.
1912
+ * @returns `false` when the move is impossible, for instance moving a
1913
+ * node into its own subtree.
1914
+ * @group Changing the tree
1915
+ */
1916
+ moveNode(movedNode, targetNode, position) {
1917
+ if (!movedNode.parent || movedNode.isParentOf(targetNode)) {
1918
+ // - Node is parent of target node
1919
+ // - Or, parent is empty
1920
+ return false;
1921
+ } else {
1922
+ movedNode.parent._doRemoveChild(movedNode);
1923
+ switch (position) {
1924
+ case "after":
1925
+ {
1926
+ if (targetNode.parent) {
1927
+ targetNode.parent.addChildAtPosition(movedNode, targetNode.parent.getChildIndex(targetNode) + 1);
1928
+ return true;
1929
+ }
1930
+ return false;
1931
+ }
1932
+ case "before":
1933
+ {
1934
+ if (targetNode.parent) {
1935
+ targetNode.parent.addChildAtPosition(movedNode, targetNode.parent.getChildIndex(targetNode));
1936
+ return true;
1937
+ }
1938
+ return false;
1939
+ }
1940
+ case "inside":
1941
+ {
1942
+ // move inside as first child
1943
+ targetNode.addChildAtPosition(movedNode, 0);
1944
+ return true;
1945
+ }
1946
+ }
1947
+ }
1948
+ }
1949
+
1950
+ /**
1951
+ * Adds a child at the start.
1952
+ *
1953
+ * @returns The new node.
1954
+ * @group Changing the tree
1955
+ */
1956
+ prepend(nodeInfo) {
1957
+ const node = this._createNode(nodeInfo);
1958
+ this.addChildAtPosition(node, 0);
1959
+ node._loadChildrenFromData(nodeInfo);
1960
+ return node;
1961
+ }
1962
+
1963
+ /**
1964
+ * Removes this node from its parent.
1965
+ *
1966
+ * @group Changing the tree
1967
+ */
1968
+ remove() {
1969
+ if (this.parent) {
1970
+ this.parent.removeChild(this);
1971
+ this.parent = null;
1972
+ }
1973
+ }
1974
+
1975
+ /**
1976
+ * Removes a child and its children.
1977
+ *
1978
+ * @group Changing the tree
1979
+ */
1980
+ removeChild(node) {
1981
+ // remove children from the index
1982
+ node.removeChildren();
1983
+ this._doRemoveChild(node);
1984
+ }
1985
+
1986
+ /**
1987
+ * Removes all children.
1988
+ *
1989
+ * @group Changing the tree
1990
+ */
1991
+ removeChildren() {
1992
+ this.iterate(child => {
1993
+ this.tree?.removeNodeFromIndex(child);
1994
+ return true;
1995
+ });
1996
+ this.children = [];
1997
+ }
1998
+
1999
+ /** @hidden */
2000
+ removeNodeFromIndex(node) {
2001
+ if (node.id != null) {
2002
+ this.idMapping?.delete(node.id);
2003
+ }
2004
+ }
2005
+
2006
+ /**
2007
+ * Updates the node's properties from node data. `children` and `parent`
2008
+ * are ignored. A string sets the name. Existing node values are not
2009
+ * removed.
2010
+ *
2011
+ * @group Changing the tree
2012
+ */
2013
+ setData(o) {
2014
+ if (!o) {
2015
+ return;
2016
+ } else if (typeof o === "string") {
2017
+ this.name = o;
2018
+ } else if (typeof o === "object") {
2019
+ for (const key in o) {
2020
+ if (Object.prototype.hasOwnProperty.call(o, key)) {
2021
+ const value = o[key];
2022
+ if (key === "label" || key === "name") {
2023
+ // You can use the 'label' key instead of 'name'; this is a legacy feature
2024
+ if (typeof value === "string") {
2025
+ this.name = value;
2026
+ }
2027
+ } else if (key !== "children" && key !== "parent") {
2028
+ // You can't update the children or the parent using this function
2029
+ this[key] = value;
2030
+ }
2031
+ }
2032
+ }
2033
+ }
2034
+ }
2035
+ _createNode(nodeData) {
2036
+ const nodeClass = this._getNodeClass();
2037
+ return new nodeClass(nodeData);
2038
+ }
2039
+ _doRemoveChild(node) {
2040
+ this.children.splice(this.getChildIndex(node), 1);
2041
+ this.tree?.removeNodeFromIndex(node);
2042
+ }
2043
+ _getNodeClass() {
2044
+ return this.nodeClass ?? this.tree?.nodeClass ?? Node;
2045
+ }
2046
+
2047
+ // Load children data from nodeInfo if it has children
2048
+ _loadChildrenFromData(nodeInfo) {
2049
+ if (isNodeRecordWithChildren(nodeInfo) && nodeInfo.children.length) {
2050
+ this.loadFromData(nodeInfo.children);
2051
+ }
2052
+ }
2053
+ _setParent(parent) {
2054
+ this.parent = parent;
2055
+ this.tree = parent.tree;
2056
+ this.tree?.addNodeToIndex(this);
2057
+ }
2058
+ }
2059
+
2060
+ class BorderDropHint {
2061
+ _hint;
2062
+ constructor(element, scrollLeft, classNames) {
2063
+ const div = element.querySelector(`:scope > .${classNames.element}`);
2064
+ if (!div) {
2065
+ this._hint = undefined;
2066
+ return;
2067
+ }
2068
+ const width = Math.max(element.offsetWidth + scrollLeft - 4, 0);
2069
+ const height = Math.max(element.clientHeight - 4, 0);
2070
+ const hint = document.createElement("span");
2071
+ hint.className = classNames.border;
2072
+ hint.style.width = `${width}px`;
2073
+ hint.style.height = `${height}px`;
2074
+ this._hint = hint;
2075
+ div.append(this._hint);
2076
+ }
2077
+ remove() {
2078
+ this._hint?.remove();
2079
+ }
2080
+ }
2081
+
2082
+ class GhostDropHint {
2083
+ _classNames;
2084
+ _element;
2085
+ _ghost;
2086
+ _node;
2087
+ constructor(node, element, position, classNames) {
2088
+ this._classNames = classNames;
2089
+ this._element = element;
2090
+ this._node = node;
2091
+ this._ghost = this._createGhostElement();
2092
+ switch (position) {
2093
+ case "after":
2094
+ this._moveAfter();
2095
+ break;
2096
+ case "before":
2097
+ this._moveBefore();
2098
+ break;
2099
+ case "inside":
2100
+ {
2101
+ if (node.isFolder() && node.is_open) {
2102
+ this._moveInsideOpenFolder();
2103
+ } else {
2104
+ this._moveInside();
2105
+ }
2106
+ }
2107
+ }
2108
+ }
2109
+ remove() {
2110
+ this._ghost.remove();
2111
+ }
2112
+ _createGhostElement() {
2113
+ const {
2114
+ circle,
2115
+ common,
2116
+ ghost: ghostClass,
2117
+ line
2118
+ } = this._classNames;
2119
+ const ghost = document.createElement("li");
2120
+ ghost.className = `${common} ${ghostClass}`;
2121
+ const circleSpan = document.createElement("span");
2122
+ circleSpan.className = `${common} ${circle}`;
2123
+ ghost.append(circleSpan);
2124
+ const lineSpan = document.createElement("span");
2125
+ lineSpan.className = `${common} ${line}`;
2126
+ ghost.append(lineSpan);
2127
+ return ghost;
2128
+ }
2129
+ _moveAfter() {
2130
+ this._element.after(this._ghost);
2131
+ }
2132
+ _moveBefore() {
2133
+ this._element.before(this._ghost);
2134
+ }
2135
+ _moveInside() {
2136
+ this._element.after(this._ghost);
2137
+ this._ghost.classList.add(this._classNames.inside);
2138
+ }
2139
+ _moveInsideOpenFolder() {
2140
+ const childElement = this._node.children[0]?.element;
2141
+ if (childElement) {
2142
+ childElement.before(this._ghost);
2143
+ }
2144
+ }
2145
+ }
2146
+
2147
+ class NodeElement {
2148
+ element;
2149
+ node;
2150
+ _classNames;
2151
+ _getScrollLeft;
2152
+ _tabIndex;
2153
+ _treeElement;
2154
+ constructor({
2155
+ classNames,
2156
+ getScrollLeft,
2157
+ node,
2158
+ tabIndex,
2159
+ treeElement
2160
+ }) {
2161
+ this._classNames = classNames;
2162
+ this._getScrollLeft = getScrollLeft;
2163
+ this.node = node;
2164
+ this._tabIndex = tabIndex;
2165
+ this._treeElement = treeElement;
2166
+ node.element ??= this._treeElement;
2167
+ this.element = node.element;
2168
+ }
2169
+ addDropHint(position) {
2170
+ if (this._mustShowBorderDropHint(position)) {
2171
+ return new BorderDropHint(this.element, this._getScrollLeft(), this._classNames);
2172
+ } else {
2173
+ return new GhostDropHint(this.node, this.element, position, this._classNames);
2174
+ }
2175
+ }
2176
+ deselect() {
2177
+ this.element.classList.remove(this._classNames.selected);
2178
+ const titleSpan = this._getTitleSpan();
2179
+ titleSpan.removeAttribute("tabindex");
2180
+ titleSpan.setAttribute("aria-selected", "false");
2181
+ titleSpan.blur();
2182
+ }
2183
+ select(mustSetFocus) {
2184
+ this.element.classList.add(this._classNames.selected);
2185
+ const titleSpan = this._getTitleSpan();
2186
+ const tabIndex = this._tabIndex;
2187
+
2188
+ // Check for null or undefined
2189
+ if (tabIndex != null) {
2190
+ titleSpan.setAttribute("tabindex", tabIndex.toString());
2191
+ }
2192
+ titleSpan.setAttribute("aria-selected", "true");
2193
+ if (mustSetFocus) {
2194
+ titleSpan.focus();
2195
+ }
2196
+ }
2197
+ _getTitleSpan() {
2198
+ return this.element.querySelector(`:scope > .${this._classNames.element} > span.${this._classNames.title}`);
2199
+ }
2200
+ _getUl() {
2201
+ return this.element.querySelector(":scope > ul");
2202
+ }
2203
+ _mustShowBorderDropHint(position) {
2204
+ return position === "inside";
2205
+ }
2206
+ }
2207
+
2208
+ const getAnimationDuration = duration => {
2209
+ if (typeof duration === "number") {
2210
+ return duration;
2211
+ }
2212
+ return duration === "slow" ? 600 : 200;
2213
+ };
2214
+ const slideDown = (element, animationSpeed, onFinished) => {
2215
+ element.style.display = "block";
2216
+ const animation = element.animate([{
2217
+ height: "0",
2218
+ overflow: "hidden"
2219
+ }, {
2220
+ height: `${element.scrollHeight}px`,
2221
+ overflow: "hidden"
2222
+ }], {
2223
+ duration: getAnimationDuration(animationSpeed)
2224
+ });
2225
+ animation.onfinish = () => {
2226
+ onFinished();
2227
+ };
2228
+ };
2229
+ const slideUp = (element, animationSpeed, onFinished) => {
2230
+ const animation = element.animate([{
2231
+ height: `${element.scrollHeight}px`,
2232
+ overflow: "hidden"
2233
+ }, {
2234
+ height: "0",
2235
+ overflow: "hidden"
2236
+ }], {
2237
+ duration: getAnimationDuration(animationSpeed)
2238
+ });
2239
+ animation.onfinish = () => {
2240
+ element.style.display = "none";
2241
+ onFinished();
2242
+ };
2243
+ };
2244
+
2245
+ class FolderElement extends NodeElement {
2246
+ _closedIconElement;
2247
+ _openedIconElement;
2248
+ _triggerEvent;
2249
+ constructor({
2250
+ classNames,
2251
+ closedIconElement,
2252
+ getScrollLeft,
2253
+ node,
2254
+ openedIconElement,
2255
+ tabIndex,
2256
+ treeElement,
2257
+ triggerEvent
2258
+ }) {
2259
+ super({
2260
+ classNames,
2261
+ getScrollLeft,
2262
+ node,
2263
+ tabIndex,
2264
+ treeElement
2265
+ });
2266
+ this._closedIconElement = closedIconElement;
2267
+ this._openedIconElement = openedIconElement;
2268
+ this._triggerEvent = triggerEvent;
2269
+ }
2270
+ close(slide, animationSpeed) {
2271
+ if (!this.node.is_open) {
2272
+ return;
2273
+ }
2274
+ this.node.is_open = false;
2275
+ const button = this._getButton();
2276
+ button.classList.add(this._classNames.closed);
2277
+ button.innerHTML = "";
2278
+ const closedIconElement = this._closedIconElement;
2279
+ if (closedIconElement) {
2280
+ const icon = closedIconElement.cloneNode(true);
2281
+ button.appendChild(icon);
2282
+ }
2283
+ const doClose = () => {
2284
+ this.element.classList.add(this._classNames.closed);
2285
+ const titleSpan = this._getTitleSpan();
2286
+ titleSpan.setAttribute("aria-expanded", "false");
2287
+ this._triggerEvent("tree.close", {
2288
+ node: this.node
2289
+ });
2290
+ };
2291
+ const ul = this._getUl();
2292
+ if (slide) {
2293
+ slideUp(ul, animationSpeed, doClose);
2294
+ } else {
2295
+ ul.style.display = "none";
2296
+ doClose();
2297
+ }
2298
+ }
2299
+ async open(slide, animationSpeed) {
2300
+ return new Promise(resolve => {
2301
+ if (this.node.is_open) {
2302
+ resolve();
2303
+ return;
2304
+ }
2305
+ this.node.is_open = true;
2306
+ const button = this._getButton();
2307
+ button.classList.remove(this._classNames.closed);
2308
+ button.innerHTML = "";
2309
+ const openedIconElement = this._openedIconElement;
2310
+ if (openedIconElement) {
2311
+ const icon = openedIconElement.cloneNode(true);
2312
+ button.appendChild(icon);
2313
+ }
2314
+ const doOpen = () => {
2315
+ this.element.classList.remove(this._classNames.closed);
2316
+ const titleSpan = this._getTitleSpan();
2317
+ titleSpan.setAttribute("aria-expanded", "true");
2318
+ this._triggerEvent("tree.open", {
2319
+ node: this.node
2320
+ });
2321
+ resolve();
2322
+ };
2323
+ const ul = this._getUl();
2324
+ if (slide) {
2325
+ slideDown(ul, animationSpeed, doOpen);
2326
+ } else {
2327
+ ul.style.display = "block";
2328
+ doOpen();
2329
+ }
2330
+ });
2331
+ }
2332
+ _mustShowBorderDropHint(position) {
2333
+ return !this.node.is_open && position === "inside";
2334
+ }
2335
+ _getButton() {
2336
+ return this.element.querySelector(`:scope > .${this._classNames.element} > a.${this._classNames.toggler}`);
2337
+ }
2338
+ }
2339
+
2340
+ // Url class for absolute and relative urls.
2341
+
2342
+ const isAbsoluteUrl = inputUrl => {
2343
+ try {
2344
+ new URL(inputUrl);
2345
+ return true;
2346
+ } catch {
2347
+ return false;
2348
+ }
2349
+ };
2350
+ const LOCALHOST = "http://localhost";
2351
+ class RequestUrl {
2352
+ _isAbsolute;
2353
+ _url;
2354
+ constructor(inputUrl) {
2355
+ if (isAbsoluteUrl(inputUrl)) {
2356
+ this._url = new URL(inputUrl);
2357
+ this._isAbsolute = true;
2358
+ } else {
2359
+ this._url = new URL(inputUrl, LOCALHOST);
2360
+ this._isAbsolute = false;
2361
+ }
2362
+ }
2363
+ setSearchParam(key, value) {
2364
+ this._url.searchParams.set(key, value);
2365
+ }
2366
+ toString() {
2367
+ if (this._isAbsolute) {
2368
+ return this._url.href;
2369
+ } else {
2370
+ return this._url.href.slice(LOCALHOST.length);
2371
+ }
2372
+ }
2373
+ }
2374
+
2375
+ class SaveStateHandler {
2376
+ _addToSelection;
2377
+ _getNodeById;
2378
+ _getSelectedNodes;
2379
+ _getTree;
2380
+ _onGetStateFromStorage;
2381
+ _onSetStateFromStorage;
2382
+ _openNode;
2383
+ _refreshElements;
2384
+ _removeFromSelection;
2385
+ _saveStateOption;
2386
+ constructor({
2387
+ addToSelection,
2388
+ getNodeById,
2389
+ getSelectedNodes,
2390
+ getTree,
2391
+ onGetStateFromStorage,
2392
+ onSetStateFromStorage,
2393
+ openNode,
2394
+ refreshElements,
2395
+ removeFromSelection,
2396
+ saveState
2397
+ }) {
2398
+ this._addToSelection = addToSelection;
2399
+ this._getNodeById = getNodeById;
2400
+ this._getSelectedNodes = getSelectedNodes;
2401
+ this._getTree = getTree;
2402
+ this._onGetStateFromStorage = onGetStateFromStorage;
2403
+ this._onSetStateFromStorage = onSetStateFromStorage;
2404
+ this._openNode = openNode;
2405
+ this._refreshElements = refreshElements;
2406
+ this._removeFromSelection = removeFromSelection;
2407
+ this._saveStateOption = saveState;
2408
+ }
2409
+ getNodeIdToBeSelected() {
2410
+ if (!this._saveStateOption) {
2411
+ return null;
2412
+ }
2413
+ const state = this.getStateFromStorage();
2414
+ if (state?.selected_node) {
2415
+ return state.selected_node[0] ?? null;
2416
+ } else {
2417
+ return null;
2418
+ }
2419
+ }
2420
+ getState() {
2421
+ const getOpenNodeIds = () => {
2422
+ const openNodes = [];
2423
+ this._getTree()?.iterate(node => {
2424
+ if (node.is_open && node.id && node.hasChildren()) {
2425
+ openNodes.push(node.id);
2426
+ }
2427
+ return true;
2428
+ });
2429
+ return openNodes;
2430
+ };
2431
+ const getSelectedNodeIds = () => {
2432
+ const selectedNodeIds = [];
2433
+ this._getSelectedNodes().forEach(node => {
2434
+ if (node.id != null) {
2435
+ selectedNodeIds.push(node.id);
2436
+ }
2437
+ });
2438
+ return selectedNodeIds;
2439
+ };
2440
+ return {
2441
+ open_nodes: getOpenNodeIds(),
2442
+ selected_node: getSelectedNodeIds()
2443
+ };
2444
+ }
2445
+ getStateFromStorage() {
2446
+ if (!this._saveStateOption) {
2447
+ return null;
2448
+ }
2449
+ const jsonData = this._loadFromStorage();
2450
+ if (jsonData) {
2451
+ return this._parseState(jsonData);
2452
+ } else {
2453
+ return null;
2454
+ }
2455
+ }
2456
+ saveState() {
2457
+ if (!this._saveStateOption) {
2458
+ return;
2459
+ }
2460
+ const state = JSON.stringify(this.getState());
2461
+ if (this._onSetStateFromStorage) {
2462
+ this._onSetStateFromStorage(state);
2463
+ } else {
2464
+ localStorage.setItem(this._getKeyName(), state);
2465
+ }
2466
+ }
2467
+
2468
+ /*
2469
+ Set initial state
2470
+ Don't handle nodes that are loaded on demand
2471
+ result: must load on demand (boolean)
2472
+ */
2473
+ setInitialState(state) {
2474
+ let mustLoadOnDemand = false;
2475
+ if (state.open_nodes) {
2476
+ mustLoadOnDemand = this._openInitialNodes(state.open_nodes);
2477
+ }
2478
+ this._resetSelection();
2479
+ if (state.selected_node) {
2480
+ this._selectInitialNodes(state.selected_node);
2481
+ }
2482
+ return mustLoadOnDemand;
2483
+ }
2484
+ async setInitialStateOnDemand(state) {
2485
+ let nodeIds = state.open_nodes;
2486
+ const openNodes = async () => {
2487
+ if (!nodeIds) {
2488
+ return;
2489
+ }
2490
+ const newNodesIds = [];
2491
+ for (const nodeId of nodeIds) {
2492
+ const node = this._getNodeById(nodeId);
2493
+ if (!node) {
2494
+ newNodesIds.push(nodeId);
2495
+ } else {
2496
+ if (!node.is_loading) {
2497
+ if (node.load_on_demand) {
2498
+ await loadAndOpenNode(node);
2499
+ } else {
2500
+ await this._openNode(node, false);
2501
+ }
2502
+ }
2503
+ }
2504
+ }
2505
+ nodeIds = newNodesIds;
2506
+ if (state.selected_node) {
2507
+ if (this._selectInitialNodes(state.selected_node)) {
2508
+ this._refreshElements(null);
2509
+ }
2510
+ }
2511
+ };
2512
+ const loadAndOpenNode = async node => {
2513
+ await this._openNode(node, false);
2514
+ await openNodes();
2515
+ };
2516
+ await openNodes();
2517
+ }
2518
+ _getKeyName() {
2519
+ if (typeof this._saveStateOption === "string") {
2520
+ return this._saveStateOption;
2521
+ } else {
2522
+ return "tree";
2523
+ }
2524
+ }
2525
+ _loadFromStorage() {
2526
+ if (this._onGetStateFromStorage) {
2527
+ return this._onGetStateFromStorage();
2528
+ } else {
2529
+ return localStorage.getItem(this._getKeyName());
2530
+ }
2531
+ }
2532
+ _openInitialNodes(nodeIds) {
2533
+ let mustLoadOnDemand = false;
2534
+ for (const nodeId of nodeIds) {
2535
+ const node = this._getNodeById(nodeId);
2536
+ if (node) {
2537
+ if (!node.load_on_demand) {
2538
+ node.is_open = true;
2539
+ } else {
2540
+ mustLoadOnDemand = true;
2541
+ }
2542
+ }
2543
+ }
2544
+ return mustLoadOnDemand;
2545
+ }
2546
+ _parseState(jsonData) {
2547
+ const state = JSON.parse(jsonData);
2548
+
2549
+ // Check if selected_node is an int (instead of an array)
2550
+ if (state.selected_node && isInt(state.selected_node)) {
2551
+ // Convert to array
2552
+ state.selected_node = [state.selected_node];
2553
+ }
2554
+ return state;
2555
+ }
2556
+ _resetSelection() {
2557
+ const selectedNodes = this._getSelectedNodes();
2558
+ selectedNodes.forEach(node => {
2559
+ this._removeFromSelection(node);
2560
+ });
2561
+ }
2562
+ _selectInitialNodes(nodeIds) {
2563
+ let selectCount = 0;
2564
+ for (const nodeId of nodeIds) {
2565
+ const node = this._getNodeById(nodeId);
2566
+ if (node) {
2567
+ selectCount += 1;
2568
+ this._addToSelection(node);
2569
+ }
2570
+ }
2571
+ return selectCount !== 0;
2572
+ }
2573
+ }
2574
+
2575
+ class ScrollParent {
2576
+ _container;
2577
+ _horizontalScrollDirection;
2578
+ _horizontalScrollTimeout;
2579
+ _refreshHitAreas;
2580
+ _verticalScrollDirection;
2581
+ _verticalScrollTimeout;
2582
+ constructor({
2583
+ container,
2584
+ refreshHitAreas
2585
+ }) {
2586
+ this._container = container;
2587
+ this._refreshHitAreas = refreshHitAreas;
2588
+ }
2589
+ checkHorizontalScrolling(pageX) {
2590
+ const newHorizontalScrollDirection = this._getNewHorizontalScrollDirection(pageX);
2591
+ if (this._horizontalScrollDirection !== newHorizontalScrollDirection) {
2592
+ this._horizontalScrollDirection = newHorizontalScrollDirection;
2593
+ if (this._horizontalScrollTimeout != null) {
2594
+ window.clearTimeout(this._horizontalScrollTimeout);
2595
+ }
2596
+ if (newHorizontalScrollDirection) {
2597
+ this._horizontalScrollTimeout = window.setTimeout(this._scrollHorizontally.bind(this), 40);
2598
+ }
2599
+ }
2600
+ }
2601
+ checkVerticalScrolling(pageY) {
2602
+ const newVerticalScrollDirection = this._getNewVerticalScrollDirection(pageY);
2603
+ if (this._verticalScrollDirection !== newVerticalScrollDirection) {
2604
+ this._verticalScrollDirection = newVerticalScrollDirection;
2605
+ if (this._verticalScrollTimeout != null) {
2606
+ window.clearTimeout(this._verticalScrollTimeout);
2607
+ this._verticalScrollTimeout = undefined;
2608
+ }
2609
+ if (newVerticalScrollDirection) {
2610
+ this._verticalScrollTimeout = window.setTimeout(this._scrollVertically.bind(this), 40);
2611
+ }
2612
+ }
2613
+ }
2614
+ getScrollLeft() {
2615
+ return this._container.scrollLeft;
2616
+ }
2617
+ scrollToY(top) {
2618
+ this._container.scrollTop = top;
2619
+ }
2620
+ stopScrolling() {
2621
+ this._horizontalScrollDirection = undefined;
2622
+ this._verticalScrollDirection = undefined;
2623
+ }
2624
+ _scrollHorizontally() {
2625
+ if (!this._horizontalScrollDirection) {
2626
+ return;
2627
+ }
2628
+ const distance = this._horizontalScrollDirection === "left" ? -20 : 20;
2629
+ this._container.scrollBy({
2630
+ behavior: "instant",
2631
+ left: distance,
2632
+ top: 0
2633
+ });
2634
+ this._refreshHitAreas();
2635
+ setTimeout(this._scrollHorizontally.bind(this), 40);
2636
+ }
2637
+ _scrollVertically() {
2638
+ if (!this._verticalScrollDirection) {
2639
+ return;
2640
+ }
2641
+ const distance = this._verticalScrollDirection === "top" ? -20 : 20;
2642
+ this._container.scrollBy({
2643
+ behavior: "instant",
2644
+ left: 0,
2645
+ top: distance
2646
+ });
2647
+ this._refreshHitAreas();
2648
+ setTimeout(this._scrollVertically.bind(this), 40);
2649
+ }
2650
+ }
2651
+
2652
+ class ContainerScrollParent extends ScrollParent {
2653
+ _scrollParentBottom;
2654
+ _scrollParentTop;
2655
+ stopScrolling() {
2656
+ super.stopScrolling();
2657
+ this._horizontalScrollDirection = undefined;
2658
+ this._verticalScrollDirection = undefined;
2659
+ }
2660
+ _getNewHorizontalScrollDirection(pageX) {
2661
+ const scrollParentOffset = getElementPosition(this._container);
2662
+ const containerWidth = this._container.getBoundingClientRect().width;
2663
+ const rightEdge = scrollParentOffset.left + containerWidth;
2664
+ const leftEdge = scrollParentOffset.left;
2665
+ const isNearRightEdge = pageX > rightEdge - 20;
2666
+ const isNearLeftEdge = pageX < leftEdge + 20;
2667
+ if (isNearRightEdge) {
2668
+ return "right";
2669
+ } else if (isNearLeftEdge) {
2670
+ return "left";
2671
+ }
2672
+ return undefined;
2673
+ }
2674
+ _getNewVerticalScrollDirection(pageY) {
2675
+ if (pageY < this._getScrollParentTop()) {
2676
+ return "top";
2677
+ }
2678
+ if (pageY > this._getScrollParentBottom()) {
2679
+ return "bottom";
2680
+ }
2681
+ return undefined;
2682
+ }
2683
+ _getScrollParentBottom() {
2684
+ if (this._scrollParentBottom == null) {
2685
+ const containerHeight = this._container.getBoundingClientRect().height;
2686
+ this._scrollParentBottom = this._getScrollParentTop() + containerHeight;
2687
+ }
2688
+ return this._scrollParentBottom;
2689
+ }
2690
+ _getScrollParentTop() {
2691
+ this._scrollParentTop ??= getOffsetTop(this._container);
2692
+ return this._scrollParentTop;
2693
+ }
2694
+ }
2695
+
2696
+ class DocumentScrollParent extends ScrollParent {
2697
+ _documentScrollHeight;
2698
+ _documentScrollWidth;
2699
+ _treeElement;
2700
+ constructor({
2701
+ refreshHitAreas,
2702
+ treeElement
2703
+ }) {
2704
+ super({
2705
+ container: document.documentElement,
2706
+ refreshHitAreas
2707
+ });
2708
+ this._treeElement = treeElement;
2709
+ }
2710
+ scrollToY(top) {
2711
+ const treeTop = getOffsetTop(this._treeElement);
2712
+ super.scrollToY(top + treeTop);
2713
+ }
2714
+ stopScrolling() {
2715
+ super.stopScrolling();
2716
+ this._documentScrollHeight = undefined;
2717
+ this._documentScrollWidth = undefined;
2718
+ }
2719
+ _getNewHorizontalScrollDirection(pageX) {
2720
+ const scrollLeft = this._container.scrollLeft;
2721
+ const windowWidth = window.innerWidth;
2722
+ const isNearRightEdge = pageX > windowWidth - 20;
2723
+ const isNearLeftEdge = pageX - scrollLeft < 20;
2724
+ if (isNearRightEdge && this._canScrollRight()) {
2725
+ return "right";
2726
+ }
2727
+ if (isNearLeftEdge) {
2728
+ return "left";
2729
+ }
2730
+ return undefined;
2731
+ }
2732
+ _getNewVerticalScrollDirection(pageY) {
2733
+ const scrollTop = this._container.scrollTop;
2734
+ const distanceTop = pageY - scrollTop;
2735
+ if (distanceTop < 20) {
2736
+ return "top";
2737
+ }
2738
+ const windowHeight = window.innerHeight;
2739
+ if (windowHeight - (pageY - scrollTop) < 20 && this._canScrollDown()) {
2740
+ return "bottom";
2741
+ }
2742
+ return undefined;
2743
+ }
2744
+ _canScrollDown() {
2745
+ return this._container.scrollTop + this._container.clientHeight < this._getDocumentScrollHeight();
2746
+ }
2747
+ _canScrollRight() {
2748
+ return this._container.scrollLeft + this._container.clientWidth < this._getDocumentScrollWidth();
2749
+ }
2750
+ _getDocumentScrollHeight() {
2751
+ // Store the original scroll height because the scroll height can increase when the drag element is moved beyond the scroll height.
2752
+ this._documentScrollHeight ??= this._container.scrollHeight;
2753
+ return this._documentScrollHeight;
2754
+ }
2755
+ _getDocumentScrollWidth() {
2756
+ // Store the original scroll width because the scroll width can increase when the drag element is moved beyond the scroll width.
2757
+ this._documentScrollWidth ??= this._container.scrollWidth;
2758
+ return this._documentScrollWidth;
2759
+ }
2760
+ }
2761
+
2762
+ const isOverflow = overflowValue => overflowValue === "auto" || overflowValue === "scroll";
2763
+ const hasOverFlow = element => {
2764
+ const style = getComputedStyle(element);
2765
+ return isOverflow(style.overflowX) || isOverflow(style.overflowY);
2766
+ };
2767
+ const getParentWithOverflow = treeElement => {
2768
+ if (hasOverFlow(treeElement)) {
2769
+ return treeElement;
2770
+ }
2771
+ let parent = treeElement.parentElement;
2772
+ while (parent) {
2773
+ if (hasOverFlow(parent)) {
2774
+ return parent;
2775
+ }
2776
+ parent = parent.parentElement;
2777
+ }
2778
+ return null;
2779
+ };
2780
+ const createScrollParent = (treeElement, refreshHitAreas) => {
2781
+ const container = getParentWithOverflow(treeElement);
2782
+ if (container && container.tagName !== "HTML") {
2783
+ return new ContainerScrollParent({
2784
+ container,
2785
+ refreshHitAreas
2786
+ });
2787
+ } else {
2788
+ return new DocumentScrollParent({
2789
+ refreshHitAreas,
2790
+ treeElement
2791
+ });
2792
+ }
2793
+ };
2794
+
2795
+ class ScrollHandler {
2796
+ _refreshHitAreas;
2797
+ _scrollParent;
2798
+ _treeElement;
2799
+ constructor({
2800
+ refreshHitAreas,
2801
+ treeElement
2802
+ }) {
2803
+ this._refreshHitAreas = refreshHitAreas;
2804
+ this._scrollParent = undefined;
2805
+ this._treeElement = treeElement;
2806
+ }
2807
+ checkScrolling(positionInfo) {
2808
+ this._checkVerticalScrolling(positionInfo);
2809
+ this._checkHorizontalScrolling(positionInfo);
2810
+ }
2811
+ getScrollLeft() {
2812
+ return this._getScrollParent().getScrollLeft();
2813
+ }
2814
+ scrollToY(top) {
2815
+ this._getScrollParent().scrollToY(top);
2816
+ }
2817
+ stopScrolling() {
2818
+ this._getScrollParent().stopScrolling();
2819
+ }
2820
+ _checkHorizontalScrolling(positionInfo) {
2821
+ this._getScrollParent().checkHorizontalScrolling(positionInfo.pageX);
2822
+ }
2823
+ _checkVerticalScrolling(positionInfo) {
2824
+ this._getScrollParent().checkVerticalScrolling(positionInfo.pageY);
2825
+ }
2826
+ _getScrollParent() {
2827
+ this._scrollParent ??= createScrollParent(this._treeElement, this._refreshHitAreas);
2828
+ return this._scrollParent;
2829
+ }
2830
+ }
2831
+
2832
+ class SelectNodeHandler {
2833
+ _getNodeById;
2834
+ _getNodeElementForNode;
2835
+ _getOnCanSelectNode;
2836
+ _getSelectable;
2837
+ _openParents;
2838
+ _saveState;
2839
+ _selectedNodes;
2840
+ _selectedSingleNode;
2841
+ _triggerEvent;
2842
+ constructor({
2843
+ getNodeById,
2844
+ getNodeElementForNode,
2845
+ getOnCanSelectNode,
2846
+ getSelectable,
2847
+ openParents,
2848
+ saveState,
2849
+ triggerEvent
2850
+ }) {
2851
+ this._getNodeById = getNodeById;
2852
+ this._getNodeElementForNode = getNodeElementForNode;
2853
+ this._getOnCanSelectNode = getOnCanSelectNode;
2854
+ this._getSelectable = getSelectable;
2855
+ this._openParents = openParents;
2856
+ this._saveState = saveState;
2857
+ this._selectedNodes = new Set();
2858
+ this._selectedSingleNode = null;
2859
+ this._triggerEvent = triggerEvent;
2860
+ }
2861
+ addToSelection(node) {
2862
+ if (node.id != null) {
2863
+ this._selectedNodes.add(node.id);
2864
+ } else {
2865
+ this._selectedSingleNode = node;
2866
+ }
2867
+ }
2868
+ clear() {
2869
+ this._selectedNodes.clear();
2870
+ this._selectedSingleNode = null;
2871
+ }
2872
+ getSelectedNode() {
2873
+ const selectedNodes = this.getSelectedNodes();
2874
+ if (selectedNodes.length) {
2875
+ return selectedNodes[0] ?? false;
2876
+ } else {
2877
+ return false;
2878
+ }
2879
+ }
2880
+ getSelectedNodes() {
2881
+ if (this._selectedSingleNode) {
2882
+ return [this._selectedSingleNode];
2883
+ } else {
2884
+ const selectedNodes = [];
2885
+ this._selectedNodes.forEach(id => {
2886
+ const node = this._getNodeById(id);
2887
+ if (node) {
2888
+ selectedNodes.push(node);
2889
+ }
2890
+ });
2891
+ return selectedNodes;
2892
+ }
2893
+ }
2894
+ getSelectedNodesUnder(parent) {
2895
+ if (this._selectedSingleNode) {
2896
+ if (parent.isParentOf(this._selectedSingleNode)) {
2897
+ return [this._selectedSingleNode];
2898
+ } else {
2899
+ return [];
2900
+ }
2901
+ } else {
2902
+ const selectedNodes = [];
2903
+ this._selectedNodes.forEach(id => {
2904
+ const node = this._getNodeById(id);
2905
+ if (node && parent.isParentOf(node)) {
2906
+ selectedNodes.push(node);
2907
+ }
2908
+ });
2909
+ return selectedNodes;
2910
+ }
2911
+ }
2912
+ isNodeSelected(node) {
2913
+ if (node.id != null) {
2914
+ return this._selectedNodes.has(node.id);
2915
+ } else if (this._selectedSingleNode) {
2916
+ return this._selectedSingleNode.element === node.element;
2917
+ } else {
2918
+ return false;
2919
+ }
2920
+ }
2921
+ removeFromSelection(node, includeChildren = false) {
2922
+ if (node.id == null) {
2923
+ if (this._selectedSingleNode && node.element === this._selectedSingleNode.element) {
2924
+ this._selectedSingleNode = null;
2925
+ }
2926
+ } else {
2927
+ this._selectedNodes.delete(node.id);
2928
+ if (includeChildren) {
2929
+ node.iterate(() => {
2930
+ if (node.id != null) {
2931
+ this._selectedNodes.delete(node.id);
2932
+ }
2933
+ return true;
2934
+ });
2935
+ }
2936
+ }
2937
+ }
2938
+
2939
+ /* Select a single node.
2940
+ * Renders the changed elements.
2941
+ * Deselects if the node is currently selected (if the mustToggle is on).
2942
+ * Deselects the previously selected node.
2943
+ * Check if the node is selectable.
2944
+ * Saves the state.
2945
+ * Options:
2946
+ * mustSetFocus: set the focus to the selected node
2947
+ * mustToggle: support deselecting the selected node
2948
+ */
2949
+ selectSingleNode(node, optionsParam) {
2950
+ const defaultOptions = {
2951
+ mustSetFocus: true,
2952
+ mustToggle: true
2953
+ };
2954
+ const selectOptions = {
2955
+ ...defaultOptions,
2956
+ ...(optionsParam ?? {})
2957
+ };
2958
+ const canSelect = () => {
2959
+ if (!this._getSelectable()) {
2960
+ return false;
2961
+ }
2962
+ const onCanSelectNode = this._getOnCanSelectNode();
2963
+ return onCanSelectNode ? onCanSelectNode(node) : true;
2964
+ };
2965
+ if (!canSelect()) {
2966
+ return;
2967
+ }
2968
+ const deselectCurrentNode = deselectedNode => {
2969
+ this.removeFromSelection(deselectedNode);
2970
+ this._getNodeElementForNode(deselectedNode).deselect();
2971
+ };
2972
+ if (this.isNodeSelected(node)) {
2973
+ if (selectOptions.mustToggle) {
2974
+ deselectCurrentNode(node);
2975
+ this._triggerEvent("tree.deselect", {
2976
+ node
2977
+ });
2978
+ }
2979
+ } else {
2980
+ const deselectedNode = this.getSelectedNode();
2981
+ if (deselectedNode) {
2982
+ deselectCurrentNode(deselectedNode);
2983
+ }
2984
+ this.addToSelection(node);
2985
+ this._openParents(node);
2986
+ this._getNodeElementForNode(node).select(selectOptions.mustSetFocus);
2987
+ this._triggerEvent("tree.select", {
2988
+ deselectedNode: deselectedNode || null,
2989
+ node
2990
+ });
2991
+ }
2992
+ this._saveState();
2993
+ }
2994
+ }
2995
+
2996
+ const defaults = {
2997
+ animationSpeed: "fast",
2998
+ autoEscape: true,
2999
+ autoOpen: false,
3000
+ // true / false / int (open n levels starting at 0)
3001
+ buttonLeft: true,
3002
+ classPrefix: DEFAULT_CLASS_PREFIX,
3003
+ // the prefix of all css classes
3004
+ // The symbol to use for a closed node - ► BLACK RIGHT-POINTING POINTER
3005
+ // http://www.fileformat.info/info/unicode/char/25ba/index.htm
3006
+ closedIcon: undefined,
3007
+ commonClassName: undefined,
3008
+ // the class of every element; the default is "<classPrefix>-common"
3009
+ data: undefined,
3010
+ dataFilter: undefined,
3011
+ dataUrl: undefined,
3012
+ dragAndDrop: false,
3013
+ keyboardSupport: true,
3014
+ nodeClass: Node,
3015
+ onCanMove: undefined,
3016
+ // Can this node be moved?
3017
+ onCanMoveTo: undefined,
3018
+ // Can this node be moved to this position? function(moved_node, target_node, position)
3019
+ onCanSelectNode: undefined,
3020
+ onCreateLi: undefined,
3021
+ onDragMove: undefined,
3022
+ onDragStop: undefined,
3023
+ onGetStateFromStorage: undefined,
3024
+ onIsMoveHandle: undefined,
3025
+ onSetStateFromStorage: undefined,
3026
+ openedIcon: undefined,
3027
+ openFolderDelay: 500,
3028
+ // The delay for opening a folder during drag and drop; the value is in milliseconds
3029
+ // The symbol to use for an open node - ▼ BLACK DOWN-POINTING TRIANGLE
3030
+ // http://www.fileformat.info/info/unicode/char/25bc/index.htm
3031
+ rtl: undefined,
3032
+ // right-to-left support; true / false (default)
3033
+ saveState: false,
3034
+ // true / false / string (local storage key; the default key is "tree")
3035
+ selectable: true,
3036
+ showEmptyFolder: false,
3037
+ slide: true,
3038
+ // must display slide animation?
3039
+ startDndDelay: 300,
3040
+ // The delay for starting dnd (in milliseconds)
3041
+ tabIndex: 0,
3042
+ treeClassName: undefined,
3043
+ // the class of the root element; the default is classPrefix
3044
+ useContextMenu: true
3045
+ };
3046
+ const setDefaultOptions = (htmlElement, inputOptions) => {
3047
+ const options = {
3048
+ ...defaults,
3049
+ ...inputOptions
3050
+ };
3051
+ options.dataUrl ??= htmlElement.dataset.url;
3052
+ options.rtl ??= getRtlOptionFromHTMLElement(htmlElement);
3053
+ options.closedIcon ??= getDefaultClosedIcon(options);
3054
+ options.openedIcon ??= "&#x25bc;";
3055
+ return options;
3056
+ };
3057
+ const getDefaultClosedIcon = options => {
3058
+ if (options.rtl) {
3059
+ // triangle to the left
3060
+ return "&#x25c0;";
3061
+ } else {
3062
+ // triangle to the right
3063
+ return "&#x25ba;";
3064
+ }
3065
+ };
3066
+ const getRtlOptionFromHTMLElement = htmlElement => {
3067
+ const dataRtl = htmlElement.dataset.rtl;
3068
+ if (dataRtl == "") {
3069
+ return true;
3070
+ } else if (dataRtl === "false") {
3071
+ return false;
3072
+ } else {
3073
+ return Boolean(dataRtl);
3074
+ }
3075
+ };
3076
+
3077
+ // Trigger a CustomEvent. Return if the event is processed (true) or cancelled (false).
3078
+ const triggerCustomEvent = (element, eventName, values) => {
3079
+ const event = new CustomEvent(eventName, {
3080
+ bubbles: true,
3081
+ cancelable: true,
3082
+ detail: values
3083
+ });
3084
+ element.dispatchEvent(event);
3085
+ return !event.defaultPrevented;
3086
+ };
3087
+
3088
+ // Generated by config/generateVersion.mjs. Do not edit.
3089
+ const version = "0.1.0";
3090
+
3091
+ // The types that appear in the public api. Consumers cannot import them from
3092
+ // the submodules directly, because those are not exposed in package.json.
3093
+ // Type only, so that the iife build keeps exposing the TreeElement class itself
3094
+ // as its global, instead of an object of named exports.
3095
+
3096
+ class TreeElement {
3097
+ /** @hidden */
3098
+ tree;
3099
+ _classNames;
3100
+ _dataLoader;
3101
+ _dndHandler;
3102
+ _htmlElement;
3103
+ _isInitialized;
3104
+ _keyHandler;
3105
+ _mouseHandler;
3106
+ _nodeMap;
3107
+ _options;
3108
+ _renderer;
3109
+ _saveStateHandler;
3110
+ _scrollHandler;
3111
+ _selectNodeHandler;
3112
+ _triggerEventProvider;
3113
+
3114
+ /** @hidden */
3115
+ constructor({
3116
+ htmlElement,
3117
+ overrideTriggerEventProvider,
3118
+ ...options
3119
+ }) {
3120
+ this._htmlElement = htmlElement;
3121
+ this._options = setDefaultOptions(htmlElement, options);
3122
+ this._classNames = createClassNames(this._options);
3123
+ this._triggerEventProvider = overrideTriggerEventProvider ?? triggerCustomEvent;
3124
+ this._isInitialized = false;
3125
+ this.tree = new Node({}, true);
3126
+ this._nodeMap = new WeakMap();
3127
+ const {
3128
+ autoEscape,
3129
+ buttonLeft,
3130
+ closedIcon,
3131
+ dataFilter,
3132
+ dragAndDrop,
3133
+ keyboardSupport,
3134
+ onCanMove,
3135
+ onCanMoveTo,
3136
+ onCreateLi,
3137
+ onDragMove,
3138
+ onDragStop,
3139
+ onGetStateFromStorage,
3140
+ onIsMoveHandle,
3141
+ onSetStateFromStorage,
3142
+ openedIcon,
3143
+ openFolderDelay,
3144
+ rtl,
3145
+ saveState: saveStateOption,
3146
+ showEmptyFolder,
3147
+ slide,
3148
+ tabIndex
3149
+ } = this._options;
3150
+ const classNames = this._classNames;
3151
+ const closeNode = this.closeNode.bind(this);
3152
+ const getNodeElement = this._getNodeElement.bind(this);
3153
+ const getNodeElementForNode = this._getNodeElementForNode.bind(this);
3154
+ const getNodeById = this.getNodeById.bind(this);
3155
+ const getSelectedNode = this.getSelectedNode.bind(this);
3156
+ const getTree = this.getTree.bind(this);
3157
+ const isFocusOnTree = this._isFocusOnTree.bind(this);
3158
+ const loadData = this.loadData.bind(this);
3159
+ const openNode = this.openNode.bind(this);
3160
+ const openParents = this._openParents.bind(this);
3161
+ const refreshElements = this._refreshElements.bind(this);
3162
+ const refreshHitAreas = this.refreshHitAreas.bind(this);
3163
+ const setNodeElement = this._setNodeElement.bind(this);
3164
+ const treeElement = this._htmlElement;
3165
+ const triggerEvent = this._triggerEvent.bind(this);
3166
+ const saveState = () => {
3167
+ saveStateHandler.saveState();
3168
+ };
3169
+ const selectNodeHandler = new SelectNodeHandler({
3170
+ getNodeById,
3171
+ getNodeElementForNode,
3172
+ getOnCanSelectNode: () => this._options.onCanSelectNode,
3173
+ getSelectable: () => this._options.selectable,
3174
+ openParents,
3175
+ saveState,
3176
+ triggerEvent
3177
+ });
3178
+ const addToSelection = selectNodeHandler.addToSelection.bind(selectNodeHandler);
3179
+ const getSelectedNodes = selectNodeHandler.getSelectedNodes.bind(selectNodeHandler);
3180
+ const isNodeSelected = selectNodeHandler.isNodeSelected.bind(selectNodeHandler);
3181
+ const removeFromSelection = selectNodeHandler.removeFromSelection.bind(selectNodeHandler);
3182
+ const selectNode = selectNodeHandler.selectSingleNode.bind(selectNodeHandler);
3183
+ const getMouseDelay = () => this._options.startDndDelay ?? 0;
3184
+ const dataLoader = new DataLoader({
3185
+ classNames,
3186
+ dataFilter,
3187
+ loadData,
3188
+ treeElement,
3189
+ triggerEvent
3190
+ });
3191
+ const saveStateHandler = new SaveStateHandler({
3192
+ addToSelection,
3193
+ getNodeById,
3194
+ getSelectedNodes,
3195
+ getTree,
3196
+ onGetStateFromStorage,
3197
+ onSetStateFromStorage,
3198
+ openNode,
3199
+ refreshElements,
3200
+ removeFromSelection,
3201
+ saveState: saveStateOption
3202
+ });
3203
+ const scrollHandler = new ScrollHandler({
3204
+ refreshHitAreas,
3205
+ treeElement
3206
+ });
3207
+ const getScrollLeft = scrollHandler.getScrollLeft.bind(scrollHandler);
3208
+ const dndHandler = new DragAndDropHandler({
3209
+ autoEscape,
3210
+ classNames,
3211
+ getNodeElement,
3212
+ getNodeElementForNode,
3213
+ getScrollLeft,
3214
+ getTree,
3215
+ onCanMove,
3216
+ onCanMoveTo,
3217
+ onDragMove,
3218
+ onDragStop,
3219
+ onIsMoveHandle,
3220
+ openFolderDelay,
3221
+ openNode,
3222
+ refreshElements,
3223
+ slide,
3224
+ treeElement,
3225
+ triggerEvent
3226
+ });
3227
+ const keyHandler = new KeyHandler({
3228
+ closeNode,
3229
+ getSelectedNode,
3230
+ isFocusOnTree,
3231
+ keyboardSupport,
3232
+ openNode,
3233
+ selectNode
3234
+ });
3235
+ const renderer = new ElementsRenderer({
3236
+ autoEscape,
3237
+ buttonLeft,
3238
+ classNames,
3239
+ closedIcon,
3240
+ dragAndDrop,
3241
+ getTree,
3242
+ htmlElement: treeElement,
3243
+ isNodeSelected,
3244
+ onCreateLi,
3245
+ openedIcon,
3246
+ rtl,
3247
+ setNodeElement,
3248
+ showEmptyFolder,
3249
+ tabIndex
3250
+ });
3251
+ const getNode = this.getNode.bind(this);
3252
+ const onMouseCapture = this._mouseCapture.bind(this);
3253
+ const onMouseDrag = this._mouseDrag.bind(this);
3254
+ const onMouseStart = this._mouseStart.bind(this);
3255
+ const onMouseStop = this._mouseStop.bind(this);
3256
+ const mouseHandler = new MouseHandler({
3257
+ classNames,
3258
+ element: treeElement,
3259
+ getMouseDelay,
3260
+ getNode,
3261
+ onClickButton: this.toggle.bind(this),
3262
+ onClickTitle: selectNode,
3263
+ onMouseCapture,
3264
+ onMouseDrag,
3265
+ onMouseStart,
3266
+ onMouseStop,
3267
+ triggerEvent,
3268
+ useContextMenu: this._options.useContextMenu
3269
+ });
3270
+ this._dataLoader = dataLoader;
3271
+ this._dndHandler = dndHandler;
3272
+ this._keyHandler = keyHandler;
3273
+ this._mouseHandler = mouseHandler;
3274
+ this._renderer = renderer;
3275
+ this._saveStateHandler = saveStateHandler;
3276
+ this._scrollHandler = scrollHandler;
3277
+ this._selectNodeHandler = selectNodeHandler;
3278
+ this._initData();
3279
+ }
3280
+
3281
+ /**
3282
+ * Adds a sibling after a node.
3283
+ *
3284
+ * @returns The new node, or `null` when the node has no parent.
3285
+ * @group Changing the tree
3286
+ */
3287
+ addNodeAfter(nodeData, existingNode) {
3288
+ const newNode = existingNode.addAfter(nodeData);
3289
+ if (newNode) {
3290
+ this._refreshElements(existingNode.parent);
3291
+ }
3292
+ return newNode;
3293
+ }
3294
+
3295
+ /**
3296
+ * Adds a sibling before a node.
3297
+ *
3298
+ * @returns The new node, or `null` when the node has no parent.
3299
+ * @group Changing the tree
3300
+ */
3301
+ addNodeBefore(nodeData, existingNode) {
3302
+ const newNode = existingNode.addBefore(nodeData);
3303
+ if (newNode) {
3304
+ this._refreshElements(existingNode.parent);
3305
+ }
3306
+ return newNode;
3307
+ }
3308
+
3309
+ /**
3310
+ * Inserts a new node between a node and its parent, taking the node as its
3311
+ * child.
3312
+ *
3313
+ * @returns The new node, or `null` when the node has no parent.
3314
+ * @group Changing the tree
3315
+ */
3316
+ addParentNode(nodeData, existingNode) {
3317
+ const newNode = existingNode.addParent(nodeData);
3318
+ if (newNode) {
3319
+ this._refreshElements(newNode.parent);
3320
+ }
3321
+ return newNode;
3322
+ }
3323
+
3324
+ /**
3325
+ * Adds a node to the selection instead of replacing it.
3326
+ *
3327
+ * @param mustSetFocus - Move the focus to the node. Default `true`.
3328
+ * @group Selection
3329
+ */
3330
+ addToSelection(node, mustSetFocus) {
3331
+ this._selectNodeHandler.addToSelection(node);
3332
+ this._openParents(node);
3333
+ this._getNodeElementForNode(node).select(mustSetFocus ?? true);
3334
+ this._saveState();
3335
+ }
3336
+
3337
+ /**
3338
+ * Adds a node as the last child of a parent.
3339
+ *
3340
+ * @example
3341
+ * ```js
3342
+ * tree.appendNode({ name: "child", id: 5 }, tree.getNodeById(1));
3343
+ * ```
3344
+ *
3345
+ * @returns The new node.
3346
+ * @group Changing the tree
3347
+ */
3348
+ appendNode(nodeData, parentNode) {
3349
+ const node = parentNode.append(nodeData);
3350
+ this._refreshElements(parentNode);
3351
+ return node;
3352
+ }
3353
+
3354
+ /**
3355
+ * Closes a folder.
3356
+ *
3357
+ * @param slide - Override the `slide` option for this call.
3358
+ * @group Opening and closing
3359
+ */
3360
+ closeNode(node, slide) {
3361
+ if (node.isFolder() || node.isEmptyFolder) {
3362
+ this._createFolderElement(node).close(slide ?? this._options.slide, this._options.animationSpeed);
3363
+ this._saveState();
3364
+ }
3365
+ }
3366
+
3367
+ /**
3368
+ * Empties the element and removes the tree's document-level keyboard
3369
+ * listener. Call it when you remove the tree from the page.
3370
+ *
3371
+ * @group Other
3372
+ */
3373
+ deinit() {
3374
+ this._htmlElement.textContent = '';
3375
+ this._dataLoader.deinit();
3376
+ this._keyHandler.deinit();
3377
+ this._mouseHandler.deinit();
3378
+ this.tree = new Node({}, true);
3379
+ }
3380
+
3381
+ /**
3382
+ * Returns the node that belongs to a `li` element the tree rendered.
3383
+ *
3384
+ * @group Finding nodes
3385
+ */
3386
+ getNode(element) {
3387
+ const liElement = element.closest(`li.${this._classNames.common}`);
3388
+ if (liElement) {
3389
+ return this._nodeMap.get(liElement) ?? null;
3390
+ } else {
3391
+ return null;
3392
+ }
3393
+ }
3394
+
3395
+ /**
3396
+ * Returns the first node for which the callback returns `true`.
3397
+ *
3398
+ * @example
3399
+ * ```js
3400
+ * const node = tree.getNodeByCallback((node) => node.children.length > 3);
3401
+ * ```
3402
+ *
3403
+ * @group Finding nodes
3404
+ */
3405
+ getNodeByCallback(callback) {
3406
+ return this.tree.getNodeByCallback(callback);
3407
+ }
3408
+
3409
+ /**
3410
+ * Returns the node with this id.
3411
+ *
3412
+ * @example
3413
+ * ```js
3414
+ * const node = tree.getNodeById(1);
3415
+ * ```
3416
+ *
3417
+ * @group Finding nodes
3418
+ */
3419
+ getNodeById(id) {
3420
+ return this.tree.getNodeById(id);
3421
+ }
3422
+
3423
+ /**
3424
+ * Returns the first node with this name.
3425
+ *
3426
+ * @group Finding nodes
3427
+ */
3428
+ getNodeByName(name) {
3429
+ return this.tree.getNodeByName(name);
3430
+ }
3431
+
3432
+ /**
3433
+ * Like `getNodeByName`, but throws when there is no such node. Convenient
3434
+ * in tests and when a missing node is a bug.
3435
+ *
3436
+ * @group Finding nodes
3437
+ */
3438
+ getNodeByNameMustExist(name) {
3439
+ return this.tree.getNodeByNameMustExist(name);
3440
+ }
3441
+
3442
+ /**
3443
+ * Returns all nodes with this property value.
3444
+ *
3445
+ * @example
3446
+ * ```js
3447
+ * tree.getNodesByProperty("color", "green");
3448
+ * ```
3449
+ *
3450
+ * @group Finding nodes
3451
+ */
3452
+ getNodesByProperty(key, value) {
3453
+ return this.tree.getNodesByProperty(key, value);
3454
+ }
3455
+
3456
+ /**
3457
+ * Returns the selected node, or `false` when nothing is selected.
3458
+ *
3459
+ * @group Selection
3460
+ */
3461
+ getSelectedNode() {
3462
+ return this._selectNodeHandler.getSelectedNode();
3463
+ }
3464
+
3465
+ /**
3466
+ * Returns the selected nodes.
3467
+ *
3468
+ * @group Selection
3469
+ */
3470
+ getSelectedNodes() {
3471
+ return this._selectNodeHandler.getSelectedNodes();
3472
+ }
3473
+
3474
+ /**
3475
+ * Returns the current state — `open_nodes` and `selected_node` — whether or
3476
+ * not `saveState` is enabled.
3477
+ *
3478
+ * @group State
3479
+ */
3480
+ getState() {
3481
+ return this._saveStateHandler.getState();
3482
+ }
3483
+
3484
+ /**
3485
+ * Returns the state as it is stored.
3486
+ *
3487
+ * @group State
3488
+ */
3489
+ getStateFromStorage() {
3490
+ return this._saveStateHandler.getStateFromStorage();
3491
+ }
3492
+
3493
+ /**
3494
+ * Returns the root node. It is not rendered; its `children` are the
3495
+ * top-level nodes. Also available as the `tree` property.
3496
+ *
3497
+ * @group Finding nodes
3498
+ */
3499
+ getTree() {
3500
+ return this.tree;
3501
+ }
3502
+
3503
+ /**
3504
+ * Returns the version of tree-element.
3505
+ *
3506
+ * @group Other
3507
+ */
3508
+ getVersion() {
3509
+ return version;
3510
+ }
3511
+
3512
+ /**
3513
+ * Returns whether the user is dragging a node.
3514
+ *
3515
+ * @group Other
3516
+ */
3517
+ isDragging() {
3518
+ return this._dndHandler.isDragging;
3519
+ }
3520
+
3521
+ /**
3522
+ * Returns whether the node is selected.
3523
+ *
3524
+ * @group Selection
3525
+ */
3526
+ isNodeSelected(node) {
3527
+ return this._selectNodeHandler.isNodeSelected(node);
3528
+ }
3529
+
3530
+ /**
3531
+ * Loads data into the tree.
3532
+ *
3533
+ * @param parentNode - Replace this node's children instead of the whole
3534
+ * tree.
3535
+ * @group Loading data
3536
+ */
3537
+ loadData(data, parentNode) {
3538
+ if (data) {
3539
+ if (parentNode) {
3540
+ this._deselectNodes(parentNode);
3541
+ this._loadSubtree(data, parentNode);
3542
+ } else {
3543
+ this._initTree(data);
3544
+ }
3545
+ if (this.isDragging()) {
3546
+ this._dndHandler.refresh();
3547
+ }
3548
+ }
3549
+ this._triggerEvent("tree.set_data", {
3550
+ node: parentNode,
3551
+ treeData: data ?? undefined
3552
+ });
3553
+ }
3554
+
3555
+ /**
3556
+ * Fetches data and loads it into the tree, or into `parentNode`.
3557
+ *
3558
+ * @param url - Defaults to the `dataUrl` option.
3559
+ * @group Loading data
3560
+ */
3561
+ async loadDataFromUrl(url, parentNode) {
3562
+ const requestUrl = url ? new RequestUrl(url) : this._createRequestUrl(parentNode);
3563
+ if (requestUrl) {
3564
+ await this._dataLoader.loadFromUrl(requestUrl, parentNode);
3565
+ }
3566
+ }
3567
+
3568
+ /**
3569
+ * Selects the next visible node, like the down arrow key.
3570
+ *
3571
+ * @group Selection
3572
+ */
3573
+ moveDown() {
3574
+ const selectedNode = this.getSelectedNode();
3575
+ if (selectedNode) {
3576
+ this._keyHandler.moveDown(selectedNode);
3577
+ }
3578
+ }
3579
+
3580
+ /**
3581
+ * Moves a node inside the tree.
3582
+ *
3583
+ * @example
3584
+ * ```js
3585
+ * tree.moveNode(tree.getNodeById(2), tree.getNodeById(1), "inside");
3586
+ * ```
3587
+ *
3588
+ * @param position - `"before"`, `"after"` or `"inside"`.
3589
+ * @group Changing the tree
3590
+ */
3591
+ moveNode(node, targetNode, position) {
3592
+ this.tree.moveNode(node, targetNode, position);
3593
+ this._refreshElements(null);
3594
+ }
3595
+
3596
+ /**
3597
+ * Selects the previous visible node, like the up arrow key.
3598
+ *
3599
+ * @group Selection
3600
+ */
3601
+ moveUp() {
3602
+ const selectedNode = this.getSelectedNode();
3603
+ if (selectedNode) {
3604
+ this._keyHandler.moveUp(selectedNode);
3605
+ }
3606
+ }
3607
+
3608
+ /**
3609
+ * Opens a folder, and the folders above it. A node marked `load_on_demand`
3610
+ * is fetched first, so await the promise when you need to know it is really
3611
+ * open.
3612
+ *
3613
+ * @example
3614
+ * ```js
3615
+ * await tree.openNode(node);
3616
+ * console.log("open");
3617
+ * ```
3618
+ *
3619
+ * @param slide - Override the `slide` option for this call.
3620
+ * @group Opening and closing
3621
+ */
3622
+ async openNode(node, slide) {
3623
+ const mustSlide = slide ?? this._options.slide;
3624
+ const doOpenNode = async (openedNode, slideOption) => {
3625
+ if (!node.children.length) {
3626
+ return;
3627
+ }
3628
+ const folderElement = this._createFolderElement(openedNode);
3629
+ await folderElement.open(slideOption, this._options.animationSpeed);
3630
+ };
3631
+ if (node.isFolder() || node.isEmptyFolder) {
3632
+ if (node.load_on_demand) {
3633
+ await this._loadFolderOnDemand(node, mustSlide);
3634
+ } else {
3635
+ let parent = node.parent;
3636
+ while (parent) {
3637
+ // nb: do not open root element
3638
+ if (parent.parent) {
3639
+ await doOpenNode(parent, false);
3640
+ }
3641
+ parent = parent.parent;
3642
+ }
3643
+ await doOpenNode(node, mustSlide);
3644
+ this._saveState();
3645
+ }
3646
+ }
3647
+ }
3648
+
3649
+ /**
3650
+ * Adds a node as the first child of a parent.
3651
+ *
3652
+ * @returns The new node.
3653
+ * @group Changing the tree
3654
+ */
3655
+ prependNode(nodeData, parentNode) {
3656
+ const node = parentNode.prepend(nodeData);
3657
+ this._refreshElements(parentNode);
3658
+ return node;
3659
+ }
3660
+
3661
+ /**
3662
+ * Re-renders the whole tree. Needed after changing node data directly
3663
+ * instead of through these methods.
3664
+ *
3665
+ * @group Changing the tree
3666
+ */
3667
+ refresh() {
3668
+ this._refreshElements(null);
3669
+ }
3670
+
3671
+ /**
3672
+ * Recomputes the drop targets. Call this if the layout changes during a
3673
+ * drag.
3674
+ *
3675
+ * @group Other
3676
+ */
3677
+ refreshHitAreas() {
3678
+ this._dndHandler.refresh();
3679
+ }
3680
+
3681
+ /**
3682
+ * Removes a node from the selection.
3683
+ *
3684
+ * @group Selection
3685
+ */
3686
+ removeFromSelection(node) {
3687
+ this._selectNodeHandler.removeFromSelection(node);
3688
+ this._getNodeElementForNode(node).deselect();
3689
+ this._saveState();
3690
+ }
3691
+
3692
+ /**
3693
+ * Removes a node and its children.
3694
+ *
3695
+ * @group Changing the tree
3696
+ */
3697
+ removeNode(node) {
3698
+ this._selectNodeHandler.removeFromSelection(node, true); // including children
3699
+
3700
+ const parent = node.parent;
3701
+ node.remove();
3702
+ this._refreshElements(parent);
3703
+ }
3704
+
3705
+ /**
3706
+ * Scrolls the node into view.
3707
+ *
3708
+ * @group Selection
3709
+ */
3710
+ scrollToNode(node) {
3711
+ if (!node.element) {
3712
+ return;
3713
+ }
3714
+ const top = getOffsetTop(node.element) - getOffsetTop(this._htmlElement);
3715
+ this._scrollHandler.scrollToY(top);
3716
+ }
3717
+
3718
+ /**
3719
+ * Replaces the selection, and opens the parents of the node.
3720
+ *
3721
+ * @param node - `null` clears the selection.
3722
+ * @param options - `mustSetFocus`: move focus to the node, default `true`.
3723
+ * `mustToggle`: deselect the node if it is already selected, default
3724
+ * `false`.
3725
+ * @group Selection
3726
+ */
3727
+ selectNode(node, options) {
3728
+ if (!node) {
3729
+ // Called with empty node -> deselect current node
3730
+ this._deselectCurrentNode();
3731
+ this._saveStateHandler.saveState();
3732
+ return;
3733
+ }
3734
+ this._selectNodeHandler.selectSingleNode(node, options);
3735
+ }
3736
+
3737
+ /**
3738
+ * Changes an option after construction. See
3739
+ * [Options](/reference/options#changing-an-option-later) for the caveats.
3740
+ *
3741
+ * @group Other
3742
+ */
3743
+ setOption(option, value) {
3744
+ this._options[option] = value;
3745
+ }
3746
+
3747
+ /**
3748
+ * Applies a state to the tree.
3749
+ *
3750
+ * @group State
3751
+ */
3752
+ setState(state) {
3753
+ this._saveStateHandler.setInitialState(state);
3754
+ this._refreshElements(null);
3755
+ }
3756
+
3757
+ /**
3758
+ * Closes an open node and opens a closed one.
3759
+ *
3760
+ * @param slide - Override the `slide` option for this call.
3761
+ * @group Opening and closing
3762
+ */
3763
+ toggle(node, slide = null) {
3764
+ const mustSlide = slide ?? this._options.slide;
3765
+ if (node.is_open) {
3766
+ this.closeNode(node, mustSlide);
3767
+ } else {
3768
+ void this.openNode(node, mustSlide);
3769
+ }
3770
+ }
3771
+
3772
+ /**
3773
+ * Returns the tree as a json string, including changes made through the
3774
+ * api.
3775
+ *
3776
+ * @group Other
3777
+ */
3778
+ toJson() {
3779
+ return JSON.stringify(this.tree.getData());
3780
+ }
3781
+
3782
+ /**
3783
+ * Updates the data of a node and re-renders it. A string updates just the
3784
+ * name.
3785
+ *
3786
+ * Changing the `id` re-indexes the node. `children` and `parent` are
3787
+ * ignored — use `loadData` or `moveNode` for those.
3788
+ *
3789
+ * @example
3790
+ * ```js
3791
+ * tree.updateNode(node, "new name");
3792
+ * tree.updateNode(node, { name: "new name", color: "red" });
3793
+ * ```
3794
+ *
3795
+ * @group Changing the tree
3796
+ */
3797
+ updateNode(node, data) {
3798
+ const idIsChanged = typeof data === "object" && data.id && data.id !== node.id;
3799
+ if (idIsChanged) {
3800
+ this.tree.removeNodeFromIndex(node);
3801
+ }
3802
+ node.setData(data);
3803
+ if (idIsChanged) {
3804
+ this.tree.addNodeToIndex(node);
3805
+ }
3806
+ if (typeof data === "object" && data.children && data.children instanceof Array) {
3807
+ node.removeChildren();
3808
+ if (data.children.length) {
3809
+ node.loadFromData(data.children);
3810
+ }
3811
+ }
3812
+ this._refreshElements(node);
3813
+ }
3814
+ _createFolderElement(node) {
3815
+ const classNames = this._classNames;
3816
+ const closedIconElement = this._renderer.closedIconElement;
3817
+ const getScrollLeft = this._scrollHandler.getScrollLeft.bind(this._scrollHandler);
3818
+ const openedIconElement = this._renderer.openedIconElement;
3819
+ const tabIndex = this._options.tabIndex;
3820
+ const treeElement = this._htmlElement;
3821
+ const triggerEvent = this._triggerEvent.bind(this);
3822
+ return new FolderElement({
3823
+ classNames,
3824
+ closedIconElement,
3825
+ getScrollLeft,
3826
+ node,
3827
+ openedIconElement,
3828
+ tabIndex,
3829
+ treeElement,
3830
+ triggerEvent
3831
+ });
3832
+ }
3833
+ _createNodeElement(node) {
3834
+ const classNames = this._classNames;
3835
+ const getScrollLeft = this._scrollHandler.getScrollLeft.bind(this._scrollHandler);
3836
+ const tabIndex = this._options.tabIndex;
3837
+ const treeElement = this._htmlElement;
3838
+ return new NodeElement({
3839
+ classNames,
3840
+ getScrollLeft,
3841
+ node,
3842
+ tabIndex,
3843
+ treeElement
3844
+ });
3845
+ }
3846
+
3847
+ /* Create a RequestUrl based on the url in the options.
3848
+ * Add a 'node' query parameter for loading on demand
3849
+ * Add a 'selected_node' query parameter if a node is selected.
3850
+ */
3851
+ _createRequestUrl(node) {
3852
+ const dataUrl = this._options.dataUrl;
3853
+ let url;
3854
+ if (typeof dataUrl === "function") {
3855
+ url = dataUrl(node);
3856
+ } else {
3857
+ url = dataUrl;
3858
+ }
3859
+ if (!url) {
3860
+ return null;
3861
+ }
3862
+ const requestUrl = new RequestUrl(url);
3863
+ if (node?.id) {
3864
+ // Load on demand of a subtree; add node parameter
3865
+ requestUrl.setSearchParam('node', node.id.toString());
3866
+ } else {
3867
+ // Add selected_node parameter
3868
+ const selectedNodeId = this._getNodeIdToBeSelected();
3869
+ if (selectedNodeId) {
3870
+ requestUrl.setSearchParam('selected_node', selectedNodeId.toString());
3871
+ }
3872
+ }
3873
+ return requestUrl;
3874
+ }
3875
+ _deselectCurrentNode() {
3876
+ const node = this.getSelectedNode();
3877
+ if (node) {
3878
+ this.removeFromSelection(node);
3879
+ }
3880
+ }
3881
+
3882
+ // Deselect the children of the node.
3883
+ _deselectNodes(parentNode) {
3884
+ const selectedNodesUnderParent = this._selectNodeHandler.getSelectedNodesUnder(parentNode);
3885
+ for (const n of selectedNodesUnderParent) {
3886
+ this._selectNodeHandler.removeFromSelection(n);
3887
+ }
3888
+ }
3889
+
3890
+ // Get the maximum level for auto open
3891
+ _getAutoOpenMaxLevel() {
3892
+ if (this._options.autoOpen === true) {
3893
+ return -1;
3894
+ } else if (typeof this._options.autoOpen === "number") {
3895
+ return this._options.autoOpen;
3896
+ } else if (typeof this._options.autoOpen === "string") {
3897
+ return parseInt(this._options.autoOpen, 10);
3898
+ }
3899
+
3900
+ /* istanbul ignore next @preserve */
3901
+ return 0;
3902
+ }
3903
+ _getNodeElement(element) {
3904
+ const node = this.getNode(element);
3905
+ if (node) {
3906
+ return this._getNodeElementForNode(node);
3907
+ } else {
3908
+ return null;
3909
+ }
3910
+ }
3911
+ _getNodeElementForNode(node) {
3912
+ if (node.isFolder()) {
3913
+ return this._createFolderElement(node);
3914
+ } else {
3915
+ return this._createNodeElement(node);
3916
+ }
3917
+ }
3918
+ _getNodeIdToBeSelected() {
3919
+ return this._saveStateHandler.getNodeIdToBeSelected();
3920
+ }
3921
+ _initData() {
3922
+ if (this._options.data) {
3923
+ this.loadData(this._options.data);
3924
+ } else {
3925
+ const dataUrl = this._createRequestUrl();
3926
+ if (dataUrl) {
3927
+ void this.loadDataFromUrl();
3928
+ } else {
3929
+ this.loadData([]);
3930
+ }
3931
+ }
3932
+ }
3933
+ _initTree(data) {
3934
+ const doInit = () => {
3935
+ if (!this._isInitialized) {
3936
+ this._isInitialized = true;
3937
+ this._triggerEvent("tree.init");
3938
+ }
3939
+ };
3940
+ this.tree = new this._options.nodeClass(null, true, this._options.nodeClass);
3941
+ this._selectNodeHandler.clear();
3942
+ this.tree.loadFromData(data);
3943
+ const mustLoadOnDemand = this._setInitialState();
3944
+ this._refreshElements(null);
3945
+ if (mustLoadOnDemand) {
3946
+ // Load data on demand and then init the tree
3947
+ void this._setInitialStateOnDemand().then(doInit);
3948
+ } else {
3949
+ doInit();
3950
+ }
3951
+ }
3952
+
3953
+ // Does an HTML element of the tree have the focus?
3954
+ _isFocusOnTree() {
3955
+ const activeElement = document.activeElement;
3956
+
3957
+ /* istanbul ignore if */
3958
+ if (!activeElement) {
3959
+ return false;
3960
+ }
3961
+
3962
+ // The keyboard must still work for input elements.
3963
+ const tagName = activeElement.tagName;
3964
+ if (tagName !== "A" && tagName !== "SPAN") {
3965
+ return false;
3966
+ }
3967
+ const node = this.getNode(activeElement);
3968
+ return node?.tree === this.tree;
3969
+ }
3970
+ _isSelectedNodeInSubtree(subtree) {
3971
+ const selectedNode = this.getSelectedNode();
3972
+ if (!selectedNode) {
3973
+ return false;
3974
+ } else {
3975
+ return subtree === selectedNode || subtree.isParentOf(selectedNode);
3976
+ }
3977
+ }
3978
+ async _loadFolderOnDemand(node, slide) {
3979
+ node.is_loading = true;
3980
+ await this.loadDataFromUrl(undefined, node);
3981
+ await this.openNode(node, slide);
3982
+ }
3983
+ _loadSubtree(data, parentNode) {
3984
+ parentNode.loadFromData(data);
3985
+ parentNode.load_on_demand = false;
3986
+ parentNode.is_loading = false;
3987
+ this._refreshElements(parentNode);
3988
+ }
3989
+ _mouseCapture(positionInfo) {
3990
+ if (!this._options.dragAndDrop) {
3991
+ return false;
3992
+ }
3993
+ return this._dndHandler.mouseCapture(positionInfo);
3994
+ }
3995
+ _mouseDrag(positionInfo) {
3996
+ /* istanbul ignore if */
3997
+ if (!this._options.dragAndDrop) {
3998
+ return false;
3999
+ }
4000
+ const result = this._dndHandler.mouseDrag(positionInfo);
4001
+ this._scrollHandler.checkScrolling(positionInfo);
4002
+ return result;
4003
+ }
4004
+ _mouseStart(positionInfo) {
4005
+ /* istanbul ignore if */
4006
+ if (!this._options.dragAndDrop) {
4007
+ return false;
4008
+ }
4009
+ return this._dndHandler.mouseStart(positionInfo);
4010
+ }
4011
+ _mouseStop(positionInfo) {
4012
+ /* istanbul ignore if */
4013
+ if (!this._options.dragAndDrop) {
4014
+ return false;
4015
+ }
4016
+ this._scrollHandler.stopScrolling();
4017
+ return this._dndHandler.mouseStop(positionInfo);
4018
+ }
4019
+ _openParents(node) {
4020
+ const parent = node.parent;
4021
+ if (parent?.parent && !parent.is_open) {
4022
+ void this.openNode(parent, false);
4023
+ }
4024
+ }
4025
+
4026
+ /*
4027
+ Redraw the tree or part of the tree.
4028
+ from_node: redraw this subtree
4029
+ */
4030
+ _refreshElements(fromNode) {
4031
+ const mustSetFocus = this._isFocusOnTree();
4032
+ const mustSelect = fromNode ? this._isSelectedNodeInSubtree(fromNode) : false;
4033
+ this._renderer.render(fromNode);
4034
+ if (mustSelect) {
4035
+ this._selectCurrentNode(mustSetFocus);
4036
+ }
4037
+ this._triggerEvent("tree.refresh");
4038
+ }
4039
+ _saveState() {
4040
+ this._saveStateHandler.saveState();
4041
+ }
4042
+ _selectCurrentNode(mustSetFocus) {
4043
+ const node = this.getSelectedNode();
4044
+ if (node) {
4045
+ const nodeElement = this._getNodeElementForNode(node);
4046
+ nodeElement.select(mustSetFocus);
4047
+ }
4048
+ }
4049
+
4050
+ // Set initial state, either by restoring the state or auto-opening nodes
4051
+ // result: must load nodes on demand?
4052
+ _setInitialState() {
4053
+ const restoreState = () => {
4054
+ // result: is state restored, must load on demand?
4055
+ const state = this._saveStateHandler.getStateFromStorage();
4056
+ if (!state) {
4057
+ return [false, false];
4058
+ } else {
4059
+ const mustLoadOnDemand = this._saveStateHandler.setInitialState(state);
4060
+
4061
+ // return true: the state is restored
4062
+ return [true, mustLoadOnDemand];
4063
+ }
4064
+ };
4065
+ const autoOpenNodes = () => {
4066
+ // result: must load on demand?
4067
+ if (this._options.autoOpen === false) {
4068
+ return false;
4069
+ }
4070
+ const maxLevel = this._getAutoOpenMaxLevel();
4071
+ let mustLoadOnDemand = false;
4072
+ this.tree.iterate((node, level) => {
4073
+ if (node.load_on_demand) {
4074
+ mustLoadOnDemand = true;
4075
+ return false;
4076
+ } else if (!node.hasChildren()) {
4077
+ return false;
4078
+ } else {
4079
+ node.is_open = true;
4080
+ return level !== maxLevel;
4081
+ }
4082
+ });
4083
+ return mustLoadOnDemand;
4084
+ };
4085
+ let [isRestored, mustLoadOnDemand] = restoreState(); // eslint-disable-line prefer-const
4086
+
4087
+ if (!isRestored) {
4088
+ mustLoadOnDemand = autoOpenNodes();
4089
+ }
4090
+ return mustLoadOnDemand;
4091
+ }
4092
+
4093
+ // Set the initial state for nodes that are loaded on demand
4094
+ async _setInitialStateOnDemand() {
4095
+ return new Promise(resolve => {
4096
+ const restoreState = () => {
4097
+ const state = this._saveStateHandler.getStateFromStorage();
4098
+ if (!state) {
4099
+ return false;
4100
+ } else {
4101
+ void this._saveStateHandler.setInitialStateOnDemand(state).then(() => {
4102
+ resolve();
4103
+ });
4104
+ return true;
4105
+ }
4106
+ };
4107
+ const autoOpenNodes = () => {
4108
+ const maxLevel = this._getAutoOpenMaxLevel();
4109
+ let loadingCount = 0;
4110
+ const loadAndOpenNode = node => {
4111
+ loadingCount += 1;
4112
+ void this.openNode(node, false).then(() => {
4113
+ loadingCount -= 1;
4114
+ openNodes();
4115
+ });
4116
+ };
4117
+ const openNodes = () => {
4118
+ this.tree.iterate((node, level) => {
4119
+ if (node.load_on_demand) {
4120
+ if (!node.is_loading) {
4121
+ loadAndOpenNode(node);
4122
+ }
4123
+ return false;
4124
+ } else {
4125
+ void this.openNode(node, false);
4126
+ return level !== maxLevel;
4127
+ }
4128
+ });
4129
+ if (loadingCount === 0) {
4130
+ resolve();
4131
+ }
4132
+ };
4133
+ openNodes();
4134
+ };
4135
+ if (!restoreState()) {
4136
+ autoOpenNodes();
4137
+ }
4138
+ });
4139
+ }
4140
+
4141
+ // Set this HTML element to this node in the node map.
4142
+ _setNodeElement(element, node) {
4143
+ this._nodeMap.set(element, node);
4144
+ }
4145
+ _triggerEvent(eventName, values) {
4146
+ return this._triggerEventProvider(this._htmlElement, eventName, values);
4147
+ }
4148
+ }
4149
+
4150
+ return TreeElement;
4151
+
4152
+ })();
4153
+ //# sourceMappingURL=tree_element.debug.js.map