tree-element 0.1.1 → 0.3.0
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 +43 -31
- package/lib/animation.d.ts +2 -2
- package/lib/animation.d.ts.map +1 -1
- package/lib/animation.js +16 -12
- package/lib/animation.js.map +1 -1
- package/lib/classNames.d.ts.map +1 -1
- package/lib/classNames.js +2 -2
- package/lib/classNames.js.map +1 -1
- package/lib/dataLoader.d.ts +1 -0
- package/lib/dataLoader.d.ts.map +1 -1
- package/lib/dataLoader.js +22 -20
- package/lib/dataLoader.js.map +1 -1
- package/lib/dragAndDropHandler/binarySearch.js +3 -3
- package/lib/dragAndDropHandler/binarySearch.js.map +1 -1
- package/lib/dragAndDropHandler/dragElement.d.ts.map +1 -1
- package/lib/dragAndDropHandler/dragElement.js +11 -13
- package/lib/dragAndDropHandler/dragElement.js.map +1 -1
- package/lib/dragAndDropHandler/generateHitAreas.d.ts.map +1 -1
- package/lib/dragAndDropHandler/generateHitAreas.js +25 -25
- package/lib/dragAndDropHandler/generateHitAreas.js.map +1 -1
- package/lib/dragAndDropHandler/index.d.ts.map +1 -1
- package/lib/dragAndDropHandler/index.js +125 -158
- package/lib/dragAndDropHandler/index.js.map +1 -1
- package/lib/dragAndDropHandler/iterateVisibleNodes.d.ts.map +1 -1
- package/lib/dragAndDropHandler/iterateVisibleNodes.js +23 -22
- package/lib/dragAndDropHandler/iterateVisibleNodes.js.map +1 -1
- package/lib/dragAndDropHandler/types.d.ts.map +1 -1
- package/lib/elementsRenderer.d.ts +20 -7
- package/lib/elementsRenderer.d.ts.map +1 -1
- package/lib/elementsRenderer.js +195 -136
- 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 +36 -13
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +386 -349
- package/lib/index.js.map +1 -1
- package/lib/keyHandler.d.ts +13 -8
- package/lib/keyHandler.d.ts.map +1 -1
- package/lib/keyHandler.js +106 -51
- package/lib/keyHandler.js.map +1 -1
- package/lib/methodTypes.d.ts +3 -3
- 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 +36 -49
- package/lib/mouseHandler.js.map +1 -1
- package/lib/mouseUtils.d.ts.map +1 -1
- package/lib/mouseUtils.js +2 -2
- package/lib/mouseUtils.js.map +1 -1
- package/lib/node.d.ts +47 -16
- package/lib/node.d.ts.map +1 -1
- package/lib/node.js +169 -171
- package/lib/node.js.map +1 -1
- package/lib/nodeElement/borderDropHint.d.ts.map +1 -1
- package/lib/nodeElement/borderDropHint.js +4 -5
- package/lib/nodeElement/borderDropHint.js.map +1 -1
- package/lib/nodeElement/folderElement.d.ts +8 -5
- package/lib/nodeElement/folderElement.d.ts.map +1 -1
- package/lib/nodeElement/folderElement.js +75 -67
- package/lib/nodeElement/folderElement.js.map +1 -1
- package/lib/nodeElement/ghostDropHint.d.ts.map +1 -1
- package/lib/nodeElement/ghostDropHint.js +13 -13
- package/lib/nodeElement/ghostDropHint.js.map +1 -1
- package/lib/nodeElement/index.d.ts +2 -1
- package/lib/nodeElement/index.d.ts.map +1 -1
- package/lib/nodeElement/index.js +38 -30
- 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 +19 -3
- package/lib/options.d.ts.map +1 -1
- package/lib/positionUtils.js +3 -3
- package/lib/positionUtils.js.map +1 -1
- package/lib/requestUrl.d.ts +1 -1
- package/lib/requestUrl.d.ts.map +1 -1
- package/lib/requestUrl.js +6 -17
- package/lib/requestUrl.js.map +1 -1
- package/lib/saveStateHandler.d.ts.map +1 -1
- package/lib/saveStateHandler.js +59 -89
- package/lib/saveStateHandler.js.map +1 -1
- package/lib/scrollHandler/containerScrollParent.d.ts.map +1 -1
- package/lib/scrollHandler/containerScrollParent.js +11 -18
- package/lib/scrollHandler/containerScrollParent.js.map +1 -1
- package/lib/scrollHandler/createScrollParent.d.ts.map +1 -1
- package/lib/scrollHandler/createScrollParent.js +12 -13
- package/lib/scrollHandler/createScrollParent.js.map +1 -1
- package/lib/scrollHandler/documentScrollParent.d.ts.map +1 -1
- package/lib/scrollHandler/documentScrollParent.js +18 -27
- package/lib/scrollHandler/documentScrollParent.js.map +1 -1
- package/lib/scrollHandler/scrollParent.d.ts.map +1 -1
- package/lib/scrollHandler/scrollParent.js +30 -34
- package/lib/scrollHandler/scrollParent.js.map +1 -1
- package/lib/scrollHandler.d.ts.map +1 -1
- package/lib/scrollHandler.js +11 -14
- package/lib/scrollHandler.js.map +1 -1
- package/lib/selectNodeHandler.d.ts +4 -2
- package/lib/selectNodeHandler.d.ts.map +1 -1
- package/lib/selectNodeHandler.js +64 -83
- package/lib/selectNodeHandler.js.map +1 -1
- package/lib/setDefaultOptions.d.ts.map +1 -1
- package/lib/setDefaultOptions.js +34 -38
- package/lib/setDefaultOptions.js.map +1 -1
- package/lib/triggerCustomEvent.d.ts.map +1 -1
- package/lib/triggerCustomEvent.js +2 -2
- package/lib/triggerCustomEvent.js.map +1 -1
- package/lib/util.d.ts.map +1 -1
- package/lib/util.js +2 -2
- 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 +43 -32
- package/src/animation.ts +14 -12
- package/src/classNames.ts +52 -52
- package/src/dataLoader.ts +79 -71
- package/src/dragAndDropHandler/binarySearch.ts +17 -17
- package/src/dragAndDropHandler/dragElement.ts +51 -50
- package/src/dragAndDropHandler/generateHitAreas.ts +156 -161
- package/src/dragAndDropHandler/index.ts +334 -348
- package/src/dragAndDropHandler/iterateVisibleNodes.ts +61 -63
- package/src/dragAndDropHandler/types.ts +5 -5
- package/src/elementsRenderer.ts +377 -294
- package/src/events.ts +241 -208
- package/src/header.txt +1 -1
- package/src/index.ts +259 -229
- package/src/keyHandler.ts +179 -115
- package/src/methodTypes.ts +7 -10
- package/src/mouseHandler.ts +316 -316
- package/src/mouseUtils.ts +14 -14
- package/src/node.ts +773 -771
- package/src/nodeElement/borderDropHint.ts +25 -25
- package/src/nodeElement/folderElement.ts +116 -102
- package/src/nodeElement/ghostDropHint.ts +64 -62
- package/src/nodeElement/index.ts +81 -73
- package/src/nodeUtils.ts +5 -5
- package/src/options.ts +25 -5
- package/src/requestUrl.ts +7 -14
- package/src/saveStateHandler.ts +191 -207
- package/src/scrollHandler/containerScrollParent.ts +40 -49
- package/src/scrollHandler/createScrollParent.ts +26 -27
- package/src/scrollHandler/documentScrollParent.ts +66 -72
- package/src/scrollHandler/scrollParent.ts +90 -86
- package/src/scrollHandler.ts +45 -45
- package/src/selectNodeHandler.ts +171 -165
- package/src/setDefaultOptions.ts +44 -32
- package/src/triggerCustomEvent.ts +7 -3
- package/src/util.ts +2 -3
- package/src/version.ts +1 -1
- package/tree_element.css +87 -69
- package/tree_element.debug.js +3866 -3831
- package/tree_element.debug.js.map +1 -1
- package/tree_element.js +2 -2
- package/tree_element.js.map +1 -1
package/src/elementsRenderer.ts
CHANGED
|
@@ -6,362 +6,445 @@ import type { IconElement, OnCreateLi } from "./options";
|
|
|
6
6
|
import { getBoolString } from "./util";
|
|
7
7
|
|
|
8
8
|
interface ElementsRendererParams {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
9
|
+
ariaLabel?: string;
|
|
10
|
+
ariaLabelledBy?: string;
|
|
11
|
+
autoEscape: boolean;
|
|
12
|
+
buttonLeft: boolean;
|
|
13
|
+
classNames: ClassNames;
|
|
14
|
+
closedIcon?: IconElement;
|
|
15
|
+
dragAndDrop: boolean;
|
|
16
|
+
getTree: GetTree;
|
|
17
|
+
htmlElement: HTMLElement;
|
|
18
|
+
isNodeSelected: IsNodeSelected;
|
|
19
|
+
onCreateLi?: OnCreateLi;
|
|
20
|
+
openedIcon?: IconElement;
|
|
21
|
+
rtl?: boolean;
|
|
22
|
+
setNodeElement: (element: HTMLElement, node: Node) => void;
|
|
23
|
+
showEmptyFolder: boolean;
|
|
24
|
+
tabIndex?: number;
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
/* Renders the tree to html.
|
|
28
|
+
*
|
|
29
|
+
* The children of a closed folder are not rendered. They are rendered when
|
|
30
|
+
* the folder is opened, see renderChildren.
|
|
31
|
+
*
|
|
32
|
+
* The elements are created by cloning templates. A template is a fully
|
|
33
|
+
* built <li> or <ul> with everything that is the same for every node
|
|
34
|
+
* (structure, roles, static classes, the toggler icon). Per node only the
|
|
35
|
+
* parts that differ are set: the title, the aria attributes and the
|
|
36
|
+
* selected and loading classes.
|
|
37
|
+
*/
|
|
25
38
|
export default class ElementsRenderer {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
39
|
+
public closedIconElement?: globalThis.Node;
|
|
40
|
+
public openedIconElement?: globalThis.Node;
|
|
41
|
+
private ariaLabel?: string;
|
|
42
|
+
private ariaLabelledBy?: string;
|
|
43
|
+
private autoEscape: boolean;
|
|
44
|
+
private buttonLeft: boolean;
|
|
45
|
+
private classNames: ClassNames;
|
|
46
|
+
private closedFolderTemplate: HTMLLIElement;
|
|
47
|
+
private dragAndDrop: boolean;
|
|
48
|
+
private getTree: GetTree;
|
|
49
|
+
private groupUlTemplate: HTMLUListElement;
|
|
50
|
+
private htmlElement: HTMLElement;
|
|
51
|
+
private isNodeSelected: IsNodeSelected;
|
|
52
|
+
private nodeTemplate: HTMLLIElement;
|
|
53
|
+
private onCreateLi?: OnCreateLi;
|
|
54
|
+
private openedFolderTemplate: HTMLLIElement;
|
|
55
|
+
private rootUlTemplate: HTMLUListElement;
|
|
56
|
+
private rtl?: boolean;
|
|
57
|
+
private setNodeElement: (element: HTMLElement, node: Node) => void;
|
|
58
|
+
private showEmptyFolder: boolean;
|
|
59
|
+
private tabIndex?: number;
|
|
60
|
+
|
|
61
|
+
constructor({
|
|
62
|
+
ariaLabel,
|
|
63
|
+
ariaLabelledBy,
|
|
64
|
+
autoEscape,
|
|
65
|
+
buttonLeft,
|
|
66
|
+
classNames,
|
|
67
|
+
closedIcon = "-",
|
|
68
|
+
dragAndDrop,
|
|
69
|
+
getTree,
|
|
70
|
+
htmlElement,
|
|
71
|
+
isNodeSelected,
|
|
72
|
+
onCreateLi,
|
|
73
|
+
openedIcon = "+",
|
|
74
|
+
rtl,
|
|
75
|
+
setNodeElement,
|
|
76
|
+
showEmptyFolder,
|
|
77
|
+
tabIndex,
|
|
78
|
+
}: ElementsRendererParams) {
|
|
79
|
+
this.ariaLabel = ariaLabel;
|
|
80
|
+
this.ariaLabelledBy = ariaLabelledBy;
|
|
81
|
+
this.autoEscape = autoEscape;
|
|
82
|
+
this.buttonLeft = buttonLeft;
|
|
83
|
+
this.classNames = classNames;
|
|
84
|
+
this.dragAndDrop = dragAndDrop;
|
|
85
|
+
this.getTree = getTree;
|
|
86
|
+
this.htmlElement = htmlElement;
|
|
87
|
+
this.isNodeSelected = isNodeSelected;
|
|
88
|
+
this.onCreateLi = onCreateLi;
|
|
89
|
+
this.rtl = rtl;
|
|
90
|
+
this.setNodeElement = setNodeElement;
|
|
91
|
+
this.showEmptyFolder = showEmptyFolder;
|
|
92
|
+
this.tabIndex = tabIndex;
|
|
93
|
+
this.openedIconElement = this.createButtonElement(openedIcon);
|
|
94
|
+
this.closedIconElement = this.createButtonElement(closedIcon);
|
|
95
|
+
|
|
96
|
+
this.rootUlTemplate = this.createUlTemplate(true);
|
|
97
|
+
this.groupUlTemplate = this.createUlTemplate(false);
|
|
98
|
+
this.nodeTemplate = this.createNodeTemplate();
|
|
99
|
+
this.openedFolderTemplate = this.createFolderTemplate(true);
|
|
100
|
+
this.closedFolderTemplate = this.createFolderTemplate(false);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public render(fromNode: Node | null): void {
|
|
104
|
+
if (fromNode?.parent) {
|
|
105
|
+
this.renderFromNode(fromNode);
|
|
106
|
+
} else {
|
|
107
|
+
this.renderFromRoot();
|
|
71
108
|
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Render the children of a rendered folder. Returns the new <ul>, or
|
|
112
|
+
* null when the folder is not rendered or has no children.
|
|
113
|
+
*/
|
|
114
|
+
public renderChildren(node: Node): HTMLUListElement | null {
|
|
115
|
+
return !node.element || !node.hasChildren()
|
|
116
|
+
? null
|
|
117
|
+
: this.createDomElements(
|
|
118
|
+
node.element,
|
|
119
|
+
node.children,
|
|
120
|
+
false,
|
|
121
|
+
node.getLevel() + 1,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
72
124
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
} else {
|
|
77
|
-
this.renderFromRoot();
|
|
78
|
-
}
|
|
125
|
+
public renderFromNode(node: Node): void {
|
|
126
|
+
if (!node.element) {
|
|
127
|
+
return;
|
|
79
128
|
}
|
|
80
129
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const currentLi = node.element;
|
|
87
|
-
const newLi = this.createLi(node, node.getLevel());
|
|
88
|
-
currentLi.replaceWith(newLi);
|
|
130
|
+
const currentLi = node.element;
|
|
131
|
+
const newLi = this.createLi(node, node.getLevel());
|
|
132
|
+
currentLi.replaceWith(newLi);
|
|
89
133
|
|
|
90
|
-
|
|
91
|
-
|
|
134
|
+
if (this.mustRenderChildren(node)) {
|
|
135
|
+
this.createDomElements(newLi, node.children, false, node.getLevel() + 1);
|
|
92
136
|
}
|
|
137
|
+
}
|
|
93
138
|
|
|
94
|
-
|
|
95
|
-
|
|
139
|
+
public renderFromRoot(): void {
|
|
140
|
+
this.htmlElement.textContent = "";
|
|
96
141
|
|
|
97
|
-
|
|
142
|
+
const tree = this.getTree();
|
|
98
143
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
144
|
+
if (tree) {
|
|
145
|
+
this.createDomElements(this.htmlElement, tree.children, true, 1);
|
|
102
146
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private attachNodeData(node: Node, li: HTMLElement): void {
|
|
150
|
+
node.element = li;
|
|
151
|
+
this.setNodeElement(li, node);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* Returns the template of a toggler icon; it is cloned for every folder.
|
|
155
|
+
* A string is parsed as html and becomes a fragment, so it can hold
|
|
156
|
+
* entities, text and elements.
|
|
157
|
+
*/
|
|
158
|
+
private createButtonElement(value: IconElement): globalThis.Node | undefined {
|
|
159
|
+
if (typeof value === "string") {
|
|
160
|
+
const template = document.createElement("template");
|
|
161
|
+
// eslint-disable-next-line no-unsanitized/property -- icon options are documented to accept html
|
|
162
|
+
template.innerHTML = value;
|
|
163
|
+
|
|
164
|
+
return template.content;
|
|
165
|
+
}
|
|
166
|
+
return value.nodeType ? value : undefined;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private createDomElements(
|
|
170
|
+
element: Element,
|
|
171
|
+
children: Node[],
|
|
172
|
+
isRootNode: boolean,
|
|
173
|
+
level: number,
|
|
174
|
+
): HTMLUListElement {
|
|
175
|
+
const template = isRootNode ? this.rootUlTemplate : this.groupUlTemplate;
|
|
176
|
+
const ul = template.cloneNode() as HTMLUListElement;
|
|
177
|
+
element.append(ul);
|
|
178
|
+
|
|
179
|
+
for (const child of children) {
|
|
180
|
+
const li = this.createLi(child, level);
|
|
181
|
+
ul.append(li);
|
|
182
|
+
|
|
183
|
+
if (this.mustRenderChildren(child)) {
|
|
184
|
+
this.createDomElements(li, child.children, false, level + 1);
|
|
185
|
+
}
|
|
107
186
|
}
|
|
108
187
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
188
|
+
return ul;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private createFolderLi(
|
|
192
|
+
node: Node,
|
|
193
|
+
level: number,
|
|
194
|
+
isSelected: boolean,
|
|
195
|
+
): HTMLLIElement {
|
|
196
|
+
const template = node.is_open
|
|
197
|
+
? this.openedFolderTemplate
|
|
198
|
+
: this.closedFolderTemplate;
|
|
199
|
+
const li = template.cloneNode(true) as HTMLLIElement;
|
|
200
|
+
|
|
201
|
+
if (isSelected) {
|
|
202
|
+
li.classList.add(this.classNames.selected);
|
|
123
203
|
}
|
|
124
204
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
ul.appendChild(li);
|
|
137
|
-
|
|
138
|
-
if (child.hasChildren()) {
|
|
139
|
-
this.createDomElements(li, child.children, false, level + 1);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
205
|
+
// li > div > [toggler], title span, [toggler]
|
|
206
|
+
const div = li.firstChild as HTMLDivElement;
|
|
207
|
+
const titleSpan = div.childNodes[
|
|
208
|
+
this.buttonLeft ? 1 : 0
|
|
209
|
+
] as HTMLSpanElement;
|
|
210
|
+
|
|
211
|
+
this.fillTitleSpan(titleSpan, node.name, isSelected, level);
|
|
212
|
+
|
|
213
|
+
if (node.is_loading) {
|
|
214
|
+
li.classList.add(this.classNames.loading);
|
|
215
|
+
titleSpan.setAttribute("aria-busy", "true");
|
|
142
216
|
}
|
|
143
217
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
level: number,
|
|
147
|
-
isSelected: boolean,
|
|
148
|
-
): HTMLLIElement {
|
|
149
|
-
const buttonClasses = this.getButtonClasses(node);
|
|
150
|
-
const folderClasses = this.getFolderClasses(node, isSelected);
|
|
151
|
-
|
|
152
|
-
const iconElement = node.is_open
|
|
153
|
-
? this.openedIconElement
|
|
154
|
-
: this.closedIconElement;
|
|
155
|
-
|
|
156
|
-
// li
|
|
157
|
-
const li = document.createElement("li");
|
|
158
|
-
li.className = `${this.classNames.common} ${folderClasses}`;
|
|
159
|
-
li.setAttribute("role", "none");
|
|
160
|
-
|
|
161
|
-
// div
|
|
162
|
-
const div = document.createElement("div");
|
|
163
|
-
div.className = `${this.classNames.element} ${this.classNames.common}`;
|
|
164
|
-
div.setAttribute("role", "none");
|
|
165
|
-
|
|
166
|
-
li.appendChild(div);
|
|
167
|
-
|
|
168
|
-
// button link
|
|
169
|
-
const buttonLink = document.createElement("a");
|
|
170
|
-
buttonLink.className = buttonClasses;
|
|
171
|
-
|
|
172
|
-
if (iconElement) {
|
|
173
|
-
buttonLink.appendChild(iconElement.cloneNode(true));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (this.buttonLeft) {
|
|
177
|
-
div.appendChild(buttonLink);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// title span
|
|
181
|
-
const titleSpan = this.createTitleSpan(
|
|
182
|
-
node.name,
|
|
183
|
-
isSelected,
|
|
184
|
-
true,
|
|
185
|
-
level,
|
|
186
|
-
);
|
|
187
|
-
titleSpan.setAttribute("aria-expanded", getBoolString(node.is_open));
|
|
188
|
-
div.appendChild(titleSpan);
|
|
218
|
+
return li;
|
|
219
|
+
}
|
|
189
220
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
221
|
+
/* Template for a folder <li>:
|
|
222
|
+
* li > div > [toggler], title span, [toggler]
|
|
223
|
+
* The toggler icon and the aria-expanded attribute depend on the open
|
|
224
|
+
* state, so there is a template for each.
|
|
225
|
+
*/
|
|
226
|
+
private createFolderTemplate(isOpen: boolean): HTMLLIElement {
|
|
227
|
+
const liClasses = [this.classNames.common, this.classNames.folder];
|
|
193
228
|
|
|
194
|
-
|
|
229
|
+
if (!isOpen) {
|
|
230
|
+
liClasses.push(this.classNames.closed);
|
|
195
231
|
}
|
|
196
232
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
233
|
+
const li = this.createLiTemplate(liClasses.join(" "));
|
|
234
|
+
const div = li.firstChild as HTMLDivElement;
|
|
235
|
+
|
|
236
|
+
// The toggler is hidden from screen readers: the icon is decoration, and
|
|
237
|
+
// aria-expanded on the treeitem has the open state.
|
|
238
|
+
const toggler = document.createElement("span");
|
|
239
|
+
toggler.className = this.getButtonClasses(isOpen);
|
|
240
|
+
toggler.setAttribute("aria-hidden", "true");
|
|
203
241
|
|
|
204
|
-
|
|
205
|
-
|
|
242
|
+
const iconElement = isOpen
|
|
243
|
+
? this.openedIconElement
|
|
244
|
+
: this.closedIconElement;
|
|
206
245
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
246
|
+
if (iconElement) {
|
|
247
|
+
toggler.append(iconElement.cloneNode(true));
|
|
248
|
+
}
|
|
210
249
|
|
|
211
|
-
|
|
250
|
+
if (this.buttonLeft) {
|
|
251
|
+
div.append(toggler);
|
|
252
|
+
}
|
|
212
253
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
254
|
+
// title span
|
|
255
|
+
const titleSpan = this.createTitleSpanTemplate(true);
|
|
256
|
+
titleSpan.setAttribute("aria-expanded", getBoolString(isOpen));
|
|
257
|
+
div.append(titleSpan);
|
|
216
258
|
|
|
217
|
-
|
|
259
|
+
if (!this.buttonLeft) {
|
|
260
|
+
div.append(toggler);
|
|
218
261
|
}
|
|
219
262
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const classString = liClasses.join(" ");
|
|
232
|
-
|
|
233
|
-
// li
|
|
234
|
-
const li = document.createElement("li");
|
|
235
|
-
li.className = classString;
|
|
236
|
-
li.setAttribute("role", "none");
|
|
237
|
-
|
|
238
|
-
// div
|
|
239
|
-
const div = document.createElement("div");
|
|
240
|
-
div.className = `${this.classNames.element} ${this.classNames.common}`;
|
|
241
|
-
div.setAttribute("role", "none");
|
|
242
|
-
|
|
243
|
-
li.appendChild(div);
|
|
244
|
-
|
|
245
|
-
// title span
|
|
246
|
-
const titleSpan = this.createTitleSpan(
|
|
247
|
-
node.name,
|
|
248
|
-
isSelected,
|
|
249
|
-
false,
|
|
250
|
-
level,
|
|
251
|
-
);
|
|
252
|
-
div.appendChild(titleSpan);
|
|
263
|
+
return li;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/* Create the <li> element
|
|
267
|
+
* Attach it to node.element.
|
|
268
|
+
* Call onCreateLi
|
|
269
|
+
*/
|
|
270
|
+
private createLi(node: Node, level: number): HTMLLIElement {
|
|
271
|
+
const isSelected = this.isNodeSelected(node);
|
|
253
272
|
|
|
254
|
-
|
|
273
|
+
const mustShowFolder =
|
|
274
|
+
node.isFolder() || (node.isEmptyFolder && this.showEmptyFolder);
|
|
275
|
+
|
|
276
|
+
const li = mustShowFolder
|
|
277
|
+
? this.createFolderLi(node, level, isSelected)
|
|
278
|
+
: this.createNodeLi(node, level, isSelected);
|
|
279
|
+
|
|
280
|
+
this.attachNodeData(node, li);
|
|
281
|
+
|
|
282
|
+
if (this.onCreateLi) {
|
|
283
|
+
this.onCreateLi(node, li, isSelected);
|
|
255
284
|
}
|
|
256
285
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
286
|
+
return li;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/*
|
|
290
|
+
Create the outer part of a <li> template: li > div
|
|
291
|
+
*/
|
|
292
|
+
private createLiTemplate(liClasses: string): HTMLLIElement {
|
|
293
|
+
const li = document.createElement("li");
|
|
294
|
+
li.className = liClasses;
|
|
295
|
+
li.setAttribute("role", "none");
|
|
264
296
|
|
|
265
|
-
|
|
297
|
+
const div = document.createElement("div");
|
|
298
|
+
div.className = `${this.classNames.element} ${this.classNames.common}`;
|
|
299
|
+
div.setAttribute("role", "none");
|
|
266
300
|
|
|
267
|
-
|
|
268
|
-
classes += ` ${this.classNames.titleFolder}`;
|
|
269
|
-
}
|
|
301
|
+
li.append(div);
|
|
270
302
|
|
|
271
|
-
|
|
303
|
+
return li;
|
|
304
|
+
}
|
|
272
305
|
|
|
273
|
-
|
|
306
|
+
private createNodeLi(
|
|
307
|
+
node: Node,
|
|
308
|
+
level: number,
|
|
309
|
+
isSelected: boolean,
|
|
310
|
+
): HTMLLIElement {
|
|
311
|
+
const li = this.nodeTemplate.cloneNode(true) as HTMLLIElement;
|
|
274
312
|
|
|
275
|
-
|
|
276
|
-
|
|
313
|
+
if (isSelected) {
|
|
314
|
+
li.classList.add(this.classNames.selected);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// li > div > title span
|
|
318
|
+
const div = li.firstChild as HTMLDivElement;
|
|
319
|
+
const titleSpan = div.firstChild as HTMLSpanElement;
|
|
320
|
+
|
|
321
|
+
this.fillTitleSpan(titleSpan, node.name, isSelected, level);
|
|
322
|
+
|
|
323
|
+
return li;
|
|
324
|
+
}
|
|
277
325
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
326
|
+
/*
|
|
327
|
+
Template for a <li> without children: li > div > title span
|
|
328
|
+
*/
|
|
329
|
+
private createNodeTemplate(): HTMLLIElement {
|
|
330
|
+
const li = this.createLiTemplate(this.classNames.common);
|
|
331
|
+
const div = li.firstChild as HTMLDivElement;
|
|
282
332
|
|
|
283
|
-
|
|
333
|
+
div.append(this.createTitleSpanTemplate(false));
|
|
284
334
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
} else {
|
|
288
|
-
titleSpan.innerHTML = nodeName;
|
|
289
|
-
}
|
|
335
|
+
return li;
|
|
336
|
+
}
|
|
290
337
|
|
|
291
|
-
|
|
338
|
+
private createTitleSpanTemplate(isFolder: boolean): HTMLSpanElement {
|
|
339
|
+
const titleSpan = document.createElement("span");
|
|
340
|
+
|
|
341
|
+
let classes = `${this.classNames.title} ${this.classNames.common}`;
|
|
342
|
+
|
|
343
|
+
if (isFolder) {
|
|
344
|
+
classes += ` ${this.classNames.titleFolder}`;
|
|
292
345
|
}
|
|
293
346
|
|
|
294
|
-
|
|
295
|
-
let classString;
|
|
296
|
-
let role;
|
|
347
|
+
classes += ` ${this.buttonLeft ? this.classNames.titleButtonLeft : this.classNames.titleButtonRight}`;
|
|
297
348
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
349
|
+
titleSpan.className = classes;
|
|
350
|
+
titleSpan.setAttribute("role", "treeitem");
|
|
351
|
+
titleSpan.setAttribute("aria-selected", "false");
|
|
352
|
+
|
|
353
|
+
// Every node is focusable, but only the tab stop is in the tab order
|
|
354
|
+
if (this.tabIndex !== undefined) {
|
|
355
|
+
titleSpan.setAttribute("tabindex", "-1");
|
|
356
|
+
}
|
|
304
357
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
}
|
|
358
|
+
return titleSpan;
|
|
359
|
+
}
|
|
309
360
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
361
|
+
private createUlTemplate(isRootNode: boolean): HTMLUListElement {
|
|
362
|
+
let classString;
|
|
363
|
+
let role;
|
|
313
364
|
|
|
314
|
-
|
|
315
|
-
|
|
365
|
+
if (isRootNode) {
|
|
366
|
+
classString = this.classNames.tree;
|
|
367
|
+
role = "tree";
|
|
316
368
|
|
|
317
|
-
|
|
369
|
+
if (this.rtl) {
|
|
370
|
+
classString += ` ${this.classNames.rtl}`;
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
classString = "";
|
|
374
|
+
role = "group";
|
|
375
|
+
}
|
|
318
376
|
|
|
319
|
-
|
|
377
|
+
if (this.dragAndDrop) {
|
|
378
|
+
classString += ` ${this.classNames.dnd}`;
|
|
320
379
|
}
|
|
321
380
|
|
|
322
|
-
|
|
323
|
-
|
|
381
|
+
const ul = document.createElement("ul");
|
|
382
|
+
ul.className = `${this.classNames.common} ${classString}`;
|
|
324
383
|
|
|
325
|
-
|
|
326
|
-
classes.push(this.classNames.closed);
|
|
327
|
-
}
|
|
384
|
+
ul.setAttribute("role", role);
|
|
328
385
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
386
|
+
if (isRootNode) {
|
|
387
|
+
if (this.ariaLabel !== undefined) {
|
|
388
|
+
ul.setAttribute("aria-label", this.ariaLabel);
|
|
389
|
+
}
|
|
334
390
|
|
|
335
|
-
|
|
391
|
+
if (this.ariaLabelledBy !== undefined) {
|
|
392
|
+
ul.setAttribute("aria-labelledby", this.ariaLabelledBy);
|
|
393
|
+
}
|
|
336
394
|
}
|
|
337
395
|
|
|
338
|
-
|
|
339
|
-
|
|
396
|
+
return ul;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/*
|
|
400
|
+
Set the parts of a cloned title span that differ per node
|
|
401
|
+
*/
|
|
402
|
+
private fillTitleSpan(
|
|
403
|
+
titleSpan: HTMLSpanElement,
|
|
404
|
+
nodeName: string,
|
|
405
|
+
isSelected: boolean,
|
|
406
|
+
level: number,
|
|
407
|
+
): void {
|
|
408
|
+
titleSpan.setAttribute("aria-level", String(level));
|
|
409
|
+
|
|
410
|
+
if (isSelected) {
|
|
411
|
+
titleSpan.setAttribute("aria-selected", "true");
|
|
412
|
+
|
|
413
|
+
const tabIndex = this.tabIndex;
|
|
340
414
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
415
|
+
if (tabIndex !== undefined) {
|
|
416
|
+
titleSpan.setAttribute("tabindex", String(tabIndex));
|
|
417
|
+
}
|
|
418
|
+
}
|
|
344
419
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
420
|
+
if (this.autoEscape) {
|
|
421
|
+
titleSpan.textContent = nodeName;
|
|
422
|
+
} else {
|
|
423
|
+
// eslint-disable-next-line no-unsanitized/property -- the user opted out of escaping with autoEscape: false
|
|
424
|
+
titleSpan.innerHTML = nodeName;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
348
427
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
428
|
+
private getButtonClasses(isOpen: boolean): string {
|
|
429
|
+
const classes = [this.classNames.toggler, this.classNames.common];
|
|
352
430
|
|
|
353
|
-
|
|
431
|
+
if (!isOpen) {
|
|
432
|
+
classes.push(this.classNames.closed);
|
|
354
433
|
}
|
|
355
434
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
isSelected: boolean,
|
|
361
|
-
) {
|
|
362
|
-
element.setAttribute("aria-label", name);
|
|
363
|
-
element.setAttribute("aria-level", `${level}`);
|
|
364
|
-
element.setAttribute("aria-selected", getBoolString(isSelected));
|
|
365
|
-
element.setAttribute("role", "treeitem");
|
|
435
|
+
if (this.buttonLeft) {
|
|
436
|
+
classes.push(this.classNames.togglerLeft);
|
|
437
|
+
} else {
|
|
438
|
+
classes.push(this.classNames.togglerRight);
|
|
366
439
|
}
|
|
440
|
+
|
|
441
|
+
return classes.join(" ");
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/*
|
|
445
|
+
The children of a closed folder are rendered when it is opened
|
|
446
|
+
*/
|
|
447
|
+
private mustRenderChildren(node: Node): boolean {
|
|
448
|
+
return node.hasChildren() && node.is_open === true;
|
|
449
|
+
}
|
|
367
450
|
}
|