tree-element 0.3.2 → 0.4.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.
- package/README.md +23 -20
- package/lib/classNames.js +4 -4
- package/lib/classNames.js.map +1 -1
- package/lib/dataLoader.d.ts +0 -1
- package/lib/dataLoader.d.ts.map +1 -1
- package/lib/dataLoader.js +10 -7
- package/lib/dataLoader.js.map +1 -1
- package/lib/dragAndDropHandler/generateHitAreas.d.ts.map +1 -1
- package/lib/dragAndDropHandler/generateHitAreas.js +3 -2
- package/lib/dragAndDropHandler/generateHitAreas.js.map +1 -1
- package/lib/dragAndDropHandler/index.d.ts +2 -4
- package/lib/dragAndDropHandler/index.d.ts.map +1 -1
- package/lib/dragAndDropHandler/index.js +2 -9
- package/lib/dragAndDropHandler/index.js.map +1 -1
- package/lib/elementsRenderer.d.ts +11 -4
- package/lib/elementsRenderer.d.ts.map +1 -1
- package/lib/elementsRenderer.js +104 -43
- package/lib/elementsRenderer.js.map +1 -1
- package/lib/events.d.ts +13 -7
- package/lib/events.d.ts.map +1 -1
- package/lib/folderOpenState.d.ts +8 -0
- package/lib/folderOpenState.d.ts.map +1 -0
- package/lib/folderOpenState.js +22 -0
- package/lib/folderOpenState.js.map +1 -0
- package/lib/index.d.ts +27 -20
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +166 -65
- package/lib/index.js.map +1 -1
- package/lib/keyHandler.d.ts +16 -3
- package/lib/keyHandler.d.ts.map +1 -1
- package/lib/keyHandler.js +79 -32
- package/lib/keyHandler.js.map +1 -1
- package/lib/methodTypes.d.ts +3 -0
- package/lib/methodTypes.d.ts.map +1 -1
- package/lib/mouseHandler.d.ts.map +1 -1
- package/lib/mouseHandler.js +12 -12
- package/lib/mouseHandler.js.map +1 -1
- package/lib/node.d.ts +12 -10
- package/lib/node.d.ts.map +1 -1
- package/lib/node.js +12 -10
- package/lib/node.js.map +1 -1
- package/lib/nodeElement/folderElement.d.ts +7 -5
- package/lib/nodeElement/folderElement.d.ts.map +1 -1
- package/lib/nodeElement/folderElement.js +35 -55
- package/lib/nodeElement/folderElement.js.map +1 -1
- package/lib/nodeElement/index.d.ts +3 -2
- package/lib/nodeElement/index.d.ts.map +1 -1
- package/lib/nodeElement/index.js +15 -12
- package/lib/nodeElement/index.js.map +1 -1
- package/lib/options.d.ts +29 -14
- package/lib/options.d.ts.map +1 -1
- package/lib/options.js +12 -0
- package/lib/options.js.map +1 -0
- package/lib/saveStateHandler.d.ts.map +1 -1
- package/lib/saveStateHandler.js +6 -6
- package/lib/saveStateHandler.js.map +1 -1
- package/lib/selectNodeHandler.d.ts +3 -0
- package/lib/selectNodeHandler.d.ts.map +1 -1
- package/lib/selectNodeHandler.js +66 -22
- package/lib/selectNodeHandler.js.map +1 -1
- package/lib/setDefaultOptions.d.ts.map +1 -1
- package/lib/setDefaultOptions.js +1 -0
- package/lib/setDefaultOptions.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/lib/version.js.map +1 -1
- package/package.json +38 -32
- package/src/classNames.ts +4 -4
- package/src/dataLoader.ts +9 -10
- package/src/dragAndDropHandler/generateHitAreas.ts +3 -2
- package/src/dragAndDropHandler/index.ts +3 -17
- package/src/elementsRenderer.ts +124 -55
- package/src/events.ts +13 -7
- package/src/folderOpenState.ts +38 -0
- package/src/index.ts +191 -82
- package/src/keyHandler.ts +104 -34
- package/src/methodTypes.ts +6 -0
- package/src/mouseHandler.ts +15 -13
- package/src/node.ts +12 -10
- package/src/nodeElement/folderElement.ts +47 -72
- package/src/nodeElement/index.ts +20 -17
- package/src/options.ts +46 -14
- package/src/saveStateHandler.ts +5 -8
- package/src/selectNodeHandler.ts +69 -25
- package/src/setDefaultOptions.ts +1 -0
- package/src/version.ts +1 -1
- package/tree_element.css +48 -5
- package/tree_element.debug.js +540 -278
- package/tree_element.debug.js.map +1 -1
- package/tree_element.js +2 -2
- package/tree_element.js.map +1 -1
package/tree_element.debug.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Tree-element 0.
|
|
2
|
+
Tree-element 0.4.1
|
|
3
3
|
|
|
4
4
|
Copyright 2026 Marco Braak
|
|
5
5
|
|
|
@@ -29,13 +29,13 @@ var TreeElement = (function () {
|
|
|
29
29
|
*/
|
|
30
30
|
let createClassNames = ({
|
|
31
31
|
classPrefix,
|
|
32
|
-
commonClassName
|
|
33
|
-
treeClassName
|
|
32
|
+
commonClassName = `${classPrefix}-common`,
|
|
33
|
+
treeClassName = classPrefix
|
|
34
34
|
}) => ({
|
|
35
35
|
border: `${classPrefix}-border`,
|
|
36
36
|
circle: `${classPrefix}-circle`,
|
|
37
37
|
closed: `${classPrefix}-closed`,
|
|
38
|
-
common: commonClassName
|
|
38
|
+
common: commonClassName,
|
|
39
39
|
dnd: `${classPrefix}-dnd`,
|
|
40
40
|
dragging: `${classPrefix}-dragging`,
|
|
41
41
|
element: `${classPrefix}-element`,
|
|
@@ -54,7 +54,7 @@ var TreeElement = (function () {
|
|
|
54
54
|
toggler: `${classPrefix}-toggler`,
|
|
55
55
|
togglerLeft: `${classPrefix}-toggler-left`,
|
|
56
56
|
togglerRight: `${classPrefix}-toggler-right`,
|
|
57
|
-
tree: treeClassName
|
|
57
|
+
tree: treeClassName
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
class DataLoader {
|
|
@@ -80,15 +80,18 @@ var TreeElement = (function () {
|
|
|
80
80
|
element.classList.add(this._classNames.loading);
|
|
81
81
|
|
|
82
82
|
// Tell screen readers that the children of the folder are loading
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
if (node) {
|
|
84
|
+
element.setAttribute("aria-busy", "true");
|
|
85
|
+
}
|
|
85
86
|
this._triggerEvent("tree.loading_data", {
|
|
86
87
|
element,
|
|
87
88
|
node
|
|
88
89
|
});
|
|
89
90
|
let stopLoading = () => {
|
|
90
91
|
element.classList.remove(this._classNames.loading);
|
|
91
|
-
|
|
92
|
+
if (node) {
|
|
93
|
+
element.removeAttribute("aria-busy");
|
|
94
|
+
}
|
|
92
95
|
this._triggerEvent("tree.loaded_data", {
|
|
93
96
|
element,
|
|
94
97
|
node
|
|
@@ -108,9 +111,12 @@ var TreeElement = (function () {
|
|
|
108
111
|
};
|
|
109
112
|
let signal = this._abortController.signal;
|
|
110
113
|
url._setSearchParam("_", Date.now().toString());
|
|
114
|
+
|
|
115
|
+
// Accept, not Content-Type: the request has no body. A cross-origin request
|
|
116
|
+
// with only an Accept header also needs no CORS preflight.
|
|
111
117
|
return fetch(url._value(), {
|
|
112
118
|
headers: {
|
|
113
|
-
|
|
119
|
+
Accept: "application/json"
|
|
114
120
|
},
|
|
115
121
|
signal
|
|
116
122
|
}).then(handleResponse).catch(error => {
|
|
@@ -124,9 +130,6 @@ var TreeElement = (function () {
|
|
|
124
130
|
});
|
|
125
131
|
});
|
|
126
132
|
}
|
|
127
|
-
_getTitleSpan(element) {
|
|
128
|
-
return element.querySelector(`:scope > .${this._classNames.element} > .${this._classNames.title}`);
|
|
129
|
-
}
|
|
130
133
|
}
|
|
131
134
|
|
|
132
135
|
// Get the top position of the HTML element.
|
|
@@ -262,8 +265,9 @@ var TreeElement = (function () {
|
|
|
262
265
|
lastTop = top;
|
|
263
266
|
};
|
|
264
267
|
let handleAfterOpenFolder = (node, nextNode) => {
|
|
265
|
-
|
|
266
|
-
|
|
268
|
+
// Not called for the current node: handleOpenFolder stops iterating there
|
|
269
|
+
if (nextNode === currentNode) {
|
|
270
|
+
// Dropping the current node after this folder would leave it where it is
|
|
267
271
|
addHitPosition(node, null, lastTop);
|
|
268
272
|
} else {
|
|
269
273
|
addHitPosition(node, "after", lastTop);
|
|
@@ -391,7 +395,6 @@ var TreeElement = (function () {
|
|
|
391
395
|
_openFolderDelay: openFolderDelay,
|
|
392
396
|
_openNode: openNode,
|
|
393
397
|
_refreshElements: refreshElements,
|
|
394
|
-
_slide: slide,
|
|
395
398
|
_treeElement: treeElement,
|
|
396
399
|
_triggerEvent: triggerEvent
|
|
397
400
|
}) {
|
|
@@ -409,7 +412,6 @@ var TreeElement = (function () {
|
|
|
409
412
|
this._openFolderDelay = openFolderDelay;
|
|
410
413
|
this._openNode = openNode;
|
|
411
414
|
this._refreshElements = refreshElements;
|
|
412
|
-
this._slide = slide;
|
|
413
415
|
this._treeElement = treeElement;
|
|
414
416
|
this._triggerEvent = triggerEvent;
|
|
415
417
|
this._hoveredArea = null;
|
|
@@ -442,9 +444,6 @@ var TreeElement = (function () {
|
|
|
442
444
|
this._dragElement._move(positionInfo.pageX, positionInfo.pageY);
|
|
443
445
|
let area = this._findHoveredArea(positionInfo.pageX, positionInfo.pageY);
|
|
444
446
|
if (area && this._canMoveToArea(area, this._currentItem)) {
|
|
445
|
-
if (!area.node.isFolder()) {
|
|
446
|
-
this._stopOpenFolderTimer();
|
|
447
|
-
}
|
|
448
447
|
if (this._hoveredArea !== area) {
|
|
449
448
|
this._hoveredArea = area;
|
|
450
449
|
|
|
@@ -503,7 +502,6 @@ var TreeElement = (function () {
|
|
|
503
502
|
if (!this._hoveredArea && currentItem && this._onDragStop) {
|
|
504
503
|
this._onDragStop(currentItem._node, positionInfo.originalEvent);
|
|
505
504
|
}
|
|
506
|
-
return false;
|
|
507
505
|
}
|
|
508
506
|
_refresh() {
|
|
509
507
|
this._removeHitAreas();
|
|
@@ -594,8 +592,7 @@ var TreeElement = (function () {
|
|
|
594
592
|
}
|
|
595
593
|
}
|
|
596
594
|
_mustCaptureElement(element) {
|
|
597
|
-
|
|
598
|
-
return nodeName !== "INPUT" && nodeName !== "SELECT" && nodeName !== "TEXTAREA";
|
|
595
|
+
return !["INPUT", "SELECT", "TEXTAREA"].includes(element.nodeName);
|
|
599
596
|
}
|
|
600
597
|
_mustOpenFolderTimer(area) {
|
|
601
598
|
let node = area.node;
|
|
@@ -614,7 +611,7 @@ var TreeElement = (function () {
|
|
|
614
611
|
}
|
|
615
612
|
_startOpenFolderTimer(folder) {
|
|
616
613
|
let openFolder = () => {
|
|
617
|
-
void this._openNode(folder
|
|
614
|
+
void this._openNode(folder).then(() => {
|
|
618
615
|
this._refresh();
|
|
619
616
|
this._updateDropHint();
|
|
620
617
|
});
|
|
@@ -649,6 +646,24 @@ var TreeElement = (function () {
|
|
|
649
646
|
let isInt = n => Number.isInteger(n);
|
|
650
647
|
let getBoolString = value => value ? "true" : "false";
|
|
651
648
|
|
|
649
|
+
/* The open state of a folder is shown on the toggler and on the <li>.
|
|
650
|
+
* These are used both when a folder is rendered and when it is opened or
|
|
651
|
+
* closed, so a rendered folder looks the same as a toggled one.
|
|
652
|
+
*/
|
|
653
|
+
|
|
654
|
+
let setTogglerOpenState = (toggler, isOpen, classNames, icons) => {
|
|
655
|
+
toggler.classList.toggle(classNames.closed, !isOpen);
|
|
656
|
+
toggler.replaceChildren();
|
|
657
|
+
let iconElement = isOpen ? icons.opened : icons.closed;
|
|
658
|
+
if (iconElement) {
|
|
659
|
+
toggler.append(iconElement.cloneNode(true));
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
let setFolderLiOpenState = (li, isOpen, classNames) => {
|
|
663
|
+
li.classList.toggle(classNames.closed, !isOpen);
|
|
664
|
+
li.setAttribute("aria-expanded", getBoolString(isOpen));
|
|
665
|
+
};
|
|
666
|
+
|
|
652
667
|
/* Renders the tree to html.
|
|
653
668
|
*
|
|
654
669
|
* The children of a closed folder are not rendered. They are rendered when
|
|
@@ -672,6 +687,7 @@ var TreeElement = (function () {
|
|
|
672
687
|
_getTree: getTree,
|
|
673
688
|
_htmlElement: htmlElement,
|
|
674
689
|
_isNodeSelected: isNodeSelected,
|
|
690
|
+
_multiSelect: multiSelect,
|
|
675
691
|
_onCreateLi: onCreateLi,
|
|
676
692
|
_openedIcon: openedIcon = "+",
|
|
677
693
|
_rtl: rtl,
|
|
@@ -688,18 +704,22 @@ var TreeElement = (function () {
|
|
|
688
704
|
this._getTree = getTree;
|
|
689
705
|
this._htmlElement = htmlElement;
|
|
690
706
|
this._isNodeSelected = isNodeSelected;
|
|
707
|
+
this._multiSelect = multiSelect;
|
|
691
708
|
this._onCreateLi = onCreateLi;
|
|
692
709
|
this._rtl = rtl;
|
|
693
710
|
this._setNodeElement = setNodeElement;
|
|
694
711
|
this._showEmptyFolder = showEmptyFolder;
|
|
695
712
|
this._tabIndex = tabIndex;
|
|
696
|
-
this.
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
this.
|
|
702
|
-
this.
|
|
713
|
+
this._icons = {
|
|
714
|
+
closed: this._createButtonElement(closedIcon),
|
|
715
|
+
opened: this._createButtonElement(openedIcon)
|
|
716
|
+
};
|
|
717
|
+
let templates = this._createTemplates();
|
|
718
|
+
this._closedFolderTemplate = templates.closedFolderTemplate;
|
|
719
|
+
this._groupUlTemplate = templates.groupUlTemplate;
|
|
720
|
+
this._nodeTemplate = templates.nodeTemplate;
|
|
721
|
+
this._openedFolderTemplate = templates.openedFolderTemplate;
|
|
722
|
+
this._rootUlTemplate = templates.rootUlTemplate;
|
|
703
723
|
}
|
|
704
724
|
_render(fromNode) {
|
|
705
725
|
if (fromNode?.parent) {
|
|
@@ -713,7 +733,7 @@ var TreeElement = (function () {
|
|
|
713
733
|
* null when the folder is not rendered or has no children.
|
|
714
734
|
*/
|
|
715
735
|
_renderChildren(node) {
|
|
716
|
-
return
|
|
736
|
+
return node.element && node.hasChildren() ? this._createDomElements(node.element, node.children, false, node.getLevel() + 1) : null;
|
|
717
737
|
}
|
|
718
738
|
_renderFromNode(node) {
|
|
719
739
|
if (!node.element) {
|
|
@@ -733,6 +753,43 @@ var TreeElement = (function () {
|
|
|
733
753
|
this._createDomElements(this._htmlElement, tree.children, true, 1);
|
|
734
754
|
}
|
|
735
755
|
}
|
|
756
|
+
|
|
757
|
+
/* Change dragAndDrop after construction. The rendered lists and the
|
|
758
|
+
* templates for new lists get or lose the dnd class.
|
|
759
|
+
*/
|
|
760
|
+
_setDragAndDrop(dragAndDrop) {
|
|
761
|
+
this._dragAndDrop = dragAndDrop;
|
|
762
|
+
this._updateTemplates();
|
|
763
|
+
for (let ul of this._htmlElement.querySelectorAll(`ul.${this._classNames.common}`)) {
|
|
764
|
+
ul.classList.toggle(this._classNames.dnd, dragAndDrop);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/* Change multiSelect after construction. Only the templates are updated;
|
|
769
|
+
* the tree must be rendered again.
|
|
770
|
+
*/
|
|
771
|
+
_setMultiSelect(multiSelect) {
|
|
772
|
+
this._multiSelect = multiSelect;
|
|
773
|
+
this._updateTemplates();
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/* Change tabIndex after construction. The rendered nodes and the
|
|
777
|
+
* templates for new nodes get the new tabindex. The tab stop is updated by
|
|
778
|
+
* TreeElement.updateTabStop.
|
|
779
|
+
*/
|
|
780
|
+
_setTabIndex(tabIndex) {
|
|
781
|
+
this._tabIndex = tabIndex;
|
|
782
|
+
this._updateTemplates();
|
|
783
|
+
for (let li of this._htmlElement.querySelectorAll("li[role=treeitem]")) {
|
|
784
|
+
if (tabIndex === undefined) {
|
|
785
|
+
li.removeAttribute("tabindex");
|
|
786
|
+
} else if (!this._multiSelect && li.getAttribute("aria-selected") === "true") {
|
|
787
|
+
li.setAttribute("tabindex", String(tabIndex));
|
|
788
|
+
} else {
|
|
789
|
+
li.setAttribute("tabindex", "-1");
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
736
793
|
_attachNodeData(node, li) {
|
|
737
794
|
node.element = li;
|
|
738
795
|
this._setNodeElement(li, node);
|
|
@@ -774,10 +831,10 @@ var TreeElement = (function () {
|
|
|
774
831
|
// li > div > [toggler], title span, [toggler]
|
|
775
832
|
let div = li.firstChild;
|
|
776
833
|
let titleSpan = div.childNodes[this._buttonLeft ? 1 : 0];
|
|
777
|
-
this.
|
|
834
|
+
this._fillNode(li, titleSpan, node.name, isSelected, level);
|
|
778
835
|
if (node.is_loading) {
|
|
779
836
|
li.classList.add(this._classNames.loading);
|
|
780
|
-
|
|
837
|
+
li.setAttribute("aria-busy", "true");
|
|
781
838
|
}
|
|
782
839
|
return li;
|
|
783
840
|
}
|
|
@@ -788,30 +845,22 @@ var TreeElement = (function () {
|
|
|
788
845
|
* state, so there is a template for each.
|
|
789
846
|
*/
|
|
790
847
|
_createFolderTemplate(isOpen) {
|
|
791
|
-
let
|
|
792
|
-
|
|
793
|
-
liClasses.push(this._classNames.closed);
|
|
794
|
-
}
|
|
795
|
-
let li = this._createLiTemplate(liClasses.join(" "));
|
|
848
|
+
let li = this._createLiTemplate(`${this._classNames.common} ${this._classNames.folder}`);
|
|
849
|
+
setFolderLiOpenState(li, isOpen, this._classNames);
|
|
796
850
|
let div = li.firstChild;
|
|
797
851
|
|
|
798
852
|
// The toggler is hidden from screen readers: the icon is decoration, and
|
|
799
853
|
// aria-expanded on the treeitem has the open state.
|
|
800
854
|
let toggler = document.createElement("span");
|
|
801
|
-
toggler.className = this._getButtonClasses(
|
|
855
|
+
toggler.className = this._getButtonClasses();
|
|
802
856
|
toggler.setAttribute("aria-hidden", "true");
|
|
803
|
-
|
|
804
|
-
if (iconElement) {
|
|
805
|
-
toggler.append(iconElement.cloneNode(true));
|
|
806
|
-
}
|
|
857
|
+
setTogglerOpenState(toggler, isOpen, this._classNames, this._icons);
|
|
807
858
|
if (this._buttonLeft) {
|
|
808
859
|
div.append(toggler);
|
|
809
860
|
}
|
|
810
861
|
|
|
811
862
|
// title span
|
|
812
|
-
|
|
813
|
-
titleSpan.setAttribute("aria-expanded", getBoolString(isOpen));
|
|
814
|
-
div.append(titleSpan);
|
|
863
|
+
div.append(this._createTitleSpanTemplate(true));
|
|
815
864
|
if (!this._buttonLeft) {
|
|
816
865
|
div.append(toggler);
|
|
817
866
|
}
|
|
@@ -835,11 +884,22 @@ var TreeElement = (function () {
|
|
|
835
884
|
|
|
836
885
|
/*
|
|
837
886
|
Create the outer part of a <li> template: li > div
|
|
887
|
+
The li is the treeitem, so the ul of its children is inside the treeitem.
|
|
838
888
|
*/
|
|
839
889
|
_createLiTemplate(liClasses) {
|
|
840
890
|
let li = document.createElement("li");
|
|
841
891
|
li.className = liClasses;
|
|
842
|
-
li.setAttribute("role", "
|
|
892
|
+
li.setAttribute("role", "treeitem");
|
|
893
|
+
|
|
894
|
+
// Only a multiselect tree marks the nodes that are not selected
|
|
895
|
+
if (this._multiSelect) {
|
|
896
|
+
li.setAttribute("aria-selected", "false");
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// Every node is focusable, but only the tab stop is in the tab order
|
|
900
|
+
if (this._tabIndex !== undefined) {
|
|
901
|
+
li.setAttribute("tabindex", "-1");
|
|
902
|
+
}
|
|
843
903
|
let div = document.createElement("div");
|
|
844
904
|
div.className = `${this._classNames.element} ${this._classNames.common}`;
|
|
845
905
|
div.setAttribute("role", "none");
|
|
@@ -855,7 +915,7 @@ var TreeElement = (function () {
|
|
|
855
915
|
// li > div > title span
|
|
856
916
|
let div = li.firstChild;
|
|
857
917
|
let titleSpan = div.firstChild;
|
|
858
|
-
this.
|
|
918
|
+
this._fillNode(li, titleSpan, node.name, isSelected, level);
|
|
859
919
|
return li;
|
|
860
920
|
}
|
|
861
921
|
|
|
@@ -868,6 +928,15 @@ var TreeElement = (function () {
|
|
|
868
928
|
div.append(this._createTitleSpanTemplate(false));
|
|
869
929
|
return li;
|
|
870
930
|
}
|
|
931
|
+
_createTemplates() {
|
|
932
|
+
return {
|
|
933
|
+
closedFolderTemplate: this._createFolderTemplate(false),
|
|
934
|
+
groupUlTemplate: this._createUlTemplate(false),
|
|
935
|
+
nodeTemplate: this._createNodeTemplate(),
|
|
936
|
+
openedFolderTemplate: this._createFolderTemplate(true),
|
|
937
|
+
rootUlTemplate: this._createUlTemplate(true)
|
|
938
|
+
};
|
|
939
|
+
}
|
|
871
940
|
_createTitleSpanTemplate(isFolder) {
|
|
872
941
|
let titleSpan = document.createElement("span");
|
|
873
942
|
let classes = `${this._classNames.title} ${this._classNames.common}`;
|
|
@@ -876,13 +945,6 @@ var TreeElement = (function () {
|
|
|
876
945
|
}
|
|
877
946
|
classes += ` ${this._buttonLeft ? this._classNames.titleButtonLeft : this._classNames.titleButtonRight}`;
|
|
878
947
|
titleSpan.className = classes;
|
|
879
|
-
titleSpan.setAttribute("role", "treeitem");
|
|
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
|
-
}
|
|
886
948
|
return titleSpan;
|
|
887
949
|
}
|
|
888
950
|
_createUlTemplate(isRootNode) {
|
|
@@ -911,20 +973,25 @@ var TreeElement = (function () {
|
|
|
911
973
|
if (this._ariaLabelledBy !== undefined) {
|
|
912
974
|
ul.setAttribute("aria-labelledby", this._ariaLabelledBy);
|
|
913
975
|
}
|
|
976
|
+
if (this._multiSelect) {
|
|
977
|
+
ul.setAttribute("aria-multiselectable", "true");
|
|
978
|
+
}
|
|
914
979
|
}
|
|
915
980
|
return ul;
|
|
916
981
|
}
|
|
917
982
|
|
|
918
983
|
/*
|
|
919
|
-
Set the parts of a cloned title span that differ per node
|
|
984
|
+
Set the parts of a cloned li and title span that differ per node
|
|
920
985
|
*/
|
|
921
|
-
|
|
922
|
-
|
|
986
|
+
_fillNode(li, titleSpan, nodeName, isSelected, level) {
|
|
987
|
+
li.setAttribute("aria-level", String(level));
|
|
923
988
|
if (isSelected) {
|
|
924
|
-
|
|
989
|
+
li.setAttribute("aria-selected", "true");
|
|
925
990
|
let tabIndex = this._tabIndex;
|
|
926
|
-
|
|
927
|
-
|
|
991
|
+
|
|
992
|
+
// With multiSelect the tab stop is the active node, see updateTabStop
|
|
993
|
+
if (tabIndex !== undefined && !this._multiSelect) {
|
|
994
|
+
li.setAttribute("tabindex", String(tabIndex));
|
|
928
995
|
}
|
|
929
996
|
}
|
|
930
997
|
if (this._autoEscape) {
|
|
@@ -933,12 +1000,13 @@ var TreeElement = (function () {
|
|
|
933
1000
|
// eslint-disable-next-line no-unsanitized/property -- the user opted out of escaping with autoEscape: false
|
|
934
1001
|
titleSpan.innerHTML = nodeName;
|
|
935
1002
|
}
|
|
1003
|
+
|
|
1004
|
+
// The treeitem is named by its title, without the html when autoEscape
|
|
1005
|
+
// is false. Otherwise the name would contain everything in the row.
|
|
1006
|
+
li.setAttribute("aria-label", titleSpan.textContent);
|
|
936
1007
|
}
|
|
937
|
-
_getButtonClasses(
|
|
1008
|
+
_getButtonClasses() {
|
|
938
1009
|
let classes = [this._classNames.toggler, this._classNames.common];
|
|
939
|
-
if (!isOpen) {
|
|
940
|
-
classes.push(this._classNames.closed);
|
|
941
|
-
}
|
|
942
1010
|
if (this._buttonLeft) {
|
|
943
1011
|
classes.push(this._classNames.togglerLeft);
|
|
944
1012
|
} else {
|
|
@@ -953,26 +1021,47 @@ var TreeElement = (function () {
|
|
|
953
1021
|
_mustRenderChildren(node) {
|
|
954
1022
|
return node.hasChildren() && node.is_open === true;
|
|
955
1023
|
}
|
|
1024
|
+
_updateTemplates() {
|
|
1025
|
+
let templates = this._createTemplates();
|
|
1026
|
+
this._closedFolderTemplate = templates.closedFolderTemplate;
|
|
1027
|
+
this._groupUlTemplate = templates.groupUlTemplate;
|
|
1028
|
+
this._nodeTemplate = templates.nodeTemplate;
|
|
1029
|
+
this._openedFolderTemplate = templates.openedFolderTemplate;
|
|
1030
|
+
this._rootUlTemplate = templates.rootUlTemplate;
|
|
1031
|
+
}
|
|
956
1032
|
}
|
|
957
1033
|
|
|
1034
|
+
/* The keys move from the selected node and select the node they move to.
|
|
1035
|
+
* With multiSelect they move from the focused node and only move the focus;
|
|
1036
|
+
* Space adds the focused node to the selection or removes it.
|
|
1037
|
+
*/
|
|
1038
|
+
|
|
958
1039
|
class KeyHandler {
|
|
959
1040
|
constructor({
|
|
960
1041
|
_closeNode: closeNode,
|
|
1042
|
+
_focusNode: focusNode,
|
|
1043
|
+
_getActiveNode: getActiveNode,
|
|
961
1044
|
_getFocusedNode: getFocusedNode,
|
|
962
1045
|
_getSelectedNode: getSelectedNode,
|
|
963
1046
|
_getTree: getTree,
|
|
964
1047
|
_keyboardSupport: keyboardSupport,
|
|
1048
|
+
_multiSelect: multiSelect,
|
|
965
1049
|
_openNode: openNode,
|
|
966
1050
|
_selectNode: selectNode,
|
|
1051
|
+
_toggleNodeInSelection: toggleNodeInSelection,
|
|
967
1052
|
_triggerEvent: triggerEvent
|
|
968
1053
|
}) {
|
|
969
1054
|
this._closeNode = closeNode;
|
|
1055
|
+
this._focusNode = focusNode;
|
|
1056
|
+
this._getActiveNode = getActiveNode;
|
|
970
1057
|
this._getFocusedNode = getFocusedNode;
|
|
971
1058
|
this._getSelectedNode = getSelectedNode;
|
|
972
1059
|
this._getTree = getTree;
|
|
973
1060
|
this._keyboardSupport = keyboardSupport;
|
|
1061
|
+
this._multiSelect = multiSelect;
|
|
974
1062
|
this._openNode = openNode;
|
|
975
1063
|
this._originalSelectNode = selectNode;
|
|
1064
|
+
this._toggleNodeInSelection = toggleNodeInSelection;
|
|
976
1065
|
this._triggerEvent = triggerEvent;
|
|
977
1066
|
if (keyboardSupport) {
|
|
978
1067
|
document.addEventListener("keydown", this._handleKeyDown);
|
|
@@ -984,10 +1073,28 @@ var TreeElement = (function () {
|
|
|
984
1073
|
}
|
|
985
1074
|
}
|
|
986
1075
|
_moveDown() {
|
|
987
|
-
return this.
|
|
1076
|
+
return this._goToNode(this._getCurrentNode()?.getNextVisibleNode());
|
|
988
1077
|
}
|
|
989
1078
|
_moveUp() {
|
|
990
|
-
return this.
|
|
1079
|
+
return this._goToNode(this._getCurrentNode()?.getPreviousVisibleNode());
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// Change keyboardSupport after construction
|
|
1083
|
+
_setKeyboardSupport(keyboardSupport) {
|
|
1084
|
+
if (keyboardSupport === this._keyboardSupport) {
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
this._keyboardSupport = keyboardSupport;
|
|
1088
|
+
if (keyboardSupport) {
|
|
1089
|
+
document.addEventListener("keydown", this._handleKeyDown);
|
|
1090
|
+
} else {
|
|
1091
|
+
document.removeEventListener("keydown", this._handleKeyDown);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// Change multiSelect after construction
|
|
1096
|
+
_setMultiSelect(multiSelect) {
|
|
1097
|
+
this._multiSelect = multiSelect;
|
|
991
1098
|
}
|
|
992
1099
|
|
|
993
1100
|
// Activate the node, for example to open its edit page.
|
|
@@ -998,6 +1105,26 @@ var TreeElement = (function () {
|
|
|
998
1105
|
});
|
|
999
1106
|
return true;
|
|
1000
1107
|
}
|
|
1108
|
+
|
|
1109
|
+
// No node is selected. These keys select the focused node, which is
|
|
1110
|
+
// usually the first node, see TreeElement.updateTabStop.
|
|
1111
|
+
_getCurrentNode() {
|
|
1112
|
+
return this._multiSelect ? this._getActiveNode() : this._getSelectedNode();
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// Move to the node: select it, or with multiSelect focus it.
|
|
1116
|
+
// Result: there was a node to move to.
|
|
1117
|
+
_goToNode(node) {
|
|
1118
|
+
if (!node) {
|
|
1119
|
+
return false;
|
|
1120
|
+
}
|
|
1121
|
+
if (this._multiSelect) {
|
|
1122
|
+
this._focusNode(node);
|
|
1123
|
+
} else {
|
|
1124
|
+
this._originalSelectNode(node);
|
|
1125
|
+
}
|
|
1126
|
+
return true;
|
|
1127
|
+
}
|
|
1001
1128
|
_handleArrowKey(key) {
|
|
1002
1129
|
switch (key) {
|
|
1003
1130
|
case "ArrowDown":
|
|
@@ -1022,49 +1149,53 @@ var TreeElement = (function () {
|
|
|
1022
1149
|
}
|
|
1023
1150
|
}
|
|
1024
1151
|
}
|
|
1152
|
+
_handleKey(key, focusedNode) {
|
|
1153
|
+
return this._getCurrentNode() ? this._handleArrowKey(key) : this._handleKeyWithoutSelection(key, focusedNode);
|
|
1154
|
+
}
|
|
1025
1155
|
_handleKeyDown = e => {
|
|
1026
1156
|
let focusedNode = this._getFocusedNode();
|
|
1027
1157
|
if (!focusedNode) {
|
|
1028
1158
|
return;
|
|
1029
1159
|
}
|
|
1030
1160
|
let isKeyHandled;
|
|
1031
|
-
let selectedNode = this._getSelectedNode();
|
|
1032
1161
|
switch (e.key) {
|
|
1162
|
+
case " ":
|
|
1163
|
+
{
|
|
1164
|
+
isKeyHandled = this._multiSelect ? this._toggle(focusedNode) : this._handleKey(e.key, focusedNode);
|
|
1165
|
+
break;
|
|
1166
|
+
}
|
|
1033
1167
|
case "End":
|
|
1034
1168
|
{
|
|
1035
|
-
isKeyHandled = this.
|
|
1169
|
+
isKeyHandled = this._goToNode(this._getTree()?.getLastChild());
|
|
1036
1170
|
break;
|
|
1037
1171
|
}
|
|
1038
1172
|
case "Enter":
|
|
1039
1173
|
{
|
|
1040
1174
|
// Without a selected node, activate the focused node
|
|
1041
|
-
isKeyHandled = this._activate(
|
|
1175
|
+
isKeyHandled = this._activate(this._multiSelect ? focusedNode : this._getSelectedNode() ?? focusedNode, e);
|
|
1042
1176
|
break;
|
|
1043
1177
|
}
|
|
1044
1178
|
case "Home":
|
|
1045
1179
|
{
|
|
1046
|
-
isKeyHandled = this.
|
|
1180
|
+
isKeyHandled = this._goToNode(this._getTree()?.children[0]);
|
|
1047
1181
|
break;
|
|
1048
1182
|
}
|
|
1049
1183
|
default:
|
|
1050
1184
|
{
|
|
1051
|
-
isKeyHandled =
|
|
1185
|
+
isKeyHandled = this._handleKey(e.key, focusedNode);
|
|
1052
1186
|
}
|
|
1053
1187
|
}
|
|
1054
1188
|
if (isKeyHandled) {
|
|
1055
1189
|
e.preventDefault();
|
|
1056
1190
|
}
|
|
1057
1191
|
};
|
|
1058
|
-
|
|
1059
|
-
// No node is selected. These keys select the focused node, which is
|
|
1060
|
-
// usually the first node, see TreeElement.updateTabStop.
|
|
1061
1192
|
_handleKeyWithoutSelection(key, focusedNode) {
|
|
1062
1193
|
switch (key) {
|
|
1063
1194
|
case " ":
|
|
1064
1195
|
case "ArrowDown":
|
|
1065
1196
|
case "ArrowRight":
|
|
1066
1197
|
{
|
|
1067
|
-
return this.
|
|
1198
|
+
return this._goToNode(focusedNode);
|
|
1068
1199
|
}
|
|
1069
1200
|
default:
|
|
1070
1201
|
{
|
|
@@ -1073,45 +1204,37 @@ var TreeElement = (function () {
|
|
|
1073
1204
|
}
|
|
1074
1205
|
}
|
|
1075
1206
|
_moveLeft() {
|
|
1076
|
-
let
|
|
1207
|
+
let currentNode = this._getCurrentNode();
|
|
1077
1208
|
|
|
1078
1209
|
/* istanbul ignore if */
|
|
1079
|
-
if (!
|
|
1210
|
+
if (!currentNode) {
|
|
1080
1211
|
return false;
|
|
1081
1212
|
}
|
|
1082
|
-
if (
|
|
1213
|
+
if (currentNode.isFolder() && currentNode.is_open) {
|
|
1083
1214
|
// Left on an open node closes the node
|
|
1084
|
-
void this._closeNode(
|
|
1215
|
+
void this._closeNode(currentNode);
|
|
1085
1216
|
return true;
|
|
1086
1217
|
}
|
|
1087
1218
|
// Left on a closed or end node moves focus to the node's parent
|
|
1088
|
-
return this.
|
|
1219
|
+
return this._goToNode(currentNode.getParent());
|
|
1089
1220
|
}
|
|
1090
1221
|
_moveRight() {
|
|
1091
|
-
let
|
|
1092
|
-
if (
|
|
1222
|
+
let currentNode = this._getCurrentNode();
|
|
1223
|
+
if (currentNode?.isFolder()) {
|
|
1093
1224
|
// folder node
|
|
1094
|
-
if (
|
|
1225
|
+
if (currentNode.is_open) {
|
|
1095
1226
|
// Right moves to the first child of an open node
|
|
1096
|
-
return this.
|
|
1227
|
+
return this._goToNode(currentNode.getNextVisibleNode());
|
|
1097
1228
|
}
|
|
1098
1229
|
// Right expands a closed node
|
|
1099
|
-
void this._openNode(
|
|
1230
|
+
void this._openNode(currentNode);
|
|
1100
1231
|
return true;
|
|
1101
1232
|
}
|
|
1102
1233
|
return false;
|
|
1103
1234
|
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
* Result: a different node was selected.
|
|
1108
|
-
*/
|
|
1109
|
-
_selectNode(node) {
|
|
1110
|
-
if (node) {
|
|
1111
|
-
this._originalSelectNode(node);
|
|
1112
|
-
return true;
|
|
1113
|
-
}
|
|
1114
|
-
return false;
|
|
1235
|
+
_toggle(node) {
|
|
1236
|
+
this._toggleNodeInSelection(node);
|
|
1237
|
+
return true;
|
|
1115
1238
|
}
|
|
1116
1239
|
}
|
|
1117
1240
|
|
|
@@ -1238,19 +1361,19 @@ var TreeElement = (function () {
|
|
|
1238
1361
|
return;
|
|
1239
1362
|
}
|
|
1240
1363
|
let div = e.target.closest(`ul.${this._classNames.tree} .${this._classNames.element}`);
|
|
1241
|
-
if (div) {
|
|
1242
|
-
|
|
1243
|
-
if (node) {
|
|
1244
|
-
e.preventDefault();
|
|
1245
|
-
e.stopPropagation();
|
|
1246
|
-
this._triggerEvent("tree.contextmenu", {
|
|
1247
|
-
node,
|
|
1248
|
-
originalEvent: e
|
|
1249
|
-
});
|
|
1250
|
-
return false;
|
|
1251
|
-
}
|
|
1364
|
+
if (!div) {
|
|
1365
|
+
return;
|
|
1252
1366
|
}
|
|
1253
|
-
|
|
1367
|
+
let node = this._getNode(div);
|
|
1368
|
+
if (!node) {
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
e.preventDefault();
|
|
1372
|
+
e.stopPropagation();
|
|
1373
|
+
this._triggerEvent("tree.contextmenu", {
|
|
1374
|
+
node,
|
|
1375
|
+
originalEvent: e
|
|
1376
|
+
});
|
|
1254
1377
|
};
|
|
1255
1378
|
_handleDblclick = e => {
|
|
1256
1379
|
if (!e.target) {
|
|
@@ -1410,14 +1533,14 @@ var TreeElement = (function () {
|
|
|
1410
1533
|
*
|
|
1411
1534
|
* @groupDescription Searching
|
|
1412
1535
|
* These work on any node, and search that node's subtree. Called on the root
|
|
1413
|
-
* node, they search the whole tree
|
|
1536
|
+
* node, they search the whole tree—which is what the tree's own methods of
|
|
1414
1537
|
* the same name do.
|
|
1415
1538
|
*
|
|
1416
1539
|
* @groupDescription Changing the tree
|
|
1417
1540
|
* ::: warning
|
|
1418
1541
|
* These methods change the data without re-rendering. The tree's
|
|
1419
|
-
* [methods](/reference/methods)
|
|
1420
|
-
* friends
|
|
1542
|
+
* [methods](/reference/methods)—`appendNode`, `removeNode`, `moveNode` and
|
|
1543
|
+
* friends—do the same and refresh the display, so prefer those. If you do
|
|
1421
1544
|
* use these, call `tree.refresh()` afterwards.
|
|
1422
1545
|
* :::
|
|
1423
1546
|
*/
|
|
@@ -1466,7 +1589,7 @@ var TreeElement = (function () {
|
|
|
1466
1589
|
*/
|
|
1467
1590
|
nodeClass;
|
|
1468
1591
|
/**
|
|
1469
|
-
The parent
|
|
1592
|
+
The parent, or `null` for the root node.
|
|
1470
1593
|
*/
|
|
1471
1594
|
parent;
|
|
1472
1595
|
/**
|
|
@@ -1558,7 +1681,9 @@ var TreeElement = (function () {
|
|
|
1558
1681
|
}
|
|
1559
1682
|
|
|
1560
1683
|
/**
|
|
1561
|
-
* Inserts a new parent between this node and its current parent.
|
|
1684
|
+
* Inserts a new parent between this node and its current parent. Its
|
|
1685
|
+
* siblings move along: the new node takes all children of the current
|
|
1686
|
+
* parent, and becomes its only child.
|
|
1562
1687
|
*
|
|
1563
1688
|
* @returns The new node, or `null` when the node has no parent.
|
|
1564
1689
|
* @group Changing the tree
|
|
@@ -1628,7 +1753,7 @@ var TreeElement = (function () {
|
|
|
1628
1753
|
* Returns the subtree as plain data, ready for `JSON.stringify`.
|
|
1629
1754
|
* Internal properties (`parent`, `children`, `element`, `tree`,
|
|
1630
1755
|
* `idMapping`, `nodeClass`, `load_on_demand`, `isEmptyFolder`) are left
|
|
1631
|
-
* out
|
|
1756
|
+
* out. Your own properties are kept.
|
|
1632
1757
|
*
|
|
1633
1758
|
* ```js
|
|
1634
1759
|
* tree.getTree().getData();
|
|
@@ -1720,7 +1845,7 @@ var TreeElement = (function () {
|
|
|
1720
1845
|
}
|
|
1721
1846
|
|
|
1722
1847
|
/**
|
|
1723
|
-
* Like `getNextNode`, but skipping nodes inside closed folders
|
|
1848
|
+
* Like `getNextNode`, but skipping nodes inside closed folders. This is
|
|
1724
1849
|
* what the arrow keys use.
|
|
1725
1850
|
*
|
|
1726
1851
|
* @group Moving around the tree
|
|
@@ -1804,7 +1929,7 @@ var TreeElement = (function () {
|
|
|
1804
1929
|
}
|
|
1805
1930
|
|
|
1806
1931
|
/**
|
|
1807
|
-
* Returns the parent
|
|
1932
|
+
* Returns the parent, or `null` for a top-level node: the root node is not
|
|
1808
1933
|
* returned.
|
|
1809
1934
|
*
|
|
1810
1935
|
* @group Moving around the tree
|
|
@@ -1852,8 +1977,8 @@ var TreeElement = (function () {
|
|
|
1852
1977
|
}
|
|
1853
1978
|
|
|
1854
1979
|
/**
|
|
1855
|
-
* Like `getPreviousNode`, but skipping nodes inside closed folders
|
|
1856
|
-
*
|
|
1980
|
+
* Like `getPreviousNode`, but skipping nodes inside closed folders.
|
|
1981
|
+
* This is what the arrow keys use.
|
|
1857
1982
|
*
|
|
1858
1983
|
* @group Moving around the tree
|
|
1859
1984
|
*/
|
|
@@ -2217,12 +2342,14 @@ var TreeElement = (function () {
|
|
|
2217
2342
|
constructor({
|
|
2218
2343
|
_classNames: classNames,
|
|
2219
2344
|
_getScrollLeft: getScrollLeft,
|
|
2345
|
+
_multiSelect: multiSelect = false,
|
|
2220
2346
|
_node: node,
|
|
2221
2347
|
_tabIndex: tabIndex,
|
|
2222
2348
|
_treeElement: treeElement
|
|
2223
2349
|
}) {
|
|
2224
2350
|
this._classNames = classNames;
|
|
2225
2351
|
this._getScrollLeft = getScrollLeft;
|
|
2352
|
+
this._multiSelect = multiSelect;
|
|
2226
2353
|
this._node = node;
|
|
2227
2354
|
this._tabIndex = tabIndex;
|
|
2228
2355
|
this._treeElement = treeElement;
|
|
@@ -2238,35 +2365,36 @@ var TreeElement = (function () {
|
|
|
2238
2365
|
return;
|
|
2239
2366
|
}
|
|
2240
2367
|
this._element.classList.remove(this._classNames.selected);
|
|
2241
|
-
|
|
2368
|
+
|
|
2369
|
+
// Only a multiselect tree marks the nodes that are not selected
|
|
2370
|
+
if (this._multiSelect) {
|
|
2371
|
+
this._element.setAttribute("aria-selected", "false");
|
|
2372
|
+
return;
|
|
2373
|
+
}
|
|
2374
|
+
this._element.removeAttribute("aria-selected");
|
|
2242
2375
|
|
|
2243
2376
|
// Check for null or undefined
|
|
2244
2377
|
if (this._tabIndex != null) {
|
|
2245
|
-
|
|
2378
|
+
this._element.setAttribute("tabindex", "-1");
|
|
2246
2379
|
}
|
|
2247
|
-
|
|
2248
|
-
titleSpan.blur();
|
|
2380
|
+
this._element.blur();
|
|
2249
2381
|
}
|
|
2250
2382
|
_select(mustSetFocus) {
|
|
2251
2383
|
if (!this._isRendered()) {
|
|
2252
2384
|
return;
|
|
2253
2385
|
}
|
|
2254
2386
|
this._element.classList.add(this._classNames.selected);
|
|
2255
|
-
|
|
2387
|
+
this._element.setAttribute("aria-selected", "true");
|
|
2256
2388
|
let tabIndex = this._tabIndex;
|
|
2257
2389
|
|
|
2258
2390
|
// Check for null or undefined
|
|
2259
|
-
if (tabIndex != null) {
|
|
2260
|
-
|
|
2391
|
+
if (tabIndex != null && !this._multiSelect) {
|
|
2392
|
+
this._element.setAttribute("tabindex", tabIndex.toString());
|
|
2261
2393
|
}
|
|
2262
|
-
titleSpan.setAttribute("aria-selected", "true");
|
|
2263
2394
|
if (mustSetFocus) {
|
|
2264
|
-
|
|
2395
|
+
this._element.focus();
|
|
2265
2396
|
}
|
|
2266
2397
|
}
|
|
2267
|
-
_getTitleSpan() {
|
|
2268
|
-
return this._element.querySelector(`:scope > .${this._classNames.element} > span.${this._classNames.title}`);
|
|
2269
|
-
}
|
|
2270
2398
|
_getUl() {
|
|
2271
2399
|
return this._element.querySelector(":scope > ul");
|
|
2272
2400
|
}
|
|
@@ -2326,10 +2454,10 @@ var TreeElement = (function () {
|
|
|
2326
2454
|
class FolderElement extends NodeElement {
|
|
2327
2455
|
constructor({
|
|
2328
2456
|
_classNames: classNames,
|
|
2329
|
-
_closedIconElement: closedIconElement,
|
|
2330
2457
|
_getScrollLeft: getScrollLeft,
|
|
2458
|
+
_icons: icons,
|
|
2459
|
+
_multiSelect: multiSelect,
|
|
2331
2460
|
_node: node,
|
|
2332
|
-
_openedIconElement: openedIconElement,
|
|
2333
2461
|
_renderChildren: renderChildren,
|
|
2334
2462
|
_tabIndex: tabIndex,
|
|
2335
2463
|
_treeElement: treeElement,
|
|
@@ -2338,12 +2466,12 @@ var TreeElement = (function () {
|
|
|
2338
2466
|
super({
|
|
2339
2467
|
_classNames: classNames,
|
|
2340
2468
|
_getScrollLeft: getScrollLeft,
|
|
2469
|
+
_multiSelect: multiSelect,
|
|
2341
2470
|
_node: node,
|
|
2342
2471
|
_tabIndex: tabIndex,
|
|
2343
2472
|
_treeElement: treeElement
|
|
2344
2473
|
});
|
|
2345
|
-
this.
|
|
2346
|
-
this._openedIconElement = openedIconElement;
|
|
2474
|
+
this._icons = icons;
|
|
2347
2475
|
this._renderChildren = renderChildren;
|
|
2348
2476
|
this._triggerEvent = triggerEvent;
|
|
2349
2477
|
}
|
|
@@ -2352,31 +2480,11 @@ var TreeElement = (function () {
|
|
|
2352
2480
|
return;
|
|
2353
2481
|
}
|
|
2354
2482
|
this._node.is_open = false;
|
|
2355
|
-
if (
|
|
2356
|
-
this.
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
return;
|
|
2360
|
-
}
|
|
2361
|
-
let button = this._getButton();
|
|
2362
|
-
button.classList.add(this._classNames.closed);
|
|
2363
|
-
button.replaceChildren();
|
|
2364
|
-
let closedIconElement = this._closedIconElement;
|
|
2365
|
-
if (closedIconElement) {
|
|
2366
|
-
let icon = closedIconElement.cloneNode(true);
|
|
2367
|
-
button.append(icon);
|
|
2368
|
-
}
|
|
2369
|
-
let ul = this._getUl();
|
|
2370
|
-
if (ul) {
|
|
2371
|
-
if (slide) {
|
|
2372
|
-
await slideUp(ul, animationSpeed);
|
|
2373
|
-
} else {
|
|
2374
|
-
ul.style.display = "none";
|
|
2375
|
-
}
|
|
2483
|
+
if (this._isRendered()) {
|
|
2484
|
+
this._setButtonState(false);
|
|
2485
|
+
await this._setUlState(this._getUl(), false, slide, animationSpeed);
|
|
2486
|
+
this._setElementState(false);
|
|
2376
2487
|
}
|
|
2377
|
-
this._element.classList.add(this._classNames.closed);
|
|
2378
|
-
let titleSpan = this._getTitleSpan();
|
|
2379
|
-
titleSpan.setAttribute("aria-expanded", "false");
|
|
2380
2488
|
this._triggerEvent("tree.close", {
|
|
2381
2489
|
node: this._node
|
|
2382
2490
|
});
|
|
@@ -2386,34 +2494,17 @@ var TreeElement = (function () {
|
|
|
2386
2494
|
return;
|
|
2387
2495
|
}
|
|
2388
2496
|
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
2497
|
|
|
2405
|
-
//
|
|
2406
|
-
|
|
2407
|
-
if (
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2498
|
+
// A folder that is not rendered is rendered open when its parent is
|
|
2499
|
+
// opened
|
|
2500
|
+
if (this._isRendered()) {
|
|
2501
|
+
this._setButtonState(true);
|
|
2502
|
+
|
|
2503
|
+
// The children are rendered the first time the folder is opened
|
|
2504
|
+
let ul = this._getUl() ?? this._renderChildren(this._node);
|
|
2505
|
+
await this._setUlState(ul, true, slide, animationSpeed);
|
|
2506
|
+
this._setElementState(true);
|
|
2413
2507
|
}
|
|
2414
|
-
this._element.classList.remove(this._classNames.closed);
|
|
2415
|
-
let titleSpan = this._getTitleSpan();
|
|
2416
|
-
titleSpan.setAttribute("aria-expanded", "true");
|
|
2417
2508
|
this._triggerEvent("tree.open", {
|
|
2418
2509
|
node: this._node
|
|
2419
2510
|
});
|
|
@@ -2424,8 +2515,34 @@ var TreeElement = (function () {
|
|
|
2424
2515
|
_getButton() {
|
|
2425
2516
|
return this._element.querySelector(`:scope > .${this._classNames.element} > .${this._classNames.toggler}`);
|
|
2426
2517
|
}
|
|
2518
|
+
_setButtonState(isOpen) {
|
|
2519
|
+
setTogglerOpenState(this._getButton(), isOpen, this._classNames, this._icons);
|
|
2520
|
+
}
|
|
2521
|
+
_setElementState(isOpen) {
|
|
2522
|
+
setFolderLiOpenState(this._element, isOpen, this._classNames);
|
|
2523
|
+
}
|
|
2524
|
+
async _setUlState(ul, isOpen, slide, animationSpeed) {
|
|
2525
|
+
if (!ul) {
|
|
2526
|
+
return;
|
|
2527
|
+
}
|
|
2528
|
+
if (slide) {
|
|
2529
|
+
await (isOpen ? slideDown : slideUp)(ul, animationSpeed);
|
|
2530
|
+
} else {
|
|
2531
|
+
ul.style.display = isOpen ? "block" : "none";
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2427
2534
|
}
|
|
2428
2535
|
|
|
2536
|
+
/* The options that setOption can change. The others are read once, in the
|
|
2537
|
+
* constructor, so changing them later would have no effect or leave the tree
|
|
2538
|
+
* in an inconsistent state.
|
|
2539
|
+
*/
|
|
2540
|
+
let changeableOptions = ["animationSpeed", "dataUrl", "dragAndDrop", "keyboardSupport", "multiSelect", "onCanSelectNode", "selectable", "slide", "startDndDelay", "tabIndex"];
|
|
2541
|
+
|
|
2542
|
+
/**
|
|
2543
|
+
* The options that `setOption` can change.
|
|
2544
|
+
*/
|
|
2545
|
+
|
|
2429
2546
|
// Url of a request. A relative url is resolved against the page, like a link
|
|
2430
2547
|
// or `fetch` would, so it also works when the page is in a subdirectory.
|
|
2431
2548
|
|
|
@@ -2466,9 +2583,7 @@ var TreeElement = (function () {
|
|
|
2466
2583
|
this._saveStateOption = saveState;
|
|
2467
2584
|
}
|
|
2468
2585
|
_getNodeIdToBeSelected() {
|
|
2469
|
-
|
|
2470
|
-
return null;
|
|
2471
|
-
}
|
|
2586
|
+
// Without the saveState option there is no state
|
|
2472
2587
|
let state = this._getStateFromStorage();
|
|
2473
2588
|
return state?.selected_node ? state.selected_node[0] ?? null : null;
|
|
2474
2589
|
}
|
|
@@ -2597,17 +2712,19 @@ var TreeElement = (function () {
|
|
|
2597
2712
|
this._removeFromSelection(node);
|
|
2598
2713
|
}
|
|
2599
2714
|
}
|
|
2715
|
+
|
|
2716
|
+
// result: is a node selected?
|
|
2600
2717
|
_selectInitialNodes(nodeIds) {
|
|
2601
|
-
let
|
|
2718
|
+
let isNodeSelected = false;
|
|
2602
2719
|
for (let nodeId of nodeIds) {
|
|
2603
2720
|
let node = this._getNodeById(nodeId);
|
|
2604
2721
|
if (!node) {
|
|
2605
2722
|
continue;
|
|
2606
2723
|
}
|
|
2607
|
-
selectCount += 1;
|
|
2608
2724
|
this._addToSelection(node);
|
|
2725
|
+
isNodeSelected = true;
|
|
2609
2726
|
}
|
|
2610
|
-
return
|
|
2727
|
+
return isNodeSelected;
|
|
2611
2728
|
}
|
|
2612
2729
|
}
|
|
2613
2730
|
|
|
@@ -2921,9 +3038,9 @@ var TreeElement = (function () {
|
|
|
2921
3038
|
} else {
|
|
2922
3039
|
this._selectedNodes.delete(node.id);
|
|
2923
3040
|
if (includeChildren) {
|
|
2924
|
-
node.iterate(
|
|
2925
|
-
if (
|
|
2926
|
-
this._selectedNodes.delete(
|
|
3041
|
+
node.iterate(child => {
|
|
3042
|
+
if (child.id != null) {
|
|
3043
|
+
this._selectedNodes.delete(child.id);
|
|
2927
3044
|
}
|
|
2928
3045
|
return true;
|
|
2929
3046
|
});
|
|
@@ -2942,6 +3059,9 @@ var TreeElement = (function () {
|
|
|
2942
3059
|
* mustToggle: support deselecting the selected node
|
|
2943
3060
|
*/
|
|
2944
3061
|
_selectSingleNode(node, optionsParam) {
|
|
3062
|
+
if (!this._canSelect(node)) {
|
|
3063
|
+
return;
|
|
3064
|
+
}
|
|
2945
3065
|
let defaultOptions = {
|
|
2946
3066
|
mustSetFocus: true,
|
|
2947
3067
|
mustToggle: true
|
|
@@ -2950,35 +3070,39 @@ var TreeElement = (function () {
|
|
|
2950
3070
|
...defaultOptions,
|
|
2951
3071
|
...optionsParam
|
|
2952
3072
|
};
|
|
2953
|
-
let
|
|
2954
|
-
if (!this._getSelectable()) {
|
|
2955
|
-
return false;
|
|
2956
|
-
}
|
|
2957
|
-
let onCanSelectNode = this._getOnCanSelectNode();
|
|
2958
|
-
return onCanSelectNode ? onCanSelectNode(node) : true;
|
|
2959
|
-
};
|
|
2960
|
-
if (!canSelect()) {
|
|
2961
|
-
return;
|
|
2962
|
-
}
|
|
2963
|
-
let deselectCurrentNode = deselectedNode => {
|
|
2964
|
-
this._removeFromSelection(deselectedNode);
|
|
2965
|
-
this._getNodeElementForNode(deselectedNode)._deselect();
|
|
2966
|
-
};
|
|
3073
|
+
let otherSelectedNodes = this._getSelectedNodes().filter(selectedNode => selectedNode !== node);
|
|
2967
3074
|
if (this._isNodeSelected(node)) {
|
|
2968
|
-
if (
|
|
2969
|
-
|
|
3075
|
+
if (otherSelectedNodes.length) {
|
|
3076
|
+
// Part of a multiple selection: keep only this node selected
|
|
3077
|
+
for (let otherNode of otherSelectedNodes) {
|
|
3078
|
+
this._deselectNode(otherNode);
|
|
3079
|
+
}
|
|
3080
|
+
for (let otherNode of otherSelectedNodes) {
|
|
3081
|
+
this._triggerEvent("tree.deselect", {
|
|
3082
|
+
node: otherNode
|
|
3083
|
+
});
|
|
3084
|
+
}
|
|
3085
|
+
} else if (selectOptions.mustToggle) {
|
|
3086
|
+
this._deselectNode(node);
|
|
2970
3087
|
this._triggerEvent("tree.deselect", {
|
|
2971
3088
|
node
|
|
2972
3089
|
});
|
|
2973
3090
|
}
|
|
2974
3091
|
} else {
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
3092
|
+
// tree.select reports the first deselected node; the others, from a
|
|
3093
|
+
// multiple selection, get a tree.deselect.
|
|
3094
|
+
let [deselectedNode = null, ...alsoDeselectedNodes] = otherSelectedNodes;
|
|
3095
|
+
for (let otherNode of otherSelectedNodes) {
|
|
3096
|
+
this._deselectNode(otherNode);
|
|
2978
3097
|
}
|
|
2979
3098
|
this._addToSelection(node);
|
|
2980
3099
|
this._openParents(node);
|
|
2981
3100
|
this._getNodeElementForNode(node)._select(selectOptions.mustSetFocus);
|
|
3101
|
+
for (let otherNode of alsoDeselectedNodes) {
|
|
3102
|
+
this._triggerEvent("tree.deselect", {
|
|
3103
|
+
node: otherNode
|
|
3104
|
+
});
|
|
3105
|
+
}
|
|
2982
3106
|
this._triggerEvent("tree.select", {
|
|
2983
3107
|
deselectedNode,
|
|
2984
3108
|
node
|
|
@@ -2987,6 +3111,43 @@ var TreeElement = (function () {
|
|
|
2987
3111
|
this._updateTabStop();
|
|
2988
3112
|
this._saveState();
|
|
2989
3113
|
}
|
|
3114
|
+
|
|
3115
|
+
/* Add a node to the selection, or remove it when it is selected, without
|
|
3116
|
+
* changing the other selected nodes.
|
|
3117
|
+
* Checks if the node is selectable and saves the state.
|
|
3118
|
+
*/
|
|
3119
|
+
_toggleNodeInSelection(node, mustSetFocus = true) {
|
|
3120
|
+
if (!this._canSelect(node)) {
|
|
3121
|
+
return;
|
|
3122
|
+
}
|
|
3123
|
+
if (this._isNodeSelected(node)) {
|
|
3124
|
+
this._deselectNode(node);
|
|
3125
|
+
this._triggerEvent("tree.deselect", {
|
|
3126
|
+
node
|
|
3127
|
+
});
|
|
3128
|
+
} else {
|
|
3129
|
+
this._addToSelection(node);
|
|
3130
|
+
this._openParents(node);
|
|
3131
|
+
this._getNodeElementForNode(node)._select(mustSetFocus);
|
|
3132
|
+
this._triggerEvent("tree.select", {
|
|
3133
|
+
deselectedNode: null,
|
|
3134
|
+
node
|
|
3135
|
+
});
|
|
3136
|
+
}
|
|
3137
|
+
this._updateTabStop();
|
|
3138
|
+
this._saveState();
|
|
3139
|
+
}
|
|
3140
|
+
_canSelect(node) {
|
|
3141
|
+
if (!this._getSelectable()) {
|
|
3142
|
+
return false;
|
|
3143
|
+
}
|
|
3144
|
+
let onCanSelectNode = this._getOnCanSelectNode();
|
|
3145
|
+
return onCanSelectNode ? onCanSelectNode(node) : true;
|
|
3146
|
+
}
|
|
3147
|
+
_deselectNode(node) {
|
|
3148
|
+
this._removeFromSelection(node);
|
|
3149
|
+
this._getNodeElementForNode(node)._deselect();
|
|
3150
|
+
}
|
|
2990
3151
|
}
|
|
2991
3152
|
|
|
2992
3153
|
/* The default icons are svg instead of text: the triangle characters come
|
|
@@ -3013,6 +3174,7 @@ var TreeElement = (function () {
|
|
|
3013
3174
|
// dataUrl: undefined,
|
|
3014
3175
|
dragAndDrop: false,
|
|
3015
3176
|
keyboardSupport: true,
|
|
3177
|
+
multiSelect: false,
|
|
3016
3178
|
nodeClass: Node,
|
|
3017
3179
|
// onCanMove: undefined, // Can this node be moved?
|
|
3018
3180
|
// onCanMoveTo: undefined, // Can this node be moved to this position? function(moved_node, target_node, position)
|
|
@@ -3078,7 +3240,7 @@ var TreeElement = (function () {
|
|
|
3078
3240
|
};
|
|
3079
3241
|
|
|
3080
3242
|
// Generated by config/generateVersion.mjs. Do not edit.
|
|
3081
|
-
let version = "0.
|
|
3243
|
+
let version = "0.4.1";
|
|
3082
3244
|
|
|
3083
3245
|
// The types that appear in the public api. Consumers cannot import them from
|
|
3084
3246
|
// the submodules directly, because those are not exposed in package.json.
|
|
@@ -3086,6 +3248,8 @@ var TreeElement = (function () {
|
|
|
3086
3248
|
// as its global, instead of an object of named exports.
|
|
3087
3249
|
|
|
3088
3250
|
class TreeElement {
|
|
3251
|
+
// With multiSelect: the node that is the tab stop, see updateTabStop
|
|
3252
|
+
_activeNode;
|
|
3089
3253
|
_classNames;
|
|
3090
3254
|
_dataLoader;
|
|
3091
3255
|
_dndHandler;
|
|
@@ -3119,6 +3283,7 @@ var TreeElement = (function () {
|
|
|
3119
3283
|
this._tree = new Node({}, true);
|
|
3120
3284
|
this._nodeMap = new WeakMap();
|
|
3121
3285
|
this._fallbackTabStop = null;
|
|
3286
|
+
this._activeNode = null;
|
|
3122
3287
|
let {
|
|
3123
3288
|
ariaLabel,
|
|
3124
3289
|
ariaLabelledBy,
|
|
@@ -3128,6 +3293,7 @@ var TreeElement = (function () {
|
|
|
3128
3293
|
dataFilter,
|
|
3129
3294
|
dragAndDrop,
|
|
3130
3295
|
keyboardSupport,
|
|
3296
|
+
multiSelect,
|
|
3131
3297
|
onCanMove,
|
|
3132
3298
|
onCanMoveTo,
|
|
3133
3299
|
onCreateLi,
|
|
@@ -3141,7 +3307,6 @@ var TreeElement = (function () {
|
|
|
3141
3307
|
rtl,
|
|
3142
3308
|
saveState: saveStateOption,
|
|
3143
3309
|
showEmptyFolder,
|
|
3144
|
-
slide,
|
|
3145
3310
|
tabIndex
|
|
3146
3311
|
} = this._options;
|
|
3147
3312
|
let classNames = this._classNames;
|
|
@@ -3179,6 +3344,17 @@ var TreeElement = (function () {
|
|
|
3179
3344
|
let isNodeSelected = selectNodeHandler._isNodeSelected.bind(selectNodeHandler);
|
|
3180
3345
|
let removeFromSelection = selectNodeHandler._removeFromSelection.bind(selectNodeHandler);
|
|
3181
3346
|
let selectNode = selectNodeHandler._selectSingleNode.bind(selectNodeHandler);
|
|
3347
|
+
let toggleNodeInSelection = selectNodeHandler._toggleNodeInSelection.bind(selectNodeHandler);
|
|
3348
|
+
|
|
3349
|
+
// With multiSelect, a click adds a node to the selection or removes it
|
|
3350
|
+
let onClickTitle = node => {
|
|
3351
|
+
if (this._options.multiSelect) {
|
|
3352
|
+
toggleNodeInSelection(node, false);
|
|
3353
|
+
this._focusNode(node);
|
|
3354
|
+
} else {
|
|
3355
|
+
selectNode(node);
|
|
3356
|
+
}
|
|
3357
|
+
};
|
|
3182
3358
|
let getMouseDelay = () => this._options.startDndDelay ?? 0;
|
|
3183
3359
|
let dataLoader = new DataLoader({
|
|
3184
3360
|
_classNames: classNames,
|
|
@@ -3219,18 +3395,21 @@ var TreeElement = (function () {
|
|
|
3219
3395
|
_openFolderDelay: openFolderDelay,
|
|
3220
3396
|
_openNode: openNode,
|
|
3221
3397
|
_refreshElements: refreshElements,
|
|
3222
|
-
_slide: slide,
|
|
3223
3398
|
_treeElement: treeElement,
|
|
3224
3399
|
_triggerEvent: triggerEvent
|
|
3225
3400
|
});
|
|
3226
3401
|
let keyHandler = new KeyHandler({
|
|
3227
3402
|
_closeNode: closeNode,
|
|
3403
|
+
_focusNode: this._focusNode.bind(this),
|
|
3404
|
+
_getActiveNode: this._getActiveNode.bind(this),
|
|
3228
3405
|
_getFocusedNode: getFocusedNode,
|
|
3229
3406
|
_getSelectedNode: getSelectedNode,
|
|
3230
3407
|
_getTree: getTree,
|
|
3231
3408
|
_keyboardSupport: keyboardSupport,
|
|
3409
|
+
_multiSelect: multiSelect,
|
|
3232
3410
|
_openNode: openNode,
|
|
3233
3411
|
_selectNode: selectNode,
|
|
3412
|
+
_toggleNodeInSelection: toggleNodeInSelection,
|
|
3234
3413
|
_triggerEvent: triggerEvent
|
|
3235
3414
|
});
|
|
3236
3415
|
let renderer = new ElementsRenderer({
|
|
@@ -3244,6 +3423,7 @@ var TreeElement = (function () {
|
|
|
3244
3423
|
_getTree: getTree,
|
|
3245
3424
|
_htmlElement: treeElement,
|
|
3246
3425
|
_isNodeSelected: isNodeSelected,
|
|
3426
|
+
_multiSelect: multiSelect,
|
|
3247
3427
|
_onCreateLi: onCreateLi,
|
|
3248
3428
|
_openedIcon: openedIcon,
|
|
3249
3429
|
_rtl: rtl,
|
|
@@ -3262,7 +3442,7 @@ var TreeElement = (function () {
|
|
|
3262
3442
|
_getMouseDelay: getMouseDelay,
|
|
3263
3443
|
_getNode: getNode,
|
|
3264
3444
|
_onClickButton: this.toggle.bind(this),
|
|
3265
|
-
_onClickTitle:
|
|
3445
|
+
_onClickTitle: onClickTitle,
|
|
3266
3446
|
_onMouseCapture: onMouseCapture,
|
|
3267
3447
|
_onMouseDrag: onMouseDrag,
|
|
3268
3448
|
_onMouseStart: onMouseStart,
|
|
@@ -3278,6 +3458,9 @@ var TreeElement = (function () {
|
|
|
3278
3458
|
this._saveStateHandler = saveStateHandler;
|
|
3279
3459
|
this._scrollHandler = scrollHandler;
|
|
3280
3460
|
this._selectNodeHandler = selectNodeHandler;
|
|
3461
|
+
if (multiSelect) {
|
|
3462
|
+
treeElement.addEventListener("focusin", this._handleFocusIn);
|
|
3463
|
+
}
|
|
3281
3464
|
this._initData();
|
|
3282
3465
|
}
|
|
3283
3466
|
|
|
@@ -3310,8 +3493,9 @@ var TreeElement = (function () {
|
|
|
3310
3493
|
}
|
|
3311
3494
|
|
|
3312
3495
|
/**
|
|
3313
|
-
* Inserts a new node between a node and its parent
|
|
3314
|
-
*
|
|
3496
|
+
* Inserts a new node between a node and its parent. The siblings of the node
|
|
3497
|
+
* move along: the new node takes all children of the parent, and becomes its
|
|
3498
|
+
* only child.
|
|
3315
3499
|
*
|
|
3316
3500
|
* @returns The new node, or `null` when the node has no parent.
|
|
3317
3501
|
* @group Changing the tree
|
|
@@ -3387,6 +3571,7 @@ var TreeElement = (function () {
|
|
|
3387
3571
|
this._dataLoader._deinit();
|
|
3388
3572
|
this._keyHandler._deinit();
|
|
3389
3573
|
this._mouseHandler._deinit();
|
|
3574
|
+
this._htmlElement.removeEventListener("focusin", this._handleFocusIn);
|
|
3390
3575
|
this._tree = new Node({}, true);
|
|
3391
3576
|
}
|
|
3392
3577
|
|
|
@@ -3480,7 +3665,7 @@ var TreeElement = (function () {
|
|
|
3480
3665
|
}
|
|
3481
3666
|
|
|
3482
3667
|
/**
|
|
3483
|
-
* Returns the current state
|
|
3668
|
+
* Returns the current state—`open_nodes` and `selected_node`—whether or
|
|
3484
3669
|
* not `saveState` is enabled.
|
|
3485
3670
|
*
|
|
3486
3671
|
* @group State
|
|
@@ -3499,7 +3684,7 @@ var TreeElement = (function () {
|
|
|
3499
3684
|
}
|
|
3500
3685
|
|
|
3501
3686
|
/**
|
|
3502
|
-
* Returns the root node. It is not rendered
|
|
3687
|
+
* Returns the root node. It is not rendered, and its `children` are the
|
|
3503
3688
|
* top-level nodes. Also available as the `tree` property.
|
|
3504
3689
|
*
|
|
3505
3690
|
* @group Finding nodes
|
|
@@ -3574,7 +3759,8 @@ var TreeElement = (function () {
|
|
|
3574
3759
|
}
|
|
3575
3760
|
|
|
3576
3761
|
/**
|
|
3577
|
-
* Selects the next visible node, like the down arrow key.
|
|
3762
|
+
* Selects the next visible node, like the down arrow key. With
|
|
3763
|
+
* `multiSelect` it moves the focus instead.
|
|
3578
3764
|
*
|
|
3579
3765
|
* @group Selection
|
|
3580
3766
|
*/
|
|
@@ -3599,7 +3785,8 @@ var TreeElement = (function () {
|
|
|
3599
3785
|
}
|
|
3600
3786
|
|
|
3601
3787
|
/**
|
|
3602
|
-
* Selects the previous visible node, like the up arrow key.
|
|
3788
|
+
* Selects the previous visible node, like the up arrow key. With
|
|
3789
|
+
* `multiSelect` it moves the focus instead.
|
|
3603
3790
|
*
|
|
3604
3791
|
* @group Selection
|
|
3605
3792
|
*/
|
|
@@ -3608,7 +3795,7 @@ var TreeElement = (function () {
|
|
|
3608
3795
|
}
|
|
3609
3796
|
|
|
3610
3797
|
/**
|
|
3611
|
-
* Opens a folder, and the folders
|
|
3798
|
+
* Opens a folder, and the folders that contain it. A node marked `load_on_demand`
|
|
3612
3799
|
* is fetched first, so await the promise when you need to know it is really
|
|
3613
3800
|
* open.
|
|
3614
3801
|
*
|
|
@@ -3624,7 +3811,7 @@ var TreeElement = (function () {
|
|
|
3624
3811
|
async openNode(node, slide) {
|
|
3625
3812
|
let mustSlide = slide ?? this._options.slide;
|
|
3626
3813
|
let doOpenNode = async (openedNode, slideOption) => {
|
|
3627
|
-
if (!
|
|
3814
|
+
if (!openedNode.children.length) {
|
|
3628
3815
|
return;
|
|
3629
3816
|
}
|
|
3630
3817
|
let folderElement = this._createFolderElement(openedNode);
|
|
@@ -3729,8 +3916,8 @@ var TreeElement = (function () {
|
|
|
3729
3916
|
*/
|
|
3730
3917
|
selectNode(node, options) {
|
|
3731
3918
|
if (!node) {
|
|
3732
|
-
// Called with empty node -> deselect
|
|
3733
|
-
this.
|
|
3919
|
+
// Called with empty node -> deselect all nodes
|
|
3920
|
+
this._deselectAllNodes();
|
|
3734
3921
|
this._saveStateHandler._saveState();
|
|
3735
3922
|
return;
|
|
3736
3923
|
}
|
|
@@ -3743,13 +3930,43 @@ var TreeElement = (function () {
|
|
|
3743
3930
|
}
|
|
3744
3931
|
|
|
3745
3932
|
/**
|
|
3746
|
-
* Changes an option after construction.
|
|
3747
|
-
*
|
|
3933
|
+
* Changes an option after construction. Only some options can be changed:
|
|
3934
|
+
* the others throw an error. See
|
|
3935
|
+
* [Options](/reference/options#changing-an-option-later).
|
|
3748
3936
|
*
|
|
3749
3937
|
* @group Other
|
|
3750
3938
|
*/
|
|
3751
3939
|
setOption(option, value) {
|
|
3940
|
+
if (!changeableOptions.includes(option)) {
|
|
3941
|
+
throw new Error(`The option ${option} can only be set in the constructor`);
|
|
3942
|
+
}
|
|
3752
3943
|
this._options[option] = value;
|
|
3944
|
+
switch (option) {
|
|
3945
|
+
case "dragAndDrop":
|
|
3946
|
+
{
|
|
3947
|
+
this._renderer._setDragAndDrop(Boolean(value));
|
|
3948
|
+
break;
|
|
3949
|
+
}
|
|
3950
|
+
case "keyboardSupport":
|
|
3951
|
+
{
|
|
3952
|
+
this._keyHandler._setKeyboardSupport(Boolean(value));
|
|
3953
|
+
break;
|
|
3954
|
+
}
|
|
3955
|
+
case "multiSelect":
|
|
3956
|
+
{
|
|
3957
|
+
this._setMultiSelect(Boolean(value));
|
|
3958
|
+
break;
|
|
3959
|
+
}
|
|
3960
|
+
case "tabIndex":
|
|
3961
|
+
{
|
|
3962
|
+
this._options.tabIndex = value ?? undefined;
|
|
3963
|
+
this._renderer._setTabIndex(this._options.tabIndex);
|
|
3964
|
+
this._fallbackTabStop = null;
|
|
3965
|
+
this._updateTabStop();
|
|
3966
|
+
break;
|
|
3967
|
+
}
|
|
3968
|
+
// No default
|
|
3969
|
+
}
|
|
3753
3970
|
}
|
|
3754
3971
|
|
|
3755
3972
|
/**
|
|
@@ -3782,8 +3999,8 @@ var TreeElement = (function () {
|
|
|
3782
3999
|
}
|
|
3783
4000
|
|
|
3784
4001
|
/**
|
|
3785
|
-
* Returns the tree as a
|
|
3786
|
-
*
|
|
4002
|
+
* Returns the tree as a JSON string, including changes made through the
|
|
4003
|
+
* API.
|
|
3787
4004
|
*
|
|
3788
4005
|
* @group Other
|
|
3789
4006
|
*/
|
|
@@ -3796,7 +4013,7 @@ var TreeElement = (function () {
|
|
|
3796
4013
|
* name.
|
|
3797
4014
|
*
|
|
3798
4015
|
* Changing the `id` re-indexes the node. `children` and `parent` are
|
|
3799
|
-
* ignored
|
|
4016
|
+
* ignored—use `loadData` or `moveNode` for those.
|
|
3800
4017
|
*
|
|
3801
4018
|
* @example
|
|
3802
4019
|
* ```js
|
|
@@ -3847,9 +4064,7 @@ var TreeElement = (function () {
|
|
|
3847
4064
|
}
|
|
3848
4065
|
_createFolderElement(node) {
|
|
3849
4066
|
let classNames = this._classNames;
|
|
3850
|
-
let closedIconElement = this._renderer._closedIconElement;
|
|
3851
4067
|
let getScrollLeft = this._scrollHandler._getScrollLeft.bind(this._scrollHandler);
|
|
3852
|
-
let openedIconElement = this._renderer._openedIconElement;
|
|
3853
4068
|
let tabIndex = this._options.tabIndex;
|
|
3854
4069
|
let treeElement = this._htmlElement;
|
|
3855
4070
|
let triggerEvent = this._triggerEvent.bind(this);
|
|
@@ -3864,10 +4079,10 @@ var TreeElement = (function () {
|
|
|
3864
4079
|
};
|
|
3865
4080
|
return new FolderElement({
|
|
3866
4081
|
_classNames: classNames,
|
|
3867
|
-
_closedIconElement: closedIconElement,
|
|
3868
4082
|
_getScrollLeft: getScrollLeft,
|
|
4083
|
+
_icons: this._renderer._icons,
|
|
4084
|
+
_multiSelect: this._options.multiSelect,
|
|
3869
4085
|
_node: node,
|
|
3870
|
-
_openedIconElement: openedIconElement,
|
|
3871
4086
|
_renderChildren: renderChildren,
|
|
3872
4087
|
_tabIndex: tabIndex,
|
|
3873
4088
|
_treeElement: treeElement,
|
|
@@ -3882,6 +4097,7 @@ var TreeElement = (function () {
|
|
|
3882
4097
|
return new NodeElement({
|
|
3883
4098
|
_classNames: classNames,
|
|
3884
4099
|
_getScrollLeft: getScrollLeft,
|
|
4100
|
+
_multiSelect: this._options.multiSelect,
|
|
3885
4101
|
_node: node,
|
|
3886
4102
|
_tabIndex: tabIndex,
|
|
3887
4103
|
_treeElement: treeElement
|
|
@@ -3911,9 +4127,8 @@ var TreeElement = (function () {
|
|
|
3911
4127
|
}
|
|
3912
4128
|
return requestUrl;
|
|
3913
4129
|
}
|
|
3914
|
-
|
|
3915
|
-
let node
|
|
3916
|
-
if (node) {
|
|
4130
|
+
_deselectAllNodes() {
|
|
4131
|
+
for (let node of this.getSelectedNodes()) {
|
|
3917
4132
|
this.removeFromSelection(node);
|
|
3918
4133
|
}
|
|
3919
4134
|
}
|
|
@@ -3926,6 +4141,20 @@ var TreeElement = (function () {
|
|
|
3926
4141
|
}
|
|
3927
4142
|
}
|
|
3928
4143
|
|
|
4144
|
+
// The treeitem of the first node, or null when the tree is empty
|
|
4145
|
+
// Move the focus to a node, without selecting it
|
|
4146
|
+
_focusNode(node) {
|
|
4147
|
+
this._activeNode = node;
|
|
4148
|
+
this._updateTabStop();
|
|
4149
|
+
node.element?.focus();
|
|
4150
|
+
}
|
|
4151
|
+
|
|
4152
|
+
// With multiSelect: the active node, if it is still rendered in the tree
|
|
4153
|
+
_getActiveNode() {
|
|
4154
|
+
let element = this._activeNode?.element;
|
|
4155
|
+
return element && this._htmlElement.contains(element) ? this._activeNode : null;
|
|
4156
|
+
}
|
|
4157
|
+
|
|
3929
4158
|
// Get the maximum level for auto open
|
|
3930
4159
|
_getAutoOpenMaxLevel() {
|
|
3931
4160
|
if (this._options.autoOpen === true) {
|
|
@@ -3941,25 +4170,16 @@ var TreeElement = (function () {
|
|
|
3941
4170
|
/* istanbul ignore next @preserve */
|
|
3942
4171
|
return 0;
|
|
3943
4172
|
}
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
_getFirstTitleSpan() {
|
|
3947
|
-
let firstElement = this._tree.children[0]?.element;
|
|
3948
|
-
return firstElement ? firstElement.querySelector(`:scope > .${this._classNames.element} > span.${this._classNames.title}`) : null;
|
|
4173
|
+
_getFirstTreeItem() {
|
|
4174
|
+
return this._tree.children[0]?.element ?? null;
|
|
3949
4175
|
}
|
|
3950
4176
|
|
|
3951
4177
|
// The node of the HTML element that has the focus, if it is in this tree
|
|
3952
4178
|
_getFocusedNode() {
|
|
3953
4179
|
let activeElement = document.activeElement;
|
|
3954
4180
|
|
|
3955
|
-
|
|
3956
|
-
if (!activeElement) {
|
|
3957
|
-
return null;
|
|
3958
|
-
}
|
|
3959
|
-
|
|
3960
|
-
// The keyboard must still work for input elements.
|
|
3961
|
-
let tagName = activeElement.tagName;
|
|
3962
|
-
if (tagName !== "A" && tagName !== "SPAN") {
|
|
4181
|
+
// An active element must exist and the keyboard must still work for input elements.
|
|
4182
|
+
if (!activeElement || !["A", "LI", "SPAN"].includes(activeElement.tagName)) {
|
|
3963
4183
|
return null;
|
|
3964
4184
|
}
|
|
3965
4185
|
let node = this.getNode(activeElement);
|
|
@@ -3975,6 +4195,16 @@ var TreeElement = (function () {
|
|
|
3975
4195
|
_getNodeIdToBeSelected() {
|
|
3976
4196
|
return this._saveStateHandler._getNodeIdToBeSelected();
|
|
3977
4197
|
}
|
|
4198
|
+
|
|
4199
|
+
// With multiSelect, the focused node becomes the tab stop
|
|
4200
|
+
_handleFocusIn = () => {
|
|
4201
|
+
let node = this._getFocusedNode();
|
|
4202
|
+
if (!node || node === this._activeNode) {
|
|
4203
|
+
return;
|
|
4204
|
+
}
|
|
4205
|
+
this._activeNode = node;
|
|
4206
|
+
this._updateTabStop();
|
|
4207
|
+
};
|
|
3978
4208
|
_hasRenderedSelectedNode() {
|
|
3979
4209
|
return this.getSelectedNodes().some(node => Boolean(node.element));
|
|
3980
4210
|
}
|
|
@@ -3998,11 +4228,17 @@ var TreeElement = (function () {
|
|
|
3998
4228
|
this._isInitialized = true;
|
|
3999
4229
|
this._triggerEvent("tree.init");
|
|
4000
4230
|
};
|
|
4231
|
+
|
|
4232
|
+
// Before the tree is replaced: then the focused node is no longer in it
|
|
4233
|
+
let focusedNode = this._getFocusedNode();
|
|
4001
4234
|
this._tree = new this._options.nodeClass(null, true, this._options.nodeClass);
|
|
4002
4235
|
this._selectNodeHandler._clear();
|
|
4003
4236
|
this._tree.loadFromData(data);
|
|
4004
4237
|
let mustLoadOnDemand = this._setInitialState();
|
|
4005
4238
|
this._refreshElements(null);
|
|
4239
|
+
if (focusedNode) {
|
|
4240
|
+
this._restoreFocus(focusedNode);
|
|
4241
|
+
}
|
|
4006
4242
|
if (mustLoadOnDemand) {
|
|
4007
4243
|
// Load data on demand and then init the tree
|
|
4008
4244
|
void this._setInitialStateOnDemand().then(doInit);
|
|
@@ -4010,15 +4246,6 @@ var TreeElement = (function () {
|
|
|
4010
4246
|
doInit();
|
|
4011
4247
|
}
|
|
4012
4248
|
}
|
|
4013
|
-
|
|
4014
|
-
// Does an HTML element of the tree have the focus?
|
|
4015
|
-
_isFocusOnTree() {
|
|
4016
|
-
return this._getFocusedNode() !== null;
|
|
4017
|
-
}
|
|
4018
|
-
_isSelectedNodeInSubtree(subtree) {
|
|
4019
|
-
let selectedNode = this.getSelectedNode();
|
|
4020
|
-
return selectedNode ? subtree === selectedNode || subtree.isParentOf(selectedNode) : false;
|
|
4021
|
-
}
|
|
4022
4249
|
async _loadFolderOnDemand(node, slide) {
|
|
4023
4250
|
node.is_loading = true;
|
|
4024
4251
|
await this.loadDataFromUrl(undefined, node);
|
|
@@ -4051,7 +4278,6 @@ var TreeElement = (function () {
|
|
|
4051
4278
|
}
|
|
4052
4279
|
_mouseStart(positionInfo) {
|
|
4053
4280
|
/* istanbul ignore if */
|
|
4054
|
-
// eslint-disable-next-line unicorn/prefer-ternary
|
|
4055
4281
|
if (!this._options.dragAndDrop) {
|
|
4056
4282
|
return false;
|
|
4057
4283
|
}
|
|
@@ -4060,10 +4286,10 @@ var TreeElement = (function () {
|
|
|
4060
4286
|
_mouseStop(positionInfo) {
|
|
4061
4287
|
/* istanbul ignore if */
|
|
4062
4288
|
if (!this._options.dragAndDrop) {
|
|
4063
|
-
return
|
|
4289
|
+
return;
|
|
4064
4290
|
}
|
|
4065
4291
|
this._scrollHandler._stopScrolling();
|
|
4066
|
-
|
|
4292
|
+
this._dndHandler._mouseStop(positionInfo);
|
|
4067
4293
|
}
|
|
4068
4294
|
_openParents(node) {
|
|
4069
4295
|
let parent = node.parent;
|
|
@@ -4077,25 +4303,32 @@ var TreeElement = (function () {
|
|
|
4077
4303
|
from_node: redraw this subtree
|
|
4078
4304
|
*/
|
|
4079
4305
|
_refreshElements(fromNode) {
|
|
4080
|
-
let
|
|
4081
|
-
|
|
4306
|
+
let focusedNode = this._getFocusedNode();
|
|
4307
|
+
|
|
4308
|
+
// The renderer marks the selected nodes, so only the focus needs restoring
|
|
4082
4309
|
this._renderer._render(fromNode);
|
|
4083
|
-
if (
|
|
4084
|
-
this.
|
|
4310
|
+
if (focusedNode) {
|
|
4311
|
+
this._restoreFocus(focusedNode);
|
|
4085
4312
|
}
|
|
4086
4313
|
this._updateTabStop();
|
|
4087
4314
|
this._triggerEvent("tree.refresh");
|
|
4088
4315
|
}
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
if (
|
|
4316
|
+
|
|
4317
|
+
// Rendering replaces the elements of the nodes, so a focused element can be
|
|
4318
|
+
// gone. Then move the focus to the new element of the same node. After
|
|
4319
|
+
// loadData the nodes are new objects, so look the node up by its id.
|
|
4320
|
+
_restoreFocus(focusedNode) {
|
|
4321
|
+
if (this._htmlElement.contains(document.activeElement)) {
|
|
4095
4322
|
return;
|
|
4096
4323
|
}
|
|
4097
|
-
let
|
|
4098
|
-
|
|
4324
|
+
let node = (focusedNode.id == null ? null : this.getNodeById(focusedNode.id)) ?? focusedNode;
|
|
4325
|
+
let element = node.element;
|
|
4326
|
+
if (element && this._htmlElement.contains(element)) {
|
|
4327
|
+
element.focus();
|
|
4328
|
+
}
|
|
4329
|
+
}
|
|
4330
|
+
_saveState() {
|
|
4331
|
+
this._saveStateHandler._saveState();
|
|
4099
4332
|
}
|
|
4100
4333
|
|
|
4101
4334
|
// Set initial state, either by restoring the state or auto-opening nodes
|
|
@@ -4150,6 +4383,25 @@ var TreeElement = (function () {
|
|
|
4150
4383
|
}
|
|
4151
4384
|
}
|
|
4152
4385
|
|
|
4386
|
+
/* Change multiSelect after construction. The attributes of the nodes
|
|
4387
|
+
* depend on it, so the tree is rendered again. The selection is kept: a
|
|
4388
|
+
* tree without multiSelect can also have several selected nodes, see
|
|
4389
|
+
* addToSelection.
|
|
4390
|
+
*/
|
|
4391
|
+
_setMultiSelect(multiSelect) {
|
|
4392
|
+
let focusedNode = this._getFocusedNode();
|
|
4393
|
+
this._keyHandler._setMultiSelect(multiSelect);
|
|
4394
|
+
this._renderer._setMultiSelect(multiSelect);
|
|
4395
|
+
if (multiSelect) {
|
|
4396
|
+
this._htmlElement.addEventListener("focusin", this._handleFocusIn);
|
|
4397
|
+
} else {
|
|
4398
|
+
this._htmlElement.removeEventListener("focusin", this._handleFocusIn);
|
|
4399
|
+
}
|
|
4400
|
+
this._activeNode = multiSelect ? focusedNode : null;
|
|
4401
|
+
this._fallbackTabStop = null;
|
|
4402
|
+
this._refreshElements(null);
|
|
4403
|
+
}
|
|
4404
|
+
|
|
4153
4405
|
// Set this HTML element to this node in the node map.
|
|
4154
4406
|
_setNodeElement(element, node) {
|
|
4155
4407
|
this._nodeMap.set(element, node);
|
|
@@ -4161,12 +4413,22 @@ var TreeElement = (function () {
|
|
|
4161
4413
|
/* Keyboard users reach the tree through the tab stop. The selected node is
|
|
4162
4414
|
* the tab stop, see NodeElement.select. Without a selected node, the first
|
|
4163
4415
|
* node is the tab stop, so the tree can always be reached.
|
|
4416
|
+
* With multiSelect the tab stop is the active node: the node that was
|
|
4417
|
+
* focused last. Without one, it is the first node.
|
|
4164
4418
|
*/
|
|
4165
4419
|
_updateTabStop() {
|
|
4166
4420
|
let tabIndex = this._options.tabIndex;
|
|
4421
|
+
let multiSelect = this._options.multiSelect;
|
|
4167
4422
|
let previousTabStop = this._fallbackTabStop;
|
|
4168
|
-
let tabStop
|
|
4169
|
-
if (
|
|
4423
|
+
let tabStop;
|
|
4424
|
+
if (tabIndex == null) {
|
|
4425
|
+
tabStop = null;
|
|
4426
|
+
} else if (multiSelect) {
|
|
4427
|
+
tabStop = this._getActiveNode()?.element ?? this._getFirstTreeItem();
|
|
4428
|
+
} else {
|
|
4429
|
+
tabStop = this._hasRenderedSelectedNode() ? null : this._getFirstTreeItem();
|
|
4430
|
+
}
|
|
4431
|
+
if (previousTabStop && previousTabStop !== tabStop && (multiSelect || previousTabStop.getAttribute("aria-selected") !== "true")) {
|
|
4170
4432
|
previousTabStop.setAttribute("tabindex", "-1");
|
|
4171
4433
|
}
|
|
4172
4434
|
if (tabStop && tabIndex != null) {
|