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
package/lib/index.js ADDED
@@ -0,0 +1,1098 @@
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
+ import createClassNames from './classNames.js';
21
+ import DataLoader from './dataLoader.js';
22
+ import { DragAndDropHandler } from './dragAndDropHandler/index.js';
23
+ import ElementsRenderer from './elementsRenderer.js';
24
+ import KeyHandler from './keyHandler.js';
25
+ import MouseHandler from './mouseHandler.js';
26
+ import { Node } from './node.js';
27
+ import NodeElement from './nodeElement/index.js';
28
+ import FolderElement from './nodeElement/folderElement.js';
29
+ import { getOffsetTop } from './positionUtils.js';
30
+ import RequestUrl from './requestUrl.js';
31
+ import SaveStateHandler from './saveStateHandler.js';
32
+ import ScrollHandler from './scrollHandler.js';
33
+ import SelectNodeHandler from './selectNodeHandler.js';
34
+ import setDefaultOptions from './setDefaultOptions.js';
35
+ import triggerCustomEvent from './triggerCustomEvent.js';
36
+ import version from './version.js';
37
+
38
+ // The types that appear in the public api. Consumers cannot import them from
39
+ // the submodules directly, because those are not exposed in package.json.
40
+ // Type only, so that the iife build keeps exposing the TreeElement class itself
41
+ // as its global, instead of an object of named exports.
42
+
43
+ class TreeElement {
44
+ /** @hidden */
45
+ tree;
46
+ _classNames;
47
+ _dataLoader;
48
+ _dndHandler;
49
+ _htmlElement;
50
+ _isInitialized;
51
+ _keyHandler;
52
+ _mouseHandler;
53
+ _nodeMap;
54
+ _options;
55
+ _renderer;
56
+ _saveStateHandler;
57
+ _scrollHandler;
58
+ _selectNodeHandler;
59
+ _triggerEventProvider;
60
+
61
+ /** @hidden */
62
+ constructor({
63
+ htmlElement,
64
+ overrideTriggerEventProvider,
65
+ ...options
66
+ }) {
67
+ this._htmlElement = htmlElement;
68
+ this._options = setDefaultOptions(htmlElement, options);
69
+ this._classNames = createClassNames(this._options);
70
+ this._triggerEventProvider = overrideTriggerEventProvider ?? triggerCustomEvent;
71
+ this._isInitialized = false;
72
+ this.tree = new Node({}, true);
73
+ this._nodeMap = new WeakMap();
74
+ const {
75
+ autoEscape,
76
+ buttonLeft,
77
+ closedIcon,
78
+ dataFilter,
79
+ dragAndDrop,
80
+ keyboardSupport,
81
+ onCanMove,
82
+ onCanMoveTo,
83
+ onCreateLi,
84
+ onDragMove,
85
+ onDragStop,
86
+ onGetStateFromStorage,
87
+ onIsMoveHandle,
88
+ onSetStateFromStorage,
89
+ openedIcon,
90
+ openFolderDelay,
91
+ rtl,
92
+ saveState: saveStateOption,
93
+ showEmptyFolder,
94
+ slide,
95
+ tabIndex
96
+ } = this._options;
97
+ const classNames = this._classNames;
98
+ const closeNode = this.closeNode.bind(this);
99
+ const getNodeElement = this._getNodeElement.bind(this);
100
+ const getNodeElementForNode = this._getNodeElementForNode.bind(this);
101
+ const getNodeById = this.getNodeById.bind(this);
102
+ const getSelectedNode = this.getSelectedNode.bind(this);
103
+ const getTree = this.getTree.bind(this);
104
+ const isFocusOnTree = this._isFocusOnTree.bind(this);
105
+ const loadData = this.loadData.bind(this);
106
+ const openNode = this.openNode.bind(this);
107
+ const openParents = this._openParents.bind(this);
108
+ const refreshElements = this._refreshElements.bind(this);
109
+ const refreshHitAreas = this.refreshHitAreas.bind(this);
110
+ const setNodeElement = this._setNodeElement.bind(this);
111
+ const treeElement = this._htmlElement;
112
+ const triggerEvent = this._triggerEvent.bind(this);
113
+ const saveState = () => {
114
+ saveStateHandler.saveState();
115
+ };
116
+ const selectNodeHandler = new SelectNodeHandler({
117
+ getNodeById,
118
+ getNodeElementForNode,
119
+ getOnCanSelectNode: () => this._options.onCanSelectNode,
120
+ getSelectable: () => this._options.selectable,
121
+ openParents,
122
+ saveState,
123
+ triggerEvent
124
+ });
125
+ const addToSelection = selectNodeHandler.addToSelection.bind(selectNodeHandler);
126
+ const getSelectedNodes = selectNodeHandler.getSelectedNodes.bind(selectNodeHandler);
127
+ const isNodeSelected = selectNodeHandler.isNodeSelected.bind(selectNodeHandler);
128
+ const removeFromSelection = selectNodeHandler.removeFromSelection.bind(selectNodeHandler);
129
+ const selectNode = selectNodeHandler.selectSingleNode.bind(selectNodeHandler);
130
+ const getMouseDelay = () => this._options.startDndDelay ?? 0;
131
+ const dataLoader = new DataLoader({
132
+ classNames,
133
+ dataFilter,
134
+ loadData,
135
+ treeElement,
136
+ triggerEvent
137
+ });
138
+ const saveStateHandler = new SaveStateHandler({
139
+ addToSelection,
140
+ getNodeById,
141
+ getSelectedNodes,
142
+ getTree,
143
+ onGetStateFromStorage,
144
+ onSetStateFromStorage,
145
+ openNode,
146
+ refreshElements,
147
+ removeFromSelection,
148
+ saveState: saveStateOption
149
+ });
150
+ const scrollHandler = new ScrollHandler({
151
+ refreshHitAreas,
152
+ treeElement
153
+ });
154
+ const getScrollLeft = scrollHandler.getScrollLeft.bind(scrollHandler);
155
+ const dndHandler = new DragAndDropHandler({
156
+ autoEscape,
157
+ classNames,
158
+ getNodeElement,
159
+ getNodeElementForNode,
160
+ getScrollLeft,
161
+ getTree,
162
+ onCanMove,
163
+ onCanMoveTo,
164
+ onDragMove,
165
+ onDragStop,
166
+ onIsMoveHandle,
167
+ openFolderDelay,
168
+ openNode,
169
+ refreshElements,
170
+ slide,
171
+ treeElement,
172
+ triggerEvent
173
+ });
174
+ const keyHandler = new KeyHandler({
175
+ closeNode,
176
+ getSelectedNode,
177
+ isFocusOnTree,
178
+ keyboardSupport,
179
+ openNode,
180
+ selectNode
181
+ });
182
+ const renderer = new ElementsRenderer({
183
+ autoEscape,
184
+ buttonLeft,
185
+ classNames,
186
+ closedIcon,
187
+ dragAndDrop,
188
+ getTree,
189
+ htmlElement: treeElement,
190
+ isNodeSelected,
191
+ onCreateLi,
192
+ openedIcon,
193
+ rtl,
194
+ setNodeElement,
195
+ showEmptyFolder,
196
+ tabIndex
197
+ });
198
+ const getNode = this.getNode.bind(this);
199
+ const onMouseCapture = this._mouseCapture.bind(this);
200
+ const onMouseDrag = this._mouseDrag.bind(this);
201
+ const onMouseStart = this._mouseStart.bind(this);
202
+ const onMouseStop = this._mouseStop.bind(this);
203
+ const mouseHandler = new MouseHandler({
204
+ classNames,
205
+ element: treeElement,
206
+ getMouseDelay,
207
+ getNode,
208
+ onClickButton: this.toggle.bind(this),
209
+ onClickTitle: selectNode,
210
+ onMouseCapture,
211
+ onMouseDrag,
212
+ onMouseStart,
213
+ onMouseStop,
214
+ triggerEvent,
215
+ useContextMenu: this._options.useContextMenu
216
+ });
217
+ this._dataLoader = dataLoader;
218
+ this._dndHandler = dndHandler;
219
+ this._keyHandler = keyHandler;
220
+ this._mouseHandler = mouseHandler;
221
+ this._renderer = renderer;
222
+ this._saveStateHandler = saveStateHandler;
223
+ this._scrollHandler = scrollHandler;
224
+ this._selectNodeHandler = selectNodeHandler;
225
+ this._initData();
226
+ }
227
+
228
+ /**
229
+ * Adds a sibling after a node.
230
+ *
231
+ * @returns The new node, or `null` when the node has no parent.
232
+ * @group Changing the tree
233
+ */
234
+ addNodeAfter(nodeData, existingNode) {
235
+ const newNode = existingNode.addAfter(nodeData);
236
+ if (newNode) {
237
+ this._refreshElements(existingNode.parent);
238
+ }
239
+ return newNode;
240
+ }
241
+
242
+ /**
243
+ * Adds a sibling before a node.
244
+ *
245
+ * @returns The new node, or `null` when the node has no parent.
246
+ * @group Changing the tree
247
+ */
248
+ addNodeBefore(nodeData, existingNode) {
249
+ const newNode = existingNode.addBefore(nodeData);
250
+ if (newNode) {
251
+ this._refreshElements(existingNode.parent);
252
+ }
253
+ return newNode;
254
+ }
255
+
256
+ /**
257
+ * Inserts a new node between a node and its parent, taking the node as its
258
+ * child.
259
+ *
260
+ * @returns The new node, or `null` when the node has no parent.
261
+ * @group Changing the tree
262
+ */
263
+ addParentNode(nodeData, existingNode) {
264
+ const newNode = existingNode.addParent(nodeData);
265
+ if (newNode) {
266
+ this._refreshElements(newNode.parent);
267
+ }
268
+ return newNode;
269
+ }
270
+
271
+ /**
272
+ * Adds a node to the selection instead of replacing it.
273
+ *
274
+ * @param mustSetFocus - Move the focus to the node. Default `true`.
275
+ * @group Selection
276
+ */
277
+ addToSelection(node, mustSetFocus) {
278
+ this._selectNodeHandler.addToSelection(node);
279
+ this._openParents(node);
280
+ this._getNodeElementForNode(node).select(mustSetFocus ?? true);
281
+ this._saveState();
282
+ }
283
+
284
+ /**
285
+ * Adds a node as the last child of a parent.
286
+ *
287
+ * @example
288
+ * ```js
289
+ * tree.appendNode({ name: "child", id: 5 }, tree.getNodeById(1));
290
+ * ```
291
+ *
292
+ * @returns The new node.
293
+ * @group Changing the tree
294
+ */
295
+ appendNode(nodeData, parentNode) {
296
+ const node = parentNode.append(nodeData);
297
+ this._refreshElements(parentNode);
298
+ return node;
299
+ }
300
+
301
+ /**
302
+ * Closes a folder.
303
+ *
304
+ * @param slide - Override the `slide` option for this call.
305
+ * @group Opening and closing
306
+ */
307
+ closeNode(node, slide) {
308
+ if (node.isFolder() || node.isEmptyFolder) {
309
+ this._createFolderElement(node).close(slide ?? this._options.slide, this._options.animationSpeed);
310
+ this._saveState();
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Empties the element and removes the tree's document-level keyboard
316
+ * listener. Call it when you remove the tree from the page.
317
+ *
318
+ * @group Other
319
+ */
320
+ deinit() {
321
+ this._htmlElement.textContent = '';
322
+ this._dataLoader.deinit();
323
+ this._keyHandler.deinit();
324
+ this._mouseHandler.deinit();
325
+ this.tree = new Node({}, true);
326
+ }
327
+
328
+ /**
329
+ * Returns the node that belongs to a `li` element the tree rendered.
330
+ *
331
+ * @group Finding nodes
332
+ */
333
+ getNode(element) {
334
+ const liElement = element.closest(`li.${this._classNames.common}`);
335
+ if (liElement) {
336
+ return this._nodeMap.get(liElement) ?? null;
337
+ } else {
338
+ return null;
339
+ }
340
+ }
341
+
342
+ /**
343
+ * Returns the first node for which the callback returns `true`.
344
+ *
345
+ * @example
346
+ * ```js
347
+ * const node = tree.getNodeByCallback((node) => node.children.length > 3);
348
+ * ```
349
+ *
350
+ * @group Finding nodes
351
+ */
352
+ getNodeByCallback(callback) {
353
+ return this.tree.getNodeByCallback(callback);
354
+ }
355
+
356
+ /**
357
+ * Returns the node with this id.
358
+ *
359
+ * @example
360
+ * ```js
361
+ * const node = tree.getNodeById(1);
362
+ * ```
363
+ *
364
+ * @group Finding nodes
365
+ */
366
+ getNodeById(id) {
367
+ return this.tree.getNodeById(id);
368
+ }
369
+
370
+ /**
371
+ * Returns the first node with this name.
372
+ *
373
+ * @group Finding nodes
374
+ */
375
+ getNodeByName(name) {
376
+ return this.tree.getNodeByName(name);
377
+ }
378
+
379
+ /**
380
+ * Like `getNodeByName`, but throws when there is no such node. Convenient
381
+ * in tests and when a missing node is a bug.
382
+ *
383
+ * @group Finding nodes
384
+ */
385
+ getNodeByNameMustExist(name) {
386
+ return this.tree.getNodeByNameMustExist(name);
387
+ }
388
+
389
+ /**
390
+ * Returns all nodes with this property value.
391
+ *
392
+ * @example
393
+ * ```js
394
+ * tree.getNodesByProperty("color", "green");
395
+ * ```
396
+ *
397
+ * @group Finding nodes
398
+ */
399
+ getNodesByProperty(key, value) {
400
+ return this.tree.getNodesByProperty(key, value);
401
+ }
402
+
403
+ /**
404
+ * Returns the selected node, or `false` when nothing is selected.
405
+ *
406
+ * @group Selection
407
+ */
408
+ getSelectedNode() {
409
+ return this._selectNodeHandler.getSelectedNode();
410
+ }
411
+
412
+ /**
413
+ * Returns the selected nodes.
414
+ *
415
+ * @group Selection
416
+ */
417
+ getSelectedNodes() {
418
+ return this._selectNodeHandler.getSelectedNodes();
419
+ }
420
+
421
+ /**
422
+ * Returns the current state — `open_nodes` and `selected_node` — whether or
423
+ * not `saveState` is enabled.
424
+ *
425
+ * @group State
426
+ */
427
+ getState() {
428
+ return this._saveStateHandler.getState();
429
+ }
430
+
431
+ /**
432
+ * Returns the state as it is stored.
433
+ *
434
+ * @group State
435
+ */
436
+ getStateFromStorage() {
437
+ return this._saveStateHandler.getStateFromStorage();
438
+ }
439
+
440
+ /**
441
+ * Returns the root node. It is not rendered; its `children` are the
442
+ * top-level nodes. Also available as the `tree` property.
443
+ *
444
+ * @group Finding nodes
445
+ */
446
+ getTree() {
447
+ return this.tree;
448
+ }
449
+
450
+ /**
451
+ * Returns the version of tree-element.
452
+ *
453
+ * @group Other
454
+ */
455
+ getVersion() {
456
+ return version;
457
+ }
458
+
459
+ /**
460
+ * Returns whether the user is dragging a node.
461
+ *
462
+ * @group Other
463
+ */
464
+ isDragging() {
465
+ return this._dndHandler.isDragging;
466
+ }
467
+
468
+ /**
469
+ * Returns whether the node is selected.
470
+ *
471
+ * @group Selection
472
+ */
473
+ isNodeSelected(node) {
474
+ return this._selectNodeHandler.isNodeSelected(node);
475
+ }
476
+
477
+ /**
478
+ * Loads data into the tree.
479
+ *
480
+ * @param parentNode - Replace this node's children instead of the whole
481
+ * tree.
482
+ * @group Loading data
483
+ */
484
+ loadData(data, parentNode) {
485
+ if (data) {
486
+ if (parentNode) {
487
+ this._deselectNodes(parentNode);
488
+ this._loadSubtree(data, parentNode);
489
+ } else {
490
+ this._initTree(data);
491
+ }
492
+ if (this.isDragging()) {
493
+ this._dndHandler.refresh();
494
+ }
495
+ }
496
+ this._triggerEvent("tree.set_data", {
497
+ node: parentNode,
498
+ treeData: data ?? undefined
499
+ });
500
+ }
501
+
502
+ /**
503
+ * Fetches data and loads it into the tree, or into `parentNode`.
504
+ *
505
+ * @param url - Defaults to the `dataUrl` option.
506
+ * @group Loading data
507
+ */
508
+ async loadDataFromUrl(url, parentNode) {
509
+ const requestUrl = url ? new RequestUrl(url) : this._createRequestUrl(parentNode);
510
+ if (requestUrl) {
511
+ await this._dataLoader.loadFromUrl(requestUrl, parentNode);
512
+ }
513
+ }
514
+
515
+ /**
516
+ * Selects the next visible node, like the down arrow key.
517
+ *
518
+ * @group Selection
519
+ */
520
+ moveDown() {
521
+ const selectedNode = this.getSelectedNode();
522
+ if (selectedNode) {
523
+ this._keyHandler.moveDown(selectedNode);
524
+ }
525
+ }
526
+
527
+ /**
528
+ * Moves a node inside the tree.
529
+ *
530
+ * @example
531
+ * ```js
532
+ * tree.moveNode(tree.getNodeById(2), tree.getNodeById(1), "inside");
533
+ * ```
534
+ *
535
+ * @param position - `"before"`, `"after"` or `"inside"`.
536
+ * @group Changing the tree
537
+ */
538
+ moveNode(node, targetNode, position) {
539
+ this.tree.moveNode(node, targetNode, position);
540
+ this._refreshElements(null);
541
+ }
542
+
543
+ /**
544
+ * Selects the previous visible node, like the up arrow key.
545
+ *
546
+ * @group Selection
547
+ */
548
+ moveUp() {
549
+ const selectedNode = this.getSelectedNode();
550
+ if (selectedNode) {
551
+ this._keyHandler.moveUp(selectedNode);
552
+ }
553
+ }
554
+
555
+ /**
556
+ * Opens a folder, and the folders above it. A node marked `load_on_demand`
557
+ * is fetched first, so await the promise when you need to know it is really
558
+ * open.
559
+ *
560
+ * @example
561
+ * ```js
562
+ * await tree.openNode(node);
563
+ * console.log("open");
564
+ * ```
565
+ *
566
+ * @param slide - Override the `slide` option for this call.
567
+ * @group Opening and closing
568
+ */
569
+ async openNode(node, slide) {
570
+ const mustSlide = slide ?? this._options.slide;
571
+ const doOpenNode = async (openedNode, slideOption) => {
572
+ if (!node.children.length) {
573
+ return;
574
+ }
575
+ const folderElement = this._createFolderElement(openedNode);
576
+ await folderElement.open(slideOption, this._options.animationSpeed);
577
+ };
578
+ if (node.isFolder() || node.isEmptyFolder) {
579
+ if (node.load_on_demand) {
580
+ await this._loadFolderOnDemand(node, mustSlide);
581
+ } else {
582
+ let parent = node.parent;
583
+ while (parent) {
584
+ // nb: do not open root element
585
+ if (parent.parent) {
586
+ await doOpenNode(parent, false);
587
+ }
588
+ parent = parent.parent;
589
+ }
590
+ await doOpenNode(node, mustSlide);
591
+ this._saveState();
592
+ }
593
+ }
594
+ }
595
+
596
+ /**
597
+ * Adds a node as the first child of a parent.
598
+ *
599
+ * @returns The new node.
600
+ * @group Changing the tree
601
+ */
602
+ prependNode(nodeData, parentNode) {
603
+ const node = parentNode.prepend(nodeData);
604
+ this._refreshElements(parentNode);
605
+ return node;
606
+ }
607
+
608
+ /**
609
+ * Re-renders the whole tree. Needed after changing node data directly
610
+ * instead of through these methods.
611
+ *
612
+ * @group Changing the tree
613
+ */
614
+ refresh() {
615
+ this._refreshElements(null);
616
+ }
617
+
618
+ /**
619
+ * Recomputes the drop targets. Call this if the layout changes during a
620
+ * drag.
621
+ *
622
+ * @group Other
623
+ */
624
+ refreshHitAreas() {
625
+ this._dndHandler.refresh();
626
+ }
627
+
628
+ /**
629
+ * Removes a node from the selection.
630
+ *
631
+ * @group Selection
632
+ */
633
+ removeFromSelection(node) {
634
+ this._selectNodeHandler.removeFromSelection(node);
635
+ this._getNodeElementForNode(node).deselect();
636
+ this._saveState();
637
+ }
638
+
639
+ /**
640
+ * Removes a node and its children.
641
+ *
642
+ * @group Changing the tree
643
+ */
644
+ removeNode(node) {
645
+ this._selectNodeHandler.removeFromSelection(node, true); // including children
646
+
647
+ const parent = node.parent;
648
+ node.remove();
649
+ this._refreshElements(parent);
650
+ }
651
+
652
+ /**
653
+ * Scrolls the node into view.
654
+ *
655
+ * @group Selection
656
+ */
657
+ scrollToNode(node) {
658
+ if (!node.element) {
659
+ return;
660
+ }
661
+ const top = getOffsetTop(node.element) - getOffsetTop(this._htmlElement);
662
+ this._scrollHandler.scrollToY(top);
663
+ }
664
+
665
+ /**
666
+ * Replaces the selection, and opens the parents of the node.
667
+ *
668
+ * @param node - `null` clears the selection.
669
+ * @param options - `mustSetFocus`: move focus to the node, default `true`.
670
+ * `mustToggle`: deselect the node if it is already selected, default
671
+ * `false`.
672
+ * @group Selection
673
+ */
674
+ selectNode(node, options) {
675
+ if (!node) {
676
+ // Called with empty node -> deselect current node
677
+ this._deselectCurrentNode();
678
+ this._saveStateHandler.saveState();
679
+ return;
680
+ }
681
+ this._selectNodeHandler.selectSingleNode(node, options);
682
+ }
683
+
684
+ /**
685
+ * Changes an option after construction. See
686
+ * [Options](/reference/options#changing-an-option-later) for the caveats.
687
+ *
688
+ * @group Other
689
+ */
690
+ setOption(option, value) {
691
+ this._options[option] = value;
692
+ }
693
+
694
+ /**
695
+ * Applies a state to the tree.
696
+ *
697
+ * @group State
698
+ */
699
+ setState(state) {
700
+ this._saveStateHandler.setInitialState(state);
701
+ this._refreshElements(null);
702
+ }
703
+
704
+ /**
705
+ * Closes an open node and opens a closed one.
706
+ *
707
+ * @param slide - Override the `slide` option for this call.
708
+ * @group Opening and closing
709
+ */
710
+ toggle(node, slide = null) {
711
+ const mustSlide = slide ?? this._options.slide;
712
+ if (node.is_open) {
713
+ this.closeNode(node, mustSlide);
714
+ } else {
715
+ void this.openNode(node, mustSlide);
716
+ }
717
+ }
718
+
719
+ /**
720
+ * Returns the tree as a json string, including changes made through the
721
+ * api.
722
+ *
723
+ * @group Other
724
+ */
725
+ toJson() {
726
+ return JSON.stringify(this.tree.getData());
727
+ }
728
+
729
+ /**
730
+ * Updates the data of a node and re-renders it. A string updates just the
731
+ * name.
732
+ *
733
+ * Changing the `id` re-indexes the node. `children` and `parent` are
734
+ * ignored — use `loadData` or `moveNode` for those.
735
+ *
736
+ * @example
737
+ * ```js
738
+ * tree.updateNode(node, "new name");
739
+ * tree.updateNode(node, { name: "new name", color: "red" });
740
+ * ```
741
+ *
742
+ * @group Changing the tree
743
+ */
744
+ updateNode(node, data) {
745
+ const idIsChanged = typeof data === "object" && data.id && data.id !== node.id;
746
+ if (idIsChanged) {
747
+ this.tree.removeNodeFromIndex(node);
748
+ }
749
+ node.setData(data);
750
+ if (idIsChanged) {
751
+ this.tree.addNodeToIndex(node);
752
+ }
753
+ if (typeof data === "object" && data.children && data.children instanceof Array) {
754
+ node.removeChildren();
755
+ if (data.children.length) {
756
+ node.loadFromData(data.children);
757
+ }
758
+ }
759
+ this._refreshElements(node);
760
+ }
761
+ _createFolderElement(node) {
762
+ const classNames = this._classNames;
763
+ const closedIconElement = this._renderer.closedIconElement;
764
+ const getScrollLeft = this._scrollHandler.getScrollLeft.bind(this._scrollHandler);
765
+ const openedIconElement = this._renderer.openedIconElement;
766
+ const tabIndex = this._options.tabIndex;
767
+ const treeElement = this._htmlElement;
768
+ const triggerEvent = this._triggerEvent.bind(this);
769
+ return new FolderElement({
770
+ classNames,
771
+ closedIconElement,
772
+ getScrollLeft,
773
+ node,
774
+ openedIconElement,
775
+ tabIndex,
776
+ treeElement,
777
+ triggerEvent
778
+ });
779
+ }
780
+ _createNodeElement(node) {
781
+ const classNames = this._classNames;
782
+ const getScrollLeft = this._scrollHandler.getScrollLeft.bind(this._scrollHandler);
783
+ const tabIndex = this._options.tabIndex;
784
+ const treeElement = this._htmlElement;
785
+ return new NodeElement({
786
+ classNames,
787
+ getScrollLeft,
788
+ node,
789
+ tabIndex,
790
+ treeElement
791
+ });
792
+ }
793
+
794
+ /* Create a RequestUrl based on the url in the options.
795
+ * Add a 'node' query parameter for loading on demand
796
+ * Add a 'selected_node' query parameter if a node is selected.
797
+ */
798
+ _createRequestUrl(node) {
799
+ const dataUrl = this._options.dataUrl;
800
+ let url;
801
+ if (typeof dataUrl === "function") {
802
+ url = dataUrl(node);
803
+ } else {
804
+ url = dataUrl;
805
+ }
806
+ if (!url) {
807
+ return null;
808
+ }
809
+ const requestUrl = new RequestUrl(url);
810
+ if (node?.id) {
811
+ // Load on demand of a subtree; add node parameter
812
+ requestUrl.setSearchParam('node', node.id.toString());
813
+ } else {
814
+ // Add selected_node parameter
815
+ const selectedNodeId = this._getNodeIdToBeSelected();
816
+ if (selectedNodeId) {
817
+ requestUrl.setSearchParam('selected_node', selectedNodeId.toString());
818
+ }
819
+ }
820
+ return requestUrl;
821
+ }
822
+ _deselectCurrentNode() {
823
+ const node = this.getSelectedNode();
824
+ if (node) {
825
+ this.removeFromSelection(node);
826
+ }
827
+ }
828
+
829
+ // Deselect the children of the node.
830
+ _deselectNodes(parentNode) {
831
+ const selectedNodesUnderParent = this._selectNodeHandler.getSelectedNodesUnder(parentNode);
832
+ for (const n of selectedNodesUnderParent) {
833
+ this._selectNodeHandler.removeFromSelection(n);
834
+ }
835
+ }
836
+
837
+ // Get the maximum level for auto open
838
+ _getAutoOpenMaxLevel() {
839
+ if (this._options.autoOpen === true) {
840
+ return -1;
841
+ } else if (typeof this._options.autoOpen === "number") {
842
+ return this._options.autoOpen;
843
+ } else if (typeof this._options.autoOpen === "string") {
844
+ return parseInt(this._options.autoOpen, 10);
845
+ }
846
+
847
+ /* istanbul ignore next @preserve */
848
+ return 0;
849
+ }
850
+ _getNodeElement(element) {
851
+ const node = this.getNode(element);
852
+ if (node) {
853
+ return this._getNodeElementForNode(node);
854
+ } else {
855
+ return null;
856
+ }
857
+ }
858
+ _getNodeElementForNode(node) {
859
+ if (node.isFolder()) {
860
+ return this._createFolderElement(node);
861
+ } else {
862
+ return this._createNodeElement(node);
863
+ }
864
+ }
865
+ _getNodeIdToBeSelected() {
866
+ return this._saveStateHandler.getNodeIdToBeSelected();
867
+ }
868
+ _initData() {
869
+ if (this._options.data) {
870
+ this.loadData(this._options.data);
871
+ } else {
872
+ const dataUrl = this._createRequestUrl();
873
+ if (dataUrl) {
874
+ void this.loadDataFromUrl();
875
+ } else {
876
+ this.loadData([]);
877
+ }
878
+ }
879
+ }
880
+ _initTree(data) {
881
+ const doInit = () => {
882
+ if (!this._isInitialized) {
883
+ this._isInitialized = true;
884
+ this._triggerEvent("tree.init");
885
+ }
886
+ };
887
+ this.tree = new this._options.nodeClass(null, true, this._options.nodeClass);
888
+ this._selectNodeHandler.clear();
889
+ this.tree.loadFromData(data);
890
+ const mustLoadOnDemand = this._setInitialState();
891
+ this._refreshElements(null);
892
+ if (mustLoadOnDemand) {
893
+ // Load data on demand and then init the tree
894
+ void this._setInitialStateOnDemand().then(doInit);
895
+ } else {
896
+ doInit();
897
+ }
898
+ }
899
+
900
+ // Does an HTML element of the tree have the focus?
901
+ _isFocusOnTree() {
902
+ const activeElement = document.activeElement;
903
+
904
+ /* istanbul ignore if */
905
+ if (!activeElement) {
906
+ return false;
907
+ }
908
+
909
+ // The keyboard must still work for input elements.
910
+ const tagName = activeElement.tagName;
911
+ if (tagName !== "A" && tagName !== "SPAN") {
912
+ return false;
913
+ }
914
+ const node = this.getNode(activeElement);
915
+ return node?.tree === this.tree;
916
+ }
917
+ _isSelectedNodeInSubtree(subtree) {
918
+ const selectedNode = this.getSelectedNode();
919
+ if (!selectedNode) {
920
+ return false;
921
+ } else {
922
+ return subtree === selectedNode || subtree.isParentOf(selectedNode);
923
+ }
924
+ }
925
+ async _loadFolderOnDemand(node, slide) {
926
+ node.is_loading = true;
927
+ await this.loadDataFromUrl(undefined, node);
928
+ await this.openNode(node, slide);
929
+ }
930
+ _loadSubtree(data, parentNode) {
931
+ parentNode.loadFromData(data);
932
+ parentNode.load_on_demand = false;
933
+ parentNode.is_loading = false;
934
+ this._refreshElements(parentNode);
935
+ }
936
+ _mouseCapture(positionInfo) {
937
+ if (!this._options.dragAndDrop) {
938
+ return false;
939
+ }
940
+ return this._dndHandler.mouseCapture(positionInfo);
941
+ }
942
+ _mouseDrag(positionInfo) {
943
+ /* istanbul ignore if */
944
+ if (!this._options.dragAndDrop) {
945
+ return false;
946
+ }
947
+ const result = this._dndHandler.mouseDrag(positionInfo);
948
+ this._scrollHandler.checkScrolling(positionInfo);
949
+ return result;
950
+ }
951
+ _mouseStart(positionInfo) {
952
+ /* istanbul ignore if */
953
+ if (!this._options.dragAndDrop) {
954
+ return false;
955
+ }
956
+ return this._dndHandler.mouseStart(positionInfo);
957
+ }
958
+ _mouseStop(positionInfo) {
959
+ /* istanbul ignore if */
960
+ if (!this._options.dragAndDrop) {
961
+ return false;
962
+ }
963
+ this._scrollHandler.stopScrolling();
964
+ return this._dndHandler.mouseStop(positionInfo);
965
+ }
966
+ _openParents(node) {
967
+ const parent = node.parent;
968
+ if (parent?.parent && !parent.is_open) {
969
+ void this.openNode(parent, false);
970
+ }
971
+ }
972
+
973
+ /*
974
+ Redraw the tree or part of the tree.
975
+ from_node: redraw this subtree
976
+ */
977
+ _refreshElements(fromNode) {
978
+ const mustSetFocus = this._isFocusOnTree();
979
+ const mustSelect = fromNode ? this._isSelectedNodeInSubtree(fromNode) : false;
980
+ this._renderer.render(fromNode);
981
+ if (mustSelect) {
982
+ this._selectCurrentNode(mustSetFocus);
983
+ }
984
+ this._triggerEvent("tree.refresh");
985
+ }
986
+ _saveState() {
987
+ this._saveStateHandler.saveState();
988
+ }
989
+ _selectCurrentNode(mustSetFocus) {
990
+ const node = this.getSelectedNode();
991
+ if (node) {
992
+ const nodeElement = this._getNodeElementForNode(node);
993
+ nodeElement.select(mustSetFocus);
994
+ }
995
+ }
996
+
997
+ // Set initial state, either by restoring the state or auto-opening nodes
998
+ // result: must load nodes on demand?
999
+ _setInitialState() {
1000
+ const restoreState = () => {
1001
+ // result: is state restored, must load on demand?
1002
+ const state = this._saveStateHandler.getStateFromStorage();
1003
+ if (!state) {
1004
+ return [false, false];
1005
+ } else {
1006
+ const mustLoadOnDemand = this._saveStateHandler.setInitialState(state);
1007
+
1008
+ // return true: the state is restored
1009
+ return [true, mustLoadOnDemand];
1010
+ }
1011
+ };
1012
+ const autoOpenNodes = () => {
1013
+ // result: must load on demand?
1014
+ if (this._options.autoOpen === false) {
1015
+ return false;
1016
+ }
1017
+ const maxLevel = this._getAutoOpenMaxLevel();
1018
+ let mustLoadOnDemand = false;
1019
+ this.tree.iterate((node, level) => {
1020
+ if (node.load_on_demand) {
1021
+ mustLoadOnDemand = true;
1022
+ return false;
1023
+ } else if (!node.hasChildren()) {
1024
+ return false;
1025
+ } else {
1026
+ node.is_open = true;
1027
+ return level !== maxLevel;
1028
+ }
1029
+ });
1030
+ return mustLoadOnDemand;
1031
+ };
1032
+ let [isRestored, mustLoadOnDemand] = restoreState(); // eslint-disable-line prefer-const
1033
+
1034
+ if (!isRestored) {
1035
+ mustLoadOnDemand = autoOpenNodes();
1036
+ }
1037
+ return mustLoadOnDemand;
1038
+ }
1039
+
1040
+ // Set the initial state for nodes that are loaded on demand
1041
+ async _setInitialStateOnDemand() {
1042
+ return new Promise(resolve => {
1043
+ const restoreState = () => {
1044
+ const state = this._saveStateHandler.getStateFromStorage();
1045
+ if (!state) {
1046
+ return false;
1047
+ } else {
1048
+ void this._saveStateHandler.setInitialStateOnDemand(state).then(() => {
1049
+ resolve();
1050
+ });
1051
+ return true;
1052
+ }
1053
+ };
1054
+ const autoOpenNodes = () => {
1055
+ const maxLevel = this._getAutoOpenMaxLevel();
1056
+ let loadingCount = 0;
1057
+ const loadAndOpenNode = node => {
1058
+ loadingCount += 1;
1059
+ void this.openNode(node, false).then(() => {
1060
+ loadingCount -= 1;
1061
+ openNodes();
1062
+ });
1063
+ };
1064
+ const openNodes = () => {
1065
+ this.tree.iterate((node, level) => {
1066
+ if (node.load_on_demand) {
1067
+ if (!node.is_loading) {
1068
+ loadAndOpenNode(node);
1069
+ }
1070
+ return false;
1071
+ } else {
1072
+ void this.openNode(node, false);
1073
+ return level !== maxLevel;
1074
+ }
1075
+ });
1076
+ if (loadingCount === 0) {
1077
+ resolve();
1078
+ }
1079
+ };
1080
+ openNodes();
1081
+ };
1082
+ if (!restoreState()) {
1083
+ autoOpenNodes();
1084
+ }
1085
+ });
1086
+ }
1087
+
1088
+ // Set this HTML element to this node in the node map.
1089
+ _setNodeElement(element, node) {
1090
+ this._nodeMap.set(element, node);
1091
+ }
1092
+ _triggerEvent(eventName, values) {
1093
+ return this._triggerEventProvider(this._htmlElement, eventName, values);
1094
+ }
1095
+ }
1096
+
1097
+ export { TreeElement as default };
1098
+ //# sourceMappingURL=index.js.map