tree-element 0.2.0 → 0.3.1

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 (123) hide show
  1. package/README.md +14 -6
  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 +23 -6
  57. package/lib/options.d.ts.map +1 -1
  58. package/lib/requestUrl.d.ts +0 -1
  59. package/lib/requestUrl.d.ts.map +1 -1
  60. package/lib/requestUrl.js +4 -22
  61. package/lib/requestUrl.js.map +1 -1
  62. package/lib/saveStateHandler.d.ts.map +1 -1
  63. package/lib/saveStateHandler.js +21 -41
  64. package/lib/saveStateHandler.js.map +1 -1
  65. package/lib/scrollHandler/containerScrollParent.d.ts.map +1 -1
  66. package/lib/scrollHandler/containerScrollParent.js +3 -8
  67. package/lib/scrollHandler/containerScrollParent.js.map +1 -1
  68. package/lib/scrollHandler/createScrollParent.d.ts.map +1 -1
  69. package/lib/scrollHandler/createScrollParent.js +4 -5
  70. package/lib/scrollHandler/createScrollParent.js.map +1 -1
  71. package/lib/scrollHandler/documentScrollParent.d.ts.map +1 -1
  72. package/lib/scrollHandler/documentScrollParent.js +3 -9
  73. package/lib/scrollHandler/documentScrollParent.js.map +1 -1
  74. package/lib/scrollHandler/scrollParent.d.ts.map +1 -1
  75. package/lib/scrollHandler/scrollParent.js +19 -17
  76. package/lib/scrollHandler/scrollParent.js.map +1 -1
  77. package/lib/selectNodeHandler.d.ts +3 -1
  78. package/lib/selectNodeHandler.d.ts.map +1 -1
  79. package/lib/selectNodeHandler.js +27 -37
  80. package/lib/selectNodeHandler.js.map +1 -1
  81. package/lib/setDefaultOptions.d.ts +3 -0
  82. package/lib/setDefaultOptions.d.ts.map +1 -1
  83. package/lib/setDefaultOptions.js +21 -20
  84. package/lib/setDefaultOptions.js.map +1 -1
  85. package/lib/util.d.ts.map +1 -1
  86. package/lib/util.js +1 -1
  87. package/lib/util.js.map +1 -1
  88. package/lib/version.d.ts +1 -1
  89. package/lib/version.js +1 -1
  90. package/lib/version.js.map +1 -1
  91. package/package.json +44 -35
  92. package/src/animation.ts +13 -11
  93. package/src/dataLoader.ts +11 -0
  94. package/src/dragAndDropHandler/dragElement.ts +2 -1
  95. package/src/dragAndDropHandler/index.ts +70 -75
  96. package/src/dragAndDropHandler/iterateVisibleNodes.ts +18 -16
  97. package/src/elementsRenderer.ts +76 -46
  98. package/src/events.ts +44 -11
  99. package/src/index.ts +210 -150
  100. package/src/keyHandler.ts +95 -41
  101. package/src/methodTypes.ts +3 -3
  102. package/src/mouseHandler.ts +10 -7
  103. package/src/node.ts +155 -148
  104. package/src/nodeElement/folderElement.ts +54 -63
  105. package/src/nodeElement/ghostDropHint.ts +4 -2
  106. package/src/nodeElement/index.ts +9 -15
  107. package/src/nodeUtils.ts +1 -1
  108. package/src/options.ts +23 -6
  109. package/src/requestUrl.ts +4 -25
  110. package/src/saveStateHandler.ts +29 -43
  111. package/src/scrollHandler/containerScrollParent.ts +3 -9
  112. package/src/scrollHandler/createScrollParent.ts +1 -2
  113. package/src/scrollHandler/documentScrollParent.ts +5 -11
  114. package/src/scrollHandler/scrollParent.ts +29 -25
  115. package/src/selectNodeHandler.ts +34 -38
  116. package/src/setDefaultOptions.ts +31 -19
  117. package/src/util.ts +1 -2
  118. package/src/version.ts +1 -1
  119. package/tree_element.css +87 -69
  120. package/tree_element.debug.js +717 -659
  121. package/tree_element.debug.js.map +1 -1
  122. package/tree_element.js +2 -2
  123. package/tree_element.js.map +1 -1
@@ -1,5 +1,5 @@
1
1
  /*
2
- Tree-element 0.2.0
2
+ Tree-element 0.3.1
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,113 +2360,84 @@ 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
- // Url class for absolute and relative urls.
2429
+ // Url of a request. A relative url is resolved against the page, like a link
2430
+ // or `fetch` would, so it also works when the page is in a subdirectory.
2355
2431
 
2356
- let isAbsoluteUrl = inputUrl => {
2357
- try {
2358
- new URL(inputUrl);
2359
- return true;
2360
- } catch {
2361
- return false;
2362
- }
2363
- };
2364
- let LOCALHOST = "http://localhost";
2365
2432
  class RequestUrl {
2366
2433
  constructor(inputUrl) {
2367
- if (isAbsoluteUrl(inputUrl)) {
2368
- this._url = new URL(inputUrl);
2369
- this._isAbsolute = true;
2370
- } else {
2371
- this._url = new URL(inputUrl, LOCALHOST);
2372
- this._isAbsolute = false;
2373
- }
2434
+ this._url = new URL(inputUrl, document.baseURI);
2374
2435
  }
2375
2436
  _setSearchParam(key, value) {
2376
2437
  this._url.searchParams.set(key, value);
2377
2438
  }
2378
2439
  _value() {
2379
- if (this._isAbsolute) {
2380
- return this._url.href;
2381
- } else {
2382
- return this._url.href.slice(LOCALHOST.length);
2383
- }
2440
+ return this._url.href;
2384
2441
  }
2385
2442
  }
2386
2443
 
@@ -2413,11 +2470,7 @@ var TreeElement = (function () {
2413
2470
  return null;
2414
2471
  }
2415
2472
  let state = this._getStateFromStorage();
2416
- if (state?.selected_node) {
2417
- return state.selected_node[0] ?? null;
2418
- } else {
2419
- return null;
2420
- }
2473
+ return state?.selected_node ? state.selected_node[0] ?? null : null;
2421
2474
  }
2422
2475
  _getState() {
2423
2476
  let getOpenNodeIds = () => {
@@ -2432,11 +2485,11 @@ var TreeElement = (function () {
2432
2485
  };
2433
2486
  let getSelectedNodeIds = () => {
2434
2487
  let selectedNodeIds = [];
2435
- this._getSelectedNodes().forEach(node => {
2488
+ for (let node of this._getSelectedNodes()) {
2436
2489
  if (node.id != null) {
2437
2490
  selectedNodeIds.push(node.id);
2438
2491
  }
2439
- });
2492
+ }
2440
2493
  return selectedNodeIds;
2441
2494
  };
2442
2495
  return {
@@ -2449,11 +2502,7 @@ var TreeElement = (function () {
2449
2502
  return null;
2450
2503
  }
2451
2504
  let jsonData = this._loadFromStorage();
2452
- if (jsonData) {
2453
- return this._parseState(jsonData);
2454
- } else {
2455
- return null;
2456
- }
2505
+ return jsonData ? this._parseState(jsonData) : null;
2457
2506
  }
2458
2507
  _saveState() {
2459
2508
  if (!this._saveStateOption) {
@@ -2473,10 +2522,7 @@ var TreeElement = (function () {
2473
2522
  result: must load on demand (boolean)
2474
2523
  */
