tree-element 0.1.1 → 0.3.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 (155) hide show
  1. package/README.md +43 -31
  2. package/lib/animation.d.ts +2 -2
  3. package/lib/animation.d.ts.map +1 -1
  4. package/lib/animation.js +16 -12
  5. package/lib/animation.js.map +1 -1
  6. package/lib/classNames.d.ts.map +1 -1
  7. package/lib/classNames.js +2 -2
  8. package/lib/classNames.js.map +1 -1
  9. package/lib/dataLoader.d.ts +1 -0
  10. package/lib/dataLoader.d.ts.map +1 -1
  11. package/lib/dataLoader.js +22 -20
  12. package/lib/dataLoader.js.map +1 -1
  13. package/lib/dragAndDropHandler/binarySearch.js +3 -3
  14. package/lib/dragAndDropHandler/binarySearch.js.map +1 -1
  15. package/lib/dragAndDropHandler/dragElement.d.ts.map +1 -1
  16. package/lib/dragAndDropHandler/dragElement.js +11 -13
  17. package/lib/dragAndDropHandler/dragElement.js.map +1 -1
  18. package/lib/dragAndDropHandler/generateHitAreas.d.ts.map +1 -1
  19. package/lib/dragAndDropHandler/generateHitAreas.js +25 -25
  20. package/lib/dragAndDropHandler/generateHitAreas.js.map +1 -1
  21. package/lib/dragAndDropHandler/index.d.ts.map +1 -1
  22. package/lib/dragAndDropHandler/index.js +125 -158
  23. package/lib/dragAndDropHandler/index.js.map +1 -1
  24. package/lib/dragAndDropHandler/iterateVisibleNodes.d.ts.map +1 -1
  25. package/lib/dragAndDropHandler/iterateVisibleNodes.js +23 -22
  26. package/lib/dragAndDropHandler/iterateVisibleNodes.js.map +1 -1
  27. package/lib/dragAndDropHandler/types.d.ts.map +1 -1
  28. package/lib/elementsRenderer.d.ts +20 -7
  29. package/lib/elementsRenderer.d.ts.map +1 -1
  30. package/lib/elementsRenderer.js +195 -136
  31. package/lib/elementsRenderer.js.map +1 -1
  32. package/lib/events.d.ts +44 -11
  33. package/lib/events.d.ts.map +1 -1
  34. package/lib/index.d.ts +36 -13
  35. package/lib/index.d.ts.map +1 -1
  36. package/lib/index.js +386 -349
  37. package/lib/index.js.map +1 -1
  38. package/lib/keyHandler.d.ts +13 -8
  39. package/lib/keyHandler.d.ts.map +1 -1
  40. package/lib/keyHandler.js +106 -51
  41. package/lib/keyHandler.js.map +1 -1
  42. package/lib/methodTypes.d.ts +3 -3
  43. package/lib/methodTypes.d.ts.map +1 -1
  44. package/lib/mouseHandler.d.ts +1 -1
  45. package/lib/mouseHandler.d.ts.map +1 -1
  46. package/lib/mouseHandler.js +36 -49
  47. package/lib/mouseHandler.js.map +1 -1
  48. package/lib/mouseUtils.d.ts.map +1 -1
  49. package/lib/mouseUtils.js +2 -2
  50. package/lib/mouseUtils.js.map +1 -1
  51. package/lib/node.d.ts +47 -16
  52. package/lib/node.d.ts.map +1 -1
  53. package/lib/node.js +169 -171
  54. package/lib/node.js.map +1 -1
  55. package/lib/nodeElement/borderDropHint.d.ts.map +1 -1
  56. package/lib/nodeElement/borderDropHint.js +4 -5
  57. package/lib/nodeElement/borderDropHint.js.map +1 -1
  58. package/lib/nodeElement/folderElement.d.ts +8 -5
  59. package/lib/nodeElement/folderElement.d.ts.map +1 -1
  60. package/lib/nodeElement/folderElement.js +75 -67
  61. package/lib/nodeElement/folderElement.js.map +1 -1
  62. package/lib/nodeElement/ghostDropHint.d.ts.map +1 -1
  63. package/lib/nodeElement/ghostDropHint.js +13 -13
  64. package/lib/nodeElement/ghostDropHint.js.map +1 -1
  65. package/lib/nodeElement/index.d.ts +2 -1
  66. package/lib/nodeElement/index.d.ts.map +1 -1
  67. package/lib/nodeElement/index.js +38 -30
  68. package/lib/nodeElement/index.js.map +1 -1
  69. package/lib/nodeUtils.d.ts.map +1 -1
  70. package/lib/nodeUtils.js +1 -1
  71. package/lib/nodeUtils.js.map +1 -1
  72. package/lib/options.d.ts +19 -3
  73. package/lib/options.d.ts.map +1 -1
  74. package/lib/positionUtils.js +3 -3
  75. package/lib/positionUtils.js.map +1 -1
  76. package/lib/requestUrl.d.ts +1 -1
  77. package/lib/requestUrl.d.ts.map +1 -1
  78. package/lib/requestUrl.js +6 -17
  79. package/lib/requestUrl.js.map +1 -1
  80. package/lib/saveStateHandler.d.ts.map +1 -1
  81. package/lib/saveStateHandler.js +59 -89
  82. package/lib/saveStateHandler.js.map +1 -1
  83. package/lib/scrollHandler/containerScrollParent.d.ts.map +1 -1
  84. package/lib/scrollHandler/containerScrollParent.js +11 -18
  85. package/lib/scrollHandler/containerScrollParent.js.map +1 -1
  86. package/lib/scrollHandler/createScrollParent.d.ts.map +1 -1
  87. package/lib/scrollHandler/createScrollParent.js +12 -13
  88. package/lib/scrollHandler/createScrollParent.js.map +1 -1
  89. package/lib/scrollHandler/documentScrollParent.d.ts.map +1 -1
  90. package/lib/scrollHandler/documentScrollParent.js +18 -27
  91. package/lib/scrollHandler/documentScrollParent.js.map +1 -1
  92. package/lib/scrollHandler/scrollParent.d.ts.map +1 -1
  93. package/lib/scrollHandler/scrollParent.js +30 -34
  94. package/lib/scrollHandler/scrollParent.js.map +1 -1
  95. package/lib/scrollHandler.d.ts.map +1 -1
  96. package/lib/scrollHandler.js +11 -14
  97. package/lib/scrollHandler.js.map +1 -1
  98. package/lib/selectNodeHandler.d.ts +4 -2
  99. package/lib/selectNodeHandler.d.ts.map +1 -1
  100. package/lib/selectNodeHandler.js +64 -83
  101. package/lib/selectNodeHandler.js.map +1 -1
  102. package/lib/setDefaultOptions.d.ts.map +1 -1
  103. package/lib/setDefaultOptions.js +34 -38
  104. package/lib/setDefaultOptions.js.map +1 -1
  105. package/lib/triggerCustomEvent.d.ts.map +1 -1
  106. package/lib/triggerCustomEvent.js +2 -2
  107. package/lib/triggerCustomEvent.js.map +1 -1
  108. package/lib/util.d.ts.map +1 -1
  109. package/lib/util.js +2 -2
  110. package/lib/util.js.map +1 -1
  111. package/lib/version.d.ts +1 -1
  112. package/lib/version.js +1 -1
  113. package/lib/version.js.map +1 -1
  114. package/package.json +43 -32
  115. package/src/animation.ts +14 -12
  116. package/src/classNames.ts +52 -52
  117. package/src/dataLoader.ts +79 -71
  118. package/src/dragAndDropHandler/binarySearch.ts +17 -17
  119. package/src/dragAndDropHandler/dragElement.ts +51 -50
  120. package/src/dragAndDropHandler/generateHitAreas.ts +156 -161
  121. package/src/dragAndDropHandler/index.ts +334 -348
  122. package/src/dragAndDropHandler/iterateVisibleNodes.ts +61 -63
  123. package/src/dragAndDropHandler/types.ts +5 -5
  124. package/src/elementsRenderer.ts +377 -294
  125. package/src/events.ts +241 -208
  126. package/src/header.txt +1 -1
  127. package/src/index.ts +259 -229
  128. package/src/keyHandler.ts +179 -115
  129. package/src/methodTypes.ts +7 -10
  130. package/src/mouseHandler.ts +316 -316
  131. package/src/mouseUtils.ts +14 -14
  132. package/src/node.ts +773 -771
  133. package/src/nodeElement/borderDropHint.ts +25 -25
  134. package/src/nodeElement/folderElement.ts +116 -102
  135. package/src/nodeElement/ghostDropHint.ts +64 -62
  136. package/src/nodeElement/index.ts +81 -73
  137. package/src/nodeUtils.ts +5 -5
  138. package/src/options.ts +25 -5
  139. package/src/requestUrl.ts +7 -14
  140. package/src/saveStateHandler.ts +191 -207
  141. package/src/scrollHandler/containerScrollParent.ts +40 -49
  142. package/src/scrollHandler/createScrollParent.ts +26 -27
  143. package/src/scrollHandler/documentScrollParent.ts +66 -72
  144. package/src/scrollHandler/scrollParent.ts +90 -86
  145. package/src/scrollHandler.ts +45 -45
  146. package/src/selectNodeHandler.ts +171 -165
  147. package/src/setDefaultOptions.ts +44 -32
  148. package/src/triggerCustomEvent.ts +7 -3
  149. package/src/util.ts +2 -3
  150. package/src/version.ts +1 -1
  151. package/tree_element.css +87 -69
  152. package/tree_element.debug.js +3866 -3831
  153. package/tree_element.debug.js.map +1 -1
  154. package/tree_element.js +2 -2
  155. package/tree_element.js.map +1 -1
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- Html-tree 0.1.1
2
+ Tree-element 0.3.0
3
3
 
