tree-element 0.4.0 → 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.
Files changed (74) hide show
  1. package/README.md +22 -20
  2. package/lib/dataLoader.d.ts.map +1 -1
  3. package/lib/dataLoader.js +4 -1
  4. package/lib/dataLoader.js.map +1 -1
  5. package/lib/dragAndDropHandler/generateHitAreas.d.ts.map +1 -1
  6. package/lib/dragAndDropHandler/generateHitAreas.js +3 -2
  7. package/lib/dragAndDropHandler/generateHitAreas.js.map +1 -1
  8. package/lib/dragAndDropHandler/index.d.ts +2 -4
  9. package/lib/dragAndDropHandler/index.d.ts.map +1 -1
  10. package/lib/dragAndDropHandler/index.js +2 -9
  11. package/lib/dragAndDropHandler/index.js.map +1 -1
  12. package/lib/elementsRenderer.d.ts +7 -2
  13. package/lib/elementsRenderer.d.ts.map +1 -1
  14. package/lib/elementsRenderer.js +70 -23
  15. package/lib/elementsRenderer.js.map +1 -1
  16. package/lib/events.d.ts +13 -7
  17. package/lib/events.d.ts.map +1 -1
  18. package/lib/folderOpenState.d.ts +8 -0
  19. package/lib/folderOpenState.d.ts.map +1 -0
  20. package/lib/folderOpenState.js +22 -0
  21. package/lib/folderOpenState.js.map +1 -0
  22. package/lib/index.d.ts +17 -16
  23. package/lib/index.d.ts.map +1 -1
  24. package/lib/index.js +93 -49
  25. package/lib/index.js.map +1 -1
  26. package/lib/keyHandler.d.ts +2 -0
  27. package/lib/keyHandler.d.ts.map +1 -1
  28. package/lib/keyHandler.js +18 -0
  29. package/lib/keyHandler.js.map +1 -1
  30. package/lib/mouseHandler.d.ts.map +1 -1
  31. package/lib/mouseHandler.js +12 -12
  32. package/lib/mouseHandler.js.map +1 -1
  33. package/lib/node.d.ts +12 -10
  34. package/lib/node.d.ts.map +1 -1
  35. package/lib/node.js +12 -10
  36. package/lib/node.js.map +1 -1
  37. package/lib/nodeElement/folderElement.d.ts +7 -5
  38. package/lib/nodeElement/folderElement.d.ts.map +1 -1
  39. package/lib/nodeElement/folderElement.js +33 -53
  40. package/lib/nodeElement/folderElement.js.map +1 -1
  41. package/lib/options.d.ts +19 -17
  42. package/lib/options.d.ts.map +1 -1
  43. package/lib/options.js +12 -0
  44. package/lib/options.js.map +1 -0
  45. package/lib/saveStateHandler.d.ts.map +1 -1
  46. package/lib/saveStateHandler.js +6 -6
  47. package/lib/saveStateHandler.js.map +1 -1
  48. package/lib/selectNodeHandler.d.ts.map +1 -1
  49. package/lib/selectNodeHandler.js +13 -1
  50. package/lib/selectNodeHandler.js.map +1 -1
  51. package/lib/version.d.ts +1 -1
  52. package/lib/version.js +1 -1
  53. package/lib/version.js.map +1 -1
  54. package/package.json +35 -29
  55. package/src/dataLoader.ts +3 -1
  56. package/src/dragAndDropHandler/generateHitAreas.ts +3 -2
  57. package/src/dragAndDropHandler/index.ts +3 -17
  58. package/src/elementsRenderer.ts +84 -33
  59. package/src/events.ts +13 -7
  60. package/src/folderOpenState.ts +38 -0
  61. package/src/index.ts +109 -59
  62. package/src/keyHandler.ts +20 -0
  63. package/src/mouseHandler.ts +15 -13
  64. package/src/node.ts +12 -10
  65. package/src/nodeElement/folderElement.ts +45 -70
  66. package/src/options.ts +36 -17
  67. package/src/saveStateHandler.ts +5 -8
  68. package/src/selectNodeHandler.ts +12 -1
  69. package/src/version.ts +1 -1
  70. package/tree_element.css +32 -5
  71. package/tree_element.debug.js +291 -165
  72. package/tree_element.debug.js.map +1 -1
  73. package/tree_element.js +2 -2
  74. 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";
