tree-element 0.2.0 → 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 (121) hide show
  1. package/README.md +12 -4
  2. package/lib/animation.d.ts +2 -2
  3. package/lib/animation.d.ts.map +1 -1
  4. package/lib/animation.js +13 -9
  5. package/lib/animation.js.map +1 -1
  6. package/lib/dataLoader.d.ts +1 -0
  7. package/lib/dataLoader.d.ts.map +1 -1
  8. package/lib/dataLoader.js +8 -0
  9. package/lib/dataLoader.js.map +1 -1
  10. package/lib/dragAndDropHandler/dragElement.d.ts.map +1 -1
  11. package/lib/dragAndDropHandler/dragElement.js +2 -1
  12. package/lib/dragAndDropHandler/dragElement.js.map +1 -1
  13. package/lib/dragAndDropHandler/index.d.ts.map +1 -1
  14. package/lib/dragAndDropHandler/index.js +56 -65
  15. package/lib/dragAndDropHandler/index.js.map +1 -1
  16. package/lib/dragAndDropHandler/iterateVisibleNodes.d.ts.map +1 -1
  17. package/lib/dragAndDropHandler/iterateVisibleNodes.js +16 -15
  18. package/lib/dragAndDropHandler/iterateVisibleNodes.js.map +1 -1
  19. package/lib/elementsRenderer.d.ts +5 -1
  20. package/lib/elementsRenderer.d.ts.map +1 -1
  21. package/lib/elementsRenderer.js +62 -39
  22. package/lib/elementsRenderer.js.map +1 -1
  23. package/lib/events.d.ts +44 -11
  24. package/lib/events.d.ts.map +1 -1
  25. package/lib/index.d.ts +31 -8
  26. package/lib/index.d.ts.map +1 -1
  27. package/lib/index.js +162 -122
  28. package/lib/index.js.map +1 -1
  29. package/lib/keyHandler.d.ts +11 -5
  30. package/lib/keyHandler.d.ts.map +1 -1
  31. package/lib/keyHandler.js +87 -33
  32. package/lib/keyHandler.js.map +1 -1
  33. package/lib/methodTypes.d.ts +2 -2
  34. package/lib/methodTypes.d.ts.map +1 -1
  35. package/lib/mouseHandler.d.ts +1 -1
  36. package/lib/mouseHandler.d.ts.map +1 -1
  37. package/lib/mouseHandler.js +10 -7
  38. package/lib/mouseHandler.js.map +1 -1
  39. package/lib/node.d.ts +45 -15
  40. package/lib/node.d.ts.map +1 -1
  41. package/lib/node.js +136 -139
  42. package/lib/node.js.map +1 -1
  43. package/lib/nodeElement/folderElement.d.ts +1 -1
  44. package/lib/nodeElement/folderElement.d.ts.map +1 -1
  45. package/lib/nodeElement/folderElement.js +46 -57
  46. package/lib/nodeElement/folderElement.js.map +1 -1
  47. package/lib/nodeElement/ghostDropHint.d.ts.map +1 -1
  48. package/lib/nodeElement/ghostDropHint.js +8 -4
  49. package/lib/nodeElement/ghostDropHint.js.map +1 -1
  50. package/lib/nodeElement/index.d.ts.map +1 -1
  51. package/lib/nodeElement/index.js +6 -6
  52. package/lib/nodeElement/index.js.map +1 -1
  53. package/lib/nodeUtils.d.ts.map +1 -1
  54. package/lib/nodeUtils.js +1 -1
  55. package/lib/nodeUtils.js.map +1 -1
  56. package/lib/options.d.ts +19 -3
  57. package/lib/options.d.ts.map +1 -1
  58. package/lib/requestUrl.d.ts.map +1 -1
  59. package/lib/requestUrl.js +2 -11
  60. package/lib/requestUrl.js.map +1 -1
  61. package/lib/saveStateHandler.d.ts.map +1 -1
  62. package/lib/saveStateHandler.js +21 -41
  63. package/lib/saveStateHandler.js.map +1 -1
  64. package/lib/scrollHandler/containerScrollParent.d.ts.map +1 -1
  65. package/lib/scrollHandler/containerScrollParent.js +3 -8
  66. package/lib/scrollHandler/containerScrollParent.js.map +1 -1
  67. package/lib/scrollHandler/createScrollParent.d.ts.map +1 -1
  68. package/lib/scrollHandler/createScrollParent.js +4 -5
  69. package/lib/scrollHandler/createScrollParent.js.map +1 -1
  70. package/lib/scrollHandler/documentScrollParent.d.ts.map +1 -1
  71. package/lib/scrollHandler/documentScrollParent.js +3 -9
  72. package/lib/scrollHandler/documentScrollParent.js.map +1 -1
  73. package/lib/scrollHandler/scrollParent.d.ts.map +1 -1
  74. package/lib/scrollHandler/scrollParent.js +19 -17
  75. package/lib/scrollHandler/scrollParent.js.map +1 -1
  76. package/lib/selectNodeHandler.d.ts +3 -1
  77. package/lib/selectNodeHandler.d.ts.map +1 -1
  78. package/lib/selectNodeHandler.js +27 -37
  79. package/lib/selectNodeHandler.js.map +1 -1
  80. package/lib/setDefaultOptions.d.ts.map +1 -1
  81. package/lib/setDefaultOptions.js +11 -10
  82. package/lib/setDefaultOptions.js.map +1 -1
  83. package/lib/util.d.ts.map +1 -1
  84. package/lib/util.js +1 -1
  85. package/lib/util.js.map +1 -1
  86. package/lib/version.d.ts +1 -1
  87. package/lib/version.js +1 -1
  88. package/lib/version.js.map +1 -1
  89. package/package.json +42 -33
  90. package/src/animation.ts +13 -11
  91. package/src/dataLoader.ts +11 -0
  92. package/src/dragAndDropHandler/dragElement.ts +2 -1
  93. package/src/dragAndDropHandler/index.ts +70 -75
  94. package/src/dragAndDropHandler/iterateVisibleNodes.ts +18 -16
  95. package/src/elementsRenderer.ts +76 -46
  96. package/src/events.ts +44 -11
  97. package/src/index.ts +210 -150
  98. package/src/keyHandler.ts +95 -41
  99. package/src/methodTypes.ts +3 -3
  100. package/src/mouseHandler.ts +10 -7
  101. package/src/node.ts +155 -148
  102. package/src/nodeElement/folderElement.ts +54 -63
  103. package/src/nodeElement/ghostDropHint.ts +4 -2
  104. package/src/nodeElement/index.ts +9 -15
  105. package/src/nodeUtils.ts +1 -1
  106. package/src/options.ts +19 -3
  107. package/src/requestUrl.ts +4 -11
  108. package/src/saveStateHandler.ts +29 -43
  109. package/src/scrollHandler/containerScrollParent.ts +3 -9
  110. package/src/scrollHandler/createScrollParent.ts +1 -2
  111. package/src/scrollHandler/documentScrollParent.ts +5 -11
  112. package/src/scrollHandler/scrollParent.ts +29 -25
  113. package/src/selectNodeHandler.ts +34 -38
  114. package/src/setDefaultOptions.ts +19 -10
  115. package/src/util.ts +1 -2
  116. package/src/version.ts +1 -1
  117. package/tree_element.css +87 -69
  118. package/tree_element.debug.js +706 -639
  119. package/tree_element.debug.js.map +1 -1
  120. package/tree_element.js +2 -2
  121. package/tree_element.js.map +1 -1