2475
2524
  _setInitialState(state) {
2476
- let mustLoadOnDemand = false;
2477
- if (state.open_nodes) {
2478
- mustLoadOnDemand = this._openInitialNodes(state.open_nodes);
2479
- }
2525
+ let mustLoadOnDemand = state.open_nodes ? this._openInitialNodes(state.open_nodes) : false;
2480
2526
  this._resetSelection();
2481
2527
  if (state.selected_node) {
2482
2528
  this._selectInitialNodes(state.selected_node);
@@ -2492,9 +2538,7 @@ var TreeElement = (function () {
2492
2538
  let newNodesIds = [];
2493
2539
  for (let nodeId of nodeIds) {
2494
2540
  let node = this._getNodeById(nodeId);
2495
- if (!node) {
2496
- newNodesIds.push(nodeId);
2497
- } else {
2541
+ if (node) {
2498
2542
  if (!node.is_loading) {
2499
2543
  if (node.load_on_demand) {
2500
2544
  await loadAndOpenNode(node);
@@ -2502,13 +2546,13 @@ var TreeElement = (function () {
2502
2546
  await this._openNode(node, false);
2503
2547
  }
2504
2548
  }
2549
+ } else {
2550
+ newNodesIds.push(nodeId);
2505
2551
  }
2506
2552
  }
2507
2553
  nodeIds = newNodesIds;
2508
- if (state.selected_node) {
2509
- if (this._selectInitialNodes(state.selected_node)) {
2510
- this._refreshElements(null);
2511
- }
2554
+ if (state.selected_node && this._selectInitialNodes(state.selected_node)) {
2555
+ this._refreshElements(null);
2512
2556
  }
2513
2557
  };
2514
2558
  let loadAndOpenNode = async node => {
@@ -2518,28 +2562,20 @@ var TreeElement = (function () {
2518
2562
  await openNodes();
2519
2563
  }
2520
2564
  _getKeyName() {
2521
- if (typeof this._saveStateOption === "string") {
2522
- return this._saveStateOption;
2523
- } else {
2524
- return "tree";
2525
- }
2565
+ return typeof this._saveStateOption === "string" ? this._saveStateOption : "tree";
2526
2566
  }
2527
2567
  _loadFromStorage() {
2528
- if (this._onGetStateFromStorage) {
2529
- return this._onGetStateFromStorage();
2530
- } else {
2531
- return localStorage.getItem(this._getKeyName());
2532
- }
2568
+ return this._onGetStateFromStorage ? this._onGetStateFromStorage() : localStorage.getItem(this._getKeyName());
2533
2569
  }
2534
2570
  _openInitialNodes(nodeIds) {
2535
2571
  let mustLoadOnDemand = false;
2536
2572
  for (let nodeId of nodeIds) {
2537
2573
  let node = this._getNodeById(nodeId);
2538
2574
  if (node) {
2539
- if (!node.load_on_demand) {
2540
- node.is_open = true;
2541
- } else {
2575
+ if (node.load_on_demand) {
2542
2576
  mustLoadOnDemand = true;
2577
+ } else {
2578
+ node.is_open = true;
2543
2579
  }
2544
2580
  }
2545
2581
  }
@@ -2557,18 +2593,19 @@ var TreeElement = (function () {
2557
2593
  }
2558
2594
  _resetSelection() {
2559
2595
  let selectedNodes = this._getSelectedNodes();
2560
- selectedNodes.forEach(node => {
2596
+ for (let node of selectedNodes) {
2561
2597
  this._removeFromSelection(node);
2562
- });
2598
+ }
2563
2599
  }
2564
2600
  _selectInitialNodes(nodeIds) {
2565
2601
  let selectCount = 0;
2566
2602
  for (let nodeId of nodeIds) {
2567
2603
  let node = this._getNodeById(nodeId);
2568
- if (node) {
2569
- selectCount += 1;
2570
- this._addToSelection(node);
2604
+ if (!node) {
2605
+ continue;
2571
2606
  }
2607
+ selectCount += 1;
2608
+ this._addToSelection(node);
2572
2609
  }
2573
2610
  return selectCount !== 0;
2574
2611
  }
@@ -2584,27 +2621,29 @@ var TreeElement = (function () {
2584
2621
  }
2585
2622
  _checkHorizontalScrolling(pageX) {
2586
2623
  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
- }
2624
+ if (this._horizontalScrollDirection === newHorizontalScrollDirection) {
2625
+ return;
2626
+ }
2627
+ this._horizontalScrollDirection = newHorizontalScrollDirection;
2628
+ if (this._horizontalScrollTimeout != null) {
2629
+ window.clearTimeout(this._horizontalScrollTimeout);
2630
+ }
2631
+ if (newHorizontalScrollDirection) {
2632
+ this._horizontalScrollTimeout = window.setTimeout(this._scrollHorizontally.bind(this), 40);
2595
2633
  }
2596
2634
  }
2597
2635
  _checkVerticalScrolling(pageY) {
2598
2636
  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
- }
2637
+ if (this._verticalScrollDirection === newVerticalScrollDirection) {
2638
+ return;
2639
+ }
2640
+ this._verticalScrollDirection = newVerticalScrollDirection;
2641
+ if (this._verticalScrollTimeout != null) {
2642
+ window.clearTimeout(this._verticalScrollTimeout);
2643
+ this._verticalScrollTimeout = undefined;
2644
+ }
2645
+ if (newVerticalScrollDirection) {
2646
+ this._verticalScrollTimeout = window.setTimeout(this._scrollVertically.bind(this), 40);
2608
2647
  }
2609
2648
  }
2610
2649
  _getScrollLeft() {
@@ -2657,22 +2696,17 @@ var TreeElement = (function () {
2657
2696
  let rightEdge = scrollParentOffset.left + containerWidth;
2658
2697
  let leftEdge = scrollParentOffset.left;
2659
2698
  let isNearRightEdge = pageX > rightEdge - 20;
2660
- let isNearLeftEdge = pageX < leftEdge + 20;
2661
2699
  if (isNearRightEdge) {
2662
2700
  return "right";
2663
- } else if (isNearLeftEdge) {
2664
- return "left";
2665
2701
  }
2666
- return undefined;
2702
+ let isNearLeftEdge = pageX < leftEdge + 20;
2703
+ return isNearLeftEdge ? "left" : undefined;
2667
2704
  }
2668
2705
  _getNewVerticalScrollDirection(pageY) {
2669
2706
  if (pageY < this._getScrollParentTop()) {
2670
2707
  return "top";
2671
2708
  }
2672
- if (pageY > this._getScrollParentBottom()) {
2673
- return "bottom";
2674
- }
2675
- return undefined;
2709
+ return pageY > this._getScrollParentBottom() ? "bottom" : undefined;
2676
2710
  }
2677
2711
  _getScrollParentBottom() {
2678
2712
  if (this._scrollParentBottom == null) {
@@ -2711,14 +2745,11 @@ var TreeElement = (function () {
2711
2745
  let scrollLeft = this._container.scrollLeft;
2712
2746
  let windowWidth = window.innerWidth;
2713
2747
  let isNearRightEdge = pageX > windowWidth - 20;
2714
- let isNearLeftEdge = pageX - scrollLeft < 20;
2715
2748
  if (isNearRightEdge && this._canScrollRight()) {
2716
2749
  return "right";
2717
2750
  }
2718
- if (isNearLeftEdge) {
2719
- return "left";
2720
- }
2721
- return undefined;
2751
+ let isNearLeftEdge = pageX - scrollLeft < 20;
2752
+ return isNearLeftEdge ? "left" : undefined;
2722
2753
  }
2723
2754
  _getNewVerticalScrollDirection(pageY) {
2724
2755
  let scrollTop = this._container.scrollTop;
@@ -2727,10 +2758,7 @@ var TreeElement = (function () {
2727
2758
  return "top";
2728
2759
  }
2729
2760
  let windowHeight = window.innerHeight;
2730
- if (windowHeight - (pageY - scrollTop) < 20 && this._canScrollDown()) {
2731
- return "bottom";
2732
- }
2733
- return undefined;
2761
+ return windowHeight - (pageY - scrollTop) < 20 && this._canScrollDown() ? "bottom" : undefined;
2734
2762
  }
2735
2763
  _canScrollDown() {
2736
2764
  return this._container.scrollTop + this._container.clientHeight < this._getDocumentScrollHeight();
@@ -2775,12 +2803,11 @@ var TreeElement = (function () {
2775
2803
  _container: container,
2776
2804
  _refreshHitAreas: refreshHitAreas
2777
2805
  });
2778
- } else {
2779
- return new DocumentScrollParent({
2780
- _refreshHitAreas: refreshHitAreas,
2781
- _treeElement: treeElement
2782
- });
2783
2806
  }
2807
+ return new DocumentScrollParent({
2808
+ _refreshHitAreas: refreshHitAreas,
2809
+ _treeElement: treeElement
2810
+ });
2784
2811
  };
2785
2812
 
2786
2813
  class ScrollHandler {
@@ -2825,7 +2852,8 @@ var TreeElement = (function () {
2825
2852
  _getSelectable: getSelectable,
2826
2853
  _openParents: openParents,
2827
2854
  _saveState: saveState,
2828
- _triggerEvent: triggerEvent
2855
+ _triggerEvent: triggerEvent,
2856
+ _updateTabStop: updateTabStop
2829
2857
  }) {
2830
2858
  this._getNodeById = getNodeById;
2831
2859
  this._getNodeElementForNode = getNodeElementForNode;
@@ -2836,12 +2864,13 @@ var TreeElement = (function () {
2836
2864
  this._selectedNodes = new Set();
2837
2865
  this._selectedSingleNode = null;
2838
2866
  this._triggerEvent = triggerEvent;
2867
+ this._updateTabStop = updateTabStop;
2839
2868
  }
2840
2869
  _addToSelection(node) {
2841
- if (node.id != null) {
2842
- this._selectedNodes.add(node.id);
2843
- } else {
2870
+ if (node.id == null) {
2844
2871
  this._selectedSingleNode = node;
2872
+ } else {
2873
+ this._selectedNodes.add(node.id);
2845
2874
  }
2846
2875
  }
2847
2876
  _clear() {
@@ -2850,52 +2879,39 @@ var TreeElement = (function () {
2850
2879
  }
2851
2880
  _getSelectedNode() {
2852
2881
  let selectedNodes = this._getSelectedNodes();
2853
- if (selectedNodes.length) {
2854
- return selectedNodes[0] ?? null;
2855
- } else {
2856
- return null;
2857
- }
2882
+ return selectedNodes.length ? selectedNodes[0] ?? null : null;
2858
2883
  }
2859
2884
  _getSelectedNodes() {
2860
2885
  if (this._selectedSingleNode) {
2861
2886
  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
2887
  }
2888
+ let selectedNodes = [];
2889
+ this._selectedNodes.forEach(id => {
2890
+ let node = this._getNodeById(id);
2891
+ if (node) {
2892
+ selectedNodes.push(node);
2893
+ }
2894
+ });
2895
+ return selectedNodes;
2872
2896
  }
2873
2897
  _getSelectedNodesUnder(parent) {
2874
2898
  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;
2899
+ return parent.isParentOf(this._selectedSingleNode) ? [this._selectedSingleNode] : [];
2889
2900
  }
2901
+ let selectedNodes = [];
2902
+ this._selectedNodes.forEach(id => {
2903
+ let node = this._getNodeById(id);
2904
+ if (node && parent.isParentOf(node)) {
2905
+ selectedNodes.push(node);
2906
+ }
2907
+ });
2908
+ return selectedNodes;
2890
2909
  }
2891
2910
  _isNodeSelected(node) {
2892
2911
  if (node.id != null) {
2893
2912
  return this._selectedNodes.has(node.id);
2894
- } else if (this._selectedSingleNode) {
2895
- return this._selectedSingleNode === node;
2896
- } else {
2897
- return false;
2898
2913
  }
2914
+ return this._selectedSingleNode ? this._selectedSingleNode === node : false;
2899
2915
  }
2900
2916
  _removeFromSelection(node, includeChildren = false) {
2901
2917
  if (node.id == null) {
@@ -2932,7 +2948,7 @@ var TreeElement = (function () {
2932
2948
  };
2933
2949
  let selectOptions = {
2934
2950
  ...defaultOptions,
2935
- ...(optionsParam ?? {})
2951
+ ...optionsParam
2936
2952
  };
2937
2953
  let canSelect = () => {
2938
2954
  if (!this._getSelectable()) {
@@ -2968,10 +2984,20 @@ var TreeElement = (function () {
2968
2984
  node
2969
2985
  });
2970
2986
  }
2987
+ this._updateTabStop();
2971
2988
  this._saveState();
2972
2989
  }
2973
2990
  }
2974
2991
 
2992
+ /* The default icons are svg instead of text: the triangle characters come
2993
+ * from different fonts on each platform, so they differ in shape and size.
2994
+ * They are sized in em to follow the font size, and use currentColor to follow
2995
+ * --tree-element-toggler-color.
2996
+ */
2997
+ let triangle = points => `<svg width="0.7em" height="0.7em" viewBox="0 0 10 10" focusable="false"><polygon points="${points}" fill="currentColor"/></svg>`;
2998
+ let triangleDown = triangle("0.5,1.5 9.5,1.5 5,9");
2999
+ let triangleLeft = triangle("9,0.5 9,9.5 1.5,5");
3000
+ let triangleRight = triangle("1,0.5 1,9.5 8.5,5");
2975
3001
  let defaults = {
2976
3002
  animationSpeed: "fast",
2977
3003
  autoEscape: true,
@@ -2980,9 +3006,7 @@ var TreeElement = (function () {
2980
3006
  buttonLeft: true,
2981
3007
  classPrefix: DEFAULT_CLASS_PREFIX,
2982
3008
  // the prefix of all css classes
2983
- // The symbol to use for a closed node - ► BLACK RIGHT-POINTING POINTER
2984
- // http://www.fileformat.info/info/unicode/char/25ba/index.htm
2985
- // closedIcon: undefined,
3009
+ // closedIcon: undefined, // the default is a triangle; see getDefaultClosedIcon
2986
3010
  // commonClassName: undefined, // the class of every element; the default is "<classPrefix>-common"
2987
3011
  // data: undefined,
2988
3012
  // dataFilter: undefined,
@@ -2999,11 +3023,9 @@ var TreeElement = (function () {
2999
3023
  // onGetStateFromStorage: undefined,
3000
3024
  // onIsMoveHandle: undefined,
3001
3025
  // onSetStateFromStorage: undefined,
3002
- // openedIcon: undefined,
3026
+ // openedIcon: undefined, // the default is a triangle pointing down
3003
3027
  openFolderDelay: 500,
3004
3028
  // The delay for opening a folder during drag and drop; the value is in milliseconds
3005
- // The symbol to use for an open node - ▼ BLACK DOWN-POINTING TRIANGLE
3006
- // http://www.fileformat.info/info/unicode/char/25bc/index.htm
3007
3029
  // rtl: undefined, // right-to-left support; true / false (default)
3008
3030
  saveState: false,
3009
3031
  // true / false / string (local storage key; the default key is "tree")
@@ -3020,32 +3042,28 @@ var TreeElement = (function () {
3020
3042
  let setDefaultOptions = (htmlElement, inputOptions) => {
3021
3043
  let options = {
3022
3044
  ...defaults,
3023
- ...inputOptions
3045
+ ...inputOptions,
3046
+ animationSpeed: inputOptions.animationSpeed ?? getDefaultAnimationSpeed()
3024
3047
  };
3025
3048
  options.dataUrl ??= htmlElement.dataset.url;
3026
3049
  options.rtl ??= getRtlOptionFromHTMLElement(htmlElement);
3027
3050
  options.closedIcon ??= getDefaultClosedIcon(options);
3028
- options.openedIcon ??= "&#x25bc;";
3051
+ options.openedIcon ??= triangleDown;
3029
3052
  return options;
3030
3053
  };
3054
+
3055
+ // No animation when the user asked the system for reduced motion
3056
+ let getDefaultAnimationSpeed = () => prefersReducedMotion() ? 0 : defaults.animationSpeed;
3057
+ let prefersReducedMotion = () => typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
3031
3058
  let getDefaultClosedIcon = options => {
3032
- if (options.rtl) {
3033
- // triangle to the left
3034
- return "&#x25c0;";
3035
- } else {
3036
- // triangle to the right
3037
- return "&#x25ba;";
3038
- }
3059
+ return options.rtl ? triangleLeft : triangleRight;
3039
3060
  };
3040
3061
  let getRtlOptionFromHTMLElement = htmlElement => {
3041
3062
  let dataRtl = htmlElement.dataset.rtl;
3042
3063
  if (dataRtl == "") {
3043
3064
  return true;
3044
- } else if (dataRtl === "false") {
3045
- return false;
3046
- } else {
3047
- return Boolean(dataRtl);
3048
3065
  }
3066
+ return dataRtl === "false" ? false : Boolean(dataRtl);
3049
3067
  };
3050
3068
 
3051
3069
  // Trigger a CustomEvent. Return if the event is processed (true) or cancelled (false).
@@ -3060,7 +3078,7 @@ var TreeElement = (function () {
3060
3078
  };
3061
3079
 
3062
3080
  // Generated by config/generateVersion.mjs. Do not edit.
3063
- let version = "0.2.0";
3081
+ let version = "0.3.1";
3064
3082
 
3065
3083
  // The types that appear in the public api. Consumers cannot import them from
3066
3084
  // the submodules directly, because those are not exposed in package.json.
@@ -3071,6 +3089,7 @@ var TreeElement = (function () {
3071
3089
  _classNames;
3072
3090
  _dataLoader;
3073
3091
  _dndHandler;
3092
+ _fallbackTabStop;
3074
3093
  _htmlElement;
3075
3094
  _isInitialized;
3076
3095
  _keyHandler;
@@ -3084,7 +3103,9 @@ var TreeElement = (function () {
3084
3103
  _tree;
3085
3104
  _triggerEventProvider;
3086
3105
 
3087
- /** @hidden */
3106
+ /**
3107
+ @hidden
3108
+ */
3088
3109
  constructor({
3089
3110
  htmlElement,
3090
3111
  overrideTriggerEventProvider,
@@ -3097,7 +3118,10 @@ var TreeElement = (function () {
3097
3118
  this._isInitialized = false;
3098
3119
  this._tree = new Node({}, true);
3099
3120
  this._nodeMap = new WeakMap();
3121
+ this._fallbackTabStop = null;
3100
3122
  let {
3123
+ ariaLabel,
3124
+ ariaLabelledBy,
3101
3125
  autoEscape,
3102
3126
  buttonLeft,
3103
3127
  closedIcon,
@@ -3124,10 +3148,10 @@ var TreeElement = (function () {
3124
3148
  let closeNode = this.closeNode.bind(this);
3125
3149
  let getNodeElement = this._getNodeElement.bind(this);
3126
3150
  let getNodeElementForNode = this._getNodeElementForNode.bind(this);
3151
+ let getFocusedNode = this._getFocusedNode.bind(this);
3127
3152
  let getNodeById = this.getNodeById.bind(this);
3128
3153
  let getSelectedNode = this.getSelectedNode.bind(this);
3129
3154
  let getTree = this.getTree.bind(this);
3130
- let isFocusOnTree = this._isFocusOnTree.bind(this);
3131
3155
  let loadData = this.loadData.bind(this);
3132
3156
  let openNode = this.openNode.bind(this);
3133
3157
  let openParents = this._openParents.bind(this);
@@ -3136,6 +3160,7 @@ var TreeElement = (function () {
3136
3160
  let setNodeElement = this._setNodeElement.bind(this);
3137
3161
  let treeElement = this._htmlElement;
3138
3162
  let triggerEvent = this._triggerEvent.bind(this);
3163
+ let updateTabStop = this._updateTabStop.bind(this);
3139
3164
  let saveState = () => {
3140
3165
  saveStateHandler._saveState();
3141
3166
  };
@@ -3146,7 +3171,8 @@ var TreeElement = (function () {
3146
3171
  _getSelectable: () => this._options.selectable,
3147
3172
  _openParents: openParents,
3148
3173
  _saveState: saveState,
3149
- _triggerEvent: triggerEvent
3174
+ _triggerEvent: triggerEvent,
3175
+ _updateTabStop: updateTabStop
3150
3176
  });
3151
3177
  let addToSelection = selectNodeHandler._addToSelection.bind(selectNodeHandler);
3152
3178
  let getSelectedNodes = selectNodeHandler._getSelectedNodes.bind(selectNodeHandler);
@@ -3199,13 +3225,17 @@ var TreeElement = (function () {
3199
3225
  });
3200
3226
  let keyHandler = new KeyHandler({
3201
3227
  _closeNode: closeNode,
3228
+ _getFocusedNode: getFocusedNode,
3202
3229
  _getSelectedNode: getSelectedNode,
3203
- _isFocusOnTree: isFocusOnTree,
3230
+ _getTree: getTree,
3204
3231
  _keyboardSupport: keyboardSupport,
3205
3232
  _openNode: openNode,
3206
- _selectNode: selectNode
3233
+ _selectNode: selectNode,
3234
+ _triggerEvent: triggerEvent
3207
3235
  });
3208
3236
  let renderer = new ElementsRenderer({
3237
+ _ariaLabel: ariaLabel,
3238
+ _ariaLabelledBy: ariaLabelledBy,
3209
3239
  _autoEscape: autoEscape,
3210
3240
  _buttonLeft: buttonLeft,
3211
3241
  _classNames: classNames,
@@ -3300,10 +3330,11 @@ var TreeElement = (function () {
3300
3330
  * @param mustSetFocus - Move the focus to the node. Default `true`.
3301
3331
  * @group Selection
3302
3332
  */
3303
- addToSelection(node, mustSetFocus) {
3333
+ addToSelection(node, mustSetFocus = true) {
3304
3334
  this._selectNodeHandler._addToSelection(node);
3305
3335
  this._openParents(node);
3306
- this._getNodeElementForNode(node)._select(mustSetFocus ?? true);
3336
+ this._getNodeElementForNode(node)._select(mustSetFocus);
3337
+ this._updateTabStop();
3307
3338
  this._saveState();
3308
3339
  }
3309
3340
 
@@ -3325,16 +3356,24 @@ var TreeElement = (function () {
3325
3356
  }
3326
3357
 
3327
3358
  /**
3328
- * Closes a folder.
3359
+ * Closes a folder. Await the promise when you need to know the closing
3360
+ * animation has finished.
3361
+ *
3362
+ * @example
3363
+ * ```js
3364
+ * await tree.closeNode(node);
3365
+ * console.log("closed");
3366
+ * ```
3329
3367
  *
3330
3368
  * @param slide - Override the `slide` option for this call.
3331
3369
  * @group Opening and closing
3332
3370
  */
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();
3371
+ async closeNode(node, slide) {
3372
+ if (!(node.isFolder() || node.isEmptyFolder)) {
3373
+ return;
3337
3374
  }
3375
+ await this._createFolderElement(node)._close(slide ?? this._options.slide, this._options.animationSpeed);
3376
+ this._saveState();
3338
3377
  }
3339
3378
 
3340
3379
  /**
@@ -3358,11 +3397,7 @@ var TreeElement = (function () {
3358
3397
  */
3359
3398
  getNode(element) {
3360
3399
  let liElement = element.closest(`li.${this._classNames.common}`);
3361
- if (liElement) {
3362
- return this._nodeMap.get(liElement) ?? null;
3363
- } else {
3364
- return null;
3365
- }
3400
+ return liElement ? this._nodeMap.get(liElement) ?? null : null;
3366
3401
  }
3367
3402
 
3368
3403
  /**
@@ -3653,6 +3688,7 @@ var TreeElement = (function () {
3653
3688
  removeFromSelection(node) {
3654
3689
  this._selectNodeHandler._removeFromSelection(node);
3655
3690
  this._getNodeElementForNode(node)._deselect();
3691
+ this._updateTabStop();
3656
3692
  this._saveState();
3657
3693
  }
3658
3694
 
@@ -3698,7 +3734,12 @@ var TreeElement = (function () {
3698
3734
  this._saveStateHandler._saveState();
3699
3735
  return;
3700
3736
  }
3701
- this._selectNodeHandler._selectSingleNode(node, options);
3737
+
3738
+ // Unlike a click, selecting the selected node again keeps it selected
3739
+ this._selectNodeHandler._selectSingleNode(node, {
3740
+ mustToggle: false,
3741
+ ...options
3742
+ });
3702
3743
  }
3703
3744
 
3704
3745
  /**
@@ -3724,17 +3765,19 @@ var TreeElement = (function () {
3724
3765
  }
3725
3766
 
3726
3767
  /**
3727
- * Closes an open node and opens a closed one.
3768
+ * Closes an open node and opens a closed one. Await the promise when you
3769
+ * need to know the node is really open or closed, like with `openNode` and
3770
+ * `closeNode`.
3728
3771
  *
3729
3772
  * @param slide - Override the `slide` option for this call.
3730
3773
  * @group Opening and closing
3731
3774
  */
3732
- toggle(node, slide = null) {
3775
+ async toggle(node, slide = null) {
3733
3776
  let mustSlide = slide ?? this._options.slide;
3734
3777
  if (node.is_open) {
3735
- this.closeNode(node, mustSlide);
3778
+ await this.closeNode(node, mustSlide);
3736
3779
  } else {
3737
- void this.openNode(node, mustSlide);
3780
+ await this.openNode(node, mustSlide);
3738
3781
  }
3739
3782
  }
3740
3783
 
@@ -3772,7 +3815,7 @@ var TreeElement = (function () {
3772
3815
  if (idIsChanged) {
3773
3816
  this._tree.addNodeToIndex(node);
3774
3817
  }
3775
- if (typeof data === "object" && data.children && data.children instanceof Array) {
3818
+ if (typeof data === "object" && data.children && Array.isArray(data.children)) {
3776
3819
  node.removeChildren();
3777
3820
  if (data.children.length) {
3778
3821
  node.loadFromData(data.children);
@@ -3780,15 +3823,45 @@ var TreeElement = (function () {
3780
3823
  }
3781
3824
  this._refreshElements(node);
3782
3825
  }
3826
+
3827
+ // Open the nodes up to the autoOpen level. A folder that is loaded on
3828
+ // demand is fetched first, and its children are opened when the data has
3829
+ // arrived.
3830
+ async _autoOpenNodesOnDemand() {
3831
+ let maxLevel = this._getAutoOpenMaxLevel();
3832
+ let openNodes = async () => {
3833
+ let loading = [];
3834
+ this._tree.iterate((node, level) => {
3835
+ if (node.load_on_demand) {
3836
+ if (!node.is_loading) {
3837
+ loading.push(this.openNode(node, false).then(openNodes));
3838
+ }
3839
+ return false;
3840
+ }
3841
+ void this.openNode(node, false);
3842
+ return level !== maxLevel;
3843
+ });
3844
+ await Promise.all(loading);
3845
+ };
3846
+ await openNodes();
3847
+ }
3783
3848
  _createFolderElement(node) {
3784
3849
  let classNames = this._classNames;
3785
3850
  let closedIconElement = this._renderer._closedIconElement;
3786
3851
  let getScrollLeft = this._scrollHandler._getScrollLeft.bind(this._scrollHandler);
3787
3852
  let openedIconElement = this._renderer._openedIconElement;
3788
- let renderChildren = this._renderer._renderChildren.bind(this._renderer);
3789
3853
  let tabIndex = this._options.tabIndex;
3790
3854
  let treeElement = this._htmlElement;
3791
3855
  let triggerEvent = this._triggerEvent.bind(this);
3856
+
3857
+ // A selected node in a closed folder is rendered when the folder is
3858
+ // opened, which can be after it was selected. It becomes the tab stop
3859
+ // then, so the fallback tab stop must go.
3860
+ let renderChildren = folder => {
3861
+ let ul = this._renderer._renderChildren(folder);
3862
+ this._updateTabStop();
3863
+ return ul;
3864
+ };
3792
3865
  return new FolderElement({
3793
3866
  _classNames: classNames,
3794
3867
  _closedIconElement: closedIconElement,
@@ -3821,12 +3894,7 @@ var TreeElement = (function () {
3821
3894
  */
3822
3895
  _createRequestUrl(node) {
3823
3896
  let dataUrl = this._options.dataUrl;
3824
- let url;
3825
- if (typeof dataUrl === "function") {
3826
- url = dataUrl(node);
3827
- } else {
3828
- url = dataUrl;
3829
- }
3897
+ let url = typeof dataUrl === "function" ? dataUrl(node) : dataUrl;
3830
3898
  if (!url) {
3831
3899
  return null;
3832
3900
  }
@@ -3862,33 +3930,54 @@ var TreeElement = (function () {
3862
3930
  _getAutoOpenMaxLevel() {
3863
3931
  if (this._options.autoOpen === true) {
3864
3932
  return -1;
3865
- } else if (typeof this._options.autoOpen === "number") {
3933
+ }
3934
+ if (typeof this._options.autoOpen === "number") {
3866
3935
  return this._options.autoOpen;
3867
- } else if (typeof this._options.autoOpen === "string") {
3936
+ }
3937
+ if (typeof this._options.autoOpen === "string") {
3868
3938
  return parseInt(this._options.autoOpen, 10);
3869
3939
  }
3870
3940
 
3871
3941
  /* istanbul ignore next @preserve */
3872
3942
  return 0;
3873
3943
  }
3874
- _getNodeElement(element) {
3875
- let node = this.getNode(element);
3876
- if (node) {
3877
- return this._getNodeElementForNode(node);
3878
- } else {
3944
+
3945
+ // The title span of the first node, or null when the tree is empty
3946
+ _getFirstTitleSpan() {
3947
+ let firstElement = this._tree.children[0]?.element;
3948
+ return firstElement ? firstElement.querySelector(`:scope > .${this._classNames.element} > span.${this._classNames.title}`) : null;
3949
+ }
3950
+
3951
+ // The node of the HTML element that has the focus, if it is in this tree
3952
+ _getFocusedNode() {
3953
+ let activeElement = document.activeElement;
3954
+
3955
+ /* istanbul ignore if */
3956
+ if (!activeElement) {
3879
3957
  return null;
3880
3958
  }
3959
+
3960
+ // The keyboard must still work for input elements.
3961
+ let tagName = activeElement.tagName;
3962
+ if (tagName !== "A" && tagName !== "SPAN") {
3963
+ return null;
3964
+ }
3965
+ let node = this.getNode(activeElement);
3966
+ return node?.tree === this._tree ? node : null;
3967
+ }
3968
+ _getNodeElement(element) {
3969
+ let node = this.getNode(element);
3970
+ return node ? this._getNodeElementForNode(node) : null;
3881
3971
  }
3882
3972
  _getNodeElementForNode(node) {
3883
- if (node.isFolder()) {
3884
- return this._createFolderElement(node);
3885
- } else {
3886
- return this._createNodeElement(node);
3887
- }
3973
+ return node.isFolder() ? this._createFolderElement(node) : this._createNodeElement(node);
3888
3974
  }
3889
3975
  _getNodeIdToBeSelected() {
3890
3976
  return this._saveStateHandler._getNodeIdToBeSelected();
3891
3977
  }
3978
+ _hasRenderedSelectedNode() {
3979
+ return this.getSelectedNodes().some(node => Boolean(node.element));
3980
+ }
3892
3981
  _initData() {
3893
3982
  if (this._options.data) {
3894
3983
  this.loadData(this._options.data);
@@ -3903,10 +3992,11 @@ var TreeElement = (function () {
3903
3992
  }
3904
3993
  _initTree(data) {
3905
3994
  let doInit = () => {
3906
- if (!this._isInitialized) {
3907
- this._isInitialized = true;
3908
- this._triggerEvent("tree.init");
3995
+ if (this._isInitialized) {
3996
+ return;
3909
3997
  }
3998
+ this._isInitialized = true;
3999
+ this._triggerEvent("tree.init");
3910
4000
  };
3911
4001
  this._tree = new this._options.nodeClass(null, true, this._options.nodeClass);
3912
4002
  this._selectNodeHandler._clear();
@@ -3923,32 +4013,22 @@ var TreeElement = (function () {
3923
4013
 
3924
4014
  // Does an HTML element of the tree have the focus?
3925
4015
  _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;
4016
+ return this._getFocusedNode() !== null;
3940
4017
  }
3941
4018
  _isSelectedNodeInSubtree(subtree) {
3942
4019
  let selectedNode = this.getSelectedNode();
3943
- if (!selectedNode) {
3944
- return false;
3945
- } else {
3946
- return subtree === selectedNode || subtree.isParentOf(selectedNode);
3947
- }
4020
+ return selectedNode ? subtree === selectedNode || subtree.isParentOf(selectedNode) : false;
3948
4021
  }
3949
4022
  async _loadFolderOnDemand(node, slide) {
3950
4023
  node.is_loading = true;
3951
4024
  await this.loadDataFromUrl(undefined, node);
4025
+
4026
+ // Loading failed, or there is no url. Opening the node now would load it
4027
+ // again, without end. Leave it closed so the user can try again.
4028
+ if (node.load_on_demand) {
4029
+ node.is_loading = false;
4030
+ return;
4031
+ }
3952
4032
  await this.openNode(node, slide);
3953
4033
  }
3954
4034
  _loadSubtree(data, parentNode) {
@@ -3958,10 +4038,7 @@ var TreeElement = (function () {
3958
4038
  this._refreshElements(parentNode);
3959
4039
  }
3960
4040
  _mouseCapture(positionInfo) {
3961
- if (!this._options.dragAndDrop) {
3962
- return false;
3963
- }
3964
- return this._dndHandler._mouseCapture(positionInfo);
4041
+ return this._options.dragAndDrop ? this._dndHandler._mouseCapture(positionInfo) : false;
3965
4042
  }
3966
4043
  _mouseDrag(positionInfo) {
3967
4044
  /* istanbul ignore if */
@@ -3974,6 +4051,7 @@ var TreeElement = (function () {
3974
4051
  }
3975
4052
  _mouseStart(positionInfo) {
3976
4053
  /* istanbul ignore if */
4054
+ // eslint-disable-next-line unicorn/prefer-ternary
3977
4055
  if (!this._options.dragAndDrop) {
3978
4056
  return false;
3979
4057
  }
@@ -4005,6 +4083,7 @@ var TreeElement = (function () {
4005
4083
  if (mustSelect) {
4006
4084
  this._selectCurrentNode(mustSetFocus);
4007
4085
  }
4086
+ this._updateTabStop();
4008
4087
  this._triggerEvent("tree.refresh");
4009
4088
  }
4010
4089
  _saveState() {
@@ -4012,10 +4091,11 @@ var TreeElement = (function () {
4012
4091
  }
4013
4092
  _selectCurrentNode(mustSetFocus) {
4014
4093
  let node = this.getSelectedNode();
4015
- if (node) {
4016
- let nodeElement = this._getNodeElementForNode(node);
4017
- nodeElement._select(mustSetFocus);
4094
+ if (!node) {
4095
+ return;
4018
4096
  }
4097
+ let nodeElement = this._getNodeElementForNode(node);
4098
+ nodeElement._select(mustSetFocus);
4019
4099
  }
4020
4100
 
4021
4101
  // Set initial state, either by restoring the state or auto-opening nodes
@@ -4024,14 +4104,13 @@ var TreeElement = (function () {
4024
4104
  let restoreState = () => {
4025
4105
  // result: is state restored, must load on demand?
4026
4106
  let state = this._saveStateHandler._getStateFromStorage();
4027
- if (!state) {
4028
- return [false, false];
4029
- } else {
4107
+ if (state) {
4030
4108
  let mustLoadOnDemand = this._saveStateHandler._setInitialState(state);
4031
4109
 
4032
4110
  // return true: the state is restored
4033
4111
  return [true, mustLoadOnDemand];
4034
4112
  }
4113
+ return [false, false];
4035
4114
  };
4036
4115
  let autoOpenNodes = () => {
4037
4116
  // result: must load on demand?
@@ -4044,12 +4123,12 @@ var TreeElement = (function () {
4044
4123
  if (node.load_on_demand) {
4045
4124
  mustLoadOnDemand = true;
4046
4125
  return false;
4047
- } else if (!node.hasChildren()) {
4048
- return false;
4049
- } else {
4126
+ }
4127
+ if (node.hasChildren()) {
4050
4128
  node.is_open = true;
4051
4129
  return level !== maxLevel;
4052
4130
  }
4131
+ return false;
4053
4132
  });
4054
4133
  return mustLoadOnDemand;
4055
4134
  };
@@ -4063,50 +4142,12 @@ var TreeElement = (function () {
4063
4142
 
4064
4143
  // Set the initial state for nodes that are loaded on demand
4065
4144
  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
- });
4145
+ let state = this._saveStateHandler._getStateFromStorage();
4146
+ if (state) {
4147
+ await this._saveStateHandler._setInitialStateOnDemand(state);
4148
+ } else {
4149
+ await this._autoOpenNodesOnDemand();
4150
+ }
4110
4151
  }
4111
4152
 
4112
4153
  // Set this HTML element to this node in the node map.
@@ -4116,6 +4157,23 @@ var TreeElement = (function () {
4116
4157
  _triggerEvent(eventName, values) {
4117
4158
  return this._triggerEventProvider(this._htmlElement, eventName, values);
4118
4159
  }
4160
+
4161
+ /* Keyboard users reach the tree through the tab stop. The selected node is
4162
+ * the tab stop, see NodeElement.select. Without a selected node, the first
4163
+ * node is the tab stop, so the tree can always be reached.
4164
+ */
4165
+ _updateTabStop() {
4166
+ let tabIndex = this._options.tabIndex;
4167
+ let previousTabStop = this._fallbackTabStop;
4168
+ let tabStop = tabIndex == null || this._hasRenderedSelectedNode() ? null : this._getFirstTitleSpan();
4169
+ if (previousTabStop && previousTabStop !== tabStop && previousTabStop.getAttribute("aria-selected") !== "true") {
4170
+ previousTabStop.setAttribute("tabindex", "-1");
4171
+ }
4172
+ if (tabStop && tabIndex != null) {
4173
+ tabStop.setAttribute("tabindex", String(tabIndex));
4174
+ }
4175
+ this._fallbackTabStop = tabStop;
4176
+ }
4119
4177
  }
4120
4178
 
4121
4179
  return TreeElement;