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/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ClassNames } from "./classNames";
|
|
|
2
2
|
import type { TreeEventName, TreeEvents } from "./events";
|
|
3
3
|
import type { PositionInfo } from "./mouseUtils";
|
|
4
4
|
import type { NodeData, NodeId, Position } from "./node";
|
|
5
|
-
import type { TreeElementOptions } from "./options";
|
|
5
|
+
import type { ChangeableOption, TreeElementOptions } from "./options";
|
|
6
6
|
import type { SavedState } from "./saveStateHandler";
|
|
7
7
|
import type { SelectNodeOptions } from "./selectNodeHandler";
|
|
8
8
|
|
|
@@ -15,6 +15,7 @@ import MouseHandler from "./mouseHandler";
|
|
|
15
15
|
import { Node } from "./node";
|
|
16
16
|
import NodeElement from "./nodeElement";
|
|
17
17
|
import FolderElement from "./nodeElement/folderElement";
|
|
18
|
+
import { changeableOptions } from "./options";
|
|
18
19
|
import { getOffsetTop } from "./positionUtils";
|
|
19
20
|
import RequestUrl from "./requestUrl";
|
|
20
21
|
import SaveStateHandler from "./saveStateHandler";
|
|
@@ -47,6 +48,8 @@ interface TreeElementParams extends Partial<TreeElementOptions> {
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
export default class TreeElement {
|
|
51
|
+
// With multiSelect: the node that is the tab stop, see updateTabStop
|
|
52
|
+
private activeNode: Node | null;
|
|
50
53
|
private classNames: ClassNames;
|
|
51
54
|
private dataLoader: DataLoader;
|
|
52
55
|
private dndHandler: DragAndDropHandler;
|
|
@@ -82,6 +85,7 @@ export default class TreeElement {
|
|
|
82
85
|
this.tree = new Node({}, true);
|
|
83
86
|
this.nodeMap = new WeakMap();
|
|
84
87
|
this.fallbackTabStop = null;
|
|
88
|
+
this.activeNode = null;
|
|
85
89
|
|
|
86
90
|
const {
|
|
87
91
|
ariaLabel,
|
|
@@ -92,6 +96,7 @@ export default class TreeElement {
|
|
|
92
96
|
dataFilter,
|
|
93
97
|
dragAndDrop,
|
|
94
98
|
keyboardSupport,
|
|
99
|
+
multiSelect,
|
|
95
100
|
onCanMove,
|
|
96
101
|
onCanMoveTo,
|
|
97
102
|
onCreateLi,
|
|
@@ -105,7 +110,6 @@ export default class TreeElement {
|
|
|
105
110
|
rtl,
|
|
106
111
|
saveState: saveStateOption,
|
|
107
112
|
showEmptyFolder,
|
|
108
|
-
slide,
|
|
109
113
|
tabIndex,
|
|
110
114
|
} = this.options;
|
|
111
115
|
|
|
@@ -153,6 +157,19 @@ export default class TreeElement {
|
|
|
153
157
|
const selectNode =
|
|
154
158
|
selectNodeHandler.selectSingleNode.bind(selectNodeHandler);
|
|
155
159
|
|
|
160
|
+
const toggleNodeInSelection =
|
|
161
|
+
selectNodeHandler.toggleNodeInSelection.bind(selectNodeHandler);
|
|
162
|
+
|
|
163
|
+
// With multiSelect, a click adds a node to the selection or removes it
|
|
164
|
+
const onClickTitle = (node: Node) => {
|
|
165
|
+
if (this.options.multiSelect) {
|
|
166
|
+
toggleNodeInSelection(node, false);
|
|
167
|
+
this.focusNode(node);
|
|
168
|
+
} else {
|
|
169
|
+
selectNode(node);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
156
173
|
const getMouseDelay = () => this.options.startDndDelay ?? 0;
|
|
157
174
|
|
|
158
175
|
const dataLoader = new DataLoader({
|
|
@@ -198,19 +215,22 @@ export default class TreeElement {
|
|
|
198
215
|
openFolderDelay,
|
|
199
216
|
openNode,
|
|
200
217
|
refreshElements,
|
|
201
|
-
slide,
|
|
202
218
|
treeElement,
|
|
203
219
|
triggerEvent,
|
|
204
220
|
});
|
|
205
221
|
|
|
206
222
|
const keyHandler = new KeyHandler({
|
|
207
223
|
closeNode,
|
|
224
|
+
focusNode: this.focusNode.bind(this),
|
|
225
|
+
getActiveNode: this.getActiveNode.bind(this),
|
|
208
226
|
getFocusedNode,
|
|
209
227
|
getSelectedNode,
|
|
210
228
|
getTree,
|
|
211
229
|
keyboardSupport,
|
|
230
|
+
multiSelect,
|
|
212
231
|
openNode,
|
|
213
232
|
selectNode,
|
|
233
|
+
toggleNodeInSelection,
|
|
214
234
|
triggerEvent,
|
|
215
235
|
});
|
|
216
236
|
|
|
@@ -225,6 +245,7 @@ export default class TreeElement {
|
|
|
225
245
|
getTree,
|
|
226
246
|
htmlElement: treeElement,
|
|
227
247
|
isNodeSelected,
|
|
248
|
+
multiSelect,
|
|
228
249
|
onCreateLi,
|
|
229
250
|
openedIcon,
|
|
230
251
|
rtl,
|
|
@@ -245,7 +266,7 @@ export default class TreeElement {
|
|
|
245
266
|
getMouseDelay,
|
|
246
267
|
getNode,
|
|
247
268
|
onClickButton: this.toggle.bind(this),
|
|
248
|
-
onClickTitle
|
|
269
|
+
onClickTitle,
|
|
249
270
|
onMouseCapture,
|
|
250
271
|
onMouseDrag,
|
|
251
272
|
onMouseStart,
|
|
@@ -263,6 +284,10 @@ export default class TreeElement {
|
|
|
263
284
|
this.scrollHandler = scrollHandler;
|
|
264
285
|
this.selectNodeHandler = selectNodeHandler;
|
|
265
286
|
|
|
287
|
+
if (multiSelect) {
|
|
288
|
+
treeElement.addEventListener("focusin", this.handleFocusIn);
|
|
289
|
+
}
|
|
290
|
+
|
|
266
291
|
this.initData();
|
|
267
292
|
}
|
|
268
293
|
|
|
@@ -299,8 +324,9 @@ export default class TreeElement {
|
|
|
299
324
|
}
|
|
300
325
|
|
|
301
326
|
/**
|
|
302
|
-
* Inserts a new node between a node and its parent
|
|
303
|
-
*
|
|
327
|
+
* Inserts a new node between a node and its parent. The siblings of the node
|
|
328
|
+
* move along: the new node takes all children of the parent, and becomes its
|
|
329
|
+
* only child.
|
|
304
330
|
*
|
|
305
331
|
* @returns The new node, or `null` when the node has no parent.
|
|
306
332
|
* @group Changing the tree
|
|
@@ -388,6 +414,7 @@ export default class TreeElement {
|
|
|
388
414
|
this.dataLoader.deinit();
|
|
389
415
|
this.keyHandler.deinit();
|
|
390
416
|
this.mouseHandler.deinit();
|
|
417
|
+
this.htmlElement.removeEventListener("focusin", this.handleFocusIn);
|
|
391
418
|
|
|
392
419
|
this.tree = new Node({}, true);
|
|
393
420
|
}
|
|
@@ -485,7 +512,7 @@ export default class TreeElement {
|
|
|
485
512
|
}
|
|
486
513
|
|
|
487
514
|
/**
|
|
488
|
-
* Returns the current state
|
|
515
|
+
* Returns the current state—`open_nodes` and `selected_node`—whether or
|
|
489
516
|
* not `saveState` is enabled.
|
|
490
517
|
*
|
|
491
518
|
* @group State
|
|
@@ -504,7 +531,7 @@ export default class TreeElement {
|
|
|
504
531
|
}
|
|
505
532
|
|
|
506
533
|
/**
|
|
507
|
-
* Returns the root node. It is not rendered
|
|
534
|
+
* Returns the root node. It is not rendered, and its `children` are the
|
|
508
535
|
* top-level nodes. Also available as the `tree` property.
|
|
509
536
|
*
|
|
510
537
|
* @group Finding nodes
|
|
@@ -584,7 +611,8 @@ export default class TreeElement {
|
|
|
584
611
|
}
|
|
585
612
|
|
|
586
613
|
/**
|
|
587
|
-
* Selects the next visible node, like the down arrow key.
|
|
614
|
+
* Selects the next visible node, like the down arrow key. With
|
|
615
|
+
* `multiSelect` it moves the focus instead.
|
|
588
616
|
*
|
|
589
617
|
* @group Selection
|
|
590
618
|
*/
|
|
@@ -609,7 +637,8 @@ export default class TreeElement {
|
|
|
609
637
|
}
|
|
610
638
|
|
|
611
639
|
/**
|
|
612
|
-
* Selects the previous visible node, like the up arrow key.
|
|
640
|
+
* Selects the previous visible node, like the up arrow key. With
|
|
641
|
+
* `multiSelect` it moves the focus instead.
|
|
613
642
|
*
|
|
614
643
|
* @group Selection
|
|
615
644
|
*/
|
|
@@ -618,7 +647,7 @@ export default class TreeElement {
|
|
|
618
647
|
}
|
|
619
648
|
|
|
620
649
|
/**
|
|
621
|
-
* Opens a folder, and the folders
|
|
650
|
+
* Opens a folder, and the folders that contain it. A node marked `load_on_demand`
|
|
622
651
|
* is fetched first, so await the promise when you need to know it is really
|
|
623
652
|
* open.
|
|
624
653
|
*
|
|
@@ -638,7 +667,7 @@ export default class TreeElement {
|
|
|
638
667
|
openedNode: Node,
|
|
639
668
|
slideOption: boolean,
|
|
640
669
|
): Promise<void> => {
|
|
641
|
-
if (!
|
|
670
|
+
if (!openedNode.children.length) {
|
|
642
671
|
return;
|
|
643
672
|
}
|
|
644
673
|
|
|
@@ -754,8 +783,8 @@ export default class TreeElement {
|
|
|
754
783
|
*/
|
|
755
784
|
public selectNode(node: Node | null, options?: SelectNodeOptions): void {
|
|
756
785
|
if (!node) {
|
|
757
|
-
// Called with empty node -> deselect
|
|
758
|
-
this.
|
|
786
|
+
// Called with empty node -> deselect all nodes
|
|
787
|
+
this.deselectAllNodes();
|
|
759
788
|
this.saveStateHandler.saveState();
|
|
760
789
|
return;
|
|
761
790
|
}
|
|
@@ -768,13 +797,47 @@ export default class TreeElement {
|
|
|
768
797
|
}
|
|
769
798
|
|
|
770
799
|
/**
|
|
771
|
-
* Changes an option after construction.
|
|
772
|
-
*
|
|
800
|
+
* Changes an option after construction. Only some options can be changed:
|
|
801
|
+
* the others throw an error. See
|
|
802
|
+
* [Options](/reference/options#changing-an-option-later).
|
|
773
803
|
*
|
|
774
804
|
* @group Other
|
|
775
805
|
*/
|
|
776
|
-
public setOption(option:
|
|
806
|
+
public setOption(option: ChangeableOption, value: unknown) {
|
|
807
|
+
if (!(changeableOptions as readonly string[]).includes(option)) {
|
|
808
|
+
throw new Error(
|
|
809
|
+
`The option ${option} can only be set in the constructor`,
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
|
|
777
813
|
(this.options as unknown as Record<string, unknown>)[option] = value;
|
|
814
|
+
|
|
815
|
+
switch (option) {
|
|
816
|
+
case "dragAndDrop": {
|
|
817
|
+
this.renderer.setDragAndDrop(Boolean(value));
|
|
818
|
+
|
|
819
|
+
break;
|
|
820
|
+
}
|
|
821
|
+
case "keyboardSupport": {
|
|
822
|
+
this.keyHandler.setKeyboardSupport(Boolean(value));
|
|
823
|
+
|
|
824
|
+
break;
|
|
825
|
+
}
|
|
826
|
+
case "multiSelect": {
|
|
827
|
+
this.setMultiSelect(Boolean(value));
|
|
828
|
+
|
|
829
|
+
break;
|
|
830
|
+
}
|
|
831
|
+
case "tabIndex": {
|
|
832
|
+
this.options.tabIndex = (value as null | number) ?? undefined;
|
|
833
|
+
this.renderer.setTabIndex(this.options.tabIndex);
|
|
834
|
+
this.fallbackTabStop = null;
|
|
835
|
+
this.updateTabStop();
|
|
836
|
+
|
|
837
|
+
break;
|
|
838
|
+
}
|
|
839
|
+
// No default
|
|
840
|
+
}
|
|
778
841
|
}
|
|
779
842
|
|
|
780
843
|
/**
|
|
@@ -808,8 +871,8 @@ export default class TreeElement {
|
|
|
808
871
|
}
|
|
809
872
|
|
|
810
873
|
/**
|
|
811
|
-
* Returns the tree as a
|
|
812
|
-
*
|
|
874
|
+
* Returns the tree as a JSON string, including changes made through the
|
|
875
|
+
* API.
|
|
813
876
|
*
|
|
814
877
|
* @group Other
|
|
815
878
|
*/
|
|
@@ -822,7 +885,7 @@ export default class TreeElement {
|
|
|
822
885
|
* name.
|
|
823
886
|
*
|
|
824
887
|
* Changing the `id` re-indexes the node. `children` and `parent` are
|
|
825
|
-
* ignored
|
|
888
|
+
* ignored—use `loadData` or `moveNode` for those.
|
|
826
889
|
*
|
|
827
890
|
* @example
|
|
828
891
|
* ```js
|
|
@@ -891,11 +954,9 @@ export default class TreeElement {
|
|
|
891
954
|
|
|
892
955
|
private createFolderElement(node: Node) {
|
|
893
956
|
const classNames = this.classNames;
|
|
894
|
-
const closedIconElement = this.renderer.closedIconElement;
|
|
895
957
|
const getScrollLeft = this.scrollHandler.getScrollLeft.bind(
|
|
896
958
|
this.scrollHandler,
|
|
897
959
|
);
|
|
898
|
-
const openedIconElement = this.renderer.openedIconElement;
|
|
899
960
|
const tabIndex = this.options.tabIndex;
|
|
900
961
|
const treeElement = this.htmlElement;
|
|
901
962
|
const triggerEvent = this.triggerEvent.bind(this);
|
|
@@ -911,10 +972,10 @@ export default class TreeElement {
|
|
|
911
972
|
|
|
912
973
|
return new FolderElement({
|
|
913
974
|
classNames,
|
|
914
|
-
closedIconElement,
|
|
915
975
|
getScrollLeft,
|
|
976
|
+
icons: this.renderer.icons,
|
|
977
|
+
multiSelect: this.options.multiSelect,
|
|
916
978
|
node,
|
|
917
|
-
openedIconElement,
|
|
918
979
|
renderChildren,
|
|
919
980
|
tabIndex,
|
|
920
981
|
treeElement,
|
|
@@ -933,6 +994,7 @@ export default class TreeElement {
|
|
|
933
994
|
return new NodeElement({
|
|
934
995
|
classNames,
|
|
935
996
|
getScrollLeft,
|
|
997
|
+
multiSelect: this.options.multiSelect,
|
|
936
998
|
node,
|
|
937
999
|
tabIndex,
|
|
938
1000
|
treeElement,
|
|
@@ -968,9 +1030,8 @@ export default class TreeElement {
|
|
|
968
1030
|
return requestUrl;
|
|
969
1031
|
}
|
|
970
1032
|
|
|
971
|
-
private
|
|
972
|
-
const node
|
|
973
|
-
if (node) {
|
|
1033
|
+
private deselectAllNodes(): void {
|
|
1034
|
+
for (const node of this.getSelectedNodes()) {
|
|
974
1035
|
this.removeFromSelection(node);
|
|
975
1036
|
}
|
|
976
1037
|
}
|
|
@@ -984,6 +1045,23 @@ export default class TreeElement {
|
|
|
984
1045
|
}
|
|
985
1046
|
}
|
|
986
1047
|
|
|
1048
|
+
// The treeitem of the first node, or null when the tree is empty
|
|
1049
|
+
// Move the focus to a node, without selecting it
|
|
1050
|
+
private focusNode(node: Node): void {
|
|
1051
|
+
this.activeNode = node;
|
|
1052
|
+
this.updateTabStop();
|
|
1053
|
+
node.element?.focus();
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// With multiSelect: the active node, if it is still rendered in the tree
|
|
1057
|
+
private getActiveNode(): Node | null {
|
|
1058
|
+
const element = this.activeNode?.element;
|
|
1059
|
+
|
|
1060
|
+
return element && this.htmlElement.contains(element)
|
|
1061
|
+
? this.activeNode
|
|
1062
|
+
: null;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
987
1065
|
// Get the maximum level for auto open
|
|
988
1066
|
private getAutoOpenMaxLevel(): number {
|
|
989
1067
|
if (this.options.autoOpen === true) {
|
|
@@ -1000,29 +1078,19 @@ export default class TreeElement {
|
|
|
1000
1078
|
return 0;
|
|
1001
1079
|
}
|
|
1002
1080
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
const firstElement = this.tree.children[0]?.element;
|
|
1006
|
-
|
|
1007
|
-
return firstElement
|
|
1008
|
-
? firstElement.querySelector<HTMLElement>(
|
|
1009
|
-
`:scope > .${this.classNames.element} > span.${this.classNames.title}`,
|
|
1010
|
-
)
|
|
1011
|
-
: null;
|
|
1081
|
+
private getFirstTreeItem(): HTMLElement | null {
|
|
1082
|
+
return this.tree.children[0]?.element ?? null;
|
|
1012
1083
|
}
|
|
1013
1084
|
|
|
1014
1085
|
// The node of the HTML element that has the focus, if it is in this tree
|
|
1015
1086
|
private getFocusedNode(): Node | null {
|
|
1016
1087
|
const activeElement = document.activeElement;
|
|
1017
1088
|
|
|
1018
|
-
|
|
1019
|
-
if (
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
// The keyboard must still work for input elements.
|
|
1024
|
-
const tagName = activeElement.tagName;
|
|
1025
|
-
if (tagName !== "A" && tagName !== "SPAN") {
|
|
1089
|
+
// An active element must exist and the keyboard must still work for input elements.
|
|
1090
|
+
if (
|
|
1091
|
+
!activeElement ||
|
|
1092
|
+
!["A", "LI", "SPAN"].includes(activeElement.tagName)
|
|
1093
|
+
) {
|
|
1026
1094
|
return null;
|
|
1027
1095
|
}
|
|
1028
1096
|
|
|
@@ -1045,6 +1113,18 @@ export default class TreeElement {
|
|
|
1045
1113
|
return this.saveStateHandler.getNodeIdToBeSelected();
|
|
1046
1114
|
}
|
|
1047
1115
|
|
|
1116
|
+
// With multiSelect, the focused node becomes the tab stop
|
|
1117
|
+
private handleFocusIn = (): void => {
|
|
1118
|
+
const node = this.getFocusedNode();
|
|
1119
|
+
|
|
1120
|
+
if (!node || node === this.activeNode) {
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
this.activeNode = node;
|
|
1125
|
+
this.updateTabStop();
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1048
1128
|
private hasRenderedSelectedNode(): boolean {
|
|
1049
1129
|
return this.getSelectedNodes().some((node) => Boolean(node.element));
|
|
1050
1130
|
}
|
|
@@ -1073,6 +1153,9 @@ export default class TreeElement {
|
|
|
1073
1153
|
this.triggerEvent("tree.init");
|
|
1074
1154
|
};
|
|
1075
1155
|
|
|
1156
|
+
// Before the tree is replaced: then the focused node is no longer in it
|
|
1157
|
+
const focusedNode = this.getFocusedNode();
|
|
1158
|
+
|
|
1076
1159
|
this.tree = new this.options.nodeClass(null, true, this.options.nodeClass);
|
|
1077
1160
|
|
|
1078
1161
|
this.selectNodeHandler.clear();
|
|
@@ -1083,6 +1166,10 @@ export default class TreeElement {
|
|
|
1083
1166
|
|
|
1084
1167
|
this.refreshElements(null);
|
|
1085
1168
|
|
|
1169
|
+
if (focusedNode) {
|
|
1170
|
+
this.restoreFocus(focusedNode);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1086
1173
|
if (mustLoadOnDemand) {
|
|
1087
1174
|
// Load data on demand and then init the tree
|
|
1088
1175
|
void this.setInitialStateOnDemand().then(doInit);
|
|
@@ -1091,19 +1178,6 @@ export default class TreeElement {
|
|
|
1091
1178
|
}
|
|
1092
1179
|
}
|
|
1093
1180
|
|
|
1094
|
-
// Does an HTML element of the tree have the focus?
|
|
1095
|
-
private isFocusOnTree(): boolean {
|
|
1096
|
-
return this.getFocusedNode() !== null;
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
private isSelectedNodeInSubtree(subtree: Node): boolean {
|
|
1100
|
-
const selectedNode = this.getSelectedNode();
|
|
1101
|
-
|
|
1102
|
-
return selectedNode
|
|
1103
|
-
? subtree === selectedNode || subtree.isParentOf(selectedNode)
|
|
1104
|
-
: false;
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
1181
|
private async loadFolderOnDemand(node: Node, slide: boolean): Promise<void> {
|
|
1108
1182
|
node.is_loading = true;
|
|
1109
1183
|
|
|
@@ -1147,7 +1221,6 @@ export default class TreeElement {
|
|
|
1147
1221
|
|
|
1148
1222
|
private mouseStart(positionInfo: PositionInfo): boolean {
|
|
1149
1223
|
/* istanbul ignore if */
|
|
1150
|
-
// eslint-disable-next-line unicorn/prefer-ternary
|
|
1151
1224
|
if (!this.options.dragAndDrop) {
|
|
1152
1225
|
return false;
|
|
1153
1226
|
}
|
|
@@ -1155,14 +1228,14 @@ export default class TreeElement {
|
|
|
1155
1228
|
return this.dndHandler.mouseStart(positionInfo);
|
|
1156
1229
|
}
|
|
1157
1230
|
|
|
1158
|
-
private mouseStop(positionInfo: PositionInfo):
|
|
1231
|
+
private mouseStop(positionInfo: PositionInfo): void {
|
|
1159
1232
|
/* istanbul ignore if */
|
|
1160
1233
|
if (!this.options.dragAndDrop) {
|
|
1161
|
-
return
|
|
1234
|
+
return;
|
|
1162
1235
|
}
|
|
1163
1236
|
|
|
1164
1237
|
this.scrollHandler.stopScrolling();
|
|
1165
|
-
|
|
1238
|
+
this.dndHandler.mouseStop(positionInfo);
|
|
1166
1239
|
}
|
|
1167
1240
|
|
|
1168
1241
|
private openParents(node: Node) {
|
|
@@ -1178,15 +1251,13 @@ export default class TreeElement {
|
|
|
1178
1251
|
from_node: redraw this subtree
|
|
1179
1252
|
*/
|
|
1180
1253
|
private refreshElements(fromNode: Node | null): void {
|
|
1181
|
-
const
|
|
1182
|
-
const mustSelect = fromNode
|
|
1183
|
-
? this.isSelectedNodeInSubtree(fromNode)
|
|
1184
|
-
: false;
|
|
1254
|
+
const focusedNode = this.getFocusedNode();
|
|
1185
1255
|
|
|
1256
|
+
// The renderer marks the selected nodes, so only the focus needs restoring
|
|
1186
1257
|
this.renderer.render(fromNode);
|
|
1187
1258
|
|
|
1188
|
-
if (
|
|
1189
|
-
this.
|
|
1259
|
+
if (focusedNode) {
|
|
1260
|
+
this.restoreFocus(focusedNode);
|
|
1190
1261
|
}
|
|
1191
1262
|
|
|
1192
1263
|
this.updateTabStop();
|
|
@@ -1194,18 +1265,26 @@ export default class TreeElement {
|
|
|
1194
1265
|
this.triggerEvent("tree.refresh");
|
|
1195
1266
|
}
|
|
1196
1267
|
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
const node = this.getSelectedNode();
|
|
1203
|
-
if (!node) {
|
|
1268
|
+
// Rendering replaces the elements of the nodes, so a focused element can be
|
|
1269
|
+
// gone. Then move the focus to the new element of the same node. After
|
|
1270
|
+
// loadData the nodes are new objects, so look the node up by its id.
|
|
1271
|
+
private restoreFocus(focusedNode: Node): void {
|
|
1272
|
+
if (this.htmlElement.contains(document.activeElement)) {
|
|
1204
1273
|
return;
|
|
1205
1274
|
}
|
|
1206
1275
|
|
|
1207
|
-
const
|
|
1208
|
-
|
|
1276
|
+
const node =
|
|
1277
|
+
(focusedNode.id == null ? null : this.getNodeById(focusedNode.id)) ??
|
|
1278
|
+
focusedNode;
|
|
1279
|
+
const element = node.element;
|
|
1280
|
+
|
|
1281
|
+
if (element && this.htmlElement.contains(element)) {
|
|
1282
|
+
element.focus();
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
private saveState(): void {
|
|
1287
|
+
this.saveStateHandler.saveState();
|
|
1209
1288
|
}
|
|
1210
1289
|
|
|
1211
1290
|
// Set initial state, either by restoring the state or auto-opening nodes
|
|
@@ -1268,6 +1347,28 @@ export default class TreeElement {
|
|
|
1268
1347
|
}
|
|
1269
1348
|
}
|
|
1270
1349
|
|
|
1350
|
+
/* Change multiSelect after construction. The attributes of the nodes
|
|
1351
|
+
* depend on it, so the tree is rendered again. The selection is kept: a
|
|
1352
|
+
* tree without multiSelect can also have several selected nodes, see
|
|
1353
|
+
* addToSelection.
|
|
1354
|
+
*/
|
|
1355
|
+
private setMultiSelect(multiSelect: boolean): void {
|
|
1356
|
+
const focusedNode = this.getFocusedNode();
|
|
1357
|
+
|
|
1358
|
+
this.keyHandler.setMultiSelect(multiSelect);
|
|
1359
|
+
this.renderer.setMultiSelect(multiSelect);
|
|
1360
|
+
|
|
1361
|
+
if (multiSelect) {
|
|
1362
|
+
this.htmlElement.addEventListener("focusin", this.handleFocusIn);
|
|
1363
|
+
} else {
|
|
1364
|
+
this.htmlElement.removeEventListener("focusin", this.handleFocusIn);
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
this.activeNode = multiSelect ? focusedNode : null;
|
|
1368
|
+
this.fallbackTabStop = null;
|
|
1369
|
+
this.refreshElements(null);
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1271
1372
|
// Set this HTML element to this node in the node map.
|
|
1272
1373
|
private setNodeElement(element: HTMLElement, node: Node) {
|
|
1273
1374
|
this.nodeMap.set(element, node);
|
|
@@ -1283,19 +1384,27 @@ export default class TreeElement {
|
|
|
1283
1384
|
/* Keyboard users reach the tree through the tab stop. The selected node is
|
|
1284
1385
|
* the tab stop, see NodeElement.select. Without a selected node, the first
|
|
1285
1386
|
* node is the tab stop, so the tree can always be reached.
|
|
1387
|
+
* With multiSelect the tab stop is the active node: the node that was
|
|
1388
|
+
* focused last. Without one, it is the first node.
|
|
1286
1389
|
*/
|
|
1287
1390
|
private updateTabStop(): void {
|
|
1288
1391
|
const tabIndex = this.options.tabIndex;
|
|
1392
|
+
const multiSelect = this.options.multiSelect;
|
|
1289
1393
|
const previousTabStop = this.fallbackTabStop;
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1394
|
+
let tabStop: HTMLElement | null;
|
|
1395
|
+
|
|
1396
|
+
if (tabIndex == null) {
|
|
1397
|
+
tabStop = null;
|
|
1398
|
+
} else if (multiSelect) {
|
|
1399
|
+
tabStop = this.getActiveNode()?.element ?? this.getFirstTreeItem();
|
|
1400
|
+
} else {
|
|
1401
|
+
tabStop = this.hasRenderedSelectedNode() ? null : this.getFirstTreeItem();
|
|
1402
|
+
}
|
|
1294
1403
|
|
|
1295
1404
|
if (
|
|
1296
1405
|
previousTabStop &&
|
|
1297
1406
|
previousTabStop !== tabStop &&
|
|
1298
|
-
previousTabStop.getAttribute("aria-selected") !== "true"
|
|
1407
|
+
(multiSelect || previousTabStop.getAttribute("aria-selected") !== "true")
|
|
1299
1408
|
) {
|
|
1300
1409
|
previousTabStop.setAttribute("tabindex", "-1");
|
|
1301
1410
|
}
|
|
@@ -1315,6 +1424,6 @@ export {
|
|
|
1315
1424
|
type TreeEvents,
|
|
1316
1425
|
} from "./events";
|
|
1317
1426
|
export { type Node, type NodeData, type NodeId, type Position } from "./node";
|
|
1318
|
-
export { type TreeElementOptions } from "./options";
|
|
1427
|
+
export { type ChangeableOption, type TreeElementOptions } from "./options";
|
|
1319
1428
|
export { type SavedState } from "./saveStateHandler";
|
|
1320
1429
|
export { type SelectNodeOptions } from "./selectNodeHandler";
|