@@ -1,5 +1,5 @@
1
1
  /*
2
- Tree-element 0.2.0
2
+ Tree-element 0.3.0
3
3
 
4
4
  Copyright 2026 Marco Braak
5
5
 
@@ -78,12 +78,17 @@ var TreeElement = (function () {
78
78
  async _loadFromUrl(url, node) {
79
79
  let element = node?.element ?? this._treeElement;
80
80
  element.classList.add(this._classNames.loading);
81
+
82
+ // Tell screen readers that the children of the folder are loading
83
+ let titleSpan = node ? this._getTitleSpan(element) : null;
84
+ titleSpan?.setAttribute("aria-busy", "true");
81
85
  this._triggerEvent("tree.loading_data", {
82
86
  element,
83
87
  node
84
88
  });
85
89
  let stopLoading = () => {
86
90
  element.classList.remove(this._classNames.loading);
91
+ titleSpan?.removeAttribute("aria-busy");
87
92
  this._triggerEvent("tree.loaded_data", {
88
93
  element,
89
94
  node
@@ -119,6 +124,9 @@ var TreeElement = (function () {
119
124
  });
120
125
  });
121
126
  }
127
+ _getTitleSpan(element) {
128
+ return element.querySelector(`:scope > .${this._classNames.element} > .${this._classNames.title}`);
129
+ }
122
130
  }
123
131
 
124
132
  // Get the top position of the HTML element.
@@ -166,7 +174,7 @@ var TreeElement = (function () {
166
174
  this._offsetX = offsetX;
167
175
  this._offsetY = offsetY;
168
176
  this._element = this._createElement(nodeName, autoEscape, classNames);
169
- treeElement.appendChild(this._element);
177
+ treeElement.append(this._element);
170
178
  }
171
179
  _move(pageX, pageY) {
172
180
  this._element.style.left = `${pageX - this._offsetX}px`;
@@ -181,6 +189,7 @@ var TreeElement = (function () {
181
189
  if (autoEscape) {
182
190
  element.textContent = nodeName;
183
191
  } else {
192
+ // eslint-disable-next-line no-unsanitized/property -- the user opted out of escaping with autoEscape: false
184
193
  element.innerHTML = nodeName;
185
194
  }
186
195
  element.style.position = "absolute";
@@ -217,25 +226,26 @@ var TreeElement = (function () {
217
226
  handleClosedFolder(node, nextNode, element);
218
227
  }
219
228
  }
220
- if (mustIterateInside) {
221
- let childrenLength = node.children.length;
222
- node.children.forEach((_, i) => {
223
- let child = node.children[i];
224
- if (child) {
225
- if (i === childrenLength - 1) {
226
- iterate(child, null);
227
- } else {
228
- let nextChild = node.children[i + 1];
229
- if (nextChild) {
230
- iterate(child, nextChild);
231
- }
229
+ if (!mustIterateInside) {
230
+ return;
231
+ }
232
+ let childrenLength = node.children.length;
233
+ for (let i of node.children.keys()) {
234
+ let child = node.children[i];
235
+ if (child) {
236
+ if (i === childrenLength - 1) {
237
+ iterate(child, null);
238
+ } else {
239
+ let nextChild = node.children[i + 1];
240
+ if (nextChild) {
241
+ iterate(child, nextChild);
232
242
  }
233
243
  }
234
- });
235
- if (node.is_open && element) {
236
- handleAfterOpenFolder(node, nextNode);
237
244
  }
238
245
  }
246
+ if (node.is_open && element) {
247
+ handleAfterOpenFolder(node, nextNode);
248
+ }
239
249
  };
240
250
  iterate(tree, null);
241
251
  };
@@ -419,10 +429,8 @@ var TreeElement = (function () {
419
429
  return null;
420
430
  }
421
431
  let nodeElement = this._getNodeElement(element);
422
- if (nodeElement && this._onCanMove) {
423
- if (!this._onCanMove(nodeElement._node)) {
424
- nodeElement = null;
425
- }
432
+ if (nodeElement && this._onCanMove && !this._onCanMove(nodeElement._node)) {
433
+ nodeElement = null;
426
434
  }
427
435
  this._currentItem = nodeElement;
428
436
  return this._currentItem != null;
@@ -453,10 +461,8 @@ var TreeElement = (function () {
453
461
  this._stopOpenFolderTimer();
454
462
  this._hoveredArea = area;
455
463
  }
456
- if (!area) {
457
- if (this._onDragMove) {
458
- this._onDragMove(this._currentItem._node, positionInfo.originalEvent);
459
- }
464
+ if (!area && this._onDragMove) {
465
+ this._onDragMove(this._currentItem._node, positionInfo.originalEvent);
460
466
  }
461
467
  return true;
462
468
  }
@@ -494,35 +500,32 @@ var TreeElement = (function () {
494
500
  this._currentItem = null;
495
501
  }
496
502
  this._isDragging = false;
497
- if (!this._hoveredArea && currentItem) {
498
- if (this._onDragStop) {
499
- this._onDragStop(currentItem._node, positionInfo.originalEvent);
500
- }
503
+ if (!this._hoveredArea && currentItem && this._onDragStop) {
504
+ this._onDragStop(currentItem._node, positionInfo.originalEvent);
501
505
  }
502
506
  return false;
503
507
  }
504
508
  _refresh() {
505
509
  this._removeHitAreas();
506
- if (this._currentItem) {
507
- let currentNode = this._currentItem._node;
508
- this._generateHitAreas(currentNode);
509
- this._currentItem = this._getNodeElementForNode(currentNode);
510
- if (this._isDragging) {
511
- this._currentItem._element.classList.add(this._classNames.moving);
512
- }
510
+ if (!this._currentItem) {
511
+ return;
512
+ }
513
+ let currentNode = this._currentItem._node;
514
+ this._generateHitAreas(currentNode);
515
+ this._currentItem = this._getNodeElementForNode(currentNode);
516
+ if (this._isDragging) {
517
+ this._currentItem._element.classList.add(this._classNames.moving);
513
518
  }
514
519
  }
515
520
  _canMoveToArea(area, currentItem) {
516
- if (!this._onCanMoveTo) {
517
- return true;
518
- }
519
- return this._onCanMoveTo(currentItem._node, area.node, area.position);
521
+ return this._onCanMoveTo ? this._onCanMoveTo(currentItem._node, area.node, area.position) : true;
520
522
  }
521
523
  _clear() {
522
- if (this._dragElement) {
523
- this._dragElement._remove();
524
- this._dragElement = null;
524
+ if (!this._dragElement) {
525
+ return;
525
526
  }
527
+ this._dragElement._remove();
528
+ this._dragElement = null;
526
529
  }
527
530
  _findHoveredArea(x, y) {
528
531
  let dimensions = this._getTreeDimensions();
@@ -532,20 +535,13 @@ var TreeElement = (function () {
532
535
  return binarySearch(this._hitAreas, area => {
533
536
  if (y < area.top) {
534
537
  return 1;
535
- } else if (y > area.bottom) {
536
- return -1;
537
- } else {
538
- return 0;
539
538
  }
539
+ return y > area.bottom ? -1 : 0;
540
540
  });
541
541
  }
542
542
  _generateHitAreas(currentNode) {
543
543
  let tree = this._getTree();
544
- if (!tree) {
545
- this._hitAreas = [];
546
- } else {
547
- this._hitAreas = generateHitAreas(tree, currentNode, this._getTreeDimensions().bottom);
548
- }
544
+ this._hitAreas = tree ? generateHitAreas(tree, currentNode, this._getTreeDimensions().bottom) : [];
549
545
  }
550
546
  _getTreeDimensions() {
551
547
  // Return the dimensions of the tree. Add a margin to the bottom to allow
@@ -561,36 +557,40 @@ var TreeElement = (function () {
561
557
  };
562
558
  }
563
559
 
564
- /* Move the dragged node to the selected position in the tree. */
560
+ /*
561
+ Move the dragged node to the selected position in the tree.
562
+ */
565
563
  _moveItem(positionInfo) {
566
- if (this._currentItem && this._hoveredArea?.position && this._canMoveToArea(this._hoveredArea, this._currentItem)) {
567
- let movedNode = this._currentItem._node;
568
- let targetNode = this._hoveredArea.node;
569
- let position = this._hoveredArea.position;
570
- let previousParent = movedNode.parent;
571
- if (position === "inside") {
572
- this._hoveredArea.node.is_open = true;
564
+ if (!(this._currentItem && this._hoveredArea?.position && this._canMoveToArea(this._hoveredArea, this._currentItem))) {
565
+ return;
566
+ }
567
+ let movedNode = this._currentItem._node;
568
+ let targetNode = this._hoveredArea.node;
569
+ let position = this._hoveredArea.position;
570
+ let previousParent = movedNode.parent;
571
+ if (position === "inside") {
572
+ this._hoveredArea.node.is_open = true;
573
+ }
574
+ let doMove = () => {
575
+ let tree = this._getTree();
576
+ if (!tree) {
577
+ return;
573
578
  }
574
- let doMove = () => {
575
- let tree = this._getTree();
576
- if (tree) {
577
- tree.moveNode(movedNode, targetNode, position);
578
- this._treeElement.textContent = "";
579
- this._refreshElements(null);
580
- }
581
- };
582
- if (this._triggerEvent("tree.move", {
583
- moveInfo: {
584
- doMove,
585
- movedNode,
586
- originalEvent: positionInfo.originalEvent,
587
- position,
588
- previousParent,
589
- targetNode
590
- }
591
- })) {
592
- doMove();
579
+ tree.moveNode(movedNode, targetNode, position);
580
+ this._treeElement.textContent = "";
581
+ this._refreshElements(null);
582
+ };
583
+ if (this._triggerEvent("tree.move", {
584
+ moveInfo: {
585
+ doMove,
586
+ movedNode,
587
+ originalEvent: positionInfo.originalEvent,
588
+ position,
589
+ previousParent,
590
+ targetNode
593
591
  }
592
+ })) {
593
+ doMove();
594
594
  }
595
595
  }
596
596
  _mustCaptureElement(element) {
@@ -626,10 +626,11 @@ var TreeElement = (function () {
626
626
  }
627
627
  }
628
628
  _stopOpenFolderTimer() {
629
- if (this._openFolderTimer) {
630
- clearTimeout(this._openFolderTimer);
631
- this._openFolderTimer = null;
629
+ if (!this._openFolderTimer) {
630
+ return;
632
631
  }
632
+ clearTimeout(this._openFolderTimer);
633
+ this._openFolderTimer = null;
633
634
  }
634
635
  _updateDropHint() {
635
636
  if (!this._hoveredArea) {
@@ -645,7 +646,7 @@ var TreeElement = (function () {
645
646
  }
646
647
  }
647
648
 
648
- let isInt = n => typeof n === "number" && n % 1 === 0;
649
+ let isInt = n => Number.isInteger(n);
649
650
  let getBoolString = value => value ? "true" : "false";
650
651
 
651
652
  /* Renders the tree to html.
@@ -661,21 +662,25 @@ var TreeElement = (function () {
661
662
  */
662
663
  class ElementsRenderer {
663
664
  constructor({
665
+ _ariaLabel: ariaLabel,
666
+ _ariaLabelledBy: ariaLabelledBy,
664
667
  _autoEscape: autoEscape,
665
668
  _buttonLeft: buttonLeft,
666
669
  _classNames: classNames,
667
- _closedIcon: closedIcon,
670
+ _closedIcon: closedIcon = "-",
668
671
  _dragAndDrop: dragAndDrop,
669
672
  _getTree: getTree,
670
673
  _htmlElement: htmlElement,
671
674
  _isNodeSelected: isNodeSelected,
672
675
  _onCreateLi: onCreateLi,
673
- _openedIcon: openedIcon,
676
+ _openedIcon: openedIcon = "+",
674
677
  _rtl: rtl,
675
678
  _setNodeElement: setNodeElement,
676
679
  _showEmptyFolder: showEmptyFolder,
677
680
  _tabIndex: tabIndex
678
681
  }) {
682
+ this._ariaLabel = ariaLabel;
683
+ this._ariaLabelledBy = ariaLabelledBy;
679
684
  this._autoEscape = autoEscape;
680
685
  this._buttonLeft = buttonLeft;
681
686
  this._classNames = classNames;
@@ -688,8 +693,8 @@ var TreeElement = (function () {
688
693
  this._setNodeElement = setNodeElement;
689
694
  this._showEmptyFolder = showEmptyFolder;
690
695
  this._tabIndex = tabIndex;
691
- this._openedIconElement = this._createButtonElement(openedIcon ?? "+");
692
- this._closedIconElement = this._createButtonElement(closedIcon ?? "-");
696
+ this._openedIconElement = this._createButtonElement(openedIcon);
697
+ this._closedIconElement = this._createButtonElement(closedIcon);
693
698
  this._rootUlTemplate = this._createUlTemplate(true);
694
699
  this._groupUlTemplate = this._createUlTemplate(false);
695
700
  this._nodeTemplate = this._createNodeTemplate();
@@ -708,10 +713,7 @@ var TreeElement = (function () {
708
713
  * null when the folder is not rendered or has no children.
709
714
  */
710
715
  _renderChildren(node) {
711
- if (!node.element || !node.hasChildren()) {
712
- return null;
713
- }
714
- return this._createDomElements(node.element, node.children, false, node.getLevel() + 1);
716
+ return !node.element || !node.hasChildren() ? null : this._createDomElements(node.element, node.children, false, node.getLevel() + 1);
715
717
  }
716
718
  _renderFromNode(node) {
717
719
  if (!node.element) {
@@ -743,21 +745,19 @@ var TreeElement = (function () {
743
745
  _createButtonElement(value) {
744
746
  if (typeof value === "string") {
745
747
  let template = document.createElement("template");
748
+ // eslint-disable-next-line no-unsanitized/property -- icon options are documented to accept html
746
749
  template.innerHTML = value;
747
750
  return template.content;
748
- } else if (value.nodeType) {
749
- return value;
750
- } else {
751
- return undefined;
752
751
  }
752
+ return value.nodeType ? value : undefined;
753
753
  }
754
754
  _createDomElements(element, children, isRootNode, level) {
755
755
  let template = isRootNode ? this._rootUlTemplate : this._groupUlTemplate;
756
756
  let ul = template.cloneNode();
757
- element.appendChild(ul);
757
+ element.append(ul);
758
758
  for (let child of children) {
759
759
  let li = this._createLi(child, level);
760
- ul.appendChild(li);
760
+ ul.append(li);
761
761
  if (this._mustRenderChildren(child)) {
762
762
  this._createDomElements(li, child.children, false, level + 1);
763
763
  }
@@ -770,19 +770,20 @@ var TreeElement = (function () {
770
770
  if (isSelected) {
771
771
  li.classList.add(this._classNames.selected);
772
772
  }
773
- if (node.is_loading) {
774
- li.classList.add(this._classNames.loading);
775
- }
776
773
 
777
- // li > div > [button link], title span, [button link]
774
+ // li > div > [toggler], title span, [toggler]
778
775
  let div = li.firstChild;
779
776
  let titleSpan = div.childNodes[this._buttonLeft ? 1 : 0];
780
777
  this._fillTitleSpan(titleSpan, node.name, isSelected, level);
778
+ if (node.is_loading) {
779
+ li.classList.add(this._classNames.loading);
780
+ titleSpan.setAttribute("aria-busy", "true");
781
+ }
781
782
  return li;
782
783
  }
783
784
 
784
785
  /* Template for a folder <li>:
785
- * li > div > [button link], title span, [button link]
786
+ * li > div > [toggler], title span, [toggler]
786
787
  * The toggler icon and the aria-expanded attribute depend on the open
787
788
  * state, so there is a template for each.
788
789
  */
@@ -794,23 +795,25 @@ var TreeElement = (function () {
794
795
  let li = this._createLiTemplate(liClasses.join(" "));
795
796
  let div = li.firstChild;
796
797
 
797
- // button link
798
- let buttonLink = document.createElement("a");
799
- buttonLink.className = this._getButtonClasses(isOpen);
798
+ // The toggler is hidden from screen readers: the icon is decoration, and
799
+ // aria-expanded on the treeitem has the open state.
800
+ let toggler = document.createElement("span");
801
+ toggler.className = this._getButtonClasses(isOpen);
802
+ toggler.setAttribute("aria-hidden", "true");
800
803
  let iconElement = isOpen ? this._openedIconElement : this._closedIconElement;
801
804
  if (iconElement) {
802
- buttonLink.appendChild(iconElement.cloneNode(true));
805
+ toggler.append(iconElement.cloneNode(true));
803
806
  }
804
807
  if (this._buttonLeft) {
805
- div.appendChild(buttonLink);
808
+ div.append(toggler);
806
809
  }
807
810
 
808
811
  // title span
809
812
  let titleSpan = this._createTitleSpanTemplate(true);
810
813
  titleSpan.setAttribute("aria-expanded", getBoolString(isOpen));
811
- div.appendChild(titleSpan);
814
+ div.append(titleSpan);
812
815
  if (!this._buttonLeft) {
813
- div.appendChild(buttonLink);
816
+ div.append(toggler);
814
817
  }
815
818
  return li;
816
819
  }
@@ -830,7 +833,9 @@ var TreeElement = (function () {
830
833
  return li;
831
834
  }
832
835
 
833
- /* Create the outer part of a <li> template: li > div */
836
+ /*
837
+ Create the outer part of a <li> template: li > div
838
+ */
834
839
  _createLiTemplate(liClasses) {
835
840
  let li = document.createElement("li");
836
841
  li.className = liClasses;
@@ -838,7 +843,7 @@ var TreeElement = (function () {
838
843
  let div = document.createElement("div");
839
844
  div.className = `${this._classNames.element} ${this._classNames.common}`;
840
845
  div.setAttribute("role", "none");
841
- li.appendChild(div);
846
+ li.append(div);
842
847
  return li;
843
848
  }
844
849
  _createNodeLi(node, level, isSelected) {
@@ -854,11 +859,13 @@ var TreeElement = (function () {
854
859
  return li;
855
860
  }
856
861
 
857
- /* Template for a <li> without children: li > div > title span */
862
+ /*
863
+ Template for a <li> without children: li > div > title span
864
+ */
858
865
  _createNodeTemplate() {
859
866
  let li = this._createLiTemplate(this._classNames.common);
860
867
  let div = li.firstChild;
861
- div.appendChild(this._createTitleSpanTemplate(false));
868
+ div.append(this._createTitleSpanTemplate(false));
862
869
  return li;
863
870
  }
864
871
  _createTitleSpanTemplate(isFolder) {
@@ -871,20 +878,25 @@ var TreeElement = (function () {
871
878
  titleSpan.className = classes;
872
879
  titleSpan.setAttribute("role", "treeitem");
873
880
  titleSpan.setAttribute("aria-selected", "false");
881
+
882
+ // Every node is focusable, but only the tab stop is in the tab order
883
+ if (this._tabIndex !== undefined) {
884
+ titleSpan.setAttribute("tabindex", "-1");
885
+ }
874
886
  return titleSpan;
875
887
  }
876
888
  _createUlTemplate(isRootNode) {
877
889
  let classString;
878
890
  let role;
879
- if (!isRootNode) {
880
- classString = "";
881
- role = "group";
882
- } else {
891
+ if (isRootNode) {
883
892
  classString = this._classNames.tree;
884
893
  role = "tree";
885
894
  if (this._rtl) {
886
895
  classString += ` ${this._classNames.rtl}`;
887
896
  }
897
+ } else {
898
+ classString = "";
899
+ role = "group";
888
900
  }
889
901
  if (this._dragAndDrop) {
890
902
  classString += ` ${this._classNames.dnd}`;
@@ -892,23 +904,33 @@ var TreeElement = (function () {
892
904
  let ul = document.createElement("ul");
893
905
  ul.className = `${this._classNames.common} ${classString}`;
894
906
  ul.setAttribute("role", role);
907
+ if (isRootNode) {
908
+ if (this._ariaLabel !== undefined) {
909
+ ul.setAttribute("aria-label", this._ariaLabel);
910
+ }
911
+ if (this._ariaLabelledBy !== undefined) {
912
+ ul.setAttribute("aria-labelledby", this._ariaLabelledBy);
913
+ }
914
+ }
895
915
  return ul;
896
916
  }
897
917
 
898
- /* Set the parts of a cloned title span that differ per node */
918
+ /*
919
+ Set the parts of a cloned title span that differ per node
920
+ */
899
921
  _fillTitleSpan(titleSpan, nodeName, isSelected, level) {
900
- titleSpan.setAttribute("aria-label", nodeName);
901
- titleSpan.setAttribute("aria-level", `${level}`);
922
+ titleSpan.setAttribute("aria-level", String(level));
902
923
  if (isSelected) {
903
924
  titleSpan.setAttribute("aria-selected", "true");
904
925
  let tabIndex = this._tabIndex;
905
926
  if (tabIndex !== undefined) {
906
- titleSpan.setAttribute("tabindex", `${tabIndex}`);
927
+ titleSpan.setAttribute("tabindex", String(tabIndex));
907
928
  }
908
929
  }
909
930
  if (this._autoEscape) {
910
931
  titleSpan.textContent = nodeName;
911
932
  } else {
933
+ // eslint-disable-next-line no-unsanitized/property -- the user opted out of escaping with autoEscape: false
912
934
  titleSpan.innerHTML = nodeName;
913
935
  }
914
936
  }
@@ -925,7 +947,9 @@ var TreeElement = (function () {
925
947
  return classes.join(" ");
926
948
  }
927
949
 
928
- /* The children of a closed folder are rendered when it is opened */
950
+ /*
951
+ The children of a closed folder are rendered when it is opened
952
+ */
929
953
  _mustRenderChildren(node) {
930
954
  return node.hasChildren() && node.is_open === true;
931
955
  }
@@ -934,18 +958,22 @@ var TreeElement = (function () {
934
958
  class KeyHandler {
935
959
  constructor({
936
960
  _closeNode: closeNode,
961
+ _getFocusedNode: getFocusedNode,
937
962
  _getSelectedNode: getSelectedNode,
938
- _isFocusOnTree: isFocusOnTree,
963
+ _getTree: getTree,
939
964
  _keyboardSupport: keyboardSupport,
940
965
  _openNode: openNode,
941
- _selectNode: selectNode
966
+ _selectNode: selectNode,
967
+ _triggerEvent: triggerEvent
942
968
  }) {
943
969
  this._closeNode = closeNode;
970
+ this._getFocusedNode = getFocusedNode;
944
971
  this._getSelectedNode = getSelectedNode;
945
- this._isFocusOnTree = isFocusOnTree;
972
+ this._getTree = getTree;
946
973
  this._keyboardSupport = keyboardSupport;
947
974
  this._openNode = openNode;
948
975
  this._originalSelectNode = selectNode;
976
+ this._triggerEvent = triggerEvent;
949
977
  if (keyboardSupport) {
950
978
  document.addEventListener("keydown", this._handleKeyDown);
951
979
  }
@@ -961,35 +989,89 @@ var TreeElement = (function () {
961
989
  _moveUp() {
962
990
  return this._selectNode(this._getSelectedNode()?.getPreviousVisibleNode());
963
991
  }
964
- _canHandleKeyboard() {
965
- return this._keyboardSupport && this._isFocusOnTree();
992
+
993
+ // Activate the node, for example to open its edit page.
994
+ _activate(node, originalEvent) {
995
+ this._triggerEvent("tree.activate", {
996
+ node,
997
+ originalEvent
998
+ });
999
+ return true;
1000
+ }
1001
+ _handleArrowKey(key) {
1002
+ switch (key) {
1003
+ case "ArrowDown":
1004
+ {
1005
+ return this._moveDown();
1006
+ }
1007
+ case "ArrowLeft":
1008
+ {
1009
+ return this._moveLeft();
1010
+ }
1011
+ case "ArrowRight":
1012
+ {
1013
+ return this._moveRight();
1014
+ }
1015
+ case "ArrowUp":
1016
+ {
1017
+ return this._moveUp();
1018
+ }
1019
+ default:
1020
+ {
1021
+ return false;
1022
+ }
1023
+ }
966
1024
  }
967
1025
  _handleKeyDown = e => {
968
- if (!this._canHandleKeyboard()) {
1026
+ let focusedNode = this._getFocusedNode();
1027
+ if (!focusedNode) {
969
1028
  return;
970
1029
  }
971
- let isKeyHandled = false;
1030
+ let isKeyHandled;
972
1031
  let selectedNode = this._getSelectedNode();
973
- if (selectedNode) {
974
- switch (e.key) {
975
- case "ArrowDown":
976
- isKeyHandled = this._moveDown();
977
- break;
978
- case "ArrowLeft":
979
- isKeyHandled = this._moveLeft();
1032
+ switch (e.key) {
1033
+ case "End":
1034
+ {
1035
+ isKeyHandled = this._selectNode(this._getTree()?.getLastChild());
980
1036
  break;
981
- case "ArrowRight":
982
- isKeyHandled = this._moveRight();
1037
+ }
1038
+ case "Enter":
1039
+ {
1040
+ // Without a selected node, activate the focused node
1041
+ isKeyHandled = this._activate(selectedNode ?? focusedNode, e);
983
1042
  break;
984
- case "ArrowUp":
985
- isKeyHandled = this._moveUp();
1043
+ }
1044
+ case "Home":
1045
+ {
1046
+ isKeyHandled = this._selectNode(this._getTree()?.children[0]);
986
1047
  break;
987
- }
1048
+ }
1049
+ default:
1050
+ {
1051
+ isKeyHandled = selectedNode ? this._handleArrowKey(e.key) : this._handleKeyWithoutSelection(e.key, focusedNode);
1052
+ }
988
1053
  }
989
1054
  if (isKeyHandled) {
990
1055
  e.preventDefault();
991
1056
  }
992
1057
  };
1058
+
1059
+ // No node is selected. These keys select the focused node, which is
1060
+ // usually the first node, see TreeElement.updateTabStop.
1061
+ _handleKeyWithoutSelection(key, focusedNode) {
1062
+ switch (key) {
1063
+ case " ":
1064
+ case "ArrowDown":
1065
+ case "ArrowRight":
1066
+ {
1067
+ return this._selectNode(focusedNode);
1068
+ }
1069
+ default:
1070
+ {
1071
+ return false;
1072
+ }
1073
+ }
1074
+ }
993
1075
  _moveLeft() {
994
1076
  let selectedNode = this._getSelectedNode();
995
1077
 
@@ -999,28 +1081,25 @@ var TreeElement = (function () {
999
1081
  }
1000
1082
  if (selectedNode.isFolder() && selectedNode.is_open) {
1001
1083
  // Left on an open node closes the node
1002
- this._closeNode(selectedNode);
1084
+ void this._closeNode(selectedNode);
1003
1085
  return true;
1004
- } else {
1005
- // Left on a closed or end node moves focus to the node's parent
1006
- return this._selectNode(selectedNode.getParent());
1007
1086
  }
1087
+ // Left on a closed or end node moves focus to the node's parent
1088
+ return this._selectNode(selectedNode.getParent());
1008
1089
  }
1009
1090
  _moveRight() {
1010
1091
  let selectedNode = this._getSelectedNode();
1011
- if (!selectedNode?.isFolder()) {
1012
- return false;
1013
- } else {
1092
+ if (selectedNode?.isFolder()) {
1014
1093
  // folder node
1015
1094
  if (selectedNode.is_open) {
1016
1095
  // Right moves to the first child of an open node
1017
1096
  return this._selectNode(selectedNode.getNextVisibleNode());
1018
- } else {
1019
- // Right expands a closed node
1020
- void this._openNode(selectedNode);
1021
- return true;
1022
1097
  }
1098
+ // Right expands a closed node
1099
+ void this._openNode(selectedNode);
1100
+ return true;
1023
1101
  }
1102
+ return false;
1024
1103
  }
1025
1104
 
1026
1105
  /* Select the node.
@@ -1028,12 +1107,11 @@ var TreeElement = (function () {
1028
1107
  * Result: a different node was selected.
1029
1108
  */
1030
1109
  _selectNode(node) {
1031
- if (!node) {
1032
- return false;
1033
- } else {
1110
+ if (node) {
1034
1111
  this._originalSelectNode(node);
1035
1112
  return true;
1036
1113
  }
1114
+ return false;
1037
1115
  }
1038
1116
  }
1039
1117
 
@@ -1137,10 +1215,12 @@ var TreeElement = (function () {
1137
1215
  }
1138
1216
  switch (clickTarget.type) {
1139
1217
  case "button":
1140
- this._onClickButton(clickTarget.node);
1141
- e.preventDefault();
1142
- e.stopPropagation();
1143
- break;
1218
+ {
1219
+ void this._onClickButton(clickTarget.node);
1220
+ e.preventDefault();
1221
+ e.stopPropagation();
1222
+ break;
1223
+ }
1144
1224
  case "label":
1145
1225
  {
1146
1226
  if (this._triggerEvent("tree.click", {
@@ -1223,10 +1303,11 @@ var TreeElement = (function () {
1223
1303
  this._removeMouseMoveEventListeners();
1224
1304
  this._isMouseDelayMet = false;
1225
1305
  this._mouseDownInfo = null;
1226
- if (this._isMouseStarted) {
1227
- this._isMouseStarted = false;
1228
- this._onMouseStop(positionInfo);
1306
+ if (!this._isMouseStarted) {
1307
+ return;
1229
1308
  }
1309
+ this._isMouseStarted = false;
1310
+ this._onMouseStop(positionInfo);
1230
1311
  }
1231
1312
  _handleStartMouse() {
1232
1313
  document.addEventListener("mousemove", this._mouseMove, {
@@ -1313,7 +1394,7 @@ var TreeElement = (function () {
1313
1394
  };
1314
1395
  }
1315
1396
 
1316
- let isNodeRecordWithChildren = data => typeof data === "object" && "children" in data && data.children instanceof Array;
1397
+ let isNodeRecordWithChildren = data => typeof data === "object" && "children" in data && Array.isArray(data.children);
1317
1398
 
1318
1399
  /*
1319
1400
  A node reads and writes the private members of other nodes (node.setParent),
@@ -1341,38 +1422,62 @@ var TreeElement = (function () {
1341
1422
  * :::
1342
1423
  */
1343
1424
  class Node {
1344
- /** @hidden */
1425
+ /**
1426
+ @hidden
1427
+ */
1345
1428
 
1346
- /** The child nodes. */
1429
+ /**
1430
+ The child nodes.
1431
+ */
1347
1432
  children;
1348
1433
  /** The `li` element, once the node is rendered. Nodes inside a closed
1349
1434
  * folder are rendered when the folder is opened. */
1350
1435
  element;
1351
- /** The id from the node data. */
1436
+ /**
1437
+ The id from the node data.
1438
+ */
1352
1439
  id;
1353
- /** @hidden */
1440
+ /**
1441
+ @hidden
1442
+ */
1354
1443
  idMapping;
1355
- /** Whether the node's children are being fetched. */
1444
+ /**
1445
+ Whether the node's children are being fetched.
1446
+ */
1356
1447
  is_loading;
1357
- /** Whether the folder is open. */
1448
+ /**
1449
+ Whether the folder is open.
1450
+ */
1358
1451
  is_open;
1359
- /** Whether the node data had an empty `children` array. */
1452
+ /**
1453
+ Whether the node data had an empty `children` array.
1454
+ */
1360
1455
  isEmptyFolder;
1361
- /** Whether the children still have to be fetched. */
1362
- load_on_demand;
1363
- /** The label. Also settable from the `label` key in node data. */
1364
- name;
1365
- /** @hidden */
1456
+ /**
1457
+ Whether the children still have to be fetched.
1458
+ */
1459
+ load_on_demand = false;
1460
+ /**
1461
+ The label. Also settable from the `label` key in node data.
1462
+ */
1463
+ name = "";
1464
+ /**
1465
+ @hidden
1466
+ */
1366
1467
  nodeClass;
1367
- /** The parent; `null` for the root node. */
1468
+ /**
1469
+ The parent; `null` for the root node.
1470
+ */
1368
1471
  parent;
1369
- /** The root node. */
1472
+ /**
1473
+ The root node.
1474
+ */
1370
1475
  tree;
1371
1476
 
1372
- /** @hidden */
1477
+ /**
1478
+ @hidden
1479
+ */
1373
1480
  constructor(nodeData = null, isRoot = false, nodeClass = Node) {
1374
- this.name = "";
1375
- this.load_on_demand = false;
1376
1481
  this.isEmptyFolder = nodeData != null && isNodeRecordWithChildren(nodeData) && nodeData.children.length === 0;
1377
1482
  this.setData(nodeData);
1378
1483
  this.children = [];
@@ -1395,15 +1500,14 @@ var TreeElement = (function () {
1395
1500
  * @group Changing the tree
1396
1501
  */
1397
1502
  addAfter(nodeInfo) {
1398
- if (!this.parent) {
1399
- return null;
1400
- } else {
1503
+ if (this.parent) {
1401
1504
  let node = this._createNode(nodeInfo);
1402
1505
  let childIndex = this.parent.getChildIndex(this);
1403
1506
  this.parent.addChildAtPosition(node, childIndex + 1);
1404
1507
  node._loadChildrenFromData(nodeInfo);
1405
1508
  return node;
1406
1509
  }
1510
+ return null;
1407
1511
  }
1408
1512
 
1409
1513
  /**
@@ -1413,15 +1517,14 @@ var TreeElement = (function () {
1413
1517
  * @group Changing the tree
1414
1518
  */
1415
1519
  addBefore(nodeInfo) {
1416
- if (!this.parent) {
1417
- return null;
1418
- } else {
1520
+ if (this.parent) {
1419
1521
  let node = this._createNode(nodeInfo);
1420
1522
  let childIndex = this.parent.getChildIndex(this);
1421
1523
  this.parent.addChildAtPosition(node, childIndex);
1422
1524
  node._loadChildrenFromData(nodeInfo);
1423
1525
  return node;
1424
1526
  }
1527
+ return null;
1425
1528
  }
1426
1529
 
1427
1530
  /**
@@ -1445,7 +1548,9 @@ var TreeElement = (function () {
1445
1548
  node._setParent(this);
1446
1549
  }
1447
1550
 
1448
- /** @hidden */
1551
+ /**
1552
+ @hidden
1553
+ */
1449
1554
  addNodeToIndex(node) {
1450
1555
  if (node.id != null) {
1451
1556
  this.idMapping?.set(node.id, node);
@@ -1459,9 +1564,7 @@ var TreeElement = (function () {
1459
1564
  * @group Changing the tree
1460
1565
  */
1461
1566
  addParent(nodeInfo) {
1462
- if (!this.parent) {
1463
- return null;
1464
- } else {
1567
+ if (this.parent) {
1465
1568
  let newParent = this._createNode(nodeInfo);
1466
1569
  if (this.tree) {
1467
1570
  newParent._setParent(this.tree);
@@ -1474,6 +1577,7 @@ var TreeElement = (function () {
1474
1577
  originalParent.addChild(newParent);
1475
1578
  return newParent;
1476
1579
  }
1580
+ return null;
1477
1581
  }
1478
1582
 
1479
1583
  /**
@@ -1540,10 +1644,11 @@ var TreeElement = (function () {
1540
1644
  return nodes.map(node => {
1541
1645
  let tmpNode = {};
1542
1646
  for (let k in node) {
1543
- if (["parent", "children", "element", "idMapping", "load_on_demand", "nodeClass", "tree", "isEmptyFolder"].indexOf(k) === -1 && Object.prototype.hasOwnProperty.call(node, k)) {
1544
- let v = node[k];
1545
- tmpNode[k] = v;
1647
+ if (["children", "element", "idMapping", "isEmptyFolder", "load_on_demand", "nodeClass", "parent", "tree"].includes(k) || !Object.prototype.hasOwnProperty.call(node, k)) {
1648
+ continue;
1546
1649
  }
1650
+ let v = node[k];
1651
+ tmpNode[k] = v;
1547
1652
  }
1548
1653
  if (node.hasChildren()) {
1549
1654
  tmpNode.children = getDataFromNodes(node.children);
@@ -1551,11 +1656,7 @@ var TreeElement = (function () {
1551
1656
  return tmpNode;
1552
1657
  });
1553
1658
  };
1554
- if (includeParent) {
1555
- return getDataFromNodes([this]);
1556
- } else {
1557
- return getDataFromNodes(this.children);
1558
- }
1659
+ return getDataFromNodes(includeParent ? [this] : this.children);
1559
1660
  }
1560
1661
 
1561
1662
  /**
@@ -1565,16 +1666,11 @@ var TreeElement = (function () {
1565
1666
  * @group Moving around the tree
1566
1667
  */
1567
1668
  getLastChild() {
1568
- if (!this.hasChildren()) {
1569
- return null;
1570
- } else {
1669
+ if (this.hasChildren()) {
1571
1670
  let lastChild = this.children[this.children.length - 1];
1572
- if (!(lastChild.hasChildren() && lastChild.is_open)) {
1573
- return lastChild;
1574
- } else {
1575
- return lastChild.getLastChild();
1576
- }
1671
+ return lastChild.hasChildren() && lastChild.is_open ? lastChild.getLastChild() : lastChild;
1577
1672
  }
1673
+ return null;
1578
1674
  }
1579
1675
 
1580
1676
  /**
@@ -1584,7 +1680,7 @@ var TreeElement = (function () {
1584
1680
  */
1585
1681
  getLevel() {
1586
1682
  let level = 0;
1587
- let node = this; // eslint-disable-line @typescript-eslint/no-this-alias
1683
+ let node = this; // eslint-disable-line @typescript-eslint/no-this-alias, unicorn/no-this-assignment
1588
1684
 
1589
1685
  while (node.parent) {
1590
1686
  level += 1;
@@ -1602,16 +1698,12 @@ var TreeElement = (function () {
1602
1698
  getNextNode(includeChildren = true) {
1603
1699
  if (includeChildren && this.hasChildren()) {
1604
1700
  return this.children[0] ?? null;
1605
- } else if (!this.parent) {
1606
- return null;
1607
- } else {
1701
+ }
1702
+ if (this.parent) {
1608
1703
  let nextSibling = this.getNextSibling();
1609
- if (nextSibling) {
1610
- return nextSibling;
1611
- } else {
1612
- return this.parent.getNextNode(false);
1613
- }
1704
+ return nextSibling ?? this.parent.getNextNode(false);
1614
1705
  }
1706
+ return null;
1615
1707
  }
1616
1708
 
1617
1709
  /**
@@ -1620,16 +1712,11 @@ var TreeElement = (function () {
1620
1712
  * @group Moving around the tree
1621
1713
  */
1622
1714
  getNextSibling() {
1623
- if (!this.parent) {
1624
- return null;
1625
- } else {
1715
+ if (this.parent) {
1626
1716
  let nextIndex = this.parent.getChildIndex(this) + 1;
1627
- if (nextIndex < this.parent.children.length) {
1628
- return this.parent.children[nextIndex] ?? null;
1629
- } else {
1630
- return null;
1631
- }
1717
+ return nextIndex < this.parent.children.length ? this.parent.children[nextIndex] ?? null : null;
1632
1718
  }
1719
+ return null;
1633
1720
  }
1634
1721
 
1635
1722
  /**
@@ -1642,20 +1729,17 @@ var TreeElement = (function () {
1642
1729
  if (this.hasChildren() && this.is_open) {
1643
1730
  // First child
1644
1731
  return this.children[0] ?? null;
1645
- } else {
1646
- if (!this.parent) {
1647
- return null;
1648
- } else {
1649
- let nextSibling = this.getNextSibling();
1650
- if (nextSibling) {
1651
- // Next sibling
1652
- return nextSibling;
1653
- } else {
1654
- // Next node of parent
1655
- return this.parent.getNextNode(false);
1656
- }
1732
+ }
1733
+ if (this.parent) {
1734
+ let nextSibling = this.getNextSibling();
1735
+ if (nextSibling) {
1736
+ // Next sibling
1737
+ return nextSibling;
1657
1738
  }
1739
+ // Next node of parent
1740
+ return this.parent.getNextNode(false);
1658
1741
  }
1742
+ return null;
1659
1743
  }
1660
1744
 
1661
1745
  /**
@@ -1668,12 +1752,12 @@ var TreeElement = (function () {
1668
1752
  this.iterate(node => {
1669
1753
  if (result) {
1670
1754
  return false;
1671
- } else if (callback(node)) {
1755
+ }
1756
+ if (callback(node)) {
1672
1757
  result = node;
1673
1758
  return false;
1674
- } else {
1675
- return true;
1676
1759
  }
1760
+ return true;
1677
1761
  });
1678
1762
  return result;
1679
1763
  }
@@ -1729,12 +1813,12 @@ var TreeElement = (function () {
1729
1813
  // Return parent except if it is the root node
1730
1814
  if (!this.parent) {
1731
1815
  return null;
1732
- } else if (!this.parent.parent) {
1733
- // Root node -> null
1734
- return null;
1735
- } else {
1816
+ }
1817
+ if (this.parent.parent) {
1736
1818
  return this.parent;
1737
1819
  }
1820
+ // Root node -> null
1821
+ return null;
1738
1822
  }
1739
1823
 
1740
1824
  /**
@@ -1744,18 +1828,14 @@ var TreeElement = (function () {
1744
1828
  * @group Moving around the tree
1745
1829
  */
1746
1830
  getPreviousNode() {
1747
- if (!this.parent) {
1748
- return null;
1749
- } else {
1831
+ if (this.parent) {
1750
1832
  let previousSibling = this.getPreviousSibling();
1751
1833
  if (!previousSibling) {
1752
1834
  return this.getParent();
1753
- } else if (previousSibling.hasChildren()) {
1754
- return previousSibling.getLastChild();
1755
- } else {
1756
- return previousSibling;
1757
1835
  }
1836
+ return previousSibling.hasChildren() ? previousSibling.getLastChild() : previousSibling;
1758
1837
  }
1838
+ return null;
1759
1839
  }
1760
1840
 
1761
1841
  /**
@@ -1764,16 +1844,11 @@ var TreeElement = (function () {
1764
1844
  * @group Moving around the tree
1765
1845
  */
1766
1846
  getPreviousSibling() {
1767
- if (!this.parent) {
1768
- return null;
1769
- } else {
1847
+ if (this.parent) {
1770
1848
  let previousIndex = this.parent.getChildIndex(this) - 1;
1771
- if (previousIndex >= 0) {
1772
- return this.parent.children[previousIndex] ?? null;
1773
- } else {
1774
- return null;
1775
- }
1849
+ return previousIndex >= 0 ? this.parent.children[previousIndex] ?? null : null;
1776
1850
  }
1851
+ return null;
1777
1852
  }
1778
1853
 
1779
1854
  /**
@@ -1783,20 +1858,19 @@ var TreeElement = (function () {
1783
1858
  * @group Moving around the tree
1784
1859
  */
1785
1860
  getPreviousVisibleNode() {
1786
- if (!this.parent) {
1787
- return null;
1788
- } else {
1861
+ if (this.parent) {
1789
1862
  let previousSibling = this.getPreviousSibling();
1790
1863
  if (!previousSibling) {
1791
1864
  return this.getParent();
1792
- } else if (!previousSibling.hasChildren() || !previousSibling.is_open) {
1865
+ }
1866
+ if (!previousSibling.hasChildren() || !previousSibling.is_open) {
1793
1867
  // Previous sibling
1794
1868
  return previousSibling;
1795
- } else {
1796
- // Last child of previous sibling
1797
- return previousSibling.getLastChild();
1798
1869
  }
1870
+ // Last child of previous sibling
1871
+ return previousSibling.getLastChild();
1799
1872
  }
1873
+ return null;
1800
1874
  }
1801
1875
 
1802
1876
  /**
@@ -1910,32 +1984,31 @@ var TreeElement = (function () {
1910
1984
  // - Node is parent of target node
1911
1985
  // - Or, parent is empty
1912
1986
  return false;
1913
- } else {
1914
- movedNode.parent._doRemoveChild(movedNode);
1915
- switch (position) {
1916
- case "after":
1917
- {
1918
- if (targetNode.parent) {
1919
- targetNode.parent.addChildAtPosition(movedNode, targetNode.parent.getChildIndex(targetNode) + 1);
1920
- return true;
1921
- }
1922
- return false;
1923
- }
1924
- case "before":
1925
- {
1926
- if (targetNode.parent) {
1927
- targetNode.parent.addChildAtPosition(movedNode, targetNode.parent.getChildIndex(targetNode));
1928
- return true;
1929
- }
1930
- return false;
1987
+ }
1988
+ movedNode.parent._doRemoveChild(movedNode);
1989
+ switch (position) {
1990
+ case "after":
1991
+ {
1992
+ if (targetNode.parent) {
1993
+ targetNode.parent.addChildAtPosition(movedNode, targetNode.parent.getChildIndex(targetNode) + 1);
1994
+ return true;
1931
1995
  }
1932
- case "inside":
1933
- {
1934
- // move inside as first child
1935
- targetNode.addChildAtPosition(movedNode, 0);
1996
+ return false;
1997
+ }
1998
+ case "before":
1999
+ {
2000
+ if (targetNode.parent) {
2001
+ targetNode.parent.addChildAtPosition(movedNode, targetNode.parent.getChildIndex(targetNode));
1936
2002
  return true;
1937
2003
  }
1938
- }
2004
+ return false;
2005
+ }
2006
+ case "inside":
2007
+ {
2008
+ // move inside as first child
2009
+ targetNode.addChildAtPosition(movedNode, 0);
2010
+ return true;
2011
+ }
1939
2012
  }
1940
2013
  }
1941
2014
 
@@ -1958,10 +2031,11 @@ var TreeElement = (function () {
1958
2031
  * @group Changing the tree
1959
2032
  */
1960
2033
  remove() {
1961
- if (this.parent) {
1962
- this.parent.removeChild(this);
1963
- this.parent = null;
2034
+ if (!this.parent) {
2035
+ return;
1964
2036
  }
2037
+ this.parent.removeChild(this);
2038
+ this.parent = null;
1965
2039
  }
1966
2040
 
1967
2041
  /**
@@ -1988,7 +2062,9 @@ var TreeElement = (function () {
1988
2062
  this.children = [];
1989
2063
  }
1990
2064
 
1991
- /** @hidden */
2065
+ /**
2066
+ @hidden
2067
+ */
1992
2068
  removeNodeFromIndex(node) {
1993
2069
  if (node.id != null) {
1994
2070
  this.idMapping?.delete(node.id);
@@ -2005,21 +2081,23 @@ var TreeElement = (function () {
2005
2081
  setData(o) {
2006
2082
  if (!o) {
2007
2083
  return;
2008
- } else if (typeof o === "string") {
2084
+ }
2085
+ if (typeof o === "string") {
2009
2086
  this.name = o;
2010
2087
  } else if (typeof o === "object") {
2011
2088
  for (let key in o) {
2012
- if (Object.prototype.hasOwnProperty.call(o, key)) {
2013
- let value = o[key];
2014
- if (key === "label" || key === "name") {
2015
- // You can use the 'label' key instead of 'name'; this is a legacy feature
2016
- if (typeof value === "string") {
2017
- this.name = value;
2018
- }
2019
- } else if (key !== "children" && key !== "parent") {
2020
- // You can't update the children or the parent using this function
2021
- this[key] = value;
2089
+ if (!Object.prototype.hasOwnProperty.call(o, key)) {
2090
+ continue;
2091
+ }
2092
+ let value = o[key];
2093
+ if (key === "label" || key === "name") {
2094
+ // You can use the 'label' key instead of 'name'; this is a legacy feature
2095
+ if (typeof value === "string") {
2096
+ this.name = value;
2022
2097
  }
2098
+ } else if (key !== "children" && key !== "parent") {
2099
+ // You can't update the children or the parent using this function
2100
+ this[key] = value;
2023
2101
  }
2024
2102
  }
2025
2103
  }
@@ -2078,11 +2156,15 @@ var TreeElement = (function () {
2078
2156
  this._ghost = this._createGhostElement();
2079
2157
  switch (position) {
2080
2158
  case "after":
2081
- this._moveAfter();
2082
- break;
2159
+ {
2160
+ this._moveAfter();
2161
+ break;
2162
+ }
2083
2163
  case "before":
2084
- this._moveBefore();
2085
- break;
2164
+ {
2165
+ this._moveBefore();
2166
+ break;
2167
+ }
2086
2168
  case "inside":
2087
2169
  {
2088
2170
  if (node.isFolder() && node.is_open) {
@@ -2149,11 +2231,7 @@ var TreeElement = (function () {
2149
2231
  this._element = node.element ?? this._treeElement;
2150
2232
  }
2151
2233
  _addDropHint(position) {
2152
- if (this._mustShowBorderDropHint(position)) {
2153
- return new BorderDropHint(this._element, this._getScrollLeft(), this._classNames);
2154
- } else {
2155
- return new GhostDropHint(this._node, this._element, position, this._classNames);
2156
- }
2234
+ return this._mustShowBorderDropHint(position) ? new BorderDropHint(this._element, this._getScrollLeft(), this._classNames) : new GhostDropHint(this._node, this._element, position, this._classNames);
2157
2235
  }
2158
2236
  _deselect() {
2159
2237
  if (!this._isRendered()) {
@@ -2161,7 +2239,11 @@ var TreeElement = (function () {
2161
2239
  }
2162
2240
  this._element.classList.remove(this._classNames.selected);
2163
2241
  let titleSpan = this._getTitleSpan();
2164
- titleSpan.removeAttribute("tabindex");
2242
+
2243
+ // Check for null or undefined
2244
+ if (this._tabIndex != null) {
2245
+ titleSpan.setAttribute("tabindex", "-1");
2246
+ }
2165
2247
  titleSpan.setAttribute("aria-selected", "false");
2166
2248
  titleSpan.blur();
2167
2249
  }
@@ -2206,7 +2288,7 @@ var TreeElement = (function () {
2206
2288
  }
2207
2289
  return duration === "slow" ? 600 : 200;
2208
2290
  };
2209
- let slideDown = (element, animationSpeed, onFinished) => {
2291
+ let slideDown = (element, animationSpeed) => {
2210
2292
  element.style.display = "block";
2211
2293
  let animation = element.animate([{
2212
2294
  height: "0",
@@ -2217,11 +2299,13 @@ var TreeElement = (function () {
2217
2299
  }], {
2218
2300
  duration: getAnimationDuration(animationSpeed)
2219
2301
  });
2220
- animation.onfinish = () => {
2221
- onFinished();
2222
- };
2302
+ return new Promise(resolve => {
2303
+ animation.onfinish = () => {
2304
+ resolve();
2305
+ };
2306
+ });
2223
2307
  };
2224
- let slideUp = (element, animationSpeed, onFinished) => {
2308
+ let slideUp = (element, animationSpeed) => {
2225
2309
  let animation = element.animate([{
2226
2310
  height: `${element.scrollHeight}px`,
2227
2311
  overflow: "hidden"
@@ -2231,10 +2315,12 @@ var TreeElement = (function () {
2231
2315
  }], {
2232
2316
  duration: getAnimationDuration(animationSpeed)
2233
2317
  });
2234
- animation.onfinish = () => {
2235
- element.style.display = "none";
2236
- onFinished();
2237
- };
2318
+ return new Promise(resolve => {
2319
+ animation.onfinish = () => {
2320
+ element.style.display = "none";
2321
+ resolve();
2322
+ };
2323
+ });
2238
2324
  };
2239
2325
 
2240
2326
  class FolderElement extends NodeElement {
@@ -2261,7 +2347,7 @@ var TreeElement = (function () {
2261
2347
  this._renderChildren = renderChildren;
2262
2348
  this._triggerEvent = triggerEvent;
2263
2349
  }
2264
- _close(slide, animationSpeed) {
2350
+ async _close(slide, animationSpeed) {
2265
2351
  if (!this._node.is_open) {
2266
2352
  return;
2267
2353
  }
@@ -2274,92 +2360,76 @@ var TreeElement = (function () {
2274
2360
  }
2275
2361
  let button = this._getButton();
2276
2362
  button.classList.add(this._classNames.closed);
2277
- button.innerHTML = "";
2363
+ button.replaceChildren();
2278
2364
  let closedIconElement = this._closedIconElement;
2279
2365
  if (closedIconElement) {
2280
2366
  let icon = closedIconElement.cloneNode(true);
2281
- button.appendChild(icon);
2367
+ button.append(icon);
2282
2368
  }
2283
- let doClose = () => {
2284
- this._element.classList.add(this._classNames.closed);
2285
- let titleSpan = this._getTitleSpan();
2286
- titleSpan.setAttribute("aria-expanded", "false");
2287
- this._triggerEvent("tree.close", {
2288
- node: this._node
2289
- });
2290
- };
2291
2369
  let ul = this._getUl();
2292
- if (!ul) {
2293
- doClose();
2294
- } else if (slide) {
2295
- slideUp(ul, animationSpeed, doClose);
2296
- } else {
2297
- ul.style.display = "none";
2298
- doClose();
2370
+ if (ul) {
2371
+ if (slide) {
2372
+ await slideUp(ul, animationSpeed);
2373
+ } else {
2374
+ ul.style.display = "none";
2375
+ }
2299
2376
  }
2377
+ this._element.classList.add(this._classNames.closed);
2378
+ let titleSpan = this._getTitleSpan();
2379
+ titleSpan.setAttribute("aria-expanded", "false");
2380
+ this._triggerEvent("tree.close", {
2381
+ node: this._node
2382
+ });
2300
2383
  }
2301
2384
  async _open(slide, animationSpeed) {
2302
- return new Promise(resolve => {
2303
- if (this._node.is_open) {
2304
- resolve();
2305
- return;
2306
- }
2307
- this._node.is_open = true;
2308
- if (!this._isRendered()) {
2309
- // The folder is rendered open when its parent is opened
2310
- this._triggerEvent("tree.open", {
2311
- node: this._node
2312
- });
2313
- resolve();
2314
- return;
2315
- }
2316
- let button = this._getButton();
2317
- button.classList.remove(this._classNames.closed);
2318
- button.innerHTML = "";
2319
- let openedIconElement = this._openedIconElement;
2320
- if (openedIconElement) {
2321
- let icon = openedIconElement.cloneNode(true);
2322
- button.appendChild(icon);
2323
- }
2324
- let doOpen = () => {
2325
- this._element.classList.remove(this._classNames.closed);
2326
- let titleSpan = this._getTitleSpan();
2327
- titleSpan.setAttribute("aria-expanded", "true");
2328
- this._triggerEvent("tree.open", {
2329
- node: this._node
2330
- });
2331
- resolve();
2332
- };
2333
-
2334
- // The children are rendered the first time the folder is opened
2335
- let ul = this._getUl() ?? this._renderChildren(this._node);
2336
- if (!ul) {
2337
- doOpen();
2338
- } else if (slide) {
2339
- slideDown(ul, animationSpeed, doOpen);
2385
+ if (this._node.is_open) {
2386
+ return;
2387
+ }
2388
+ this._node.is_open = true;
2389
+ if (!this._isRendered()) {
2390
+ // The folder is rendered open when its parent is opened
2391
+ this._triggerEvent("tree.open", {
2392
+ node: this._node
2393
+ });
2394
+ return;
2395
+ }
2396
+ let button = this._getButton();
2397
+ button.classList.remove(this._classNames.closed);
2398
+ button.replaceChildren();
2399
+ let openedIconElement = this._openedIconElement;
2400
+ if (openedIconElement) {
2401
+ let icon = openedIconElement.cloneNode(true);
2402
+ button.append(icon);
2403
+ }
2404
+
2405
+ // The children are rendered the first time the folder is opened
2406
+ let ul = this._getUl() ?? this._renderChildren(this._node);
2407
+ if (ul) {
2408
+ if (slide) {
2409
+ await slideDown(ul, animationSpeed);
2340
2410
  } else {
2341
2411
  ul.style.display = "block";
2342
- doOpen();
2343
2412
  }
2413
+ }
2414
+ this._element.classList.remove(this._classNames.closed);
2415
+ let titleSpan = this._getTitleSpan();
2416
+ titleSpan.setAttribute("aria-expanded", "true");
2417
+ this._triggerEvent("tree.open", {
2418
+ node: this._node
2344
2419
  });
2345
2420
  }
2346
2421
  _mustShowBorderDropHint(position) {
2347
2422
  return !this._node.is_open && position === "inside";
2348
2423
  }
2349
2424
  _getButton() {
2350
- return this._element.querySelector(`:scope > .${this._classNames.element} > a.${this._classNames.toggler}`);
2425
+ return this._element.querySelector(`:scope > .${this._classNames.element} > .${this._classNames.toggler}`);
2351
2426
  }
2352
2427
  }
2353
2428
 
2354
2429
  // Url class for absolute and relative urls.
2355
2430
 
2356
2431
  let isAbsoluteUrl = inputUrl => {
2357
- try {
2358
- new URL(inputUrl);
2359
- return true;
2360
- } catch {
2361
- return false;
2362
- }
2432
+ return URL.canParse(inputUrl);
2363
2433
  };
2364
2434
  let LOCALHOST = "http://localhost";
2365
2435
  class RequestUrl {
@@ -2376,11 +2446,7 @@ var TreeElement = (function () {
2376
2446
  this._url.searchParams.set(key, value);
2377
2447
  }
2378
2448
  _value() {
2379
- if (this._isAbsolute) {
2380
- return this._url.href;
2381
- } else {
2382
- return this._url.href.slice(LOCALHOST.length);
2383
- }
2449
+ return this._isAbsolute ? this._url.href : this._url.href.slice(LOCALHOST.length);
2384
2450
  }
2385
2451
  }
2386
2452
 
@@ -2413,11 +2479,7 @@ var TreeElement = (function () {
2413
2479
  return null;
2414
2480
  }
2415
2481
  let state = this._getStateFromStorage();
2416
- if (state?.selected_node) {
2417
- return state.selected_node[0] ?? null;
2418
- } else {
2419
- return null;
2420
- }
2482
+ return state?.selected_node ? state.selected_node[0] ?? null : null;
2421
2483
  }
2422
2484
  _getState() {
2423
2485
  let getOpenNodeIds = () => {
@@ -2432,11 +2494,11 @@ var TreeElement = (function () {
2432
2494
  };
2433
2495
  let getSelectedNodeIds = () => {
2434
2496
  let selectedNodeIds = [];
2435
- this._getSelectedNodes().forEach(node => {
2497
+ for (let node of this._getSelectedNodes()) {
2436
2498
  if (node.id != null) {
2437
2499
  selectedNodeIds.push(node.id);
2438
2500
  }
2439
- });
2501
+ }
2440
2502
  return selectedNodeIds;
2441
2503
  };
2442
2504
  return {
@@ -2449,11 +2511,7 @@ var TreeElement = (function () {
2449
2511
  return null;
2450
2512
  }
2451
2513
  let jsonData = this._loadFromStorage();
2452
- if (jsonData) {
2453
- return this._parseState(jsonData);
2454
- } else {
2455
- return null;
2456
- }
2514
+ return jsonData ? this._parseState(jsonData) : null;
2457
2515
  }
2458
2516
  _saveState() {
2459
2517
  if (!this._saveStateOption) {
@@ -2473,10 +2531,7 @@ var TreeElement = (function () {
2473
2531
  result: must load on demand (boolean)
2474
2532
  */
2475
2533
  _setInitialState(state) {
2476
- let mustLoadOnDemand = false;
2477
- if (state.open_nodes) {
2478
- mustLoadOnDemand = this._openInitialNodes(state.open_nodes);
2479
- }
2534
+ let mustLoadOnDemand = state.open_nodes ? this._openInitialNodes(state.open_nodes) : false;
2480
2535
  this._resetSelection();
2481
2536
  if (state.selected_node) {
2482
2537
  this._selectInitialNodes(state.selected_node);
@@ -2492,9 +2547,7 @@ var TreeElement = (function () {
2492
2547
  let newNodesIds = [];
2493
2548
  for (let nodeId of nodeIds) {
2494
2549
  let node = this._getNodeById(nodeId);
2495
- if (!node) {
2496
- newNodesIds.push(nodeId);
2497
- } else {
2550
+ if (node) {
2498
2551
  if (!node.is_loading) {
2499
2552
  if (node.load_on_demand) {
2500
2553
  await loadAndOpenNode(node);
@@ -2502,13 +2555,13 @@ var TreeElement = (function () {
2502
2555
  await this._openNode(node, false);
2503
2556
  }
2504
2557
  }
2558
+ } else {
2559
+ newNodesIds.push(nodeId);
2505
2560
  }
2506
2561
  }
2507
2562
  nodeIds = newNodesIds;
2508
- if (state.selected_node) {
2509
- if (this._selectInitialNodes(state.selected_node)) {
2510
- this._refreshElements(null);
2511
- }
2563
+ if (state.selected_node && this._selectInitialNodes(state.selected_node)) {
2564
+ this._refreshElements(null);
2512
2565
  }
2513
2566
  };
2514
2567
  let loadAndOpenNode = async node => {
@@ -2518,28 +2571,20 @@ var TreeElement = (function () {
2518
2571
  await openNodes();
2519
2572
  }
2520
2573
  _getKeyName() {
2521
- if (typeof this._saveStateOption === "string") {
2522
- return this._saveStateOption;
2523
- } else {
2524
- return "tree";
2525
- }
2574
+ return typeof this._saveStateOption === "string" ? this._saveStateOption : "tree";
2526
2575
  }
2527
2576
  _loadFromStorage() {
2528
- if (this._onGetStateFromStorage) {
2529
- return this._onGetStateFromStorage();
2530
- } else {
2531
- return localStorage.getItem(this._getKeyName());
2532
- }
2577
+ return this._onGetStateFromStorage ? this._onGetStateFromStorage() : localStorage.getItem(this._getKeyName());
2533
2578
  }
2534
2579
  _openInitialNodes(nodeIds) {
2535
2580
  let mustLoadOnDemand = false;
2536
2581
  for (let nodeId of nodeIds) {
2537
2582
  let node = this._getNodeById(nodeId);
2538
2583
  if (node) {
2539
- if (!node.load_on_demand) {
2540
- node.is_open = true;
2541
- } else {
2584
+ if (node.load_on_demand) {
2542
2585
  mustLoadOnDemand = true;
2586
+ } else {
2587
+ node.is_open = true;
2543
2588
  }
2544
2589
  }
2545
2590
  }
@@ -2557,18 +2602,19 @@ var TreeElement = (function () {
2557
2602
  }
2558
2603
  _resetSelection() {
2559
2604
  let selectedNodes = this._getSelectedNodes();
2560
- selectedNodes.forEach(node => {
2605
+ for (let node of selectedNodes) {
2561
2606
  this._removeFromSelection(node);
2562
- });
2607
+ }
2563
2608
  }
2564
2609
  _selectInitialNodes(nodeIds) {
2565
2610
  let selectCount = 0;
2566
2611
  for (let nodeId of nodeIds) {
2567
2612
  let node = this._getNodeById(nodeId);
2568
- if (node) {
2569
- selectCount += 1;
2570
- this._addToSelection(node);
2613
+ if (!node) {
2614
+ continue;
2571
2615
  }
2616
+ selectCount += 1;
2617
+ this._addToSelection(node);
2572
2618
  }
2573
2619
  return selectCount !== 0;
2574
2620
  }
@@ -2584,27 +2630,29 @@ var TreeElement = (function () {
2584
2630
  }
2585
2631
  _checkHorizontalScrolling(pageX) {
2586
2632
  let newHorizontalScrollDirection = this._getNewHorizontalScrollDirection(pageX);
2587
- if (this._horizontalScrollDirection !== newHorizontalScrollDirection) {
2588
- this._horizontalScrollDirection = newHorizontalScrollDirection;
2589
- if (this._horizontalScrollTimeout != null) {
2590
- window.clearTimeout(this._horizontalScrollTimeout);
2591
- }
2592
- if (newHorizontalScrollDirection) {
2593
- this._horizontalScrollTimeout = window.setTimeout(this._scrollHorizontally.bind(this), 40);
2594
- }
2633
+ if (this._horizontalScrollDirection === newHorizontalScrollDirection) {
2634
+ return;
2635
+ }
2636
+ this._horizontalScrollDirection = newHorizontalScrollDirection;
2637
+ if (this._horizontalScrollTimeout != null) {
2638
+ window.clearTimeout(this._horizontalScrollTimeout);
2639
+ }
2640
+ if (newHorizontalScrollDirection) {
2641
+ this._horizontalScrollTimeout = window.setTimeout(this._scrollHorizontally.bind(this), 40);
2595
2642
  }
2596
2643
  }
2597
2644
  _checkVerticalScrolling(pageY) {
2598
2645
  let newVerticalScrollDirection = this._getNewVerticalScrollDirection(pageY);
2599
- if (this._verticalScrollDirection !== newVerticalScrollDirection) {
2600
- this._verticalScrollDirection = newVerticalScrollDirection;
2601
- if (this._verticalScrollTimeout != null) {
2602
- window.clearTimeout(this._verticalScrollTimeout);
2603
- this._verticalScrollTimeout = undefined;
2604
- }
2605
- if (newVerticalScrollDirection) {
2606
- this._verticalScrollTimeout = window.setTimeout(this._scrollVertically.bind(this), 40);
2607
- }
2646
+ if (this._verticalScrollDirection === newVerticalScrollDirection) {
2647
+ return;
2648
+ }
2649
+ this._verticalScrollDirection = newVerticalScrollDirection;
2650
+ if (this._verticalScrollTimeout != null) {
2651
+ window.clearTimeout(this._verticalScrollTimeout);
2652
+ this._verticalScrollTimeout = undefined;
2653
+ }
2654
+ if (newVerticalScrollDirection) {
2655
+ this._verticalScrollTimeout = window.setTimeout(this._scrollVertically.bind(this), 40);
2608
2656
  }
2609
2657
  }
2610
2658
  _getScrollLeft() {
@@ -2657,22 +2705,17 @@ var TreeElement = (function () {
2657
2705
  let rightEdge = scrollParentOffset.left + containerWidth;
2658
2706
  let leftEdge = scrollParentOffset.left;
2659
2707
  let isNearRightEdge = pageX > rightEdge - 20;
2660
- let isNearLeftEdge = pageX < leftEdge + 20;
2661
2708
  if (isNearRightEdge) {
2662
2709
  return "right";
2663
- } else if (isNearLeftEdge) {
2664
- return "left";
2665
2710
  }
2666
- return undefined;
2711
+ let isNearLeftEdge = pageX < leftEdge + 20;
2712
+ return isNearLeftEdge ? "left" : undefined;
2667
2713
  }
2668
2714
  _getNewVerticalScrollDirection(pageY) {
2669
2715
  if (pageY < this._getScrollParentTop()) {
2670
2716
  return "top";
2671
2717
  }
2672
- if (pageY > this._getScrollParentBottom()) {
2673
- return "bottom";
2674
- }
2675
- return undefined;
2718
+ return pageY > this._getScrollParentBottom() ? "bottom" : undefined;
2676
2719
  }
2677
2720
  _getScrollParentBottom() {
2678
2721
  if (this._scrollParentBottom == null) {
@@ -2711,14 +2754,11 @@ var TreeElement = (function () {
2711
2754
  let scrollLeft = this._container.scrollLeft;
2712
2755
  let windowWidth = window.innerWidth;
2713
2756
  let isNearRightEdge = pageX > windowWidth - 20;
2714
- let isNearLeftEdge = pageX - scrollLeft < 20;
2715
2757
  if (isNearRightEdge && this._canScrollRight()) {
2716
2758
  return "right";
2717
2759
  }
2718
- if (isNearLeftEdge) {
2719
- return "left";
2720
- }
2721
- return undefined;
2760
+ let isNearLeftEdge = pageX - scrollLeft < 20;
2761
+ return isNearLeftEdge ? "left" : undefined;
2722
2762
  }
2723
2763
  _getNewVerticalScrollDirection(pageY) {
2724
2764
  let scrollTop = this._container.scrollTop;
@@ -2727,10 +2767,7 @@ var TreeElement = (function () {
2727
2767
  return "top";
2728
2768
  }
2729
2769
  let windowHeight = window.innerHeight;
2730
- if (windowHeight - (pageY - scrollTop) < 20 && this._canScrollDown()) {
2731
- return "bottom";
2732
- }
2733
- return undefined;
2770
+ return windowHeight - (pageY - scrollTop) < 20 && this._canScrollDown() ? "bottom" : undefined;
2734
2771
  }
2735
2772
  _canScrollDown() {
2736
2773
  return this._container.scrollTop + this._container.clientHeight < this._getDocumentScrollHeight();
@@ -2775,12 +2812,11 @@ var TreeElement = (function () {
2775
2812
  _container: container,
2776
2813
  _refreshHitAreas: refreshHitAreas
2777
2814
  });
2778
- } else {
2779
- return new DocumentScrollParent({
2780
- _refreshHitAreas: refreshHitAreas,
2781
- _treeElement: treeElement
2782
- });
2783
2815
  }
2816
+ return new DocumentScrollParent({
2817
+ _refreshHitAreas: refreshHitAreas,
2818
+ _treeElement: treeElement
2819
+ });
2784
2820
  };
2785
2821
 
2786
2822
  class ScrollHandler {
@@ -2825,7 +2861,8 @@ var TreeElement = (function () {
2825
2861
  _getSelectable: getSelectable,
2826
2862
  _openParents: openParents,
2827
2863
  _saveState: saveState,
2828
- _triggerEvent: triggerEvent
2864
+ _triggerEvent: triggerEvent,
2865
+ _updateTabStop: updateTabStop
2829
2866
  }) {
2830
2867
  this._getNodeById = getNodeById;
2831
2868
  this._getNodeElementForNode = getNodeElementForNode;
@@ -2836,12 +2873,13 @@ var TreeElement = (function () {
2836
2873
  this._selectedNodes = new Set();
2837
2874
  this._selectedSingleNode = null;
2838
2875
  this._triggerEvent = triggerEvent;
2876
+ this._updateTabStop = updateTabStop;
2839
2877
  }
2840
2878
  _addToSelection(node) {
2841
- if (node.id != null) {
2842
- this._selectedNodes.add(node.id);
2843
- } else {
2879
+ if (node.id == null) {
2844
2880
  this._selectedSingleNode = node;
2881
+ } else {
2882
+ this._selectedNodes.add(node.id);
2845
2883
  }
2846
2884
  }
2847
2885
  _clear() {
@@ -2850,52 +2888,39 @@ var TreeElement = (function () {
2850
2888
  }
2851
2889
  _getSelectedNode() {
2852
2890
  let selectedNodes = this._getSelectedNodes();
2853
- if (selectedNodes.length) {
2854
- return selectedNodes[0] ?? null;
2855
- } else {
2856
- return null;
2857
- }
2891
+ return selectedNodes.length ? selectedNodes[0] ?? null : null;
2858
2892
  }
2859
2893
  _getSelectedNodes() {
2860
2894
  if (this._selectedSingleNode) {
2861
2895
  return [this._selectedSingleNode];
2862
- } else {
2863
- let selectedNodes = [];
2864
- this._selectedNodes.forEach(id => {
2865
- let node = this._getNodeById(id);
2866
- if (node) {
2867
- selectedNodes.push(node);
2868
- }
2869
- });
2870
- return selectedNodes;
2871
2896
  }
2897
+ let selectedNodes = [];
2898
+ this._selectedNodes.forEach(id => {
2899
+ let node = this._getNodeById(id);
2900
+ if (node) {
2901
+ selectedNodes.push(node);
2902
+ }
2903
+ });
2904
+ return selectedNodes;
2872
2905
  }
2873
2906
  _getSelectedNodesUnder(parent) {
2874
2907
  if (this._selectedSingleNode) {
2875
- if (parent.isParentOf(this._selectedSingleNode)) {
2876
- return [this._selectedSingleNode];
2877
- } else {
2878
- return [];
2879
- }
2880
- } else {
2881
- let selectedNodes = [];
2882
- this._selectedNodes.forEach(id => {
2883
- let node = this._getNodeById(id);
2884
- if (node && parent.isParentOf(node)) {
2885
- selectedNodes.push(node);
2886
- }
2887
- });
2888
- return selectedNodes;
2908
+ return parent.isParentOf(this._selectedSingleNode) ? [this._selectedSingleNode] : [];
2889
2909
  }
2910
+ let selectedNodes = [];
2911
+ this._selectedNodes.forEach(id => {
2912
+ let node = this._getNodeById(id);
2913
+ if (node && parent.isParentOf(node)) {
2914
+ selectedNodes.push(node);
2915
+ }
2916
+ });
2917
+ return selectedNodes;
2890
2918
  }
2891
2919
  _isNodeSelected(node) {
2892
2920
  if (node.id != null) {
2893
2921
  return this._selectedNodes.has(node.id);
2894
- } else if (this._selectedSingleNode) {
2895
- return this._selectedSingleNode === node;
2896
- } else {
2897
- return false;
2898
2922
  }
2923
+ return this._selectedSingleNode ? this._selectedSingleNode === node : false;
2899
2924
  }
2900
2925
  _removeFromSelection(node, includeChildren = false) {
2901
2926
  if (node.id == null) {
@@ -2932,7 +2957,7 @@ var TreeElement = (function () {
2932
2957
  };
2933
2958
  let selectOptions = {
2934
2959
  ...defaultOptions,
2935
- ...(optionsParam ?? {})
2960
+ ...optionsParam
2936
2961
  };
2937
2962
  let canSelect = () => {
2938
2963
  if (!this._getSelectable()) {
@@ -2968,6 +2993,7 @@ var TreeElement = (function () {
2968
2993
  node
2969
2994
  });
2970
2995
  }
2996
+ this._updateTabStop();
2971
2997
  this._saveState();
2972
2998
  }
2973
2999
  }
@@ -2981,7 +3007,7 @@ var TreeElement = (function () {
2981
3007
  classPrefix: DEFAULT_CLASS_PREFIX,
2982
3008
  // the prefix of all css classes
2983
3009
  // The symbol to use for a closed node - ► BLACK RIGHT-POINTING POINTER
2984
- // http://www.fileformat.info/info/unicode/char/25ba/index.htm
3010
+ // https://www.fileformat.info/info/unicode/char/25ba/index.htm
2985
3011
  // closedIcon: undefined,
2986
3012
  // commonClassName: undefined, // the class of every element; the default is "<classPrefix>-common"
2987
3013
  // data: undefined,
@@ -3003,7 +3029,7 @@ var TreeElement = (function () {
3003
3029
  openFolderDelay: 500,
3004
3030
  // The delay for opening a folder during drag and drop; the value is in milliseconds
3005
3031
  // The symbol to use for an open node - ▼ BLACK DOWN-POINTING TRIANGLE
3006
- // http://www.fileformat.info/info/unicode/char/25bc/index.htm
3032
+ // https://www.fileformat.info/info/unicode/char/25bc/index.htm
3007
3033
  // rtl: undefined, // right-to-left support; true / false (default)
3008
3034
  saveState: false,
3009
3035
  // true / false / string (local storage key; the default key is "tree")
@@ -3020,7 +3046,8 @@ var TreeElement = (function () {
3020
3046
  let setDefaultOptions = (htmlElement, inputOptions) => {
3021
3047
  let options = {
3022
3048
  ...defaults,
3023
- ...inputOptions
3049
+ ...inputOptions,
3050
+ animationSpeed: inputOptions.animationSpeed ?? getDefaultAnimationSpeed()
3024
3051
  };
3025
3052
  options.dataUrl ??= htmlElement.dataset.url;
3026
3053
  options.rtl ??= getRtlOptionFromHTMLElement(htmlElement);
@@ -3028,24 +3055,24 @@ var TreeElement = (function () {
3028
3055
  options.openedIcon ??= "&#x25bc;";
3029
3056
  return options;
3030
3057
  };
3058
+
3059
+ // No animation when the user asked the system for reduced motion
3060
+ let getDefaultAnimationSpeed = () => prefersReducedMotion() ? 0 : defaults.animationSpeed;
3061
+ let prefersReducedMotion = () => typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
3031
3062
  let getDefaultClosedIcon = options => {
3032
3063
  if (options.rtl) {
3033
3064
  // triangle to the left
3034
3065
  return "&#x25c0;";
3035
- } else {
3036
- // triangle to the right
3037
- return "&#x25ba;";
3038
3066
  }
3067
+ // triangle to the right
3068
+ return "&#x25ba;";
3039
3069
  };
3040
3070
  let getRtlOptionFromHTMLElement = htmlElement => {
3041
3071
  let dataRtl = htmlElement.dataset.rtl;
3042
3072
  if (dataRtl == "") {
3043
3073
  return true;
3044
- } else if (dataRtl === "false") {
3045
- return false;
3046
- } else {
3047
- return Boolean(dataRtl);
3048
3074
  }
3075
+ return dataRtl === "false" ? false : Boolean(dataRtl);
3049
3076
  };
3050
3077
 
3051
3078
  // Trigger a CustomEvent. Return if the event is processed (true) or cancelled (false).
@@ -3060,7 +3087,7 @@ var TreeElement = (function () {
3060
3087
  };
3061
3088
 
3062
3089
  // Generated by config/generateVersion.mjs. Do not edit.
3063
- let version = "0.2.0";
3090
+ let version = "0.3.0";
3064
3091
 
3065
3092
  // The types that appear in the public api. Consumers cannot import them from
3066
3093
  // the submodules directly, because those are not exposed in package.json.
@@ -3071,6 +3098,7 @@ var TreeElement = (function () {
3071
3098
  _classNames;
3072
3099
  _dataLoader;
3073
3100
  _dndHandler;
3101
+ _fallbackTabStop;
3074
3102
  _htmlElement;
3075
3103
  _isInitialized;
3076
3104
  _keyHandler;
@@ -3084,7 +3112,9 @@ var TreeElement = (function () {
3084
3112
  _tree;
3085
3113
  _triggerEventProvider;
3086
3114
 
3087
- /** @hidden */
3115
+ /**
3116
+ @hidden
3117
+ */
3088
3118
  constructor({
3089
3119
  htmlElement,
3090
3120
  overrideTriggerEventProvider,
@@ -3097,7 +3127,10 @@ var TreeElement = (function () {
3097
3127
  this._isInitialized = false;
3098
3128
  this._tree = new Node({}, true);
3099
3129
  this._nodeMap = new WeakMap();
3130
+ this._fallbackTabStop = null;
3100
3131
  let {
3132
+ ariaLabel,
3133
+ ariaLabelledBy,
3101
3134
  autoEscape,
3102
3135
  buttonLeft,
3103
3136
  closedIcon,
@@ -3124,10 +3157,10 @@ var TreeElement = (function () {
3124
3157
  let closeNode = this.closeNode.bind(this);
3125
3158
  let getNodeElement = this._getNodeElement.bind(this);
3126
3159
  let getNodeElementForNode = this._getNodeElementForNode.bind(this);
3160
+ let getFocusedNode = this._getFocusedNode.bind(this);
3127
3161
  let getNodeById = this.getNodeById.bind(this);
3128
3162
  let getSelectedNode = this.getSelectedNode.bind(this);
3129
3163
  let getTree = this.getTree.bind(this);
3130
- let isFocusOnTree = this._isFocusOnTree.bind(this);
3131
3164
  let loadData = this.loadData.bind(this);
3132
3165
  let openNode = this.openNode.bind(this);
3133
3166
  let openParents = this._openParents.bind(this);
@@ -3136,6 +3169,7 @@ var TreeElement = (function () {
3136
3169
  let setNodeElement = this._setNodeElement.bind(this);
3137
3170
  let treeElement = this._htmlElement;
3138
3171
  let triggerEvent = this._triggerEvent.bind(this);
3172
+ let updateTabStop = this._updateTabStop.bind(this);
3139
3173
  let saveState = () => {
3140
3174
  saveStateHandler._saveState();
3141
3175
  };
@@ -3146,7 +3180,8 @@ var TreeElement = (function () {
3146
3180
  _getSelectable: () => this._options.selectable,
3147
3181
  _openParents: openParents,
3148
3182
  _saveState: saveState,
3149
- _triggerEvent: triggerEvent
3183
+ _triggerEvent: triggerEvent,
3184
+ _updateTabStop: updateTabStop
3150
3185
  });
3151
3186
  let addToSelection = selectNodeHandler._addToSelection.bind(selectNodeHandler);
3152
3187
  let getSelectedNodes = selectNodeHandler._getSelectedNodes.bind(selectNodeHandler);
@@ -3199,13 +3234,17 @@ var TreeElement = (function () {
3199
3234
  });
3200
3235
  let keyHandler = new KeyHandler({
3201
3236
  _closeNode: closeNode,
3237
+ _getFocusedNode: getFocusedNode,
3202
3238
  _getSelectedNode: getSelectedNode,
3203
- _isFocusOnTree: isFocusOnTree,
3239
+ _getTree: getTree,
3204
3240
  _keyboardSupport: keyboardSupport,
3205
3241
  _openNode: openNode,
3206
- _selectNode: selectNode
3242
+ _selectNode: selectNode,
3243
+ _triggerEvent: triggerEvent
3207
3244
  });
3208
3245
  let renderer = new ElementsRenderer({
3246
+ _ariaLabel: ariaLabel,
3247
+ _ariaLabelledBy: ariaLabelledBy,
3209
3248
  _autoEscape: autoEscape,
3210
3249
  _buttonLeft: buttonLeft,
3211
3250
  _classNames: classNames,
@@ -3300,10 +3339,11 @@ var TreeElement = (function () {
3300
3339
  * @param mustSetFocus - Move the focus to the node. Default `true`.
3301
3340
  * @group Selection
3302
3341
  */
3303
- addToSelection(node, mustSetFocus) {
3342
+ addToSelection(node, mustSetFocus = true) {
3304
3343
  this._selectNodeHandler._addToSelection(node);
3305
3344
  this._openParents(node);
3306
- this._getNodeElementForNode(node)._select(mustSetFocus ?? true);
3345
+ this._getNodeElementForNode(node)._select(mustSetFocus);
3346
+ this._updateTabStop();
3307
3347
  this._saveState();
3308
3348
  }
3309
3349
 
@@ -3325,16 +3365,24 @@ var TreeElement = (function () {
3325
3365
  }
3326
3366
 
3327
3367
  /**
3328
- * Closes a folder.
3368
+ * Closes a folder. Await the promise when you need to know the closing
3369
+ * animation has finished.
3370
+ *
3371
+ * @example
3372
+ * ```js
3373
+ * await tree.closeNode(node);
3374
+ * console.log("closed");
3375
+ * ```
3329
3376
  *
3330
3377
  * @param slide - Override the `slide` option for this call.
3331
3378
  * @group Opening and closing
3332
3379
  */
3333
- closeNode(node, slide) {
3334
- if (node.isFolder() || node.isEmptyFolder) {
3335
- this._createFolderElement(node)._close(slide ?? this._options.slide, this._options.animationSpeed);
3336
- this._saveState();
3380
+ async closeNode(node, slide) {
3381
+ if (!(node.isFolder() || node.isEmptyFolder)) {
3382
+ return;
3337
3383
  }
3384
+ await this._createFolderElement(node)._close(slide ?? this._options.slide, this._options.animationSpeed);
3385
+ this._saveState();
3338
3386
  }
3339
3387
 
3340
3388
  /**
@@ -3358,11 +3406,7 @@ var TreeElement = (function () {
3358
3406
  */
3359
3407
  getNode(element) {
3360
3408
  let liElement = element.closest(`li.${this._classNames.common}`);
3361
- if (liElement) {
3362
- return this._nodeMap.get(liElement) ?? null;
3363
- } else {
3364
- return null;
3365
- }
3409
+ return liElement ? this._nodeMap.get(liElement) ?? null : null;
3366
3410
  }
3367
3411
 
3368
3412
  /**
@@ -3653,6 +3697,7 @@ var TreeElement = (function () {
3653
3697
  removeFromSelection(node) {
3654
3698
  this._selectNodeHandler._removeFromSelection(node);
3655
3699
  this._getNodeElementForNode(node)._deselect();
3700
+ this._updateTabStop();
3656
3701
  this._saveState();
3657
3702
  }
3658
3703
 
@@ -3698,7 +3743,12 @@ var TreeElement = (function () {
3698
3743
  this._saveStateHandler._saveState();
3699
3744
  return;
3700
3745
  }
3701
- this._selectNodeHandler._selectSingleNode(node, options);
3746
+
3747
+ // Unlike a click, selecting the selected node again keeps it selected
3748
+ this._selectNodeHandler._selectSingleNode(node, {
3749
+ mustToggle: false,
3750
+ ...options
3751
+ });
3702
3752
  }
3703
3753
 
3704
3754
  /**
@@ -3724,17 +3774,19 @@ var TreeElement = (function () {
3724
3774
  }
3725
3775
 
3726
3776
  /**
3727
- * Closes an open node and opens a closed one.
3777
+ * Closes an open node and opens a closed one. Await the promise when you
3778
+ * need to know the node is really open or closed, like with `openNode` and
3779
+ * `closeNode`.
3728
3780
  *
3729
3781
  * @param slide - Override the `slide` option for this call.
3730
3782
  * @group Opening and closing
3731
3783
  */
3732
- toggle(node, slide = null) {
3784
+ async toggle(node, slide = null) {
3733
3785
  let mustSlide = slide ?? this._options.slide;
3734
3786
  if (node.is_open) {
3735
- this.closeNode(node, mustSlide);
3787
+ await this.closeNode(node, mustSlide);
3736
3788
  } else {
3737
- void this.openNode(node, mustSlide);
3789
+ await this.openNode(node, mustSlide);
3738
3790
  }
3739
3791
  }
3740
3792
 
@@ -3772,7 +3824,7 @@ var TreeElement = (function () {
3772
3824
  if (idIsChanged) {
3773
3825
  this._tree.addNodeToIndex(node);
3774
3826
  }
3775
- if (typeof data === "object" && data.children && data.children instanceof Array) {
3827
+ if (typeof data === "object" && data.children && Array.isArray(data.children)) {
3776
3828
  node.removeChildren();
3777
3829
  if (data.children.length) {
3778
3830
  node.loadFromData(data.children);
@@ -3780,15 +3832,45 @@ var TreeElement = (function () {
3780
3832
  }
3781
3833
  this._refreshElements(node);
3782
3834
  }
3835
+
3836
+ // Open the nodes up to the autoOpen level. A folder that is loaded on
3837
+ // demand is fetched first, and its children are opened when the data has
3838
+ // arrived.
3839
+ async _autoOpenNodesOnDemand() {
3840
+ let maxLevel = this._getAutoOpenMaxLevel();
3841
+ let openNodes = async () => {
3842
+ let loading = [];
3843
+ this._tree.iterate((node, level) => {
3844
+ if (node.load_on_demand) {
3845
+ if (!node.is_loading) {
3846
+ loading.push(this.openNode(node, false).then(openNodes));
3847
+ }
3848
+ return false;
3849
+ }
3850
+ void this.openNode(node, false);
3851
+ return level !== maxLevel;
3852
+ });
3853
+ await Promise.all(loading);
3854
+ };
3855
+ await openNodes();
3856
+ }
3783
3857
  _createFolderElement(node) {
3784
3858
  let classNames = this._classNames;
3785
3859
  let closedIconElement = this._renderer._closedIconElement;
3786
3860
  let getScrollLeft = this._scrollHandler._getScrollLeft.bind(this._scrollHandler);
3787
3861
  let openedIconElement = this._renderer._openedIconElement;
3788
- let renderChildren = this._renderer._renderChildren.bind(this._renderer);
3789
3862
  let tabIndex = this._options.tabIndex;
3790
3863
  let treeElement = this._htmlElement;
3791
3864
  let triggerEvent = this._triggerEvent.bind(this);
3865
+
3866
+ // A selected node in a closed folder is rendered when the folder is
3867
+ // opened, which can be after it was selected. It becomes the tab stop
3868
+ // then, so the fallback tab stop must go.
3869
+ let renderChildren = folder => {
3870
+ let ul = this._renderer._renderChildren(folder);
3871
+ this._updateTabStop();
3872
+ return ul;
3873
+ };
3792
3874
  return new FolderElement({
3793
3875
  _classNames: classNames,
3794
3876
  _closedIconElement: closedIconElement,
@@ -3821,12 +3903,7 @@ var TreeElement = (function () {
3821
3903
  */
3822
3904
  _createRequestUrl(node) {
3823
3905
  let dataUrl = this._options.dataUrl;
3824
- let url;
3825
- if (typeof dataUrl === "function") {
3826
- url = dataUrl(node);
3827
- } else {
3828
- url = dataUrl;
3829
- }
3906
+ let url = typeof dataUrl === "function" ? dataUrl(node) : dataUrl;
3830
3907
  if (!url) {
3831
3908
  return null;
3832
3909
  }
@@ -3862,33 +3939,54 @@ var TreeElement = (function () {
3862
3939
  _getAutoOpenMaxLevel() {
3863
3940
  if (this._options.autoOpen === true) {
3864
3941
  return -1;
3865
- } else if (typeof this._options.autoOpen === "number") {
3942
+ }
3943
+ if (typeof this._options.autoOpen === "number") {
3866
3944
  return this._options.autoOpen;
3867
- } else if (typeof this._options.autoOpen === "string") {
3945
+ }
3946
+ if (typeof this._options.autoOpen === "string") {
3868
3947
  return parseInt(this._options.autoOpen, 10);
3869
3948
  }
3870
3949
 
3871
3950
  /* istanbul ignore next @preserve */
3872
3951
  return 0;
3873
3952
  }
3874
- _getNodeElement(element) {
3875
- let node = this.getNode(element);
3876
- if (node) {
3877
- return this._getNodeElementForNode(node);
3878
- } else {
3953
+
3954
+ // The title span of the first node, or null when the tree is empty
3955
+ _getFirstTitleSpan() {
3956
+ let firstElement = this._tree.children[0]?.element;
3957
+ return firstElement ? firstElement.querySelector(`:scope > .${this._classNames.element} > span.${this._classNames.title}`) : null;
3958
+ }
3959
+
3960
+ // The node of the HTML element that has the focus, if it is in this tree
3961
+ _getFocusedNode() {
3962
+ let activeElement = document.activeElement;
3963
+
3964
+ /* istanbul ignore if */
3965
+ if (!activeElement) {
3966
+ return null;
3967
+ }
3968
+
3969
+ // The keyboard must still work for input elements.
3970
+ let tagName = activeElement.tagName;
3971
+ if (tagName !== "A" && tagName !== "SPAN") {
3879
3972
  return null;
3880
3973
  }
3974
+ let node = this.getNode(activeElement);
3975
+ return node?.tree === this._tree ? node : null;
3976
+ }
3977
+ _getNodeElement(element) {
3978
+ let node = this.getNode(element);
3979
+ return node ? this._getNodeElementForNode(node) : null;
3881
3980
  }
3882
3981
  _getNodeElementForNode(node) {
3883
- if (node.isFolder()) {
3884
- return this._createFolderElement(node);
3885
- } else {
3886
- return this._createNodeElement(node);
3887
- }
3982
+ return node.isFolder() ? this._createFolderElement(node) : this._createNodeElement(node);
3888
3983
  }
3889
3984
  _getNodeIdToBeSelected() {
3890
3985
  return this._saveStateHandler._getNodeIdToBeSelected();
3891
3986
  }
3987
+ _hasRenderedSelectedNode() {
3988
+ return this.getSelectedNodes().some(node => Boolean(node.element));
3989
+ }
3892
3990
  _initData() {
3893
3991
  if (this._options.data) {
3894
3992
  this.loadData(this._options.data);
@@ -3903,10 +4001,11 @@ var TreeElement = (function () {
3903
4001
  }
3904
4002
  _initTree(data) {
3905
4003
  let doInit = () => {
3906
- if (!this._isInitialized) {
3907
- this._isInitialized = true;
3908
- this._triggerEvent("tree.init");
4004
+ if (this._isInitialized) {
4005
+ return;
3909
4006
  }
4007
+ this._isInitialized = true;
4008
+ this._triggerEvent("tree.init");
3910
4009
  };
3911
4010
  this._tree = new this._options.nodeClass(null, true, this._options.nodeClass);
3912
4011
  this._selectNodeHandler._clear();
@@ -3923,32 +4022,22 @@ var TreeElement = (function () {
3923
4022
 
3924
4023
  // Does an HTML element of the tree have the focus?
3925
4024
  _isFocusOnTree() {
3926
- let activeElement = document.activeElement;
3927
-
3928
- /* istanbul ignore if */
3929
- if (!activeElement) {
3930
- return false;
3931
- }
3932
-
3933
- // The keyboard must still work for input elements.
3934
- let tagName = activeElement.tagName;
3935
- if (tagName !== "A" && tagName !== "SPAN") {
3936
- return false;
3937
- }
3938
- let node = this.getNode(activeElement);
3939
- return node?.tree === this._tree;
4025
+ return this._getFocusedNode() !== null;
3940
4026
  }
3941
4027
  _isSelectedNodeInSubtree(subtree) {
3942
4028
  let selectedNode = this.getSelectedNode();
3943
- if (!selectedNode) {
3944
- return false;
3945
- } else {
3946
- return subtree === selectedNode || subtree.isParentOf(selectedNode);
3947
- }
4029
+ return selectedNode ? subtree === selectedNode || subtree.isParentOf(selectedNode) : false;
3948
4030
  }
3949
4031
  async _loadFolderOnDemand(node, slide) {
3950
4032
  node.is_loading = true;
3951
4033
  await this.loadDataFromUrl(undefined, node);
4034
+
4035
+ // Loading failed, or there is no url. Opening the node now would load it
4036
+ // again, without end. Leave it closed so the user can try again.
4037
+ if (node.load_on_demand) {
4038
+ node.is_loading = false;
4039
+ return;
4040
+ }
3952
4041
  await this.openNode(node, slide);
3953
4042
  }
3954
4043
  _loadSubtree(data, parentNode) {
@@ -3958,10 +4047,7 @@ var TreeElement = (function () {
3958
4047
  this._refreshElements(parentNode);
3959
4048
  }
3960
4049
  _mouseCapture(positionInfo) {
3961
- if (!this._options.dragAndDrop) {
3962
- return false;
3963
- }
3964
- return this._dndHandler._mouseCapture(positionInfo);
4050
+ return this._options.dragAndDrop ? this._dndHandler._mouseCapture(positionInfo) : false;
3965
4051
  }
3966
4052
  _mouseDrag(positionInfo) {
3967
4053
  /* istanbul ignore if */
@@ -3974,6 +4060,7 @@ var TreeElement = (function () {
3974
4060
  }
3975
4061
  _mouseStart(positionInfo) {
3976
4062
  /* istanbul ignore if */
4063
+ // eslint-disable-next-line unicorn/prefer-ternary
3977
4064
  if (!this._options.dragAndDrop) {
3978
4065
  return false;
3979
4066
  }
@@ -4005,6 +4092,7 @@ var TreeElement = (function () {
4005
4092
  if (mustSelect) {
4006
4093
  this._selectCurrentNode(mustSetFocus);
4007
4094
  }
4095
+ this._updateTabStop();
4008
4096
  this._triggerEvent("tree.refresh");
4009
4097
  }
4010
4098
  _saveState() {
@@ -4012,10 +4100,11 @@ var TreeElement = (function () {
4012
4100
  }
4013
4101
  _selectCurrentNode(mustSetFocus) {
4014
4102
  let node = this.getSelectedNode();
4015
- if (node) {
4016
- let nodeElement = this._getNodeElementForNode(node);
4017
- nodeElement._select(mustSetFocus);
4103
+ if (!node) {
4104
+ return;
4018
4105
  }
4106
+ let nodeElement = this._getNodeElementForNode(node);
4107
+ nodeElement._select(mustSetFocus);
4019
4108
  }
4020
4109
 
4021
4110
  // Set initial state, either by restoring the state or auto-opening nodes
@@ -4024,14 +4113,13 @@ var TreeElement = (function () {
4024
4113
  let restoreState = () => {
4025
4114
  // result: is state restored, must load on demand?
4026
4115
  let state = this._saveStateHandler._getStateFromStorage();
4027
- if (!state) {
4028
- return [false, false];
4029
- } else {
4116
+ if (state) {
4030
4117
  let mustLoadOnDemand = this._saveStateHandler._setInitialState(state);
4031
4118
 
4032
4119
  // return true: the state is restored
4033
4120
  return [true, mustLoadOnDemand];
4034
4121
  }
4122
+ return [false, false];
4035
4123
  };
4036
4124
  let autoOpenNodes = () => {
4037
4125
  // result: must load on demand?
@@ -4044,12 +4132,12 @@ var TreeElement = (function () {
4044
4132
  if (node.load_on_demand) {
4045
4133
  mustLoadOnDemand = true;
4046
4134
  return false;
4047
- } else if (!node.hasChildren()) {
4048
- return false;
4049
- } else {
4135
+ }
4136
+ if (node.hasChildren()) {
4050
4137
  node.is_open = true;
4051
4138
  return level !== maxLevel;
4052
4139
  }
4140
+ return false;
4053
4141
  });
4054
4142
  return mustLoadOnDemand;
4055
4143
  };
@@ -4063,50 +4151,12 @@ var TreeElement = (function () {
4063
4151
 
4064
4152
  // Set the initial state for nodes that are loaded on demand
4065
4153
  async _setInitialStateOnDemand() {
4066
- return new Promise(resolve => {
4067
- let restoreState = () => {
4068
- let state = this._saveStateHandler._getStateFromStorage();
4069
- if (!state) {
4070
- return false;
4071
- } else {
4072
- void this._saveStateHandler._setInitialStateOnDemand(state).then(() => {
4073
- resolve();
4074
- });
4075
- return true;
4076
- }
4077
- };
4078
- let autoOpenNodes = () => {
4079
- let maxLevel = this._getAutoOpenMaxLevel();
4080
- let loadingCount = 0;
4081
- let loadAndOpenNode = node => {
4082
- loadingCount += 1;
4083
- void this.openNode(node, false).then(() => {
4084
- loadingCount -= 1;
4085
- openNodes();
4086
- });
4087
- };
4088
- let openNodes = () => {
4089
- this._tree.iterate((node, level) => {
4090
- if (node.load_on_demand) {
4091
- if (!node.is_loading) {
4092
- loadAndOpenNode(node);
4093
- }
4094
- return false;
4095
- } else {
4096
- void this.openNode(node, false);
4097
- return level !== maxLevel;
4098
- }
4099
- });
4100
- if (loadingCount === 0) {
4101
- resolve();
4102
- }
4103
- };
4104
- openNodes();
4105
- };
4106
- if (!restoreState()) {
4107
- autoOpenNodes();
4108
- }
4109
- });
4154
+ let state = this._saveStateHandler._getStateFromStorage();
4155
+ if (state) {
4156
+ await this._saveStateHandler._setInitialStateOnDemand(state);
4157
+ } else {
4158
+ await this._autoOpenNodesOnDemand();
4159
+ }
4110
4160
  }
4111
4161
 
4112
4162
  // Set this HTML element to this node in the node map.
@@ -4116,6 +4166,23 @@ var TreeElement = (function () {
4116
4166
  _triggerEvent(eventName, values) {
4117
4167
  return this._triggerEventProvider(this._htmlElement, eventName, values);
4118
4168
  }
4169
+
4170
+ /* Keyboard users reach the tree through the tab stop. The selected node is
4171
+ * the tab stop, see NodeElement.select. Without a selected node, the first
4172
+ * node is the tab stop, so the tree can always be reached.
4173
+ */
4174
+ _updateTabStop() {
4175
+ let tabIndex = this._options.tabIndex;
4176
+ let previousTabStop = this._fallbackTabStop;
4177
+ let tabStop = tabIndex == null || this._hasRenderedSelectedNode() ? null : this._getFirstTitleSpan();
4178
+ if (previousTabStop && previousTabStop !== tabStop && previousTabStop.getAttribute("aria-selected") !== "true") {
4179
+ previousTabStop.setAttribute("tabindex", "-1");
4180
+ }
4181
+ if (tabStop && tabIndex != null) {
4182
+ tabStop.setAttribute("tabindex", String(tabIndex));
4183
+ }
4184
+ this._fallbackTabStop = tabStop;
4185
+ }
4119
4186
  }
4120
4187
 
4121
4188
  return TreeElement;