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