4
4
  Copyright 2026 Marco Braak
5
5
 
@@ -41,11 +41,10 @@ import version from './version.js';
41
41
  // as its global, instead of an object of named exports.
42
42
 
43
43
  class TreeElement {
44
- /** @hidden */
45
- tree;
46
44
  _classNames;
47
45
  _dataLoader;
48
46
  _dndHandler;
47
+ _fallbackTabStop;
49
48
  _htmlElement;
50
49
  _isInitialized;
51
50
  _keyHandler;
@@ -56,9 +55,12 @@ class TreeElement {
56
55
  _saveStateHandler;
57
56
  _scrollHandler;
58
57
  _selectNodeHandler;
58
+ _tree;
59
59
  _triggerEventProvider;
60
60
 
61
- /** @hidden */
61
+ /**
62
+ @hidden
63
+ */
62
64
  constructor({
63
65
  htmlElement,
64
66
  overrideTriggerEventProvider,
@@ -69,9 +71,12 @@ class TreeElement {
69
71
  this._classNames = createClassNames(this._options);
70
72
  this._triggerEventProvider = overrideTriggerEventProvider ?? triggerCustomEvent;
71
73
  this._isInitialized = false;
72
- this.tree = new Node({}, true);
74
+ this._tree = new Node({}, true);
73
75
  this._nodeMap = new WeakMap();
74
- const {
76
+ this._fallbackTabStop = null;
77
+ let {
78
+ ariaLabel,
79
+ ariaLabelledBy,
75
80
  autoEscape,
76
81
  buttonLeft,
77
82
  closedIcon,
@@ -94,125 +99,131 @@ class TreeElement {
94
99
  slide,
95
100
  tabIndex
96
101
  } = 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();
102
+ let classNames = this._classNames;
103
+ let closeNode = this.closeNode.bind(this);
104
+ let getNodeElement = this._getNodeElement.bind(this);
105
+ let getNodeElementForNode = this._getNodeElementForNode.bind(this);
106
+ let getFocusedNode = this._getFocusedNode.bind(this);
107
+ let getNodeById = this.getNodeById.bind(this);
108
+ let getSelectedNode = this.getSelectedNode.bind(this);
109
+ let getTree = this.getTree.bind(this);
110
+ let loadData = this.loadData.bind(this);
111
+ let openNode = this.openNode.bind(this);
112
+ let openParents = this._openParents.bind(this);
113
+ let refreshElements = this._refreshElements.bind(this);
114
+ let refreshHitAreas = this.refreshHitAreas.bind(this);
115
+ let setNodeElement = this._setNodeElement.bind(this);
116
+ let treeElement = this._htmlElement;
117
+ let triggerEvent = this._triggerEvent.bind(this);
118
+ let updateTabStop = this._updateTabStop.bind(this);
119
+ let saveState = () => {
120
+ saveStateHandler._saveState();
115
121
  };
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
122
+ let selectNodeHandler = new SelectNodeHandler({
123
+ _getNodeById: getNodeById,
124
+ _getNodeElementForNode: getNodeElementForNode,
125
+ _getOnCanSelectNode: () => this._options.onCanSelectNode,
126
+ _getSelectable: () => this._options.selectable,
127
+ _openParents: openParents,
128
+ _saveState: saveState,
129
+ _triggerEvent: triggerEvent,
130
+ _updateTabStop: updateTabStop
124
131
  });
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
132
+ let addToSelection = selectNodeHandler._addToSelection.bind(selectNodeHandler);
133
+ let getSelectedNodes = selectNodeHandler._getSelectedNodes.bind(selectNodeHandler);
134
+ let isNodeSelected = selectNodeHandler._isNodeSelected.bind(selectNodeHandler);
135
+ let removeFromSelection = selectNodeHandler._removeFromSelection.bind(selectNodeHandler);
136
+ let selectNode = selectNodeHandler._selectSingleNode.bind(selectNodeHandler);
137
+ let getMouseDelay = () => this._options.startDndDelay ?? 0;
138
+ let dataLoader = new DataLoader({
139
+ _classNames: classNames,
140
+ _dataFilter: dataFilter,
141
+ _loadData: loadData,
142
+ _treeElement: treeElement,
143
+ _triggerEvent: triggerEvent
137
144
  });
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
145
+ let saveStateHandler = new SaveStateHandler({
146
+ _addToSelection: addToSelection,
147
+ _getNodeById: getNodeById,
148
+ _getSelectedNodes: getSelectedNodes,
149
+ _getTree: getTree,
150
+ _onGetStateFromStorage: onGetStateFromStorage,
151
+ _onSetStateFromStorage: onSetStateFromStorage,
152
+ _openNode: openNode,
153
+ _refreshElements: refreshElements,
154
+ _removeFromSelection: removeFromSelection,
155
+ _saveState: saveStateOption
149
156
  });
150
- const scrollHandler = new ScrollHandler({
151
- refreshHitAreas,
152
- treeElement
157
+ let scrollHandler = new ScrollHandler({
158
+ _refreshHitAreas: refreshHitAreas,
159
+ _treeElement: treeElement
153
160
  });
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
161
+ let getScrollLeft = scrollHandler._getScrollLeft.bind(scrollHandler);
162
+ let dndHandler = new DragAndDropHandler({
163
+ _autoEscape: autoEscape,
164
+ _classNames: classNames,
165
+ _getNodeElement: getNodeElement,
166
+ _getNodeElementForNode: getNodeElementForNode,
167
+ _getScrollLeft: getScrollLeft,
168
+ _getTree: getTree,
169
+ _onCanMove: onCanMove,
170
+ _onCanMoveTo: onCanMoveTo,
171
+ _onDragMove: onDragMove,
172
+ _onDragStop: onDragStop,
173
+ _onIsMoveHandle: onIsMoveHandle,
174
+ _openFolderDelay: openFolderDelay,
175
+ _openNode: openNode,
176
+ _refreshElements: refreshElements,
177
+ _slide: slide,
178
+ _treeElement: treeElement,
179
+ _triggerEvent: triggerEvent
173
180
  });
174
- const keyHandler = new KeyHandler({
175
- closeNode,
176
- getSelectedNode,
177
- isFocusOnTree,
178
- keyboardSupport,
179
- openNode,
180
- selectNode
181
+ let keyHandler = new KeyHandler({
182
+ _closeNode: closeNode,
183
+ _getFocusedNode: getFocusedNode,
184
+ _getSelectedNode: getSelectedNode,
185
+ _getTree: getTree,
186
+ _keyboardSupport: keyboardSupport,
187
+ _openNode: openNode,
188
+ _selectNode: selectNode,
189
+ _triggerEvent: triggerEvent
181
190
  });
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
191
+ let renderer = new ElementsRenderer({
192
+ _ariaLabel: ariaLabel,
193
+ _ariaLabelledBy: ariaLabelledBy,
194
+ _autoEscape: autoEscape,
195
+ _buttonLeft: buttonLeft,
196
+ _classNames: classNames,
197
+ _closedIcon: closedIcon,
198
+ _dragAndDrop: dragAndDrop,
199
+ _getTree: getTree,
200
+ _htmlElement: treeElement,
201
+ _isNodeSelected: isNodeSelected,
202
+ _onCreateLi: onCreateLi,
203
+ _openedIcon: openedIcon,
204
+ _rtl: rtl,
205
+ _setNodeElement: setNodeElement,
206
+ _showEmptyFolder: showEmptyFolder,
207
+ _tabIndex: tabIndex
197
208
  });
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
209
+ let getNode = this.getNode.bind(this);
210
+ let onMouseCapture = this._mouseCapture.bind(this);
211
+ let onMouseDrag = this._mouseDrag.bind(this);
212
+ let onMouseStart = this._mouseStart.bind(this);
213
+ let onMouseStop = this._mouseStop.bind(this);
214
+ let mouseHandler = new MouseHandler({
215
+ _classNames: classNames,
216
+ _element: treeElement,
217
+ _getMouseDelay: getMouseDelay,
218
+ _getNode: getNode,
219
+ _onClickButton: this.toggle.bind(this),
220
+ _onClickTitle: selectNode,
221
+ _onMouseCapture: onMouseCapture,
222
+ _onMouseDrag: onMouseDrag,
223
+ _onMouseStart: onMouseStart,
224
+ _onMouseStop: onMouseStop,
225
+ _triggerEvent: triggerEvent,
226
+ _useContextMenu: this._options.useContextMenu
216
227
  });
217
228
  this._dataLoader = dataLoader;
218
229
  this._dndHandler = dndHandler;
@@ -232,7 +243,7 @@ class TreeElement {
232
243
  * @group Changing the tree
233
244
  */
234
245
  addNodeAfter(nodeData, existingNode) {
235
- const newNode = existingNode.addAfter(nodeData);
246
+ let newNode = existingNode.addAfter(nodeData);
236
247
  if (newNode) {
237
248
  this._refreshElements(existingNode.parent);
238
249
  }
@@ -246,7 +257,7 @@ class TreeElement {
246
257
  * @group Changing the tree
247
258
  */
248
259
  addNodeBefore(nodeData, existingNode) {
249
- const newNode = existingNode.addBefore(nodeData);
260
+ let newNode = existingNode.addBefore(nodeData);
250
261
  if (newNode) {
251
262
  this._refreshElements(existingNode.parent);
252
263
  }
@@ -261,7 +272,7 @@ class TreeElement {
261
272
  * @group Changing the tree
262
273
  */
263
274
  addParentNode(nodeData, existingNode) {
264
- const newNode = existingNode.addParent(nodeData);
275
+ let newNode = existingNode.addParent(nodeData);
265
276
  if (newNode) {
266
277
  this._refreshElements(newNode.parent);
267
278
  }
@@ -274,10 +285,11 @@ class TreeElement {
274
285
  * @param mustSetFocus - Move the focus to the node. Default `true`.
275
286
  * @group Selection
276
287
  */
277
- addToSelection(node, mustSetFocus) {
278
- this._selectNodeHandler.addToSelection(node);
288
+ addToSelection(node, mustSetFocus = true) {
289
+ this._selectNodeHandler._addToSelection(node);
279
290
  this._openParents(node);
280
- this._getNodeElementForNode(node).select(mustSetFocus ?? true);
291
+ this._getNodeElementForNode(node)._select(mustSetFocus);
292
+ this._updateTabStop();
281
293
  this._saveState();
282
294
  }
283
295
 
@@ -293,22 +305,30 @@ class TreeElement {
293
305
  * @group Changing the tree
294
306
  */
295
307
  appendNode(nodeData, parentNode) {
296
- const node = parentNode.append(nodeData);
308
+ let node = parentNode.append(nodeData);
297
309
  this._refreshElements(parentNode);
298
310
  return node;
299
311
  }
300
312
 
301
313
  /**
302
- * Closes a folder.
314
+ * Closes a folder. Await the promise when you need to know the closing
315
+ * animation has finished.
316
+ *
317
+ * @example
318
+ * ```js
319
+ * await tree.closeNode(node);
320
+ * console.log("closed");
321
+ * ```
303
322
  *
304
323
  * @param slide - Override the `slide` option for this call.
305
324
  * @group Opening and closing
306
325
  */
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();
326
+ async closeNode(node, slide) {
327
+ if (!(node.isFolder() || node.isEmptyFolder)) {
328
+ return;
311
329
  }
330
+ await this._createFolderElement(node)._close(slide ?? this._options.slide, this._options.animationSpeed);
331
+ this._saveState();
312
332
  }
313
333
 
314
334
  /**
@@ -318,11 +338,11 @@ class TreeElement {
318
338
  * @group Other
319
339
  */
320
340
  deinit() {
321
- this._htmlElement.textContent = '';
322
- this._dataLoader.deinit();
323
- this._keyHandler.deinit();
324
- this._mouseHandler.deinit();
325
- this.tree = new Node({}, true);
341
+ this._htmlElement.textContent = "";
342
+ this._dataLoader._deinit();
343
+ this._keyHandler._deinit();
344
+ this._mouseHandler._deinit();
345
+ this._tree = new Node({}, true);
326
346
  }
327
347
 
328
348
  /**
@@ -331,12 +351,8 @@ class TreeElement {
331
351
  * @group Finding nodes
332
352
  */
333
353
  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
- }
354
+ let liElement = element.closest(`li.${this._classNames.common}`);
355
+ return liElement ? this._nodeMap.get(liElement) ?? null : null;
340
356
  }
341
357
 
342
358
  /**
@@ -350,7 +366,7 @@ class TreeElement {
350
366
  * @group Finding nodes
351
367
  */
352
368
  getNodeByCallback(callback) {
353
- return this.tree.getNodeByCallback(callback);
369
+ return this._tree.getNodeByCallback(callback);
354
370
  }
355
371
 
356
372
  /**
@@ -364,7 +380,7 @@ class TreeElement {
364
380
  * @group Finding nodes
365
381
  */
366
382
  getNodeById(id) {
367
- return this.tree.getNodeById(id);
383
+ return this._tree.getNodeById(id);
368
384
  }
369
385
 
370
386
  /**
@@ -373,7 +389,7 @@ class TreeElement {
373
389
  * @group Finding nodes
374
390
  */
375
391
  getNodeByName(name) {
376
- return this.tree.getNodeByName(name);
392
+ return this._tree.getNodeByName(name);
377
393
  }
378
394
 
379
395
  /**
@@ -383,7 +399,7 @@ class TreeElement {
383
399
  * @group Finding nodes
384
400
  */
385
401
  getNodeByNameMustExist(name) {
386
- return this.tree.getNodeByNameMustExist(name);
402
+ return this._tree.getNodeByNameMustExist(name);
387
403
  }
388
404
 
389
405
  /**
@@ -397,16 +413,16 @@ class TreeElement {
397
413
  * @group Finding nodes
398
414
  */
399
415
  getNodesByProperty(key, value) {
400
- return this.tree.getNodesByProperty(key, value);
416
+ return this._tree.getNodesByProperty(key, value);
401
417
  }
402
418
 
403
419
  /**
404
- * Returns the selected node, or `false` when nothing is selected.
420
+ * Returns the selected node, or `null` when nothing is selected.
405
421
  *
406
422
  * @group Selection
407
423
  */
408
424
  getSelectedNode() {
409
- return this._selectNodeHandler.getSelectedNode();
425
+ return this._selectNodeHandler._getSelectedNode();
410
426
  }
411
427
 
412
428
  /**
@@ -415,7 +431,7 @@ class TreeElement {
415
431
  * @group Selection
416
432
  */
417
433
  getSelectedNodes() {
418
- return this._selectNodeHandler.getSelectedNodes();
434
+ return this._selectNodeHandler._getSelectedNodes();
419
435
  }
420
436
 
421
437
  /**
@@ -425,7 +441,7 @@ class TreeElement {
425
441
  * @group State
426
442
  */
427
443
  getState() {
428
- return this._saveStateHandler.getState();
444
+ return this._saveStateHandler._getState();
429
445
  }
430
446
 
431
447
  /**
@@ -434,7 +450,7 @@ class TreeElement {
434
450
  * @group State
435
451
  */
436
452
  getStateFromStorage() {
437
- return this._saveStateHandler.getStateFromStorage();
453
+ return this._saveStateHandler._getStateFromStorage();
438
454
  }
439
455
 
440
456
  /**
@@ -444,7 +460,7 @@ class TreeElement {
444
460
  * @group Finding nodes
445
461
  */
446
462
  getTree() {
447
- return this.tree;
463
+ return this._tree;
448
464
  }
449
465
 
450
466
  /**
@@ -462,7 +478,7 @@ class TreeElement {
462
478
  * @group Other
463
479
  */
464
480
  isDragging() {
465
- return this._dndHandler.isDragging;
481
+ return this._dndHandler._isDragging;
466
482
  }
467
483
 
468
484
  /**
@@ -471,7 +487,7 @@ class TreeElement {
471
487
  * @group Selection
472
488
  */
473
489
  isNodeSelected(node) {
474
- return this._selectNodeHandler.isNodeSelected(node);
490
+ return this._selectNodeHandler._isNodeSelected(node);
475
491
  }
476
492
 
477
493
  /**
@@ -490,7 +506,7 @@ class TreeElement {
490
506
  this._initTree(data);
491
507
  }
492
508
  if (this.isDragging()) {
493
- this._dndHandler.refresh();
509
+ this._dndHandler._refresh();
494
510
  }
495
511
  }
496
512
  this._triggerEvent("tree.set_data", {
@@ -506,9 +522,9 @@ class TreeElement {
506
522
  * @group Loading data
507
523
  */
508
524
  async loadDataFromUrl(url, parentNode) {
509
- const requestUrl = url ? new RequestUrl(url) : this._createRequestUrl(parentNode);
525
+ let requestUrl = url ? new RequestUrl(url) : this._createRequestUrl(parentNode);
510
526
  if (requestUrl) {
511
- await this._dataLoader.loadFromUrl(requestUrl, parentNode);
527
+ await this._dataLoader._loadFromUrl(requestUrl, parentNode);
512
528
  }
513
529
  }
514
530
 
@@ -518,10 +534,7 @@ class TreeElement {
518
534
  * @group Selection
519
535
  */
520
536
  moveDown() {
521
- const selectedNode = this.getSelectedNode();
522
- if (selectedNode) {
523
- this._keyHandler.moveDown(selectedNode);
524
- }
537
+ this._keyHandler._moveDown();
525
538
  }
526
539
 
527
540
  /**
@@ -536,7 +549,7 @@ class TreeElement {
536
549
  * @group Changing the tree
537
550
  */
538
551
  moveNode(node, targetNode, position) {
539
- this.tree.moveNode(node, targetNode, position);
552
+ this._tree.moveNode(node, targetNode, position);
540
553
  this._refreshElements(null);
541
554
  }
542
555
 
@@ -546,10 +559,7 @@ class TreeElement {
546
559
  * @group Selection
547
560
  */
548
561
  moveUp() {
549
- const selectedNode = this.getSelectedNode();
550
- if (selectedNode) {
551
- this._keyHandler.moveUp(selectedNode);
552
- }
562
+ this._keyHandler._moveUp();
553
563
  }
554
564
 
555
565
  /**
@@ -567,13 +577,13 @@ class TreeElement {
567
577
  * @group Opening and closing
568
578
  */
569
579
  async openNode(node, slide) {
570
- const mustSlide = slide ?? this._options.slide;
571
- const doOpenNode = async (openedNode, slideOption) => {
580
+ let mustSlide = slide ?? this._options.slide;
581
+ let doOpenNode = async (openedNode, slideOption) => {
572
582
  if (!node.children.length) {
573
583
  return;
574
584
  }
575
- const folderElement = this._createFolderElement(openedNode);
576
- await folderElement.open(slideOption, this._options.animationSpeed);
585
+ let folderElement = this._createFolderElement(openedNode);
586
+ await folderElement._open(slideOption, this._options.animationSpeed);
577
587
  };
578
588
  if (node.isFolder() || node.isEmptyFolder) {
579
589
  if (node.load_on_demand) {
@@ -600,7 +610,7 @@ class TreeElement {
600
610
  * @group Changing the tree
601
611
  */
602
612
  prependNode(nodeData, parentNode) {
603
- const node = parentNode.prepend(nodeData);
613
+ let node = parentNode.prepend(nodeData);
604
614
  this._refreshElements(parentNode);
605
615
  return node;
606
616
  }
@@ -622,7 +632,7 @@ class TreeElement {
622
632
  * @group Other
623
633
  */
624
634
  refreshHitAreas() {
625
- this._dndHandler.refresh();
635
+ this._dndHandler._refresh();
626
636
  }
627
637
 
628
638
  /**
@@ -631,8 +641,9 @@ class TreeElement {
631
641
  * @group Selection
632
642
  */
633
643
  removeFromSelection(node) {
634
- this._selectNodeHandler.removeFromSelection(node);
635
- this._getNodeElementForNode(node).deselect();
644
+ this._selectNodeHandler._removeFromSelection(node);
645
+ this._getNodeElementForNode(node)._deselect();
646
+ this._updateTabStop();
636
647
  this._saveState();
637
648
  }
638
649
 
@@ -642,9 +653,9 @@ class TreeElement {
642
653
  * @group Changing the tree
643
654
  */
644
655
  removeNode(node) {
645
- this._selectNodeHandler.removeFromSelection(node, true); // including children
656
+ this._selectNodeHandler._removeFromSelection(node, true); // including children
646
657
 
647
- const parent = node.parent;
658
+ let parent = node.parent;
648
659
  node.remove();
649
660
  this._refreshElements(parent);
650
661
  }
@@ -658,8 +669,8 @@ class TreeElement {
658
669
  if (!node.element) {
659
670
  return;
660
671
  }
661
- const top = getOffsetTop(node.element) - getOffsetTop(this._htmlElement);
662
- this._scrollHandler.scrollToY(top);
672
+ let top = getOffsetTop(node.element) - getOffsetTop(this._htmlElement);
673
+ this._scrollHandler._scrollToY(top);
663
674
  }
664
675
 
665
676
  /**
@@ -675,10 +686,15 @@ class TreeElement {
675
686
  if (!node) {
676
687
  // Called with empty node -> deselect current node
677
688
  this._deselectCurrentNode();
678
- this._saveStateHandler.saveState();
689
+ this._saveStateHandler._saveState();
679
690
  return;
680
691
  }
681
- this._selectNodeHandler.selectSingleNode(node, options);
692
+
693
+ // Unlike a click, selecting the selected node again keeps it selected
694
+ this._selectNodeHandler._selectSingleNode(node, {
695
+ mustToggle: false,
696
+ ...options
697
+ });
682
698
  }
683
699
 
684
700
  /**
@@ -692,27 +708,31 @@ class TreeElement {
692
708
  }
693
709
 
694
710
  /**
695
- * Applies a state to the tree.
711
+ * Applies a state to the tree, and saves it to storage when `saveState` is
712
+ * enabled.
696
713
  *
697
714
  * @group State
698
715
  */
699
716
  setState(state) {
700
- this._saveStateHandler.setInitialState(state);
717
+ this._saveStateHandler._setInitialState(state);
701
718
  this._refreshElements(null);
719
+ this._saveState();
702
720
  }
703
721
 
704
722
  /**
705
- * Closes an open node and opens a closed one.
723
+ * Closes an open node and opens a closed one. Await the promise when you
724
+ * need to know the node is really open or closed, like with `openNode` and
725
+ * `closeNode`.
706
726
  *
707
727
  * @param slide - Override the `slide` option for this call.
708
728
  * @group Opening and closing
709
729
  */
710
- toggle(node, slide = null) {
711
- const mustSlide = slide ?? this._options.slide;
730
+ async toggle(node, slide = null) {
731
+ let mustSlide = slide ?? this._options.slide;
712
732
  if (node.is_open) {
713
- this.closeNode(node, mustSlide);
733
+ await this.closeNode(node, mustSlide);
714
734
  } else {
715
- void this.openNode(node, mustSlide);
735
+ await this.openNode(node, mustSlide);
716
736
  }
717
737
  }
718
738
 
@@ -723,7 +743,7 @@ class TreeElement {
723
743
  * @group Other
724
744
  */
725
745
  toJson() {
726
- return JSON.stringify(this.tree.getData());
746
+ return JSON.stringify(this._tree.getData());
727
747
  }
728
748
 
729
749
  /**
@@ -742,15 +762,15 @@ class TreeElement {
742
762
  * @group Changing the tree
743
763
  */
744
764
  updateNode(node, data) {
745
- const idIsChanged = typeof data === "object" && data.id && data.id !== node.id;
765
+ let idIsChanged = typeof data === "object" && data.id && data.id !== node.id;
746
766
  if (idIsChanged) {
747
- this.tree.removeNodeFromIndex(node);
767
+ this._tree.removeNodeFromIndex(node);
748
768
  }
749
769
  node.setData(data);
750
770
  if (idIsChanged) {
751
- this.tree.addNodeToIndex(node);
771
+ this._tree.addNodeToIndex(node);
752
772
  }
753
- if (typeof data === "object" && data.children && data.children instanceof Array) {
773
+ if (typeof data === "object" && data.children && Array.isArray(data.children)) {
754
774
  node.removeChildren();
755
775
  if (data.children.length) {
756
776
  node.loadFromData(data.children);
@@ -758,69 +778,96 @@ class TreeElement {
758
778
  }
759
779
  this._refreshElements(node);
760
780
  }
781
+
782
+ // Open the nodes up to the autoOpen level. A folder that is loaded on
783
+ // demand is fetched first, and its children are opened when the data has
784
+ // arrived.
785
+ async _autoOpenNodesOnDemand() {
786
+ let maxLevel = this._getAutoOpenMaxLevel();
787
+ let openNodes = async () => {
788
+ let loading = [];
789
+ this._tree.iterate((node, level) => {
790
+ if (node.load_on_demand) {
791
+ if (!node.is_loading) {
792
+ loading.push(this.openNode(node, false).then(openNodes));
793
+ }
794
+ return false;
795
+ }
796
+ void this.openNode(node, false);
797
+ return level !== maxLevel;
798
+ });
799
+ await Promise.all(loading);
800
+ };
801
+ await openNodes();
802
+ }
761
803
  _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);
804
+ let classNames = this._classNames;
805
+ let closedIconElement = this._renderer._closedIconElement;
806
+ let getScrollLeft = this._scrollHandler._getScrollLeft.bind(this._scrollHandler);
807
+ let openedIconElement = this._renderer._openedIconElement;
808
+ let tabIndex = this._options.tabIndex;
809
+ let treeElement = this._htmlElement;
810
+ let triggerEvent = this._triggerEvent.bind(this);
811
+
812
+ // A selected node in a closed folder is rendered when the folder is
813
+ // opened, which can be after it was selected. It becomes the tab stop
814
+ // then, so the fallback tab stop must go.
815
+ let renderChildren = folder => {
816
+ let ul = this._renderer._renderChildren(folder);
817
+ this._updateTabStop();
818
+ return ul;
819
+ };
769
820
  return new FolderElement({
770
- classNames,
771
- closedIconElement,
772
- getScrollLeft,
773
- node,
774
- openedIconElement,
775
- tabIndex,
776
- treeElement,
777
- triggerEvent
821
+ _classNames: classNames,
822
+ _closedIconElement: closedIconElement,
823
+ _getScrollLeft: getScrollLeft,
824
+ _node: node,
825
+ _openedIconElement: openedIconElement,
826
+ _renderChildren: renderChildren,
827
+ _tabIndex: tabIndex,
828
+ _treeElement: treeElement,
829
+ _triggerEvent: triggerEvent
778
830
  });
779
831
  }
780
832
  _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;
833
+ let classNames = this._classNames;
834
+ let getScrollLeft = this._scrollHandler._getScrollLeft.bind(this._scrollHandler);
835
+ let tabIndex = this._options.tabIndex;
836
+ let treeElement = this._htmlElement;
785
837
  return new NodeElement({
786
- classNames,
787
- getScrollLeft,
788
- node,
789
- tabIndex,
790
- treeElement
838
+ _classNames: classNames,
839
+ _getScrollLeft: getScrollLeft,
840
+ _node: node,
841
+ _tabIndex: tabIndex,
842
+ _treeElement: treeElement
791
843
  });
792
844
  }
793
845
 
794
846
  /* 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
- */
847
+ * Add a 'node' query parameter for loading on demand
848
+ * Add a 'selected_node' query parameter if a node is selected.
849
+ */
798
850
  _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
- }
851
+ let dataUrl = this._options.dataUrl;
852
+ let url = typeof dataUrl === "function" ? dataUrl(node) : dataUrl;
806
853
  if (!url) {
807
854
  return null;
808
855
  }
809
- const requestUrl = new RequestUrl(url);
856
+ let requestUrl = new RequestUrl(url);
810
857
  if (node?.id) {
811
858
  // Load on demand of a subtree; add node parameter
812
- requestUrl.setSearchParam('node', node.id.toString());
859
+ requestUrl._setSearchParam("node", node.id.toString());
813
860
  } else {
814
861
  // Add selected_node parameter
815
- const selectedNodeId = this._getNodeIdToBeSelected();
862
+ let selectedNodeId = this._getNodeIdToBeSelected();
816
863
  if (selectedNodeId) {
817
- requestUrl.setSearchParam('selected_node', selectedNodeId.toString());
864
+ requestUrl._setSearchParam("selected_node", selectedNodeId.toString());
818
865
  }
819
866
  }
820
867
  return requestUrl;
821
868
  }
822
869
  _deselectCurrentNode() {
823
- const node = this.getSelectedNode();
870
+ let node = this.getSelectedNode();
824
871
  if (node) {
825
872
  this.removeFromSelection(node);
826
873
  }
@@ -828,9 +875,9 @@ class TreeElement {
828
875
 
829
876
  // Deselect the children of the node.
830
877
  _deselectNodes(parentNode) {
831
- const selectedNodesUnderParent = this._selectNodeHandler.getSelectedNodesUnder(parentNode);
832
- for (const n of selectedNodesUnderParent) {
833
- this._selectNodeHandler.removeFromSelection(n);
878
+ let selectedNodesUnderParent = this._selectNodeHandler._getSelectedNodesUnder(parentNode);
879
+ for (let n of selectedNodesUnderParent) {
880
+ this._selectNodeHandler._removeFromSelection(n);
834
881
  }
835
882
  }
836
883
 
@@ -838,38 +885,59 @@ class TreeElement {
838
885
  _getAutoOpenMaxLevel() {
839
886
  if (this._options.autoOpen === true) {
840
887
  return -1;
841
- } else if (typeof this._options.autoOpen === "number") {
888
+ }
889
+ if (typeof this._options.autoOpen === "number") {
842
890
  return this._options.autoOpen;
843
- } else if (typeof this._options.autoOpen === "string") {
891
+ }
892
+ if (typeof this._options.autoOpen === "string") {
844
893
  return parseInt(this._options.autoOpen, 10);
845
894
  }
846
895
 
847
896
  /* istanbul ignore next @preserve */
848
897
  return 0;
849
898
  }
850
- _getNodeElement(element) {
851
- const node = this.getNode(element);
852
- if (node) {
853
- return this._getNodeElementForNode(node);
854
- } else {
899
+
900
+ // The title span of the first node, or null when the tree is empty
901
+ _getFirstTitleSpan() {
902
+ let firstElement = this._tree.children[0]?.element;
903
+ return firstElement ? firstElement.querySelector(`:scope > .${this._classNames.element} > span.${this._classNames.title}`) : null;
904
+ }
905
+
906
+ // The node of the HTML element that has the focus, if it is in this tree
907
+ _getFocusedNode() {
908
+ let activeElement = document.activeElement;
909
+
910
+ /* istanbul ignore if */
911
+ if (!activeElement) {
855
912
  return null;
856
913
  }
914
+
915
+ // The keyboard must still work for input elements.
916
+ let tagName = activeElement.tagName;
917
+ if (tagName !== "A" && tagName !== "SPAN") {
918
+ return null;
919
+ }
920
+ let node = this.getNode(activeElement);
921
+ return node?.tree === this._tree ? node : null;
922
+ }
923
+ _getNodeElement(element) {
924
+ let node = this.getNode(element);
925
+ return node ? this._getNodeElementForNode(node) : null;
857
926
  }
858
927
  _getNodeElementForNode(node) {
859
- if (node.isFolder()) {
860
- return this._createFolderElement(node);
861
- } else {
862
- return this._createNodeElement(node);
863
- }
928
+ return node.isFolder() ? this._createFolderElement(node) : this._createNodeElement(node);
864
929
  }
865
930
  _getNodeIdToBeSelected() {
866
- return this._saveStateHandler.getNodeIdToBeSelected();
931
+ return this._saveStateHandler._getNodeIdToBeSelected();
932
+ }
933
+ _hasRenderedSelectedNode() {
934
+ return this.getSelectedNodes().some(node => Boolean(node.element));
867
935
  }
868
936
  _initData() {
869
937
  if (this._options.data) {
870
938
  this.loadData(this._options.data);
871
939
  } else {
872
- const dataUrl = this._createRequestUrl();
940
+ let dataUrl = this._createRequestUrl();
873
941
  if (dataUrl) {
874
942
  void this.loadDataFromUrl();
875
943
  } else {
@@ -878,16 +946,17 @@ class TreeElement {
878
946
  }
879
947
  }
880
948
  _initTree(data) {
881
- const doInit = () => {
882
- if (!this._isInitialized) {
883
- this._isInitialized = true;
884
- this._triggerEvent("tree.init");
949
+ let doInit = () => {
950
+ if (this._isInitialized) {
951
+ return;
885
952
  }
953
+ this._isInitialized = true;
954
+ this._triggerEvent("tree.init");
886
955
  };
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();
956
+ this._tree = new this._options.nodeClass(null, true, this._options.nodeClass);
957
+ this._selectNodeHandler._clear();
958
+ this._tree.loadFromData(data);
959
+ let mustLoadOnDemand = this._setInitialState();
891
960
  this._refreshElements(null);
892
961
  if (mustLoadOnDemand) {
893
962
  // Load data on demand and then init the tree
@@ -899,32 +968,22 @@ class TreeElement {
899
968
 
900
969
  // Does an HTML element of the tree have the focus?
901
970
  _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;
971
+ return this._getFocusedNode() !== null;
916
972
  }
917
973
  _isSelectedNodeInSubtree(subtree) {
918
- const selectedNode = this.getSelectedNode();
919
- if (!selectedNode) {
920
- return false;
921
- } else {
922
- return subtree === selectedNode || subtree.isParentOf(selectedNode);
923
- }
974
+ let selectedNode = this.getSelectedNode();
975
+ return selectedNode ? subtree === selectedNode || subtree.isParentOf(selectedNode) : false;
924
976
  }
925
977
  async _loadFolderOnDemand(node, slide) {
926
978
  node.is_loading = true;
927
979
  await this.loadDataFromUrl(undefined, node);
980
+
981
+ // Loading failed, or there is no url. Opening the node now would load it
982
+ // again, without end. Leave it closed so the user can try again.
983
+ if (node.load_on_demand) {
984
+ node.is_loading = false;
985
+ return;
986
+ }
928
987
  await this.openNode(node, slide);
929
988
  }
930
989
  _loadSubtree(data, parentNode) {
@@ -934,37 +993,35 @@ class TreeElement {
934
993
  this._refreshElements(parentNode);
935
994
  }
936
995
  _mouseCapture(positionInfo) {
937
- if (!this._options.dragAndDrop) {
938
- return false;
939
- }
940
- return this._dndHandler.mouseCapture(positionInfo);
996
+ return this._options.dragAndDrop ? this._dndHandler._mouseCapture(positionInfo) : false;
941
997
  }
942
998
  _mouseDrag(positionInfo) {
943
999
  /* istanbul ignore if */
944
1000
  if (!this._options.dragAndDrop) {
945
1001
  return false;
946
1002
  }
947
- const result = this._dndHandler.mouseDrag(positionInfo);
948
- this._scrollHandler.checkScrolling(positionInfo);
1003
+ let result = this._dndHandler._mouseDrag(positionInfo);
1004
+ this._scrollHandler._checkScrolling(positionInfo);
949
1005
  return result;
950
1006
  }
951
1007
  _mouseStart(positionInfo) {
952
1008
  /* istanbul ignore if */
1009
+ // eslint-disable-next-line unicorn/prefer-ternary
953
1010
  if (!this._options.dragAndDrop) {
954
1011
  return false;
955
1012
  }
956
- return this._dndHandler.mouseStart(positionInfo);
1013
+ return this._dndHandler._mouseStart(positionInfo);
957
1014
  }
958
1015
  _mouseStop(positionInfo) {
959
1016
  /* istanbul ignore if */
960
1017
  if (!this._options.dragAndDrop) {
961
1018
  return false;
962
1019
  }
963
- this._scrollHandler.stopScrolling();
964
- return this._dndHandler.mouseStop(positionInfo);
1020
+ this._scrollHandler._stopScrolling();
1021
+ return this._dndHandler._mouseStop(positionInfo);
965
1022
  }
966
1023
  _openParents(node) {
967
- const parent = node.parent;
1024
+ let parent = node.parent;
968
1025
  if (parent?.parent && !parent.is_open) {
969
1026
  void this.openNode(parent, false);
970
1027
  }
@@ -975,57 +1032,58 @@ class TreeElement {
975
1032
  from_node: redraw this subtree
976
1033
  */
977
1034
  _refreshElements(fromNode) {
978
- const mustSetFocus = this._isFocusOnTree();
979
- const mustSelect = fromNode ? this._isSelectedNodeInSubtree(fromNode) : false;
980
- this._renderer.render(fromNode);
1035
+ let mustSetFocus = this._isFocusOnTree();
1036
+ let mustSelect = fromNode ? this._isSelectedNodeInSubtree(fromNode) : false;
1037
+ this._renderer._render(fromNode);
981
1038
  if (mustSelect) {
982
1039
  this._selectCurrentNode(mustSetFocus);
983
1040
  }
1041
+ this._updateTabStop();
984
1042
  this._triggerEvent("tree.refresh");
985
1043
  }
986
1044
  _saveState() {
987
- this._saveStateHandler.saveState();
1045
+ this._saveStateHandler._saveState();
988
1046
  }
989
1047
  _selectCurrentNode(mustSetFocus) {
990
- const node = this.getSelectedNode();
991
- if (node) {
992
- const nodeElement = this._getNodeElementForNode(node);
993
- nodeElement.select(mustSetFocus);
1048
+ let node = this.getSelectedNode();
1049
+ if (!node) {
1050
+ return;
994
1051
  }
1052
+ let nodeElement = this._getNodeElementForNode(node);
1053
+ nodeElement._select(mustSetFocus);
995
1054
  }
996
1055
 
997
1056
  // Set initial state, either by restoring the state or auto-opening nodes
998
1057
  // result: must load nodes on demand?
999
1058
  _setInitialState() {
1000
- const restoreState = () => {
1059
+ let restoreState = () => {
1001
1060
  // 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);
1061
+ let state = this._saveStateHandler._getStateFromStorage();
1062
+ if (state) {
1063
+ let mustLoadOnDemand = this._saveStateHandler._setInitialState(state);
1007
1064
 
1008
1065
  // return true: the state is restored
1009
1066
  return [true, mustLoadOnDemand];
1010
1067
  }
1068
+ return [false, false];
1011
1069
  };
1012
- const autoOpenNodes = () => {
1070
+ let autoOpenNodes = () => {
1013
1071
  // result: must load on demand?
1014
1072
  if (this._options.autoOpen === false) {
1015
1073
  return false;
1016
1074
  }
1017
- const maxLevel = this._getAutoOpenMaxLevel();
1075
+ let maxLevel = this._getAutoOpenMaxLevel();
1018
1076
  let mustLoadOnDemand = false;
1019
- this.tree.iterate((node, level) => {
1077
+ this._tree.iterate((node, level) => {
1020
1078
  if (node.load_on_demand) {
1021
1079
  mustLoadOnDemand = true;
1022
1080
  return false;
1023
- } else if (!node.hasChildren()) {
1024
- return false;
1025
- } else {
1081
+ }
1082
+ if (node.hasChildren()) {
1026
1083
  node.is_open = true;
1027
1084
  return level !== maxLevel;
1028
1085
  }
1086
+ return false;
1029
1087
  });
1030
1088
  return mustLoadOnDemand;
1031
1089
  };
@@ -1039,50 +1097,12 @@ class TreeElement {
1039
1097
 
1040
1098
  // Set the initial state for nodes that are loaded on demand
1041
1099
  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
- });
1100
+ let state = this._saveStateHandler._getStateFromStorage();
1101
+ if (state) {
1102
+ await this._saveStateHandler._setInitialStateOnDemand(state);
1103
+ } else {
1104
+ await this._autoOpenNodesOnDemand();
1105
+ }
1086
1106
  }
1087
1107
 
1088
1108
  // Set this HTML element to this node in the node map.
@@ -1092,6 +1112,23 @@ class TreeElement {
1092
1112
  _triggerEvent(eventName, values) {
1093
1113
  return this._triggerEventProvider(this._htmlElement, eventName, values);
1094
1114
  }
1115
+
1116
+ /* Keyboard users reach the tree through the tab stop. The selected node is
1117
+ * the tab stop, see NodeElement.select. Without a selected node, the first
1118
+ * node is the tab stop, so the tree can always be reached.
1119
+ */
1120
+ _updateTabStop() {
1121
+ let tabIndex = this._options.tabIndex;
1122
+ let previousTabStop = this._fallbackTabStop;
1123
+ let tabStop = tabIndex == null || this._hasRenderedSelectedNode() ? null : this._getFirstTitleSpan();
1124
+ if (previousTabStop && previousTabStop !== tabStop && previousTabStop.getAttribute("aria-selected") !== "true") {
1125
+ previousTabStop.setAttribute("tabindex", "-1");
1126
+ }
1127
+ if (tabStop && tabIndex != null) {
1128
+ tabStop.setAttribute("tabindex", String(tabIndex));
1129
+ }
1130
+ this._fallbackTabStop = tabStop;
1131
+ }
1095
1132
  }
1096
1133
 
1097
1134
  export { TreeElement as default };