@@ -109,7 +110,6 @@ export default class TreeElement {
109
110
  rtl,
110
111
  saveState: saveStateOption,
111
112
  showEmptyFolder,
112
- slide,
113
113
  tabIndex,
114
114
  } = this.options;
115
115
 
@@ -215,7 +215,6 @@ export default class TreeElement {
215
215
  openFolderDelay,
216
216
  openNode,
217
217
  refreshElements,
218
- slide,
219
218
  treeElement,
220
219
  triggerEvent,
221
220
  });
@@ -325,8 +324,9 @@ export default class TreeElement {
325
324
  }
326
325
 
327
326
  /**
328
- * Inserts a new node between a node and its parent, taking the node as its
329
- * child.
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.
330
330
  *
331
331
  * @returns The new node, or `null` when the node has no parent.
332
332
  * @group Changing the tree
@@ -512,7 +512,7 @@ export default class TreeElement {
512
512
  }
513
513
 
514
514
  /**
515
- * Returns the current state — `open_nodes` and `selected_node` — whether or
515
+ * Returns the current state—`open_nodes` and `selected_node`—whether or
516
516
  * not `saveState` is enabled.
517
517
  *
518
518
  * @group State
@@ -531,7 +531,7 @@ export default class TreeElement {
531
531
  }
532
532
 
533
533
  /**
534
- * Returns the root node. It is not rendered; its `children` are the
534
+ * Returns the root node. It is not rendered, and its `children` are the
535
535
  * top-level nodes. Also available as the `tree` property.
536
536
  *
537
537
  * @group Finding nodes
@@ -647,7 +647,7 @@ export default class TreeElement {
647
647
  }
648
648
 
649
649
  /**
650
- * Opens a folder, and the folders above it. A node marked `load_on_demand`
650
+ * Opens a folder, and the folders that contain it. A node marked `load_on_demand`
651
651
  * is fetched first, so await the promise when you need to know it is really
652
652
  * open.
653
653
  *
@@ -667,7 +667,7 @@ export default class TreeElement {
667
667
  openedNode: Node,
668
668
  slideOption: boolean,
669
669
  ): Promise<void> => {
670
- if (!node.children.length) {
670
+ if (!openedNode.children.length) {
671
671
  return;
672
672
  }
673
673
 
@@ -797,13 +797,47 @@ export default class TreeElement {
797
797
  }
798
798
 
799
799
  /**
800
- * Changes an option after construction. See
801
- * [Options](/reference/options#changing-an-option-later) for the caveats.
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).
802
803
  *
803
804
  * @group Other
804
805
  */
805
- public setOption(option: string, value: unknown) {
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
+
806
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
+ }
807
841
  }
808
842
 
809
843
  /**
@@ -837,8 +871,8 @@ export default class TreeElement {
837
871
  }
838
872
 
839
873
  /**
840
- * Returns the tree as a json string, including changes made through the
841
- * api.
874
+ * Returns the tree as a JSON string, including changes made through the
875
+ * API.
842
876
  *
843
877
  * @group Other
844
878
  */
