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