tree-element 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -6
- package/lib/animation.d.ts +2 -2
- package/lib/animation.d.ts.map +1 -1
- package/lib/animation.js +13 -9
- package/lib/animation.js.map +1 -1
- package/lib/dataLoader.d.ts +1 -0
- package/lib/dataLoader.d.ts.map +1 -1
- package/lib/dataLoader.js +8 -0
- package/lib/dataLoader.js.map +1 -1
- package/lib/dragAndDropHandler/dragElement.d.ts.map +1 -1
- package/lib/dragAndDropHandler/dragElement.js +2 -1
- package/lib/dragAndDropHandler/dragElement.js.map +1 -1
- package/lib/dragAndDropHandler/index.d.ts.map +1 -1
- package/lib/dragAndDropHandler/index.js +56 -65
- package/lib/dragAndDropHandler/index.js.map +1 -1
- package/lib/dragAndDropHandler/iterateVisibleNodes.d.ts.map +1 -1
- package/lib/dragAndDropHandler/iterateVisibleNodes.js +16 -15
- package/lib/dragAndDropHandler/iterateVisibleNodes.js.map +1 -1
- package/lib/elementsRenderer.d.ts +5 -1
- package/lib/elementsRenderer.d.ts.map +1 -1
- package/lib/elementsRenderer.js +62 -39
- package/lib/elementsRenderer.js.map +1 -1
- package/lib/events.d.ts +44 -11
- package/lib/events.d.ts.map +1 -1
- package/lib/index.d.ts +31 -8
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +162 -122
- package/lib/index.js.map +1 -1
- package/lib/keyHandler.d.ts +11 -5
- package/lib/keyHandler.d.ts.map +1 -1
- package/lib/keyHandler.js +87 -33
- package/lib/keyHandler.js.map +1 -1
- package/lib/methodTypes.d.ts +2 -2
- package/lib/methodTypes.d.ts.map +1 -1
- package/lib/mouseHandler.d.ts +1 -1
- package/lib/mouseHandler.d.ts.map +1 -1
- package/lib/mouseHandler.js +10 -7
- package/lib/mouseHandler.js.map +1 -1
- package/lib/node.d.ts +45 -15
- package/lib/node.d.ts.map +1 -1
- package/lib/node.js +136 -139
- package/lib/node.js.map +1 -1
- package/lib/nodeElement/folderElement.d.ts +1 -1
- package/lib/nodeElement/folderElement.d.ts.map +1 -1
- package/lib/nodeElement/folderElement.js +46 -57
- package/lib/nodeElement/folderElement.js.map +1 -1
- package/lib/nodeElement/ghostDropHint.d.ts.map +1 -1
- package/lib/nodeElement/ghostDropHint.js +8 -4
- package/lib/nodeElement/ghostDropHint.js.map +1 -1
- package/lib/nodeElement/index.d.ts.map +1 -1
- package/lib/nodeElement/index.js +6 -6
- package/lib/nodeElement/index.js.map +1 -1
- package/lib/nodeUtils.d.ts.map +1 -1
- package/lib/nodeUtils.js +1 -1
- package/lib/nodeUtils.js.map +1 -1
- package/lib/options.d.ts +23 -6
- package/lib/options.d.ts.map +1 -1
- package/lib/requestUrl.d.ts +0 -1
- package/lib/requestUrl.d.ts.map +1 -1
- package/lib/requestUrl.js +4 -22
- package/lib/requestUrl.js.map +1 -1
- package/lib/saveStateHandler.d.ts.map +1 -1
- package/lib/saveStateHandler.js +21 -41
- package/lib/saveStateHandler.js.map +1 -1
- package/lib/scrollHandler/containerScrollParent.d.ts.map +1 -1
- package/lib/scrollHandler/containerScrollParent.js +3 -8
- package/lib/scrollHandler/containerScrollParent.js.map +1 -1
- package/lib/scrollHandler/createScrollParent.d.ts.map +1 -1
- package/lib/scrollHandler/createScrollParent.js +4 -5
- package/lib/scrollHandler/createScrollParent.js.map +1 -1
- package/lib/scrollHandler/documentScrollParent.d.ts.map +1 -1
- package/lib/scrollHandler/documentScrollParent.js +3 -9
- package/lib/scrollHandler/documentScrollParent.js.map +1 -1
- package/lib/scrollHandler/scrollParent.d.ts.map +1 -1
- package/lib/scrollHandler/scrollParent.js +19 -17
- package/lib/scrollHandler/scrollParent.js.map +1 -1
- package/lib/selectNodeHandler.d.ts +3 -1
- package/lib/selectNodeHandler.d.ts.map +1 -1
- package/lib/selectNodeHandler.js +27 -37
- package/lib/selectNodeHandler.js.map +1 -1
- package/lib/setDefaultOptions.d.ts +3 -0
- package/lib/setDefaultOptions.d.ts.map +1 -1
- package/lib/setDefaultOptions.js +21 -20
- package/lib/setDefaultOptions.js.map +1 -1
- package/lib/util.d.ts.map +1 -1
- package/lib/util.js +1 -1
- package/lib/util.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 +44 -35
- package/src/animation.ts +13 -11
- package/src/dataLoader.ts +11 -0
- package/src/dragAndDropHandler/dragElement.ts +2 -1
- package/src/dragAndDropHandler/index.ts +70 -75
- package/src/dragAndDropHandler/iterateVisibleNodes.ts +18 -16
- package/src/elementsRenderer.ts +76 -46
- package/src/events.ts +44 -11
- package/src/index.ts +210 -150
- package/src/keyHandler.ts +95 -41
- package/src/methodTypes.ts +3 -3
- package/src/mouseHandler.ts +10 -7
- package/src/node.ts +155 -148
- package/src/nodeElement/folderElement.ts +54 -63
- package/src/nodeElement/ghostDropHint.ts +4 -2
- package/src/nodeElement/index.ts +9 -15
- package/src/nodeUtils.ts +1 -1
- package/src/options.ts +23 -6
- package/src/requestUrl.ts +4 -25
- package/src/saveStateHandler.ts +29 -43
- package/src/scrollHandler/containerScrollParent.ts +3 -9
- package/src/scrollHandler/createScrollParent.ts +1 -2
- package/src/scrollHandler/documentScrollParent.ts +5 -11
- package/src/scrollHandler/scrollParent.ts +29 -25
- package/src/selectNodeHandler.ts +34 -38
- package/src/setDefaultOptions.ts +31 -19
- package/src/util.ts +1 -2
- package/src/version.ts +1 -1
- package/tree_element.css +87 -69
- package/tree_element.debug.js +717 -659
- 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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ClassNames } from "./classNames";
|
|
2
|
-
import type {
|
|
2
|
+
import type { TreeEventName, TreeEvents } from "./events";
|
|
3
3
|
import type { PositionInfo } from "./mouseUtils";
|
|
4
4
|
import type { NodeData, NodeId, Position } from "./node";
|
|
5
5
|
import type { TreeElementOptions } from "./options";
|
|
@@ -28,19 +28,6 @@ import __version__ from "./version";
|
|
|
28
28
|
// the submodules directly, because those are not exposed in package.json.
|
|
29
29
|
// Type only, so that the iife build keeps exposing the TreeElement class itself
|
|
30
30
|
// as its global, instead of an object of named exports.
|
|
31
|
-
export type {
|
|
32
|
-
MoveInfo,
|
|
33
|
-
Node,
|
|
34
|
-
NodeData,
|
|
35
|
-
NodeId,
|
|
36
|
-
Position,
|
|
37
|
-
SavedState,
|
|
38
|
-
SelectNodeOptions,
|
|
39
|
-
TreeElementOptions,
|
|
40
|
-
TreeEvent,
|
|
41
|
-
TreeEventName,
|
|
42
|
-
TreeEvents,
|
|
43
|
-
};
|
|
44
31
|
|
|
45
32
|
export type TriggerEventProvider = (
|
|
46
33
|
element: HTMLElement,
|
|
@@ -49,7 +36,9 @@ export type TriggerEventProvider = (
|
|
|
49
36
|
) => boolean;
|
|
50
37
|
|
|
51
38
|
interface TreeElementParams extends Partial<TreeElementOptions> {
|
|
52
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
The element the tree is rendered into. Its content is replaced.
|
|
41
|
+
*/
|
|
53
42
|
htmlElement: HTMLElement;
|
|
54
43
|
/** Replaces how events are dispatched. It must return whether the event
|
|
55
44
|
* was not cancelled. This exists for tests and for integrating with
|
|
@@ -61,6 +50,7 @@ export default class TreeElement {
|
|
|
61
50
|
private classNames: ClassNames;
|
|
62
51
|
private dataLoader: DataLoader;
|
|
63
52
|
private dndHandler: DragAndDropHandler;
|
|
53
|
+
private fallbackTabStop: HTMLElement | null;
|
|
64
54
|
private htmlElement: HTMLElement;
|
|
65
55
|
private isInitialized: boolean;
|
|
66
56
|
private keyHandler: KeyHandler;
|
|
@@ -74,7 +64,9 @@ export default class TreeElement {
|
|
|
74
64
|
private tree: Node;
|
|
75
65
|
private triggerEventProvider: TriggerEventProvider;
|
|
76
66
|
|
|
77
|
-
/**
|
|
67
|
+
/**
|
|
68
|
+
@hidden
|
|
69
|
+
*/
|
|
78
70
|
constructor({
|
|
79
71
|
htmlElement,
|
|
80
72
|
overrideTriggerEventProvider,
|
|
@@ -89,8 +81,11 @@ export default class TreeElement {
|
|
|
89
81
|
this.isInitialized = false;
|
|
90
82
|
this.tree = new Node({}, true);
|
|
91
83
|
this.nodeMap = new WeakMap();
|
|
84
|
+
this.fallbackTabStop = null;
|
|
92
85
|
|
|
93
86
|
const {
|
|
87
|
+
ariaLabel,
|
|
88
|
+
ariaLabelledBy,
|
|
94
89
|
autoEscape,
|
|
95
90
|
buttonLeft,
|
|
96
91
|
closedIcon,
|
|
@@ -118,10 +113,10 @@ export default class TreeElement {
|
|
|
118
113
|
const closeNode = this.closeNode.bind(this);
|
|
119
114
|
const getNodeElement = this.getNodeElement.bind(this);
|
|
120
115
|
const getNodeElementForNode = this.getNodeElementForNode.bind(this);
|
|
116
|
+
const getFocusedNode = this.getFocusedNode.bind(this);
|
|
121
117
|
const getNodeById = this.getNodeById.bind(this);
|
|
122
118
|
const getSelectedNode = this.getSelectedNode.bind(this);
|
|
123
119
|
const getTree = this.getTree.bind(this);
|
|
124
|
-
const isFocusOnTree = this.isFocusOnTree.bind(this);
|
|
125
120
|
const loadData = this.loadData.bind(this);
|
|
126
121
|
const openNode = this.openNode.bind(this);
|
|
127
122
|
const openParents = this.openParents.bind(this);
|
|
@@ -130,6 +125,7 @@ export default class TreeElement {
|
|
|
130
125
|
const setNodeElement = this.setNodeElement.bind(this);
|
|
131
126
|
const treeElement = this.htmlElement;
|
|
132
127
|
const triggerEvent = this.triggerEvent.bind(this);
|
|
128
|
+
const updateTabStop = this.updateTabStop.bind(this);
|
|
133
129
|
|
|
134
130
|
const saveState = () => {
|
|
135
131
|
saveStateHandler.saveState();
|
|
@@ -143,6 +139,7 @@ export default class TreeElement {
|
|
|
143
139
|
openParents,
|
|
144
140
|
saveState,
|
|
145
141
|
triggerEvent,
|
|
142
|
+
updateTabStop,
|
|
146
143
|
});
|
|
147
144
|
|
|
148
145
|
const addToSelection =
|
|
@@ -208,14 +205,18 @@ export default class TreeElement {
|
|
|
208
205
|
|
|
209
206
|
const keyHandler = new KeyHandler({
|
|
210
207
|
closeNode,
|
|
208
|
+
getFocusedNode,
|
|
211
209
|
getSelectedNode,
|
|
212
|
-
|
|
210
|
+
getTree,
|
|
213
211
|
keyboardSupport,
|
|
214
212
|
openNode,
|
|
215
213
|
selectNode,
|
|
214
|
+
triggerEvent,
|
|
216
215
|
});
|
|
217
216
|
|
|
218
217
|
const renderer = new ElementsRenderer({
|
|
218
|
+
ariaLabel,
|
|
219
|
+
ariaLabelledBy,
|
|
219
220
|
autoEscape,
|
|
220
221
|
buttonLeft,
|
|
221
222
|
classNames,
|
|
@@ -320,12 +321,13 @@ export default class TreeElement {
|
|
|
320
321
|
* @param mustSetFocus - Move the focus to the node. Default `true`.
|
|
321
322
|
* @group Selection
|
|
322
323
|
*/
|
|
323
|
-
public addToSelection(node: Node, mustSetFocus
|
|
324
|
+
public addToSelection(node: Node, mustSetFocus = true) {
|
|
324
325
|
this.selectNodeHandler.addToSelection(node);
|
|
325
326
|
this.openParents(node);
|
|
326
327
|
|
|
327
|
-
this.getNodeElementForNode(node).select(mustSetFocus
|
|
328
|
+
this.getNodeElementForNode(node).select(mustSetFocus);
|
|
328
329
|
|
|
330
|
+
this.updateTabStop();
|
|
329
331
|
this.saveState();
|
|
330
332
|
}
|
|
331
333
|
|
|
@@ -349,20 +351,29 @@ export default class TreeElement {
|
|
|
349
351
|
}
|
|
350
352
|
|
|
351
353
|
/**
|
|
352
|
-
* Closes a folder.
|
|
354
|
+
* Closes a folder. Await the promise when you need to know the closing
|
|
355
|
+
* animation has finished.
|
|
356
|
+
*
|
|
357
|
+
* @example
|
|
358
|
+
* ```js
|
|
359
|
+
* await tree.closeNode(node);
|
|
360
|
+
* console.log("closed");
|
|
361
|
+
* ```
|
|
353
362
|
*
|
|
354
363
|
* @param slide - Override the `slide` option for this call.
|
|
355
364
|
* @group Opening and closing
|
|
356
365
|
*/
|
|
357
|
-
public closeNode(node: Node, slide?: boolean): void {
|
|
358
|
-
if (node.isFolder() || node.isEmptyFolder) {
|
|
359
|
-
|
|
360
|
-
slide ?? this.options.slide,
|
|
361
|
-
this.options.animationSpeed,
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
this.saveState();
|
|
366
|
+
public async closeNode(node: Node, slide?: boolean): Promise<void> {
|
|
367
|
+
if (!(node.isFolder() || node.isEmptyFolder)) {
|
|
368
|
+
return;
|
|
365
369
|
}
|
|
370
|
+
|
|
371
|
+
await this.createFolderElement(node).close(
|
|
372
|
+
slide ?? this.options.slide,
|
|
373
|
+
this.options.animationSpeed,
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
this.saveState();
|
|
366
377
|
}
|
|
367
378
|
|
|
368
379
|
/**
|
|
@@ -391,11 +402,7 @@ export default class TreeElement {
|
|
|
391
402
|
`li.${this.classNames.common}`,
|
|
392
403
|
);
|
|
393
404
|
|
|
394
|
-
|
|
395
|
-
return this.nodeMap.get(liElement) ?? null;
|
|
396
|
-
} else {
|
|
397
|
-
return null;
|
|
398
|
-
}
|
|
405
|
+
return liElement ? (this.nodeMap.get(liElement) ?? null) : null;
|
|
399
406
|
}
|
|
400
407
|
|
|
401
408
|
/**
|
|
@@ -704,6 +711,7 @@ export default class TreeElement {
|
|
|
704
711
|
this.selectNodeHandler.removeFromSelection(node);
|
|
705
712
|
|
|
706
713
|
this.getNodeElementForNode(node).deselect();
|
|
714
|
+
this.updateTabStop();
|
|
707
715
|
this.saveState();
|
|
708
716
|
}
|
|
709
717
|
|
|
@@ -752,7 +760,11 @@ export default class TreeElement {
|
|
|
752
760
|
return;
|
|
753
761
|
}
|
|
754
762
|
|
|
755
|
-
|
|
763
|
+
// Unlike a click, selecting the selected node again keeps it selected
|
|
764
|
+
this.selectNodeHandler.selectSingleNode(node, {
|
|
765
|
+
mustToggle: false,
|
|
766
|
+
...options,
|
|
767
|
+
});
|
|
756
768
|
}
|
|
757
769
|
|
|
758
770
|
/**
|
|
@@ -778,18 +790,20 @@ export default class TreeElement {
|
|
|
778
790
|
}
|
|
779
791
|
|
|
780
792
|
/**
|
|
781
|
-
* Closes an open node and opens a closed one.
|
|
793
|
+
* Closes an open node and opens a closed one. Await the promise when you
|
|
794
|
+
* need to know the node is really open or closed, like with `openNode` and
|
|
795
|
+
* `closeNode`.
|
|
782
796
|
*
|
|
783
797
|
* @param slide - Override the `slide` option for this call.
|
|
784
798
|
* @group Opening and closing
|
|
785
799
|
*/
|
|
786
|
-
public toggle(node: Node, slide: boolean | null = null) {
|
|
800
|
+
public async toggle(node: Node, slide: boolean | null = null): Promise<void> {
|
|
787
801
|
const mustSlide = slide ?? this.options.slide;
|
|
788
802
|
|
|
789
803
|
if (node.is_open) {
|
|
790
|
-
this.closeNode(node, mustSlide);
|
|
804
|
+
await this.closeNode(node, mustSlide);
|
|
791
805
|
} else {
|
|
792
|
-
|
|
806
|
+
await this.openNode(node, mustSlide);
|
|
793
807
|
}
|
|
794
808
|
}
|
|
795
809
|
|
|
@@ -835,7 +849,7 @@ export default class TreeElement {
|
|
|
835
849
|
if (
|
|
836
850
|
typeof data === "object" &&
|
|
837
851
|
data.children &&
|
|
838
|
-
data.children
|
|
852
|
+
Array.isArray(data.children)
|
|
839
853
|
) {
|
|
840
854
|
node.removeChildren();
|
|
841
855
|
|
|
@@ -847,6 +861,34 @@ export default class TreeElement {
|
|
|
847
861
|
this.refreshElements(node);
|
|
848
862
|
}
|
|
849
863
|
|
|
864
|
+
// Open the nodes up to the autoOpen level. A folder that is loaded on
|
|
865
|
+
// demand is fetched first, and its children are opened when the data has
|
|
866
|
+
// arrived.
|
|
867
|
+
private async autoOpenNodesOnDemand(): Promise<void> {
|
|
868
|
+
const maxLevel = this.getAutoOpenMaxLevel();
|
|
869
|
+
|
|
870
|
+
const openNodes = async (): Promise<void> => {
|
|
871
|
+
const loading: Promise<void>[] = [];
|
|
872
|
+
|
|
873
|
+
this.tree.iterate((node: Node, level: number) => {
|
|
874
|
+
if (node.load_on_demand) {
|
|
875
|
+
if (!node.is_loading) {
|
|
876
|
+
loading.push(this.openNode(node, false).then(openNodes));
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
return false;
|
|
880
|
+
}
|
|
881
|
+
void this.openNode(node, false);
|
|
882
|
+
|
|
883
|
+
return level !== maxLevel;
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
await Promise.all(loading);
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
await openNodes();
|
|
890
|
+
}
|
|
891
|
+
|
|
850
892
|
private createFolderElement(node: Node) {
|
|
851
893
|
const classNames = this.classNames;
|
|
852
894
|
const closedIconElement = this.renderer.closedIconElement;
|
|
@@ -854,11 +896,19 @@ export default class TreeElement {
|
|
|
854
896
|
this.scrollHandler,
|
|
855
897
|
);
|
|
856
898
|
const openedIconElement = this.renderer.openedIconElement;
|
|
857
|
-
const renderChildren = this.renderer.renderChildren.bind(this.renderer);
|
|
858
899
|
const tabIndex = this.options.tabIndex;
|
|
859
900
|
const treeElement = this.htmlElement;
|
|
860
901
|
const triggerEvent = this.triggerEvent.bind(this);
|
|
861
902
|
|
|
903
|
+
// A selected node in a closed folder is rendered when the folder is
|
|
904
|
+
// opened, which can be after it was selected. It becomes the tab stop
|
|
905
|
+
// then, so the fallback tab stop must go.
|
|
906
|
+
const renderChildren = (folder: Node) => {
|
|
907
|
+
const ul = this.renderer.renderChildren(folder);
|
|
908
|
+
this.updateTabStop();
|
|
909
|
+
return ul;
|
|
910
|
+
};
|
|
911
|
+
|
|
862
912
|
return new FolderElement({
|
|
863
913
|
classNames,
|
|
864
914
|
closedIconElement,
|
|
@@ -896,13 +946,7 @@ export default class TreeElement {
|
|
|
896
946
|
private createRequestUrl(node?: Node): null | RequestUrl {
|
|
897
947
|
const dataUrl = this.options.dataUrl;
|
|
898
948
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
if (typeof dataUrl === "function") {
|
|
902
|
-
url = dataUrl(node);
|
|
903
|
-
} else {
|
|
904
|
-
url = dataUrl;
|
|
905
|
-
}
|
|
949
|
+
const url = typeof dataUrl === "function" ? dataUrl(node) : dataUrl;
|
|
906
950
|
|
|
907
951
|
if (!url) {
|
|
908
952
|
return null;
|
|
@@ -944,9 +988,11 @@ export default class TreeElement {
|
|
|
944
988
|
private getAutoOpenMaxLevel(): number {
|
|
945
989
|
if (this.options.autoOpen === true) {
|
|
946
990
|
return -1;
|
|
947
|
-
}
|
|
991
|
+
}
|
|
992
|
+
if (typeof this.options.autoOpen === "number") {
|
|
948
993
|
return this.options.autoOpen;
|
|
949
|
-
}
|
|
994
|
+
}
|
|
995
|
+
if (typeof this.options.autoOpen === "string") {
|
|
950
996
|
return parseInt(this.options.autoOpen, 10);
|
|
951
997
|
}
|
|
952
998
|
|
|
@@ -954,27 +1000,55 @@ export default class TreeElement {
|
|
|
954
1000
|
return 0;
|
|
955
1001
|
}
|
|
956
1002
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1003
|
+
// The title span of the first node, or null when the tree is empty
|
|
1004
|
+
private getFirstTitleSpan(): HTMLElement | null {
|
|
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;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// The node of the HTML element that has the focus, if it is in this tree
|
|
1015
|
+
private getFocusedNode(): Node | null {
|
|
1016
|
+
const activeElement = document.activeElement;
|
|
1017
|
+
|
|
1018
|
+
/* istanbul ignore if */
|
|
1019
|
+
if (!activeElement) {
|
|
1020
|
+
return null;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// The keyboard must still work for input elements.
|
|
1024
|
+
const tagName = activeElement.tagName;
|
|
1025
|
+
if (tagName !== "A" && tagName !== "SPAN") {
|
|
962
1026
|
return null;
|
|
963
1027
|
}
|
|
1028
|
+
|
|
1029
|
+
const node = this.getNode(activeElement as HTMLElement);
|
|
1030
|
+
return node?.tree === this.tree ? node : null;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
private getNodeElement(element: HTMLElement): NodeElement | null {
|
|
1034
|
+
const node = this.getNode(element);
|
|
1035
|
+
return node ? this.getNodeElementForNode(node) : null;
|
|
964
1036
|
}
|
|
965
1037
|
|
|
966
1038
|
private getNodeElementForNode(node: Node): NodeElement {
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
return this.createNodeElement(node);
|
|
971
|
-
}
|
|
1039
|
+
return node.isFolder()
|
|
1040
|
+
? this.createFolderElement(node)
|
|
1041
|
+
: this.createNodeElement(node);
|
|
972
1042
|
}
|
|
973
1043
|
|
|
974
1044
|
private getNodeIdToBeSelected(): NodeId | null {
|
|
975
1045
|
return this.saveStateHandler.getNodeIdToBeSelected();
|
|
976
1046
|
}
|
|
977
1047
|
|
|
1048
|
+
private hasRenderedSelectedNode(): boolean {
|
|
1049
|
+
return this.getSelectedNodes().some((node) => Boolean(node.element));
|
|
1050
|
+
}
|
|
1051
|
+
|
|
978
1052
|
private initData(): void {
|
|
979
1053
|
if (this.options.data) {
|
|
980
1054
|
this.loadData(this.options.data);
|
|
@@ -991,10 +1065,12 @@ export default class TreeElement {
|
|
|
991
1065
|
|
|
992
1066
|
private initTree(data: NodeData[]): void {
|
|
993
1067
|
const doInit = (): void => {
|
|
994
|
-
if (
|
|
995
|
-
|
|
996
|
-
this.triggerEvent("tree.init");
|
|
1068
|
+
if (this.isInitialized) {
|
|
1069
|
+
return;
|
|
997
1070
|
}
|
|
1071
|
+
|
|
1072
|
+
this.isInitialized = true;
|
|
1073
|
+
this.triggerEvent("tree.init");
|
|
998
1074
|
};
|
|
999
1075
|
|
|
1000
1076
|
this.tree = new this.options.nodeClass(null, true, this.options.nodeClass);
|
|
@@ -1017,31 +1093,15 @@ export default class TreeElement {
|
|
|
1017
1093
|
|
|
1018
1094
|
// Does an HTML element of the tree have the focus?
|
|
1019
1095
|
private isFocusOnTree(): boolean {
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
/* istanbul ignore if */
|
|
1023
|
-
if (!activeElement) {
|
|
1024
|
-
return false;
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
// The keyboard must still work for input elements.
|
|
1028
|
-
const tagName = activeElement.tagName;
|
|
1029
|
-
if (tagName !== "A" && tagName !== "SPAN") {
|
|
1030
|
-
return false;
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
const node = this.getNode(activeElement as HTMLElement);
|
|
1034
|
-
return node?.tree === this.tree;
|
|
1096
|
+
return this.getFocusedNode() !== null;
|
|
1035
1097
|
}
|
|
1036
1098
|
|
|
1037
1099
|
private isSelectedNodeInSubtree(subtree: Node): boolean {
|
|
1038
1100
|
const selectedNode = this.getSelectedNode();
|
|
1039
1101
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
return subtree === selectedNode || subtree.isParentOf(selectedNode);
|
|
1044
|
-
}
|
|
1102
|
+
return selectedNode
|
|
1103
|
+
? subtree === selectedNode || subtree.isParentOf(selectedNode)
|
|
1104
|
+
: false;
|
|
1045
1105
|
}
|
|
1046
1106
|
|
|
1047
1107
|
private async loadFolderOnDemand(node: Node, slide: boolean): Promise<void> {
|
|
@@ -1049,6 +1109,13 @@ export default class TreeElement {
|
|
|
1049
1109
|
|
|
1050
1110
|
await this.loadDataFromUrl(undefined, node);
|
|
1051
1111
|
|
|
1112
|
+
// Loading failed, or there is no url. Opening the node now would load it
|
|
1113
|
+
// again, without end. Leave it closed so the user can try again.
|
|
1114
|
+
if (node.load_on_demand) {
|
|
1115
|
+
node.is_loading = false;
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1052
1119
|
await this.openNode(node, slide);
|
|
1053
1120
|
}
|
|
1054
1121
|
|
|
@@ -1062,11 +1129,9 @@ export default class TreeElement {
|
|
|
1062
1129
|
}
|
|
1063
1130
|
|
|
1064
1131
|
private mouseCapture(positionInfo: PositionInfo): boolean | null {
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
return this.dndHandler.mouseCapture(positionInfo);
|
|
1132
|
+
return this.options.dragAndDrop
|
|
1133
|
+
? this.dndHandler.mouseCapture(positionInfo)
|
|
1134
|
+
: false;
|
|
1070
1135
|
}
|
|
1071
1136
|
|
|
1072
1137
|
private mouseDrag(positionInfo: PositionInfo): boolean {
|
|
@@ -1082,6 +1147,7 @@ export default class TreeElement {
|
|
|
1082
1147
|
|
|
1083
1148
|
private mouseStart(positionInfo: PositionInfo): boolean {
|
|
1084
1149
|
/* istanbul ignore if */
|
|
1150
|
+
// eslint-disable-next-line unicorn/prefer-ternary
|
|
1085
1151
|
if (!this.options.dragAndDrop) {
|
|
1086
1152
|
return false;
|
|
1087
1153
|
}
|
|
@@ -1123,6 +1189,8 @@ export default class TreeElement {
|
|
|
1123
1189
|
this.selectCurrentNode(mustSetFocus);
|
|
1124
1190
|
}
|
|
1125
1191
|
|
|
1192
|
+
this.updateTabStop();
|
|
1193
|
+
|
|
1126
1194
|
this.triggerEvent("tree.refresh");
|
|
1127
1195
|
}
|
|
1128
1196
|
|
|
@@ -1132,10 +1200,12 @@ export default class TreeElement {
|
|
|
1132
1200
|
|
|
1133
1201
|
private selectCurrentNode(mustSetFocus: boolean): void {
|
|
1134
1202
|
const node = this.getSelectedNode();
|
|
1135
|
-
if (node) {
|
|
1136
|
-
|
|
1137
|
-
nodeElement.select(mustSetFocus);
|
|
1203
|
+
if (!node) {
|
|
1204
|
+
return;
|
|
1138
1205
|
}
|
|
1206
|
+
|
|
1207
|
+
const nodeElement = this.getNodeElementForNode(node);
|
|
1208
|
+
nodeElement.select(mustSetFocus);
|
|
1139
1209
|
}
|
|
1140
1210
|
|
|
1141
1211
|
// Set initial state, either by restoring the state or auto-opening nodes
|
|
@@ -1145,14 +1215,13 @@ export default class TreeElement {
|
|
|
1145
1215
|
// result: is state restored, must load on demand?
|
|
1146
1216
|
const state = this.saveStateHandler.getStateFromStorage();
|
|
1147
1217
|
|
|
1148
|
-
if (
|
|
1149
|
-
return [false, false];
|
|
1150
|
-
} else {
|
|
1218
|
+
if (state) {
|
|
1151
1219
|
const mustLoadOnDemand = this.saveStateHandler.setInitialState(state);
|
|
1152
1220
|
|
|
1153
1221
|
// return true: the state is restored
|
|
1154
1222
|
return [true, mustLoadOnDemand];
|
|
1155
1223
|
}
|
|
1224
|
+
return [false, false];
|
|
1156
1225
|
};
|
|
1157
1226
|
|
|
1158
1227
|
const autoOpenNodes = (): boolean => {
|
|
@@ -1168,12 +1237,12 @@ export default class TreeElement {
|
|
|
1168
1237
|
if (node.load_on_demand) {
|
|
1169
1238
|
mustLoadOnDemand = true;
|
|
1170
1239
|
return false;
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
} else {
|
|
1240
|
+
}
|
|
1241
|
+
if (node.hasChildren()) {
|
|
1174
1242
|
node.is_open = true;
|
|
1175
1243
|
return level !== maxLevel;
|
|
1176
1244
|
}
|
|
1245
|
+
return false;
|
|
1177
1246
|
});
|
|
1178
1247
|
|
|
1179
1248
|
return mustLoadOnDemand;
|
|
@@ -1190,60 +1259,13 @@ export default class TreeElement {
|
|
|
1190
1259
|
|
|
1191
1260
|
// Set the initial state for nodes that are loaded on demand
|
|
1192
1261
|
private async setInitialStateOnDemand(): Promise<void> {
|
|
1193
|
-
|
|
1194
|
-
const restoreState = (): boolean => {
|
|
1195
|
-
const state = this.saveStateHandler.getStateFromStorage();
|
|
1196
|
-
|
|
1197
|
-
if (!state) {
|
|
1198
|
-
return false;
|
|
1199
|
-
} else {
|
|
1200
|
-
void this.saveStateHandler.setInitialStateOnDemand(state).then(() => {
|
|
1201
|
-
resolve();
|
|
1202
|
-
});
|
|
1203
|
-
|
|
1204
|
-
return true;
|
|
1205
|
-
}
|
|
1206
|
-
};
|
|
1207
|
-
|
|
1208
|
-
const autoOpenNodes = (): void => {
|
|
1209
|
-
const maxLevel = this.getAutoOpenMaxLevel();
|
|
1210
|
-
let loadingCount = 0;
|
|
1211
|
-
|
|
1212
|
-
const loadAndOpenNode = (node: Node): void => {
|
|
1213
|
-
loadingCount += 1;
|
|
1214
|
-
void this.openNode(node, false).then(() => {
|
|
1215
|
-
loadingCount -= 1;
|
|
1216
|
-
openNodes();
|
|
1217
|
-
});
|
|
1218
|
-
};
|
|
1219
|
-
|
|
1220
|
-
const openNodes = (): void => {
|
|
1221
|
-
this.tree.iterate((node: Node, level: number) => {
|
|
1222
|
-
if (node.load_on_demand) {
|
|
1223
|
-
if (!node.is_loading) {
|
|
1224
|
-
loadAndOpenNode(node);
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
return false;
|
|
1228
|
-
} else {
|
|
1229
|
-
void this.openNode(node, false);
|
|
1230
|
-
|
|
1231
|
-
return level !== maxLevel;
|
|
1232
|
-
}
|
|
1233
|
-
});
|
|
1234
|
-
|
|
1235
|
-
if (loadingCount === 0) {
|
|
1236
|
-
resolve();
|
|
1237
|
-
}
|
|
1238
|
-
};
|
|
1239
|
-
|
|
1240
|
-
openNodes();
|
|
1241
|
-
};
|
|
1262
|
+
const state = this.saveStateHandler.getStateFromStorage();
|
|
1242
1263
|
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1264
|
+
if (state) {
|
|
1265
|
+
await this.saveStateHandler.setInitialStateOnDemand(state);
|
|
1266
|
+
} else {
|
|
1267
|
+
await this.autoOpenNodesOnDemand();
|
|
1268
|
+
}
|
|
1247
1269
|
}
|
|
1248
1270
|
|
|
1249
1271
|
// Set this HTML element to this node in the node map.
|
|
@@ -1257,4 +1279,42 @@ export default class TreeElement {
|
|
|
1257
1279
|
): boolean {
|
|
1258
1280
|
return this.triggerEventProvider(this.htmlElement, eventName, values);
|
|
1259
1281
|
}
|
|
1282
|
+
|
|
1283
|
+
/* Keyboard users reach the tree through the tab stop. The selected node is
|
|
1284
|
+
* the tab stop, see NodeElement.select. Without a selected node, the first
|
|
1285
|
+
* node is the tab stop, so the tree can always be reached.
|
|
1286
|
+
*/
|
|
1287
|
+
private updateTabStop(): void {
|
|
1288
|
+
const tabIndex = this.options.tabIndex;
|
|
1289
|
+
const previousTabStop = this.fallbackTabStop;
|
|
1290
|
+
const tabStop =
|
|
1291
|
+
tabIndex == null || this.hasRenderedSelectedNode()
|
|
1292
|
+
? null
|
|
1293
|
+
: this.getFirstTitleSpan();
|
|
1294
|
+
|
|
1295
|
+
if (
|
|
1296
|
+
previousTabStop &&
|
|
1297
|
+
previousTabStop !== tabStop &&
|
|
1298
|
+
previousTabStop.getAttribute("aria-selected") !== "true"
|
|
1299
|
+
) {
|
|
1300
|
+
previousTabStop.setAttribute("tabindex", "-1");
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
if (tabStop && tabIndex != null) {
|
|
1304
|
+
tabStop.setAttribute("tabindex", String(tabIndex));
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
this.fallbackTabStop = tabStop;
|
|
1308
|
+
}
|
|
1260
1309
|
}
|
|
1310
|
+
|
|
1311
|
+
export {
|
|
1312
|
+
type MoveInfo,
|
|
1313
|
+
type TreeEvent,
|
|
1314
|
+
type TreeEventName,
|
|
1315
|
+
type TreeEvents,
|
|
1316
|
+
} from "./events";
|
|
1317
|
+
export { type Node, type NodeData, type NodeId, type Position } from "./node";
|
|
1318
|
+
export { type TreeElementOptions } from "./options";
|
|
1319
|
+
export { type SavedState } from "./saveStateHandler";
|
|
1320
|
+
export { type SelectNodeOptions } from "./selectNodeHandler";
|