@@ -851,7 +885,7 @@ export default class TreeElement {
851
885
  * name.
852
886
  *
853
887
  * Changing the `id` re-indexes the node. `children` and `parent` are
854
- * ignored — use `loadData` or `moveNode` for those.
888
+ * ignored—use `loadData` or `moveNode` for those.
855
889
  *
856
890
  * @example
857
891
  * ```js
@@ -920,11 +954,9 @@ export default class TreeElement {
920
954
 
921
955
  private createFolderElement(node: Node) {
922
956
  const classNames = this.classNames;
923
- const closedIconElement = this.renderer.closedIconElement;
924
957
  const getScrollLeft = this.scrollHandler.getScrollLeft.bind(
925
958
  this.scrollHandler,
926
959
  );
927
- const openedIconElement = this.renderer.openedIconElement;
928
960
  const tabIndex = this.options.tabIndex;
929
961
  const treeElement = this.htmlElement;
930
962
  const triggerEvent = this.triggerEvent.bind(this);
@@ -940,11 +972,10 @@ export default class TreeElement {
940
972
 
941
973
  return new FolderElement({
942
974
  classNames,
943
- closedIconElement,
944
975
  getScrollLeft,
976
+ icons: this.renderer.icons,
945
977
  multiSelect: this.options.multiSelect,
946
978
  node,
947
- openedIconElement,
948
979
  renderChildren,
949
980
  tabIndex,
950
981
  treeElement,
@@ -1055,14 +1086,11 @@ export default class TreeElement {
1055
1086
  private getFocusedNode(): Node | null {
1056
1087
  const activeElement = document.activeElement;
1057
1088
 
1058
- /* istanbul ignore if */
1059
- if (!activeElement) {
1060
- return null;
1061
- }
1062
-
1063
- // The keyboard must still work for input elements.
1064
- const tagName = activeElement.tagName;
1065
- if (tagName !== "A" && tagName !== "LI" && 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
+ ) {
1066
1094
  return null;
1067
1095
  }
1068
1096
 
@@ -1125,6 +1153,9 @@ export default class TreeElement {
1125
1153
  this.triggerEvent("tree.init");
1126
1154
  };
1127
1155
 
1156
+ // Before the tree is replaced: then the focused node is no longer in it
1157
+ const focusedNode = this.getFocusedNode();
1158
+
1128
1159
  this.tree = new this.options.nodeClass(null, true, this.options.nodeClass);
1129
1160
 
1130
1161
  this.selectNodeHandler.clear();
@@ -1135,6 +1166,10 @@ export default class TreeElement {
1135
1166
 
1136
1167
  this.refreshElements(null);
1137
1168
 
1169
+ if (focusedNode) {
1170
+ this.restoreFocus(focusedNode);
1171
+ }
1172
+
1138
1173
  if (mustLoadOnDemand) {
1139
1174
  // Load data on demand and then init the tree
1140
1175
  void this.setInitialStateOnDemand().then(doInit);
@@ -1143,19 +1178,6 @@ export default class TreeElement {
1143
1178
  }
1144
1179
  }
1145
1180
 
1146
- // Does an HTML element of the tree have the focus?
1147
- private isFocusOnTree(): boolean {
1148
- return this.getFocusedNode() !== null;
1149
- }
1150
-
1151
- private isSelectedNodeInSubtree(subtree: Node): boolean {
1152
- const selectedNode = this.getSelectedNode();
1153
-
1154
- return selectedNode
1155
- ? subtree === selectedNode || subtree.isParentOf(selectedNode)
1156
- : false;
1157
- }
1158
-
1159
1181
  private async loadFolderOnDemand(node: Node, slide: boolean): Promise<void> {
1160
1182
  node.is_loading = true;
1161
1183
 
@@ -1206,14 +1228,14 @@ export default class TreeElement {
1206
1228
  return this.dndHandler.mouseStart(positionInfo);
1207
1229
  }
1208
1230
 
1209
- private mouseStop(positionInfo: PositionInfo): boolean {
1231
+ private mouseStop(positionInfo: PositionInfo): void {
1210
1232
  /* istanbul ignore if */
1211
1233
  if (!this.options.dragAndDrop) {
1212
- return false;
1234
+ return;
1213
1235
  }
1214
1236
 
1215
1237
  this.scrollHandler.stopScrolling();
1216
- return this.dndHandler.mouseStop(positionInfo);
1238
+ this.dndHandler.mouseStop(positionInfo);
1217
1239
  }
1218
1240
 
1219
1241
  private openParents(node: Node) {
@@ -1229,15 +1251,13 @@ export default class TreeElement {
1229
1251
  from_node: redraw this subtree
1230
1252
  */
1231
1253
  private refreshElements(fromNode: Node | null): void {
1232
- const mustSetFocus = this.isFocusOnTree();
1233
- const mustSelect = fromNode
1234
- ? this.isSelectedNodeInSubtree(fromNode)
1235
- : false;
1254
+ const focusedNode = this.getFocusedNode();
1236
1255
 
1256
+ // The renderer marks the selected nodes, so only the focus needs restoring
1237
1257
  this.renderer.render(fromNode);
1238
1258
 
1239
- if (mustSelect) {
1240
- this.selectCurrentNode(mustSetFocus);
1259
+ if (focusedNode) {
1260
+ this.restoreFocus(focusedNode);
1241
1261
  }
1242
1262
 
1243
1263
  this.updateTabStop();
@@ -1245,18 +1265,26 @@ export default class TreeElement {
1245
1265
  this.triggerEvent("tree.refresh");
1246
1266
  }
1247
1267
 
1248
- private saveState(): void {
1249
- this.saveStateHandler.saveState();
1250
- }
1251
-
1252
- private selectCurrentNode(mustSetFocus: boolean): void {
1253
- const node = this.getSelectedNode();
1254
- 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)) {
1255
1273
  return;
1256
1274
  }
1257
1275
 
1258
- const nodeElement = this.getNodeElementForNode(node);
1259
- nodeElement.select(mustSetFocus);
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();
1260
1288
  }
1261
1289
 
1262
1290
  // Set initial state, either by restoring the state or auto-opening nodes
@@ -1319,6 +1347,28 @@ export default class TreeElement {
1319
1347
  }
1320
1348
  }
1321
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
+
1322
1372
  // Set this HTML element to this node in the node map.
1323
1373
  private setNodeElement(element: HTMLElement, node: Node) {
1324
1374
  this.nodeMap.set(element, node);
@@ -1374,6 +1424,6 @@ export {
1374
1424
  type TreeEvents,
1375
1425
  } from "./events";
1376
1426
  export { type Node, type NodeData, type NodeId, type Position } from "./node";
1377
- export { type TreeElementOptions } from "./options";
1427
+ export { type ChangeableOption, type TreeElementOptions } from "./options";
1378
1428
  export { type SavedState } from "./saveStateHandler";
1379
1429
  export { type SelectNodeOptions } from "./selectNodeHandler";
package/src/keyHandler.ts CHANGED
@@ -93,6 +93,26 @@ export default class KeyHandler {
93
93
  return this.goToNode(this.getCurrentNode()?.getPreviousVisibleNode());
94
94
  }
95
95
 
96
+ // Change keyboardSupport after construction
97
+ public setKeyboardSupport(keyboardSupport: boolean): void {
98
+ if (keyboardSupport === this.keyboardSupport) {
99
+ return;
100
+ }
101
+
102
+ this.keyboardSupport = keyboardSupport;
103
+
104
+ if (keyboardSupport) {
105
+ document.addEventListener("keydown", this.handleKeyDown);
106
+ } else {
107
+ document.removeEventListener("keydown", this.handleKeyDown);
108
+ }
109
+ }
110
+
111
+ // Change multiSelect after construction
112
+ public setMultiSelect(multiSelect: boolean): void {
113
+ this.multiSelect = multiSelect;
114
+ }
115
+
96
116
  // Activate the node, for example to open its edit page.
97
117
  private activate(node: Node, originalEvent: KeyboardEvent): boolean {
98
118
  this.triggerEvent("tree.activate", { node, originalEvent });
@@ -180,7 +180,7 @@ class MouseHandler {
180
180
  }
181
181
  };
182
182
 
183
- private handleContextmenu = (e: MouseEvent) => {
183
+ private handleContextmenu = (e: MouseEvent): void => {
184
184
  if (!e.target) {
185
185
  return;
186
186
  }
@@ -189,21 +189,23 @@ class MouseHandler {
189
189
  `ul.${this.classNames.tree} .${this.classNames.element}`,
190
190
  );
191
191
 
192
- if (div) {
193
- const node = this.getNode(div);
194
- if (node) {
195
- e.preventDefault();
196
- e.stopPropagation();
192
+ if (!div) {
193
+ return;
194
+ }
197
195
 
198
- this.triggerEvent("tree.contextmenu", {
199
- node,
200
- originalEvent: e,
201
- });
202
- return false;
203
- }
196
+ const node = this.getNode(div);
197
+
198
+ if (!node) {
199
+ return;
204
200
  }
205
201
 
206
- return null;
202
+ e.preventDefault();
203
+ e.stopPropagation();
204
+
205
+ this.triggerEvent("tree.contextmenu", {
206
+ node,
207
+ originalEvent: e,
208
+ });
207
209
  };
208
210
 
209
211
  private handleDblclick = (e: MouseEvent): void => {
package/src/node.ts CHANGED
@@ -28,14 +28,14 @@ type IterateCallback = (node: Node, level: number) => boolean;
28
28
  *
29
29
  * @groupDescription Searching
30
30
  * These work on any node, and search that node's subtree. Called on the root
31
- * node, they search the whole tree — which is what the tree's own methods of
31
+ * node, they search the whole tree—which is what the tree's own methods of
32
32
  * the same name do.
33
33
  *
34
34
  * @groupDescription Changing the tree
35
35
  * ::: warning
36
36
  * These methods change the data without re-rendering. The tree's
37
- * [methods](/reference/methods) — `appendNode`, `removeNode`, `moveNode` and
38
- * friends — do the same and refresh the display, so prefer those. If you do
37
+ * [methods](/reference/methods)—`appendNode`, `removeNode`, `moveNode` and
38
+ * friends—do the same and refresh the display, so prefer those. If you do
39
39
  * use these, call `tree.refresh()` afterwards.
40
40
  * :::
41
41
  */
@@ -85,7 +85,7 @@ export class Node {
85
85
  */
86
86
  public nodeClass?: typeof Node;
87
87
  /**
88
- The parent; `null` for the root node.
88
+ The parent, or `null` for the root node.
89
89
  */
90
90
  public parent: Node | null;
91
91
  /**
@@ -191,7 +191,9 @@ export class Node {
191
191
  }
192
192
 
193
193
  /**
194
- * Inserts a new parent between this node and its current parent.
194
+ * Inserts a new parent between this node and its current parent. Its
195
+ * siblings move along: the new node takes all children of the current
196
+ * parent, and becomes its only child.
195
197
  *
196
198
  * @returns The new node, or `null` when the node has no parent.
197
199
  * @group Changing the tree
@@ -268,7 +270,7 @@ export class Node {
268
270
  * Returns the subtree as plain data, ready for `JSON.stringify`.
269
271
  * Internal properties (`parent`, `children`, `element`, `tree`,
270
272
  * `idMapping`, `nodeClass`, `load_on_demand`, `isEmptyFolder`) are left
271
- * out; your own properties are kept.
273
+ * out. Your own properties are kept.
272
274
  *
273
275
  * ```js
274
276
  * tree.getTree().getData();
@@ -384,7 +386,7 @@ export class Node {
384
386
  }
385
387
 
386
388
  /**
387
- * Like `getNextNode`, but skipping nodes inside closed folders — this is
389
+ * Like `getNextNode`, but skipping nodes inside closed folders. This is
388
390
  * what the arrow keys use.
389
391
  *
390
392
  * @group Moving around the tree
@@ -472,7 +474,7 @@ export class Node {
472
474
  }
473
475
 
474
476
  /**
475
- * Returns the parent: `null` for a top-level node — the root node is not
477
+ * Returns the parent, or `null` for a top-level node: the root node is not
476
478
  * returned.
477
479
  *
478
480
  * @group Moving around the tree
@@ -525,8 +527,8 @@ export class Node {
525
527
  }
526
528
 
527
529
  /**
528
- * Like `getPreviousNode`, but skipping nodes inside closed folders —
529
- * this is what the arrow keys use.
530
+ * Like `getPreviousNode`, but skipping nodes inside closed folders.
531
+ * This is what the arrow keys use.
530
532
  *
531
533
  * @group Moving around the tree
532
534
  */
@@ -1,33 +1,32 @@
1
1
  import type { AnimationSpeed } from "../animation";
2
+ import type { FolderIcons } from "../folderOpenState";
2
3
  import type { TriggerEvent } from "../methodTypes";
3
4
  import type { Node, Position } from "../node";
4
5
  import type { NodeElementParams } from "./index";
5
6
 
6
7
  import { slideDown, slideUp } from "../animation";
8
+ import { setFolderLiOpenState, setTogglerOpenState } from "../folderOpenState";
7
9
  import NodeElement from "./index";
8
10
 
9
11
  export type RenderChildren = (node: Node) => HTMLUListElement | null;
10
12
 
11
13
  interface FolderElementParams extends NodeElementParams {
12
- closedIconElement?: globalThis.Node;
13
- openedIconElement?: globalThis.Node;
14
+ icons: FolderIcons;
14
15
  renderChildren: RenderChildren;
15
16
  triggerEvent: TriggerEvent;
16
17
  }
17
18
 
18
19
  class FolderElement extends NodeElement {
19
- private closedIconElement?: globalThis.Node;
20
- private openedIconElement?: globalThis.Node;
20
+ private icons: FolderIcons;
21
21
  private renderChildren: RenderChildren;
22
22
  private triggerEvent: TriggerEvent;
23
23
 
24
24
  constructor({
25
25
  classNames,
26
- closedIconElement,
27
26
  getScrollLeft,
27
+ icons,
28
28
  multiSelect,
29
29
  node,
30
- openedIconElement,
31
30
  renderChildren,
32
31
  tabIndex,
33
32
  treeElement,
@@ -42,8 +41,7 @@ class FolderElement extends NodeElement {
42
41
  treeElement,
43
42
  });
44
43
 
45
- this.closedIconElement = closedIconElement;
46
- this.openedIconElement = openedIconElement;
44
+ this.icons = icons;
47
45
  this.renderChildren = renderChildren;
48
46
  this.triggerEvent = triggerEvent;
49
47
  }
@@ -58,39 +56,13 @@ class FolderElement extends NodeElement {
58
56
 
59
57
  this.node.is_open = false;
60
58
 
61
- if (!this.isRendered()) {
62
- this.triggerEvent("tree.close", { node: this.node });
63
- return;
64
- }
65
-
66
- const button = this.getButton();
67
- button.classList.add(this.classNames.closed);
68
- button.replaceChildren();
69
-
70
- const closedIconElement = this.closedIconElement;
71
-
72
- if (closedIconElement) {
73
- const icon = closedIconElement.cloneNode(true);
74
- button.append(icon);
59
+ if (this.isRendered()) {
60
+ this.setButtonState(false);
61
+ await this.setUlState(this.getUl(), false, slide, animationSpeed);
62
+ this.setElementState(false);
75
63
  }
76
64
 
77
- const ul = this.getUl();
78
-
79
- if (ul) {
80
- if (slide) {
81
- await slideUp(ul, animationSpeed);
82
- } else {
83
- ul.style.display = "none";
84
- }
85
- }
86
-
87
- this.element.classList.add(this.classNames.closed);
88
-
89
- this.element.setAttribute("aria-expanded", "false");
90
-
91
- this.triggerEvent("tree.close", {
92
- node: this.node,
93
- });
65
+ this.triggerEvent("tree.close", { node: this.node });
94
66
  }
95
67
 
96
68
  public async open(
@@ -103,41 +75,19 @@ class FolderElement extends NodeElement {
103
75
 
104
76
  this.node.is_open = true;
105
77
 
106
- if (!this.isRendered()) {
107
- // The folder is rendered open when its parent is opened
108
- this.triggerEvent("tree.open", { node: this.node });
109
- return;
110
- }
111
-
112
- const button = this.getButton();
113
- button.classList.remove(this.classNames.closed);
114
- button.replaceChildren();
115
-
116
- const openedIconElement = this.openedIconElement;
117
-
118
- if (openedIconElement) {
119
- const icon = openedIconElement.cloneNode(true);
120
- button.append(icon);
121
- }
78
+ // A folder that is not rendered is rendered open when its parent is
79
+ // opened
80
+ if (this.isRendered()) {
81
+ this.setButtonState(true);
122
82
 
123
- // The children are rendered the first time the folder is opened
124
- const ul = this.getUl() ?? this.renderChildren(this.node);
83
+ // The children are rendered the first time the folder is opened
84
+ const ul = this.getUl() ?? this.renderChildren(this.node);
85
+ await this.setUlState(ul, true, slide, animationSpeed);
125
86
 
126
- if (ul) {
127
- if (slide) {
128
- await slideDown(ul, animationSpeed);
129
- } else {
130
- ul.style.display = "block";
131
- }
87
+ this.setElementState(true);
132
88
  }
133
89
 
134
- this.element.classList.remove(this.classNames.closed);
135
-
136
- this.element.setAttribute("aria-expanded", "true");
137
-
138
- this.triggerEvent("tree.open", {
139
- node: this.node,
140
- });
90
+ this.triggerEvent("tree.open", { node: this.node });
141
91
  }
142
92
 
143
93
  protected mustShowBorderDropHint(position: Position): boolean {
@@ -149,6 +99,31 @@ class FolderElement extends NodeElement {
149
99
  `:scope > .${this.classNames.element} > .${this.classNames.toggler}`,
150
100
  ) as HTMLElement;
151
101
  }
102
+
103
+ private setButtonState(isOpen: boolean): void {
104
+ setTogglerOpenState(this.getButton(), isOpen, this.classNames, this.icons);
105
+ }
106
+
107
+ private setElementState(isOpen: boolean): void {
108
+ setFolderLiOpenState(this.element, isOpen, this.classNames);
109
+ }
110
+
111
+ private async setUlState(
112
+ ul: HTMLUListElement | null,
113
+ isOpen: boolean,
114
+ slide: boolean,
115
+ animationSpeed: AnimationSpeed,
116
+ ): Promise<void> {
117
+ if (!ul) {
118
+ return;
119
+ }
120
+
121
+ if (slide) {
122
+ await (isOpen ? slideDown : slideUp)(ul, animationSpeed);
123
+ } else {
124
+ ul.style.display = isOpen ? "block" : "none";
125
+ }
126
+ }
152
127
  }
153
128
 
154
129
  export default FolderElement;