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/lib/index.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
|
|
|
@@ -26,6 +26,7 @@ import MouseHandler from './mouseHandler.js';
|
|
|
26
26
|
import { Node } from './node.js';
|
|
27
27
|
import NodeElement from './nodeElement/index.js';
|
|
28
28
|
import FolderElement from './nodeElement/folderElement.js';
|
|
29
|
+
import { changeableOptions } from './options.js';
|
|
29
30
|
import { getOffsetTop } from './positionUtils.js';
|
|
30
31
|
import RequestUrl from './requestUrl.js';
|
|
31
32
|
import SaveStateHandler from './saveStateHandler.js';
|
|
@@ -41,6 +42,8 @@ import version from './version.js';
|
|
|
41
42
|
// as its global, instead of an object of named exports.
|
|
42
43
|
|
|
43
44
|
class TreeElement {
|
|
45
|
+
// With multiSelect: the node that is the tab stop, see updateTabStop
|
|
46
|
+
_activeNode;
|
|
44
47
|
_classNames;
|
|
45
48
|
_dataLoader;
|
|
46
49
|
_dndHandler;
|
|
@@ -74,6 +77,7 @@ class TreeElement {
|
|
|
74
77
|
this._tree = new Node({}, true);
|
|
75
78
|
this._nodeMap = new WeakMap();
|
|
76
79
|
this._fallbackTabStop = null;
|
|
80
|
+
this._activeNode = null;
|
|
77
81
|
let {
|
|
78
82
|
ariaLabel,
|
|
79
83
|
ariaLabelledBy,
|
|
@@ -83,6 +87,7 @@ class TreeElement {
|
|
|
83
87
|
dataFilter,
|
|
84
88
|
dragAndDrop,
|
|
85
89
|
keyboardSupport,
|
|
90
|
+
multiSelect,
|
|
86
91
|
onCanMove,
|
|
87
92
|
onCanMoveTo,
|
|
88
93
|
onCreateLi,
|
|
@@ -96,7 +101,6 @@ class TreeElement {
|
|
|
96
101
|
rtl,
|
|
97
102
|
saveState: saveStateOption,
|
|
98
103
|
showEmptyFolder,
|
|
99
|
-
slide,
|
|
100
104
|
tabIndex
|
|
101
105
|
} = this._options;
|
|
102
106
|
let classNames = this._classNames;
|
|
@@ -134,6 +138,17 @@ class TreeElement {
|
|
|
134
138
|
let isNodeSelected = selectNodeHandler._isNodeSelected.bind(selectNodeHandler);
|
|
135
139
|
let removeFromSelection = selectNodeHandler._removeFromSelection.bind(selectNodeHandler);
|
|
136
140
|
let selectNode = selectNodeHandler._selectSingleNode.bind(selectNodeHandler);
|
|
141
|
+
let toggleNodeInSelection = selectNodeHandler._toggleNodeInSelection.bind(selectNodeHandler);
|
|
142
|
+
|
|
143
|
+
// With multiSelect, a click adds a node to the selection or removes it
|
|
144
|
+
let onClickTitle = node => {
|
|
145
|
+
if (this._options.multiSelect) {
|
|
146
|
+
toggleNodeInSelection(node, false);
|
|
147
|
+
this._focusNode(node);
|
|
148
|
+
} else {
|
|
149
|
+
selectNode(node);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
137
152
|
let getMouseDelay = () => this._options.startDndDelay ?? 0;
|
|
138
153
|
let dataLoader = new DataLoader({
|
|
139
154
|
_classNames: classNames,
|
|
@@ -174,18 +189,21 @@ class TreeElement {
|
|
|
174
189
|
_openFolderDelay: openFolderDelay,
|
|
175
190
|
_openNode: openNode,
|
|
176
191
|
_refreshElements: refreshElements,
|
|
177
|
-
_slide: slide,
|
|
178
192
|
_treeElement: treeElement,
|
|
179
193
|
_triggerEvent: triggerEvent
|
|
180
194
|
});
|
|
181
195
|
let keyHandler = new KeyHandler({
|
|
182
196
|
_closeNode: closeNode,
|
|
197
|
+
_focusNode: this._focusNode.bind(this),
|
|
198
|
+
_getActiveNode: this._getActiveNode.bind(this),
|
|
183
199
|
_getFocusedNode: getFocusedNode,
|
|
184
200
|
_getSelectedNode: getSelectedNode,
|
|
185
201
|
_getTree: getTree,
|
|
186
202
|
_keyboardSupport: keyboardSupport,
|
|
203
|
+
_multiSelect: multiSelect,
|
|
187
204
|
_openNode: openNode,
|
|
188
205
|
_selectNode: selectNode,
|
|
206
|
+
_toggleNodeInSelection: toggleNodeInSelection,
|
|
189
207
|
_triggerEvent: triggerEvent
|
|
190
208
|
});
|
|
191
209
|
let renderer = new ElementsRenderer({
|
|
@@ -199,6 +217,7 @@ class TreeElement {
|
|
|
199
217
|
_getTree: getTree,
|
|
200
218
|
_htmlElement: treeElement,
|
|
201
219
|
_isNodeSelected: isNodeSelected,
|
|
220
|
+
_multiSelect: multiSelect,
|
|
202
221
|
_onCreateLi: onCreateLi,
|
|
203
222
|
_openedIcon: openedIcon,
|
|
204
223
|
_rtl: rtl,
|
|
@@ -217,7 +236,7 @@ class TreeElement {
|
|
|
217
236
|
_getMouseDelay: getMouseDelay,
|
|
218
237
|
_getNode: getNode,
|
|
219
238
|
_onClickButton: this.toggle.bind(this),
|
|
220
|
-
_onClickTitle:
|
|
239
|
+
_onClickTitle: onClickTitle,
|
|
221
240
|
_onMouseCapture: onMouseCapture,
|
|
222
241
|
_onMouseDrag: onMouseDrag,
|
|
223
242
|
_onMouseStart: onMouseStart,
|
|
@@ -233,6 +252,9 @@ class TreeElement {
|
|
|
233
252
|
this._saveStateHandler = saveStateHandler;
|
|
234
253
|
this._scrollHandler = scrollHandler;
|
|
235
254
|
this._selectNodeHandler = selectNodeHandler;
|
|
255
|
+
if (multiSelect) {
|
|
256
|
+
treeElement.addEventListener("focusin", this._handleFocusIn);
|
|
257
|
+
}
|
|
236
258
|
this._initData();
|
|
237
259
|
}
|
|
238
260
|
|
|
@@ -265,8 +287,9 @@ class TreeElement {
|
|
|
265
287
|
}
|
|
266
288
|
|
|
267
289
|
/**
|
|
268
|
-
* Inserts a new node between a node and its parent
|
|
269
|
-
*
|
|
290
|
+
* Inserts a new node between a node and its parent. The siblings of the node
|
|
291
|
+
* move along: the new node takes all children of the parent, and becomes its
|
|
292
|
+
* only child.
|
|
270
293
|
*
|
|
271
294
|
* @returns The new node, or `null` when the node has no parent.
|
|
272
295
|
* @group Changing the tree
|
|
@@ -342,6 +365,7 @@ class TreeElement {
|
|
|
342
365
|
this._dataLoader._deinit();
|
|
343
366
|
this._keyHandler._deinit();
|
|
344
367
|
this._mouseHandler._deinit();
|
|
368
|
+
this._htmlElement.removeEventListener("focusin", this._handleFocusIn);
|
|
345
369
|
this._tree = new Node({}, true);
|
|
346
370
|
}
|
|
347
371
|
|
|
@@ -435,7 +459,7 @@ class TreeElement {
|
|
|
435
459
|
}
|
|
436
460
|
|
|
437
461
|
/**
|
|
438
|
-
* Returns the current state
|
|
462
|
+
* Returns the current state—`open_nodes` and `selected_node`—whether or
|
|
439
463
|
* not `saveState` is enabled.
|
|
440
464
|
*
|
|
441
465
|
* @group State
|
|
@@ -454,7 +478,7 @@ class TreeElement {
|
|
|
454
478
|
}
|
|
455
479
|
|
|
456
480
|
/**
|
|
457
|
-
* Returns the root node. It is not rendered
|
|
481
|
+
* Returns the root node. It is not rendered, and its `children` are the
|
|
458
482
|
* top-level nodes. Also available as the `tree` property.
|
|
459
483
|
*
|
|
460
484
|
* @group Finding nodes
|
|
@@ -529,7 +553,8 @@ class TreeElement {
|
|
|
529
553
|
}
|
|
530
554
|
|
|
531
555
|
/**
|
|
532
|
-
* Selects the next visible node, like the down arrow key.
|
|
556
|
+
* Selects the next visible node, like the down arrow key. With
|
|
557
|
+
* `multiSelect` it moves the focus instead.
|
|
533
558
|
*
|
|
534
559
|
* @group Selection
|
|
535
560
|
*/
|
|
@@ -554,7 +579,8 @@ class TreeElement {
|
|
|
554
579
|
}
|
|
555
580
|
|
|
556
581
|
/**
|
|
557
|
-
* Selects the previous visible node, like the up arrow key.
|
|
582
|
+
* Selects the previous visible node, like the up arrow key. With
|
|
583
|
+
* `multiSelect` it moves the focus instead.
|
|
558
584
|
*
|
|
559
585
|
* @group Selection
|
|
560
586
|
*/
|
|
@@ -563,7 +589,7 @@ class TreeElement {
|
|
|
563
589
|
}
|
|
564
590
|
|
|
565
591
|
/**
|
|
566
|
-
* Opens a folder, and the folders
|
|
592
|
+
* Opens a folder, and the folders that contain it. A node marked `load_on_demand`
|
|
567
593
|
* is fetched first, so await the promise when you need to know it is really
|
|
568
594
|
* open.
|
|
569
595
|
*
|
|
@@ -579,7 +605,7 @@ class TreeElement {
|
|
|
579
605
|
async openNode(node, slide) {
|
|
580
606
|
let mustSlide = slide ?? this._options.slide;
|
|
581
607
|
let doOpenNode = async (openedNode, slideOption) => {
|
|
582
|
-
if (!
|
|
608
|
+
if (!openedNode.children.length) {
|
|
583
609
|
return;
|
|
584
610
|
}
|
|
585
611
|
let folderElement = this._createFolderElement(openedNode);
|
|
@@ -684,8 +710,8 @@ class TreeElement {
|
|
|
684
710
|
*/
|
|
685
711
|
selectNode(node, options) {
|
|
686
712
|
if (!node) {
|
|
687
|
-
// Called with empty node -> deselect
|
|
688
|
-
this.
|
|
713
|
+
// Called with empty node -> deselect all nodes
|
|
714
|
+
this._deselectAllNodes();
|
|
689
715
|
this._saveStateHandler._saveState();
|
|
690
716
|
return;
|
|
691
717
|
}
|
|
@@ -698,13 +724,43 @@ class TreeElement {
|
|
|
698
724
|
}
|
|
699
725
|
|
|
700
726
|
/**
|
|
701
|
-
* Changes an option after construction.
|
|
702
|
-
*
|
|
727
|
+
* Changes an option after construction. Only some options can be changed:
|
|
728
|
+
* the others throw an error. See
|
|
729
|
+
* [Options](/reference/options#changing-an-option-later).
|
|
703
730
|
*
|
|
704
731
|
* @group Other
|
|
705
732
|
*/
|
|
706
733
|
setOption(option, value) {
|
|
734
|
+
if (!changeableOptions.includes(option)) {
|
|
735
|
+
throw new Error(`The option ${option} can only be set in the constructor`);
|
|
736
|
+
}
|
|
707
737
|
this._options[option] = value;
|
|
738
|
+
switch (option) {
|
|
739
|
+
case "dragAndDrop":
|
|
740
|
+
{
|
|
741
|
+
this._renderer._setDragAndDrop(Boolean(value));
|
|
742
|
+
break;
|
|
743
|
+
}
|
|
744
|
+
case "keyboardSupport":
|
|
745
|
+
{
|
|
746
|
+
this._keyHandler._setKeyboardSupport(Boolean(value));
|
|
747
|
+
break;
|
|
748
|
+
}
|
|
749
|
+
case "multiSelect":
|
|
750
|
+
{
|
|
751
|
+
this._setMultiSelect(Boolean(value));
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
case "tabIndex":
|
|
755
|
+
{
|
|
756
|
+
this._options.tabIndex = value ?? undefined;
|
|
757
|
+
this._renderer._setTabIndex(this._options.tabIndex);
|
|
758
|
+
this._fallbackTabStop = null;
|
|
759
|
+
this._updateTabStop();
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
// No default
|
|
763
|
+
}
|
|
708
764
|
}
|
|
709
765
|
|
|
710
766
|
/**
|
|
@@ -737,8 +793,8 @@ class TreeElement {
|
|
|
737
793
|
}
|
|
738
794
|
|
|
739
795
|
/**
|
|
740
|
-
* Returns the tree as a
|
|
741
|
-
*
|
|
796
|
+
* Returns the tree as a JSON string, including changes made through the
|
|
797
|
+
* API.
|
|
742
798
|
*
|
|
743
799
|
* @group Other
|
|
744
800
|
*/
|
|
@@ -751,7 +807,7 @@ class TreeElement {
|
|
|
751
807
|
* name.
|
|
752
808
|
*
|
|
753
809
|
* Changing the `id` re-indexes the node. `children` and `parent` are
|
|
754
|
-
* ignored
|
|
810
|
+
* ignored—use `loadData` or `moveNode` for those.
|
|
755
811
|
*
|
|
756
812
|
* @example
|
|
757
813
|
* ```js
|
|
@@ -802,9 +858,7 @@ class TreeElement {
|
|
|
802
858
|
}
|
|
803
859
|
_createFolderElement(node) {
|
|
804
860
|
let classNames = this._classNames;
|
|
805
|
-
let closedIconElement = this._renderer._closedIconElement;
|
|
806
861
|
let getScrollLeft = this._scrollHandler._getScrollLeft.bind(this._scrollHandler);
|
|
807
|
-
let openedIconElement = this._renderer._openedIconElement;
|
|
808
862
|
let tabIndex = this._options.tabIndex;
|
|
809
863
|
let treeElement = this._htmlElement;
|
|
810
864
|
let triggerEvent = this._triggerEvent.bind(this);
|
|
@@ -819,10 +873,10 @@ class TreeElement {
|
|
|
819
873
|
};
|
|
820
874
|
return new FolderElement({
|
|
821
875
|
_classNames: classNames,
|
|
822
|
-
_closedIconElement: closedIconElement,
|
|
823
876
|
_getScrollLeft: getScrollLeft,
|
|
877
|
+
_icons: this._renderer._icons,
|
|
878
|
+
_multiSelect: this._options.multiSelect,
|
|
824
879
|
_node: node,
|
|
825
|
-
_openedIconElement: openedIconElement,
|
|
826
880
|
_renderChildren: renderChildren,
|
|
827
881
|
_tabIndex: tabIndex,
|
|
828
882
|
_treeElement: treeElement,
|
|
@@ -837,6 +891,7 @@ class TreeElement {
|
|
|
837
891
|
return new NodeElement({
|
|
838
892
|
_classNames: classNames,
|
|
839
893
|
_getScrollLeft: getScrollLeft,
|
|
894
|
+
_multiSelect: this._options.multiSelect,
|
|
840
895
|
_node: node,
|
|
841
896
|
_tabIndex: tabIndex,
|
|
842
897
|
_treeElement: treeElement
|
|
@@ -866,9 +921,8 @@ class TreeElement {
|
|
|
866
921
|
}
|
|
867
922
|
return requestUrl;
|
|
868
923
|
}
|
|
869
|
-
|
|
870
|
-
let node
|
|
871
|
-
if (node) {
|
|
924
|
+
_deselectAllNodes() {
|
|
925
|
+
for (let node of this.getSelectedNodes()) {
|
|
872
926
|
this.removeFromSelection(node);
|
|
873
927
|
}
|
|
874
928
|
}
|
|
@@ -881,6 +935,20 @@ class TreeElement {
|
|
|
881
935
|
}
|
|
882
936
|
}
|
|
883
937
|
|
|
938
|
+
// The treeitem of the first node, or null when the tree is empty
|
|
939
|
+
// Move the focus to a node, without selecting it
|
|
940
|
+
_focusNode(node) {
|
|
941
|
+
this._activeNode = node;
|
|
942
|
+
this._updateTabStop();
|
|
943
|
+
node.element?.focus();
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// With multiSelect: the active node, if it is still rendered in the tree
|
|
947
|
+
_getActiveNode() {
|
|
948
|
+
let element = this._activeNode?.element;
|
|
949
|
+
return element && this._htmlElement.contains(element) ? this._activeNode : null;
|
|
950
|
+
}
|
|
951
|
+
|
|
884
952
|
// Get the maximum level for auto open
|
|
885
953
|
_getAutoOpenMaxLevel() {
|
|
886
954
|
if (this._options.autoOpen === true) {
|
|
@@ -896,25 +964,16 @@ class TreeElement {
|
|
|
896
964
|
/* istanbul ignore next @preserve */
|
|
897
965
|
return 0;
|
|
898
966
|
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
_getFirstTitleSpan() {
|
|
902
|
-
let firstElement = this._tree.children[0]?.element;
|
|
903
|
-
return firstElement ? firstElement.querySelector(`:scope > .${this._classNames.element} > span.${this._classNames.title}`) : null;
|
|
967
|
+
_getFirstTreeItem() {
|
|
968
|
+
return this._tree.children[0]?.element ?? null;
|
|
904
969
|
}
|
|
905
970
|
|
|
906
971
|
// The node of the HTML element that has the focus, if it is in this tree
|
|
907
972
|
_getFocusedNode() {
|
|
908
973
|
let activeElement = document.activeElement;
|
|
909
974
|
|
|
910
|
-
|
|
911
|
-
if (!activeElement) {
|
|
912
|
-
return null;
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
// The keyboard must still work for input elements.
|
|
916
|
-
let tagName = activeElement.tagName;
|
|
917
|
-
if (tagName !== "A" && tagName !== "SPAN") {
|
|
975
|
+
// An active element must exist and the keyboard must still work for input elements.
|
|
976
|
+
if (!activeElement || !["A", "LI", "SPAN"].includes(activeElement.tagName)) {
|
|
918
977
|
return null;
|
|
919
978
|
}
|
|
920
979
|
let node = this.getNode(activeElement);
|
|
@@ -930,6 +989,16 @@ class TreeElement {
|
|
|
930
989
|
_getNodeIdToBeSelected() {
|
|
931
990
|
return this._saveStateHandler._getNodeIdToBeSelected();
|
|
932
991
|
}
|
|
992
|
+
|
|
993
|
+
// With multiSelect, the focused node becomes the tab stop
|
|
994
|
+
_handleFocusIn = () => {
|
|
995
|
+
let node = this._getFocusedNode();
|
|
996
|
+
if (!node || node === this._activeNode) {
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
this._activeNode = node;
|
|
1000
|
+
this._updateTabStop();
|
|
1001
|
+
};
|
|
933
1002
|
_hasRenderedSelectedNode() {
|
|
934
1003
|
return this.getSelectedNodes().some(node => Boolean(node.element));
|
|
935
1004
|
}
|
|
@@ -953,11 +1022,17 @@ class TreeElement {
|
|
|
953
1022
|
this._isInitialized = true;
|
|
954
1023
|
this._triggerEvent("tree.init");
|
|
955
1024
|
};
|
|
1025
|
+
|
|
1026
|
+
// Before the tree is replaced: then the focused node is no longer in it
|
|
1027
|
+
let focusedNode = this._getFocusedNode();
|
|
956
1028
|
this._tree = new this._options.nodeClass(null, true, this._options.nodeClass);
|
|
957
1029
|
this._selectNodeHandler._clear();
|
|
958
1030
|
this._tree.loadFromData(data);
|
|
959
1031
|
let mustLoadOnDemand = this._setInitialState();
|
|
960
1032
|
this._refreshElements(null);
|
|
1033
|
+
if (focusedNode) {
|
|
1034
|
+
this._restoreFocus(focusedNode);
|
|
1035
|
+
}
|
|
961
1036
|
if (mustLoadOnDemand) {
|
|
962
1037
|
// Load data on demand and then init the tree
|
|
963
1038
|
void this._setInitialStateOnDemand().then(doInit);
|
|
@@ -965,15 +1040,6 @@ class TreeElement {
|
|
|
965
1040
|
doInit();
|
|
966
1041
|
}
|
|
967
1042
|
}
|
|
968
|
-
|
|
969
|
-
// Does an HTML element of the tree have the focus?
|
|
970
|
-
_isFocusOnTree() {
|
|
971
|
-
return this._getFocusedNode() !== null;
|
|
972
|
-
}
|
|
973
|
-
_isSelectedNodeInSubtree(subtree) {
|
|
974
|
-
let selectedNode = this.getSelectedNode();
|
|
975
|
-
return selectedNode ? subtree === selectedNode || subtree.isParentOf(selectedNode) : false;
|
|
976
|
-
}
|
|
977
1043
|
async _loadFolderOnDemand(node, slide) {
|
|
978
1044
|
node.is_loading = true;
|
|
979
1045
|
await this.loadDataFromUrl(undefined, node);
|
|
@@ -1006,7 +1072,6 @@ class TreeElement {
|
|
|
1006
1072
|
}
|
|
1007
1073
|
_mouseStart(positionInfo) {
|
|
1008
1074
|
/* istanbul ignore if */
|
|
1009
|
-
// eslint-disable-next-line unicorn/prefer-ternary
|
|
1010
1075
|
if (!this._options.dragAndDrop) {
|
|
1011
1076
|
return false;
|
|
1012
1077
|
}
|
|
@@ -1015,10 +1080,10 @@ class TreeElement {
|
|
|
1015
1080
|
_mouseStop(positionInfo) {
|
|
1016
1081
|
/* istanbul ignore if */
|
|
1017
1082
|
if (!this._options.dragAndDrop) {
|
|
1018
|
-
return
|
|
1083
|
+
return;
|
|
1019
1084
|
}
|
|
1020
1085
|
this._scrollHandler._stopScrolling();
|
|
1021
|
-
|
|
1086
|
+
this._dndHandler._mouseStop(positionInfo);
|
|
1022
1087
|
}
|
|
1023
1088
|
_openParents(node) {
|
|
1024
1089
|
let parent = node.parent;
|
|
@@ -1032,25 +1097,32 @@ class TreeElement {
|
|
|
1032
1097
|
from_node: redraw this subtree
|
|
1033
1098
|
*/
|
|
1034
1099
|
_refreshElements(fromNode) {
|
|
1035
|
-
let
|
|
1036
|
-
|
|
1100
|
+
let focusedNode = this._getFocusedNode();
|
|
1101
|
+
|
|
1102
|
+
// The renderer marks the selected nodes, so only the focus needs restoring
|
|
1037
1103
|
this._renderer._render(fromNode);
|
|
1038
|
-
if (
|
|
1039
|
-
this.
|
|
1104
|
+
if (focusedNode) {
|
|
1105
|
+
this._restoreFocus(focusedNode);
|
|
1040
1106
|
}
|
|
1041
1107
|
this._updateTabStop();
|
|
1042
1108
|
this._triggerEvent("tree.refresh");
|
|
1043
1109
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
if (
|
|
1110
|
+
|
|
1111
|
+
// Rendering replaces the elements of the nodes, so a focused element can be
|
|
1112
|
+
// gone. Then move the focus to the new element of the same node. After
|
|
1113
|
+
// loadData the nodes are new objects, so look the node up by its id.
|
|
1114
|
+
_restoreFocus(focusedNode) {
|
|
1115
|
+
if (this._htmlElement.contains(document.activeElement)) {
|
|
1050
1116
|
return;
|
|
1051
1117
|
}
|
|
1052
|
-
let
|
|
1053
|
-
|
|
1118
|
+
let node = (focusedNode.id == null ? null : this.getNodeById(focusedNode.id)) ?? focusedNode;
|
|
1119
|
+
let element = node.element;
|
|
1120
|
+
if (element && this._htmlElement.contains(element)) {
|
|
1121
|
+
element.focus();
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
_saveState() {
|
|
1125
|
+
this._saveStateHandler._saveState();
|
|
1054
1126
|
}
|
|
1055
1127
|
|
|
1056
1128
|
// Set initial state, either by restoring the state or auto-opening nodes
|
|
@@ -1105,6 +1177,25 @@ class TreeElement {
|
|
|
1105
1177
|
}
|
|
1106
1178
|
}
|
|
1107
1179
|
|
|
1180
|
+
/* Change multiSelect after construction. The attributes of the nodes
|
|
1181
|
+
* depend on it, so the tree is rendered again. The selection is kept: a
|
|
1182
|
+
* tree without multiSelect can also have several selected nodes, see
|
|
1183
|
+
* addToSelection.
|
|
1184
|
+
*/
|
|
1185
|
+
_setMultiSelect(multiSelect) {
|
|
1186
|
+
let focusedNode = this._getFocusedNode();
|
|
1187
|
+
this._keyHandler._setMultiSelect(multiSelect);
|
|
1188
|
+
this._renderer._setMultiSelect(multiSelect);
|
|
1189
|
+
if (multiSelect) {
|
|
1190
|
+
this._htmlElement.addEventListener("focusin", this._handleFocusIn);
|
|
1191
|
+
} else {
|
|
1192
|
+
this._htmlElement.removeEventListener("focusin", this._handleFocusIn);
|
|
1193
|
+
}
|
|
1194
|
+
this._activeNode = multiSelect ? focusedNode : null;
|
|
1195
|
+
this._fallbackTabStop = null;
|
|
1196
|
+
this._refreshElements(null);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1108
1199
|
// Set this HTML element to this node in the node map.
|
|
1109
1200
|
_setNodeElement(element, node) {
|
|
1110
1201
|
this._nodeMap.set(element, node);
|
|
@@ -1116,12 +1207,22 @@ class TreeElement {
|
|
|
1116
1207
|
/* Keyboard users reach the tree through the tab stop. The selected node is
|
|
1117
1208
|
* the tab stop, see NodeElement.select. Without a selected node, the first
|
|
1118
1209
|
* node is the tab stop, so the tree can always be reached.
|
|
1210
|
+
* With multiSelect the tab stop is the active node: the node that was
|
|
1211
|
+
* focused last. Without one, it is the first node.
|
|
1119
1212
|
*/
|
|
1120
1213
|
_updateTabStop() {
|
|
1121
1214
|
let tabIndex = this._options.tabIndex;
|
|
1215
|
+
let multiSelect = this._options.multiSelect;
|
|
1122
1216
|
let previousTabStop = this._fallbackTabStop;
|
|
1123
|
-
let tabStop
|
|
1124
|
-
if (
|
|
1217
|
+
let tabStop;
|
|
1218
|
+
if (tabIndex == null) {
|
|
1219
|
+
tabStop = null;
|
|
1220
|
+
} else if (multiSelect) {
|
|
1221
|
+
tabStop = this._getActiveNode()?.element ?? this._getFirstTreeItem();
|
|
1222
|
+
} else {
|
|
1223
|
+
tabStop = this._hasRenderedSelectedNode() ? null : this._getFirstTreeItem();
|
|
1224
|
+
}
|
|
1225
|
+
if (previousTabStop && previousTabStop !== tabStop && (multiSelect || previousTabStop.getAttribute("aria-selected") !== "true")) {
|
|
1125
1226
|
previousTabStop.setAttribute("tabindex", "-1");
|
|
1126
1227
|
}
|
|
1127
1228
|
if (tabStop && tabIndex != null) {
|