tree-element 0.3.1 → 0.4.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 (59) hide show
  1. package/README.md +1 -0
  2. package/lib/classNames.js +4 -4
  3. package/lib/classNames.js.map +1 -1
  4. package/lib/dataLoader.d.ts +0 -1
  5. package/lib/dataLoader.d.ts.map +1 -1
  6. package/lib/dataLoader.js +6 -6
  7. package/lib/dataLoader.js.map +1 -1
  8. package/lib/elementsRenderer.d.ts +4 -2
  9. package/lib/elementsRenderer.d.ts.map +1 -1
  10. package/lib/elementsRenderer.js +35 -21
  11. package/lib/elementsRenderer.js.map +1 -1
  12. package/lib/index.d.ts +10 -4
  13. package/lib/index.d.ts.map +1 -1
  14. package/lib/index.js +75 -18
  15. package/lib/index.js.map +1 -1
  16. package/lib/keyHandler.d.ts +14 -3
  17. package/lib/keyHandler.d.ts.map +1 -1
  18. package/lib/keyHandler.js +61 -32
  19. package/lib/keyHandler.js.map +1 -1
  20. package/lib/methodTypes.d.ts +3 -0
  21. package/lib/methodTypes.d.ts.map +1 -1
  22. package/lib/nodeElement/folderElement.d.ts +1 -1
  23. package/lib/nodeElement/folderElement.d.ts.map +1 -1
  24. package/lib/nodeElement/folderElement.js +4 -4
  25. package/lib/nodeElement/folderElement.js.map +1 -1
  26. package/lib/nodeElement/index.d.ts +3 -2
  27. package/lib/nodeElement/index.d.ts.map +1 -1
  28. package/lib/nodeElement/index.js +15 -12
  29. package/lib/nodeElement/index.js.map +1 -1
  30. package/lib/options.d.ts +13 -0
  31. package/lib/options.d.ts.map +1 -1
  32. package/lib/selectNodeHandler.d.ts +3 -0
  33. package/lib/selectNodeHandler.d.ts.map +1 -1
  34. package/lib/selectNodeHandler.js +54 -22
  35. package/lib/selectNodeHandler.js.map +1 -1
  36. package/lib/setDefaultOptions.d.ts.map +1 -1
  37. package/lib/setDefaultOptions.js +5 -4
  38. package/lib/setDefaultOptions.js.map +1 -1
  39. package/lib/version.d.ts +1 -1
  40. package/lib/version.js +1 -1
  41. package/lib/version.js.map +1 -1
  42. package/package.json +19 -19
  43. package/src/classNames.ts +4 -4
  44. package/src/dataLoader.ts +6 -9
  45. package/src/elementsRenderer.ts +42 -24
  46. package/src/index.ts +83 -24
  47. package/src/keyHandler.ts +84 -34
  48. package/src/methodTypes.ts +6 -0
  49. package/src/nodeElement/folderElement.ts +4 -4
  50. package/src/nodeElement/index.ts +20 -17
  51. package/src/options.ts +13 -0
  52. package/src/selectNodeHandler.ts +58 -25
  53. package/src/setDefaultOptions.ts +5 -4
  54. package/src/version.ts +1 -1
  55. package/tree_element.css +16 -0
  56. package/tree_element.debug.js +260 -124
  57. package/tree_element.debug.js.map +1 -1
  58. package/tree_element.js +2 -2
  59. package/tree_element.js.map +1 -1
@@ -75,9 +75,9 @@ class SelectNodeHandler {
75
75
  } else {
76
76
  this._selectedNodes.delete(node.id);
77
77
  if (includeChildren) {
78
- node.iterate(() => {
79
- if (node.id != null) {
80
- this._selectedNodes.delete(node.id);
78
+ node.iterate(child => {
79
+ if (child.id != null) {
80
+ this._selectedNodes.delete(child.id);
81
81
  }
82
82
  return true;
83
83
  });
@@ -96,6 +96,9 @@ class SelectNodeHandler {
96
96
  * mustToggle: support deselecting the selected node
97
97
  */
98
98
  _selectSingleNode(node, optionsParam) {
99
+ if (!this._canSelect(node)) {
100
+ return;
101
+ }
99
102
  let defaultOptions = {
100
103
  mustSetFocus: true,
101
104
  mustToggle: true
@@ -104,31 +107,23 @@ class SelectNodeHandler {
104
107
  ...defaultOptions,
105
108
  ...optionsParam
106
109
  };
107
- let canSelect = () => {
108
- if (!this._getSelectable()) {
109
- return false;
110
- }
111
- let onCanSelectNode = this._getOnCanSelectNode();
112
- return onCanSelectNode ? onCanSelectNode(node) : true;
113
- };
114
- if (!canSelect()) {
115
- return;
116
- }
117
- let deselectCurrentNode = deselectedNode => {
118
- this._removeFromSelection(deselectedNode);
119
- this._getNodeElementForNode(deselectedNode)._deselect();
120
- };
110
+ let otherSelectedNodes = this._getSelectedNodes().filter(selectedNode => selectedNode !== node);
121
111
  if (this._isNodeSelected(node)) {
122
- if (selectOptions.mustToggle) {
123
- deselectCurrentNode(node);
112
+ if (otherSelectedNodes.length) {
113
+ // Part of a multiple selection: keep only this node selected
114
+ for (let otherNode of otherSelectedNodes) {
115
+ this._deselectNode(otherNode);
116
+ }
117
+ } else if (selectOptions.mustToggle) {
118
+ this._deselectNode(node);
124
119
  this._triggerEvent("tree.deselect", {
125
120
  node
126
121
  });
127
122
  }
128
123
  } else {
129
- let deselectedNode = this._getSelectedNode();
130
- if (deselectedNode) {
131
- deselectCurrentNode(deselectedNode);
124
+ let deselectedNode = otherSelectedNodes[0] ?? null;
125
+ for (let otherNode of otherSelectedNodes) {
126
+ this._deselectNode(otherNode);
132
127
  }
133
128
  this._addToSelection(node);
134
129
  this._openParents(node);
@@ -141,6 +136,43 @@ class SelectNodeHandler {
141
136
  this._updateTabStop();
142
137
  this._saveState();
143
138
  }
139
+
140
+ /* Add a node to the selection, or remove it when it is selected, without
141
+ * changing the other selected nodes.
142
+ * Checks if the node is selectable and saves the state.
143
+ */
144
+ _toggleNodeInSelection(node, mustSetFocus = true) {
145
+ if (!this._canSelect(node)) {
146
+ return;
147
+ }
148
+ if (this._isNodeSelected(node)) {
149
+ this._deselectNode(node);
150
+ this._triggerEvent("tree.deselect", {
151
+ node
152
+ });
153
+ } else {
154
+ this._addToSelection(node);
155
+ this._openParents(node);
156
+ this._getNodeElementForNode(node)._select(mustSetFocus);
157
+ this._triggerEvent("tree.select", {
158
+ deselectedNode: null,
159
+ node
160
+ });
161
+ }
162
+ this._updateTabStop();
163
+ this._saveState();
164
+ }
165
+ _canSelect(node) {
166
+ if (!this._getSelectable()) {
167
+ return false;
168
+ }
169
+ let onCanSelectNode = this._getOnCanSelectNode();
170
+ return onCanSelectNode ? onCanSelectNode(node) : true;
171
+ }
172
+ _deselectNode(node) {
173
+ this._removeFromSelection(node);
174
+ this._getNodeElementForNode(node)._deselect();
175
+ }
144
176
  }
145
177
 
146
178
  export { SelectNodeHandler as default };
@@ -1 +1 @@
1
- {"version":3,"file":"selectNodeHandler.js","sources":["../src/selectNodeHandler.ts"],"sourcesContent":["import type {\n GetNodeById,\n GetNodeElementForNode,\n OpenParents,\n TriggerEvent,\n} from \"./methodTypes\";\nimport type { Node, NodeId } from \"./node\";\nimport type { OnCanSelectNode } from \"./options\";\n\nexport interface SelectNodeOptions {\n mustSetFocus?: boolean;\n mustToggle?: boolean;\n}\n\ninterface SelectNodeHandlerParameters {\n getNodeById: GetNodeById;\n getNodeElementForNode: GetNodeElementForNode;\n getOnCanSelectNode: () => OnCanSelectNode | undefined;\n getSelectable: () => boolean;\n openParents: OpenParents;\n saveState: () => void;\n triggerEvent: TriggerEvent;\n updateTabStop: () => void;\n}\n\nexport default class SelectNodeHandler {\n private getNodeById: GetNodeById;\n private getNodeElementForNode: GetNodeElementForNode;\n private getOnCanSelectNode: () => OnCanSelectNode | undefined;\n private getSelectable: () => boolean;\n private openParents: OpenParents;\n private saveState: () => void;\n private selectedNodes: Set<NodeId>;\n private selectedSingleNode: Node | null;\n private triggerEvent: TriggerEvent;\n private updateTabStop: () => void;\n\n constructor({\n getNodeById,\n getNodeElementForNode,\n getOnCanSelectNode,\n getSelectable,\n openParents,\n saveState,\n triggerEvent,\n updateTabStop,\n }: SelectNodeHandlerParameters) {\n this.getNodeById = getNodeById;\n this.getNodeElementForNode = getNodeElementForNode;\n this.getOnCanSelectNode = getOnCanSelectNode;\n this.getSelectable = getSelectable;\n this.openParents = openParents;\n this.saveState = saveState;\n this.selectedNodes = new Set<NodeId>();\n this.selectedSingleNode = null;\n this.triggerEvent = triggerEvent;\n this.updateTabStop = updateTabStop;\n }\n\n public addToSelection(node: Node): void {\n if (node.id == null) {\n this.selectedSingleNode = node;\n } else {\n this.selectedNodes.add(node.id);\n }\n }\n\n public clear(): void {\n this.selectedNodes.clear();\n this.selectedSingleNode = null;\n }\n\n public getSelectedNode(): Node | null {\n const selectedNodes = this.getSelectedNodes();\n\n return selectedNodes.length ? (selectedNodes[0] ?? null) : null;\n }\n\n public getSelectedNodes(): Node[] {\n if (this.selectedSingleNode) {\n return [this.selectedSingleNode];\n }\n\n const selectedNodes: Node[] = [];\n\n this.selectedNodes.forEach((id) => {\n const node = this.getNodeById(id);\n if (node) {\n selectedNodes.push(node);\n }\n });\n\n return selectedNodes;\n }\n\n public getSelectedNodesUnder(parent: Node): Node[] {\n if (this.selectedSingleNode) {\n return parent.isParentOf(this.selectedSingleNode)\n ? [this.selectedSingleNode]\n : [];\n }\n\n const selectedNodes: Node[] = [];\n\n this.selectedNodes.forEach((id) => {\n const node = this.getNodeById(id);\n if (node && parent.isParentOf(node)) {\n selectedNodes.push(node);\n }\n });\n\n return selectedNodes;\n }\n\n public isNodeSelected(node: Node): boolean {\n if (node.id != null) {\n return this.selectedNodes.has(node.id);\n }\n return this.selectedSingleNode ? this.selectedSingleNode === node : false;\n }\n\n public removeFromSelection(node: Node, includeChildren = false): void {\n if (node.id == null) {\n if (this.selectedSingleNode === node) {\n this.selectedSingleNode = null;\n }\n } else {\n this.selectedNodes.delete(node.id);\n\n if (includeChildren) {\n node.iterate(() => {\n if (node.id != null) {\n this.selectedNodes.delete(node.id);\n }\n return true;\n });\n }\n }\n }\n\n /* Select a single node.\n * Renders the changed elements.\n * Deselects if the node is currently selected (if the mustToggle is on).\n * Deselects the previously selected node.\n * Check if the node is selectable.\n * Saves the state.\n * Options:\n * mustSetFocus: set the focus to the selected node\n * mustToggle: support deselecting the selected node\n */\n public selectSingleNode(node: Node, optionsParam?: SelectNodeOptions) {\n const defaultOptions = { mustSetFocus: true, mustToggle: true };\n const selectOptions = { ...defaultOptions, ...optionsParam };\n\n const canSelect = (): boolean => {\n if (!this.getSelectable()) {\n return false;\n }\n\n const onCanSelectNode = this.getOnCanSelectNode();\n return onCanSelectNode ? onCanSelectNode(node) : true;\n };\n\n if (!canSelect()) {\n return;\n }\n\n const deselectCurrentNode = (deselectedNode: Node) => {\n this.removeFromSelection(deselectedNode);\n this.getNodeElementForNode(deselectedNode).deselect();\n };\n\n if (this.isNodeSelected(node)) {\n if (selectOptions.mustToggle) {\n deselectCurrentNode(node);\n this.triggerEvent(\"tree.deselect\", { node });\n }\n } else {\n const deselectedNode = this.getSelectedNode();\n\n if (deselectedNode) {\n deselectCurrentNode(deselectedNode);\n }\n\n this.addToSelection(node);\n this.openParents(node);\n this.getNodeElementForNode(node).select(selectOptions.mustSetFocus);\n\n this.triggerEvent(\"tree.select\", {\n deselectedNode,\n node,\n });\n }\n\n this.updateTabStop();\n this.saveState();\n }\n}\n"],"names":["getNodeById","getNodeElementForNode","getOnCanSelectNode","getSelectable","openParents","saveState","selectedNodes","selectedSingleNode","triggerEvent","updateTabStop","addToSelection","clear","getSelectedNode","getSelectedNodes","getSelectedNodesUnder","isNodeSelected","removeFromSelection","selectSingleNode","deselect","select"],"mappings":"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"}
1
+ {"version":3,"file":"selectNodeHandler.js","sources":["../src/selectNodeHandler.ts"],"sourcesContent":["import type {\n GetNodeById,\n GetNodeElementForNode,\n OpenParents,\n TriggerEvent,\n} from \"./methodTypes\";\nimport type { Node, NodeId } from \"./node\";\nimport type { OnCanSelectNode } from \"./options\";\n\nexport interface SelectNodeOptions {\n mustSetFocus?: boolean;\n mustToggle?: boolean;\n}\n\ninterface SelectNodeHandlerParameters {\n getNodeById: GetNodeById;\n getNodeElementForNode: GetNodeElementForNode;\n getOnCanSelectNode: () => OnCanSelectNode | undefined;\n getSelectable: () => boolean;\n openParents: OpenParents;\n saveState: () => void;\n triggerEvent: TriggerEvent;\n updateTabStop: () => void;\n}\n\nexport default class SelectNodeHandler {\n private getNodeById: GetNodeById;\n private getNodeElementForNode: GetNodeElementForNode;\n private getOnCanSelectNode: () => OnCanSelectNode | undefined;\n private getSelectable: () => boolean;\n private openParents: OpenParents;\n private saveState: () => void;\n private selectedNodes: Set<NodeId>;\n private selectedSingleNode: Node | null;\n private triggerEvent: TriggerEvent;\n private updateTabStop: () => void;\n\n constructor({\n getNodeById,\n getNodeElementForNode,\n getOnCanSelectNode,\n getSelectable,\n openParents,\n saveState,\n triggerEvent,\n updateTabStop,\n }: SelectNodeHandlerParameters) {\n this.getNodeById = getNodeById;\n this.getNodeElementForNode = getNodeElementForNode;\n this.getOnCanSelectNode = getOnCanSelectNode;\n this.getSelectable = getSelectable;\n this.openParents = openParents;\n this.saveState = saveState;\n this.selectedNodes = new Set<NodeId>();\n this.selectedSingleNode = null;\n this.triggerEvent = triggerEvent;\n this.updateTabStop = updateTabStop;\n }\n\n public addToSelection(node: Node): void {\n if (node.id == null) {\n this.selectedSingleNode = node;\n } else {\n this.selectedNodes.add(node.id);\n }\n }\n\n public clear(): void {\n this.selectedNodes.clear();\n this.selectedSingleNode = null;\n }\n\n public getSelectedNode(): Node | null {\n const selectedNodes = this.getSelectedNodes();\n\n return selectedNodes.length ? (selectedNodes[0] ?? null) : null;\n }\n\n public getSelectedNodes(): Node[] {\n if (this.selectedSingleNode) {\n return [this.selectedSingleNode];\n }\n\n const selectedNodes: Node[] = [];\n\n this.selectedNodes.forEach((id) => {\n const node = this.getNodeById(id);\n if (node) {\n selectedNodes.push(node);\n }\n });\n\n return selectedNodes;\n }\n\n public getSelectedNodesUnder(parent: Node): Node[] {\n if (this.selectedSingleNode) {\n return parent.isParentOf(this.selectedSingleNode)\n ? [this.selectedSingleNode]\n : [];\n }\n\n const selectedNodes: Node[] = [];\n\n this.selectedNodes.forEach((id) => {\n const node = this.getNodeById(id);\n if (node && parent.isParentOf(node)) {\n selectedNodes.push(node);\n }\n });\n\n return selectedNodes;\n }\n\n public isNodeSelected(node: Node): boolean {\n if (node.id != null) {\n return this.selectedNodes.has(node.id);\n }\n return this.selectedSingleNode ? this.selectedSingleNode === node : false;\n }\n\n public removeFromSelection(node: Node, includeChildren = false): void {\n if (node.id == null) {\n if (this.selectedSingleNode === node) {\n this.selectedSingleNode = null;\n }\n } else {\n this.selectedNodes.delete(node.id);\n\n if (includeChildren) {\n node.iterate((child: Node) => {\n if (child.id != null) {\n this.selectedNodes.delete(child.id);\n }\n return true;\n });\n }\n }\n }\n\n /* Select a single node.\n * Renders the changed elements.\n * Deselects if the node is currently selected (if the mustToggle is on).\n * Deselects the previously selected node.\n * Check if the node is selectable.\n * Saves the state.\n * Options:\n * mustSetFocus: set the focus to the selected node\n * mustToggle: support deselecting the selected node\n */\n public selectSingleNode(node: Node, optionsParam?: SelectNodeOptions) {\n if (!this.canSelect(node)) {\n return;\n }\n\n const defaultOptions = { mustSetFocus: true, mustToggle: true };\n const selectOptions = { ...defaultOptions, ...optionsParam };\n\n const otherSelectedNodes = this.getSelectedNodes().filter(\n (selectedNode) => selectedNode !== node,\n );\n\n if (this.isNodeSelected(node)) {\n if (otherSelectedNodes.length) {\n // Part of a multiple selection: keep only this node selected\n for (const otherNode of otherSelectedNodes) {\n this.deselectNode(otherNode);\n }\n } else if (selectOptions.mustToggle) {\n this.deselectNode(node);\n this.triggerEvent(\"tree.deselect\", { node });\n }\n } else {\n const deselectedNode = otherSelectedNodes[0] ?? null;\n\n for (const otherNode of otherSelectedNodes) {\n this.deselectNode(otherNode);\n }\n\n this.addToSelection(node);\n this.openParents(node);\n this.getNodeElementForNode(node).select(selectOptions.mustSetFocus);\n\n this.triggerEvent(\"tree.select\", {\n deselectedNode,\n node,\n });\n }\n\n this.updateTabStop();\n this.saveState();\n }\n\n /* Add a node to the selection, or remove it when it is selected, without\n * changing the other selected nodes.\n * Checks if the node is selectable and saves the state.\n */\n public toggleNodeInSelection(node: Node, mustSetFocus = true): void {\n if (!this.canSelect(node)) {\n return;\n }\n\n if (this.isNodeSelected(node)) {\n this.deselectNode(node);\n this.triggerEvent(\"tree.deselect\", { node });\n } else {\n this.addToSelection(node);\n this.openParents(node);\n this.getNodeElementForNode(node).select(mustSetFocus);\n\n this.triggerEvent(\"tree.select\", { deselectedNode: null, node });\n }\n\n this.updateTabStop();\n this.saveState();\n }\n\n private canSelect(node: Node): boolean {\n if (!this.getSelectable()) {\n return false;\n }\n\n const onCanSelectNode = this.getOnCanSelectNode();\n return onCanSelectNode ? onCanSelectNode(node) : true;\n }\n\n private deselectNode(node: Node): void {\n this.removeFromSelection(node);\n this.getNodeElementForNode(node).deselect();\n }\n}\n"],"names":["getNodeById","getNodeElementForNode","getOnCanSelectNode","getSelectable","openParents","saveState","selectedNodes","selectedSingleNode","triggerEvent","updateTabStop","addToSelection","clear","getSelectedNode","getSelectedNodes","getSelectedNodesUnder","isNodeSelected","removeFromSelection","selectSingleNode","canSelect","deselectNode","select","toggleNodeInSelection","deselect"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"setDefaultOptions.d.ts","sourceRoot":"","sources":["../src/setDefaultOptions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAapD,eAAO,MAAM,YAAY,QAAkC,CAAC;AAC5D,eAAO,MAAM,YAAY,QAAgC,CAAC;AAC1D,eAAO,MAAM,aAAa,QAAgC,CAAC;AAsC3D,QAAA,MAAM,iBAAiB,GACrB,aAAa,WAAW,EACxB,cAAc,OAAO,CAAC,kBAAkB,CAAC,KACxC,kBAaF,CAAC;AAuBF,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"setDefaultOptions.d.ts","sourceRoot":"","sources":["../src/setDefaultOptions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAapD,eAAO,MAAM,YAAY,QAAoC,CAAC;AAC9D,eAAO,MAAM,YAAY,QAAoC,CAAC;AAC9D,eAAO,MAAM,aAAa,QAAoC,CAAC;AAuC/D,QAAA,MAAM,iBAAiB,GACrB,aAAa,WAAW,EACxB,cAAc,OAAO,CAAC,kBAAkB,CAAC,KACxC,kBAaF,CAAC;AAuBF,eAAe,iBAAiB,CAAC"}
@@ -6,10 +6,10 @@ import { Node } from './node.js';
6
6
  * They are sized in em to follow the font size, and use currentColor to follow
7
7
  * --tree-element-toggler-color.
8
8
  */
9
- let triangle = points => `<svg width="0.7em" height="0.7em" viewBox="0 0 10 10" focusable="false"><polygon points="${points}" fill="currentColor"/></svg>`;
10
- let triangleDown = triangle("0.5,1.5 9.5,1.5 5,9");
11
- let triangleLeft = triangle("9,0.5 9,9.5 1.5,5");
12
- let triangleRight = triangle("1,0.5 1,9.5 8.5,5");
9
+ let triangle = points => `<svg width="0.8em" height="0.8em" viewBox="0 0 10 10" focusable="false"><polygon points="${points}" fill="currentColor"/></svg>`;
10
+ let triangleDown = triangle("0.5,0.5 9.5,0.5 5,9.5");
11
+ let triangleLeft = triangle("9.5,0.5 9.5,9.5 0.5,5");
12
+ let triangleRight = triangle("0.5,0.5 0.5,9.5 9.5,5");
13
13
  let defaults = {
14
14
  animationSpeed: "fast",
15
15
  autoEscape: true,
@@ -25,6 +25,7 @@ let defaults = {
25
25
  // dataUrl: undefined,
26
26
  dragAndDrop: false,
27
27
  keyboardSupport: true,
28
+ multiSelect: false,
28
29
  nodeClass: Node,
29
30
  // onCanMove: undefined, // Can this node be moved?
30
31
  // onCanMoveTo: undefined, // Can this node be moved to this position? function(moved_node, target_node, position)
@@ -1 +1 @@
1
- {"version":3,"file":"setDefaultOptions.js","sources":["../src/setDefaultOptions.ts"],"sourcesContent":["import type { AnimationSpeed } from \"./animation\";\nimport type { TreeElementOptions } from \"./options\";\n\nimport { DEFAULT_CLASS_PREFIX } from \"./classNames\";\nimport { Node } from \"./node\";\n\n/* The default icons are svg instead of text: the triangle characters come\n * from different fonts on each platform, so they differ in shape and size.\n * They are sized in em to follow the font size, and use currentColor to follow\n * --tree-element-toggler-color.\n */\nconst triangle = (points: string): string =>\n `<svg width=\"0.7em\" height=\"0.7em\" viewBox=\"0 0 10 10\" focusable=\"false\"><polygon points=\"${points}\" fill=\"currentColor\"/></svg>`;\n\nexport const triangleDown = triangle(\"0.5,1.5 9.5,1.5 5,9\");\nexport const triangleLeft = triangle(\"9,0.5 9,9.5 1.5,5\");\nexport const triangleRight = triangle(\"1,0.5 1,9.5 8.5,5\");\n\nconst defaults: TreeElementOptions = {\n animationSpeed: \"fast\",\n autoEscape: true,\n autoOpen: false, // true / false / int (open n levels starting at 0)\n buttonLeft: true,\n classPrefix: DEFAULT_CLASS_PREFIX, // the prefix of all css classes\n // closedIcon: undefined, // the default is a triangle; see getDefaultClosedIcon\n // commonClassName: undefined, // the class of every element; the default is \"<classPrefix>-common\"\n // data: undefined,\n // dataFilter: undefined,\n // dataUrl: undefined,\n dragAndDrop: false,\n keyboardSupport: true,\n nodeClass: Node,\n // onCanMove: undefined, // Can this node be moved?\n // onCanMoveTo: undefined, // Can this node be moved to this position? function(moved_node, target_node, position)\n // onCanSelectNode: undefined,\n // onCreateLi: undefined,\n // onDragMove: undefined,\n // onDragStop: undefined,\n // onGetStateFromStorage: undefined,\n // onIsMoveHandle: undefined,\n // onSetStateFromStorage: undefined,\n // openedIcon: undefined, // the default is a triangle pointing down\n openFolderDelay: 500, // The delay for opening a folder during drag and drop; the value is in milliseconds\n // rtl: undefined, // right-to-left support; true / false (default)\n saveState: false, // true / false / string (local storage key; the default key is \"tree\")\n selectable: true,\n showEmptyFolder: false,\n slide: true, // must display slide animation?\n startDndDelay: 300, // The delay for starting dnd (in milliseconds)\n tabIndex: 0,\n // treeClassName: undefined, // the class of the root element; the default is classPrefix\n useContextMenu: true,\n};\n\nconst setDefaultOptions = (\n htmlElement: HTMLElement,\n inputOptions: Partial<TreeElementOptions>,\n): TreeElementOptions => {\n const options = {\n ...defaults,\n ...inputOptions,\n animationSpeed: inputOptions.animationSpeed ?? getDefaultAnimationSpeed(),\n };\n\n options.dataUrl ??= htmlElement.dataset.url;\n options.rtl ??= getRtlOptionFromHTMLElement(htmlElement);\n options.closedIcon ??= getDefaultClosedIcon(options);\n options.openedIcon ??= triangleDown;\n\n return options;\n};\n\n// No animation when the user asked the system for reduced motion\nconst getDefaultAnimationSpeed = (): AnimationSpeed =>\n prefersReducedMotion() ? 0 : defaults.animationSpeed;\n\nconst prefersReducedMotion = (): boolean =>\n typeof window.matchMedia === \"function\" &&\n window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n\nconst getDefaultClosedIcon = (options: TreeElementOptions): string => {\n return options.rtl ? triangleLeft : triangleRight;\n};\n\nconst getRtlOptionFromHTMLElement = (htmlElement: HTMLElement): boolean => {\n const dataRtl = htmlElement.dataset.rtl;\n\n if (dataRtl == \"\") {\n return true;\n }\n return dataRtl === \"false\" ? false : Boolean(dataRtl);\n};\n\nexport default setDefaultOptions;\n"],"names":[],"mappings":";;;AAMA;AACA;AACA;AACA;AACA;AACA,IAAM,QAAQ,GAAI,MAAc,IAC9B,CAAA,yFAAA,EAA4F,MAAM,CAAA,6BAAA,CAA+B;IAEtH,YAAY,GAAG,QAAQ,CAAC,qBAAqB;IAC7C,YAAY,GAAG,QAAQ,CAAC,mBAAmB;IAC3C,aAAa,GAAG,QAAQ,CAAC,mBAAmB;AAEzD,IAAM,QAA4B,GAAG;AACnC,EAAA,cAAc,EAAE,MAAM;AACtB,EAAA,UAAU,EAAE,IAAI;AAChB,EAAA,QAAQ,EAAE,KAAK;AAAE;AACjB,EAAA,UAAU,EAAE,IAAI;AAChB,EAAA,WAAW,EAAE,oBAAoB;AAAE;AACnC;AACA;AACA;AACA;AACA;AACA,EAAA,WAAW,EAAE,KAAK;AAClB,EAAA,eAAe,EAAE,IAAI;AACrB,EAAA,SAAS,EAAE,IAAI;AACf;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA,eAAe,EAAE,GAAG;AAAE;AACtB;AACA,EAAA,SAAS,EAAE,KAAK;AAAE;AAClB,EAAA,UAAU,EAAE,IAAI;AAChB,EAAA,eAAe,EAAE,KAAK;AACtB,EAAA,KAAK,EAAE,IAAI;AAAE;AACb,EAAA,aAAa,EAAE,GAAG;AAAE;AACpB,EAAA,QAAQ,EAAE,CAAC;AACX;AACA,EAAA,cAAc,EAAE;AAClB,CAAC;AAED,IAAM,iBAAiB,GAAG,CACxB,WAAwB,EACxB,YAAyC,KAClB;AACvB,EAAA,IAAM,OAAO,GAAG;AACd,IAAA,GAAG,QAAQ;AACX,IAAA,GAAG,YAAY;AACf,IAAA,cAAc,EAAE,YAAY,CAAC,cAAc,IAAI,wBAAwB;GACxE;AAED,EAAA,OAAO,CAAC,OAAO,KAAK,WAAW,CAAC,OAAO,CAAC,GAAG;AAC3C,EAAA,OAAO,CAAC,GAAG,KAAK,2BAA2B,CAAC,WAAW,CAAC;AACxD,EAAA,OAAO,CAAC,UAAU,KAAK,oBAAoB,CAAC,OAAO,CAAC;EACpD,OAAO,CAAC,UAAU,KAAK,YAAY;AAEnC,EAAA,OAAO,OAAO;AAChB;;AAEA;AACA,IAAM,wBAAwB,GAAG,MAC/B,oBAAoB,EAAE,GAAG,CAAC,GAAG,QAAQ,CAAC,cAAc;AAEtD,IAAM,oBAAoB,GAAG,MAC3B,OAAO,MAAM,CAAC,UAAU,KAAK,UAAU,IACvC,MAAM,CAAC,UAAU,CAAC,kCAAkC,CAAC,CAAC,OAAO;AAE/D,IAAM,oBAAoB,GAAI,OAA2B,IAAa;AACpE,EAAA,OAAO,OAAO,CAAC,GAAG,GAAG,YAAY,GAAG,aAAa;AACnD,CAAC;AAED,IAAM,2BAA2B,GAAI,WAAwB,IAAc;AACzE,EAAA,IAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,GAAG;EAEvC,IAAI,OAAO,IAAI,EAAE,EAAE;AACjB,IAAA,OAAO,IAAI;AACb,EAAA;EACA,OAAO,OAAO,KAAK,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC;AACvD,CAAC;;"}
1
+ {"version":3,"file":"setDefaultOptions.js","sources":["../src/setDefaultOptions.ts"],"sourcesContent":["import type { AnimationSpeed } from \"./animation\";\nimport type { TreeElementOptions } from \"./options\";\n\nimport { DEFAULT_CLASS_PREFIX } from \"./classNames\";\nimport { Node } from \"./node\";\n\n/* The default icons are svg instead of text: the triangle characters come\n * from different fonts on each platform, so they differ in shape and size.\n * They are sized in em to follow the font size, and use currentColor to follow\n * --tree-element-toggler-color.\n */\nconst triangle = (points: string): string =>\n `<svg width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 10 10\" focusable=\"false\"><polygon points=\"${points}\" fill=\"currentColor\"/></svg>`;\n\nexport const triangleDown = triangle(\"0.5,0.5 9.5,0.5 5,9.5\");\nexport const triangleLeft = triangle(\"9.5,0.5 9.5,9.5 0.5,5\");\nexport const triangleRight = triangle(\"0.5,0.5 0.5,9.5 9.5,5\");\n\nconst defaults: TreeElementOptions = {\n animationSpeed: \"fast\",\n autoEscape: true,\n autoOpen: false, // true / false / int (open n levels starting at 0)\n buttonLeft: true,\n classPrefix: DEFAULT_CLASS_PREFIX, // the prefix of all css classes\n // closedIcon: undefined, // the default is a triangle; see getDefaultClosedIcon\n // commonClassName: undefined, // the class of every element; the default is \"<classPrefix>-common\"\n // data: undefined,\n // dataFilter: undefined,\n // dataUrl: undefined,\n dragAndDrop: false,\n keyboardSupport: true,\n multiSelect: false,\n nodeClass: Node,\n // onCanMove: undefined, // Can this node be moved?\n // onCanMoveTo: undefined, // Can this node be moved to this position? function(moved_node, target_node, position)\n // onCanSelectNode: undefined,\n // onCreateLi: undefined,\n // onDragMove: undefined,\n // onDragStop: undefined,\n // onGetStateFromStorage: undefined,\n // onIsMoveHandle: undefined,\n // onSetStateFromStorage: undefined,\n // openedIcon: undefined, // the default is a triangle pointing down\n openFolderDelay: 500, // The delay for opening a folder during drag and drop; the value is in milliseconds\n // rtl: undefined, // right-to-left support; true / false (default)\n saveState: false, // true / false / string (local storage key; the default key is \"tree\")\n selectable: true,\n showEmptyFolder: false,\n slide: true, // must display slide animation?\n startDndDelay: 300, // The delay for starting dnd (in milliseconds)\n tabIndex: 0,\n // treeClassName: undefined, // the class of the root element; the default is classPrefix\n useContextMenu: true,\n};\n\nconst setDefaultOptions = (\n htmlElement: HTMLElement,\n inputOptions: Partial<TreeElementOptions>,\n): TreeElementOptions => {\n const options = {\n ...defaults,\n ...inputOptions,\n animationSpeed: inputOptions.animationSpeed ?? getDefaultAnimationSpeed(),\n };\n\n options.dataUrl ??= htmlElement.dataset.url;\n options.rtl ??= getRtlOptionFromHTMLElement(htmlElement);\n options.closedIcon ??= getDefaultClosedIcon(options);\n options.openedIcon ??= triangleDown;\n\n return options;\n};\n\n// No animation when the user asked the system for reduced motion\nconst getDefaultAnimationSpeed = (): AnimationSpeed =>\n prefersReducedMotion() ? 0 : defaults.animationSpeed;\n\nconst prefersReducedMotion = (): boolean =>\n typeof window.matchMedia === \"function\" &&\n window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n\nconst getDefaultClosedIcon = (options: TreeElementOptions): string => {\n return options.rtl ? triangleLeft : triangleRight;\n};\n\nconst getRtlOptionFromHTMLElement = (htmlElement: HTMLElement): boolean => {\n const dataRtl = htmlElement.dataset.rtl;\n\n if (dataRtl == \"\") {\n return true;\n }\n return dataRtl === \"false\" ? false : Boolean(dataRtl);\n};\n\nexport default setDefaultOptions;\n"],"names":[],"mappings":";;;AAMA;AACA;AACA;AACA;AACA;AACA,IAAM,QAAQ,GAAI,MAAc,IAC9B,CAAA,yFAAA,EAA4F,MAAM,CAAA,6BAAA,CAA+B;IAEtH,YAAY,GAAG,QAAQ,CAAC,uBAAuB;IAC/C,YAAY,GAAG,QAAQ,CAAC,uBAAuB;IAC/C,aAAa,GAAG,QAAQ,CAAC,uBAAuB;AAE7D,IAAM,QAA4B,GAAG;AACnC,EAAA,cAAc,EAAE,MAAM;AACtB,EAAA,UAAU,EAAE,IAAI;AAChB,EAAA,QAAQ,EAAE,KAAK;AAAE;AACjB,EAAA,UAAU,EAAE,IAAI;AAChB,EAAA,WAAW,EAAE,oBAAoB;AAAE;AACnC;AACA;AACA;AACA;AACA;AACA,EAAA,WAAW,EAAE,KAAK;AAClB,EAAA,eAAe,EAAE,IAAI;AACrB,EAAA,WAAW,EAAE,KAAK;AAClB,EAAA,SAAS,EAAE,IAAI;AACf;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA,eAAe,EAAE,GAAG;AAAE;AACtB;AACA,EAAA,SAAS,EAAE,KAAK;AAAE;AAClB,EAAA,UAAU,EAAE,IAAI;AAChB,EAAA,eAAe,EAAE,KAAK;AACtB,EAAA,KAAK,EAAE,IAAI;AAAE;AACb,EAAA,aAAa,EAAE,GAAG;AAAE;AACpB,EAAA,QAAQ,EAAE,CAAC;AACX;AACA,EAAA,cAAc,EAAE;AAClB,CAAC;AAED,IAAM,iBAAiB,GAAG,CACxB,WAAwB,EACxB,YAAyC,KAClB;AACvB,EAAA,IAAM,OAAO,GAAG;AACd,IAAA,GAAG,QAAQ;AACX,IAAA,GAAG,YAAY;AACf,IAAA,cAAc,EAAE,YAAY,CAAC,cAAc,IAAI,wBAAwB;GACxE;AAED,EAAA,OAAO,CAAC,OAAO,KAAK,WAAW,CAAC,OAAO,CAAC,GAAG;AAC3C,EAAA,OAAO,CAAC,GAAG,KAAK,2BAA2B,CAAC,WAAW,CAAC;AACxD,EAAA,OAAO,CAAC,UAAU,KAAK,oBAAoB,CAAC,OAAO,CAAC;EACpD,OAAO,CAAC,UAAU,KAAK,YAAY;AAEnC,EAAA,OAAO,OAAO;AAChB;;AAEA;AACA,IAAM,wBAAwB,GAAG,MAC/B,oBAAoB,EAAE,GAAG,CAAC,GAAG,QAAQ,CAAC,cAAc;AAEtD,IAAM,oBAAoB,GAAG,MAC3B,OAAO,MAAM,CAAC,UAAU,KAAK,UAAU,IACvC,MAAM,CAAC,UAAU,CAAC,kCAAkC,CAAC,CAAC,OAAO;AAE/D,IAAM,oBAAoB,GAAI,OAA2B,IAAa;AACpE,EAAA,OAAO,OAAO,CAAC,GAAG,GAAG,YAAY,GAAG,aAAa;AACnD,CAAC;AAED,IAAM,2BAA2B,GAAI,WAAwB,IAAc;AACzE,EAAA,IAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,GAAG;EAEvC,IAAI,OAAO,IAAI,EAAE,EAAE;AACjB,IAAA,OAAO,IAAI;AACb,EAAA;EACA,OAAO,OAAO,KAAK,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC;AACvD,CAAC;;"}
package/lib/version.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- declare const version = "0.3.1";
1
+ declare const version = "0.4.0";
2
2
  export default version;
3
3
  //# sourceMappingURL=version.d.ts.map
package/lib/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Generated by config/generateVersion.mjs. Do not edit.
2
- let version = "0.3.1";
2
+ let version = "0.4.0";
3
3
 
4
4
  export { version as default };
5
5
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sources":["../src/version.ts"],"sourcesContent":["// Generated by config/generateVersion.mjs. Do not edit.\nconst version = \"0.3.1\";\n\nexport default version;\n"],"names":[],"mappings":"AAAA;AACA,IAAM,OAAO,GAAG;;"}
1
+ {"version":3,"file":"version.js","sources":["../src/version.ts"],"sourcesContent":["// Generated by config/generateVersion.mjs. Do not edit.\nconst version = \"0.4.0\";\n\nexport default version;\n"],"names":[],"mappings":"AAAA;AACA,IAAM,OAAO,GAAG;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tree-element",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Tree widget in plain javascript",
5
5
  "keywords": [
6
6
  "tree"
@@ -61,29 +61,29 @@
61
61
  "docs-check": "kiira check"
62
62
  },
63
63
  "devDependencies": {
64
- "@axe-core/playwright": "^4.13.0",
64
+ "@axe-core/playwright": "^4.14.0",
65
65
  "@babel/cli": "^8.0.6",
66
66
  "@babel/core": "^8.0.7",
67
67
  "@babel/preset-env": "^8.0.7",
68
68
  "@babel/preset-typescript": "^8.0.7",
69
- "@cspell/eslint-plugin": "^10.3.6",
70
- "@eslint/css": "^2.0.0",
69
+ "@cspell/eslint-plugin": "^10.4.0",
70
+ "@eslint/css": "^2.0.1",
71
71
  "@eslint/js": "^10.0.1",
72
- "@playwright/test": "^1.64.0",
72
+ "@playwright/test": "^1.64.1",
73
73
  "@rollup/plugin-babel": "^7.1.0",
74
74
  "@rollup/plugin-node-resolve": "^16.0.3",
75
75
  "@rollup/plugin-terser": "^1.0.0",
76
76
  "@testing-library/dom": "^10.4.2",
77
77
  "@testing-library/jest-dom": "^7.0.1",
78
- "@testing-library/user-event": "^14.6.7",
78
+ "@testing-library/user-event": "^14.6.8",
79
79
  "@types/debug": "^4.1.13",
80
80
  "@types/node": "^26.6.5",
81
- "@vitest/coverage-istanbul": "^5.0.3",
81
+ "@vitest/coverage-istanbul": "^5.0.5",
82
82
  "@vitest/eslint-plugin": "^1.6.27",
83
83
  "axe-core": "^4.14.0",
84
84
  "babel-plugin-istanbul": "^8.0.0",
85
85
  "babel-plugin-prefix-private-members": "^0.3.1",
86
- "eslint": "^10.12.0",
86
+ "eslint": "^10.13.0",
87
87
  "eslint-import-resolver-typescript": "^4.4.5",
88
88
  "eslint-plugin-compat": "^7.0.2",
89
89
  "eslint-plugin-import-x": "^4.17.1",
@@ -94,30 +94,30 @@
94
94
  "eslint-plugin-playwright": "^2.12.1",
95
95
  "eslint-plugin-testing-library": "^7.16.2",
96
96
  "eslint-plugin-tsdoc": "^0.5.4",
97
- "eslint-plugin-unicorn": "^77.0.0",
97
+ "eslint-plugin-unicorn": "^78.0.0",
98
98
  "jest-extended": "^7.0.0",
99
- "jsdom": "^30.1.2",
99
+ "jsdom": "^30.1.3",
100
100
  "jsdom-testing-mocks": "^1.16.0",
101
101
  "jsonfile": "^6.2.1",
102
- "kiira": "^0.6.0",
103
- "kiira-core": "^0.6.0",
102
+ "kiira": "^0.7.0",
103
+ "kiira-core": "^0.7.0",
104
104
  "lodash": "^4.18.1",
105
105
  "msw": "^3.0.3",
106
- "postcss": "^8.5.29",
106
+ "postcss": "^8.5.30",
107
107
  "postcss-cli": "^12.0.0",
108
- "postcss-import": "^17.0.0",
108
+ "postcss-import": "^17.0.1",
109
109
  "postcss-load-config": "^6.0.1",
110
- "postcss-nested": "^8.0.1",
110
+ "postcss-nested": "^9.0.0",
111
111
  "prettier": "^3.9.10",
112
- "rollup": "^4.64.5",
112
+ "rollup": "^4.64.7",
113
113
  "rollup-plugin-serve": "^3.0.0",
114
114
  "rollup-plugin-tsconfig-paths": "^1.5.2",
115
115
  "tslib": "^2.8.1",
116
116
  "typedoc": "^0.28.20",
117
- "typedoc-plugin-markdown": "^4.13.1",
117
+ "typedoc-plugin-markdown": "^4.14.0",
118
118
  "typescript": "^6.0.3",
119
- "typescript-eslint": "^8.71.1",
119
+ "typescript-eslint": "^8.71.2",
120
120
  "vitepress": "^1.6.4",
121
- "vitest": "^5.0.3"
121
+ "vitest": "^5.0.5"
122
122
  }
123
123
  }
package/src/classNames.ts CHANGED
@@ -39,13 +39,13 @@ export const DEFAULT_CLASS_PREFIX = "tree-element";
39
39
  */
40
40
  const createClassNames = ({
41
41
  classPrefix,
42
- commonClassName,
43
- treeClassName,
42
+ commonClassName = `${classPrefix}-common`,
43
+ treeClassName = classPrefix,
44
44
  }: ClassNamesOptions): ClassNames => ({
45
45
  border: `${classPrefix}-border`,
46
46
  circle: `${classPrefix}-circle`,
47
47
  closed: `${classPrefix}-closed`,
48
- common: commonClassName ?? `${classPrefix}-common`,
48
+ common: commonClassName,
49
49
  dnd: `${classPrefix}-dnd`,
50
50
  dragging: `${classPrefix}-dragging`,
51
51
  element: `${classPrefix}-element`,
@@ -64,7 +64,7 @@ const createClassNames = ({
64
64
  toggler: `${classPrefix}-toggler`,
65
65
  togglerLeft: `${classPrefix}-toggler-left`,
66
66
  togglerRight: `${classPrefix}-toggler-right`,
67
- tree: treeClassName ?? classPrefix,
67
+ tree: treeClassName,
68
68
  });
69
69
 
70
70
  export default createClassNames;
package/src/dataLoader.ts CHANGED
@@ -44,8 +44,9 @@ export default class DataLoader {
44
44
  element.classList.add(this.classNames.loading);
45
45
 
46
46
  // Tell screen readers that the children of the folder are loading
47
- const titleSpan = node ? this.getTitleSpan(element) : null;
48
- titleSpan?.setAttribute("aria-busy", "true");
47
+ if (node) {
48
+ element.setAttribute("aria-busy", "true");
49
+ }
49
50
 
50
51
  this.triggerEvent("tree.loading_data", {
51
52
  element,
@@ -54,7 +55,9 @@ export default class DataLoader {
54
55
 
55
56
  const stopLoading = (): void => {
56
57
  element.classList.remove(this.classNames.loading);
57
- titleSpan?.removeAttribute("aria-busy");
58
+ if (node) {
59
+ element.removeAttribute("aria-busy");
60
+ }
58
61
  this.triggerEvent("tree.loaded_data", {
59
62
  element,
60
63
  node,
@@ -92,10 +95,4 @@ export default class DataLoader {
92
95
  this.triggerEvent("tree.load_failed", { error });
93
96
  });
94
97
  }
95
-
96
- private getTitleSpan(element: HTMLElement): HTMLElement | null {
97
- return element.querySelector<HTMLElement>(
98
- `:scope > .${this.classNames.element} > .${this.classNames.title}`,
99
- );
100
- }
101
98
  }
@@ -16,6 +16,7 @@ interface ElementsRendererParams {
16
16
  getTree: GetTree;
17
17
  htmlElement: HTMLElement;
18
18
  isNodeSelected: IsNodeSelected;
19
+ multiSelect: boolean;
19
20
  onCreateLi?: OnCreateLi;
20
21
  openedIcon?: IconElement;
21
22
  rtl?: boolean;
@@ -49,6 +50,7 @@ export default class ElementsRenderer {
49
50
  private groupUlTemplate: HTMLUListElement;
50
51
  private htmlElement: HTMLElement;
51
52
  private isNodeSelected: IsNodeSelected;
53
+ private multiSelect: boolean;
52
54
  private nodeTemplate: HTMLLIElement;
53
55
  private onCreateLi?: OnCreateLi;
54
56
  private openedFolderTemplate: HTMLLIElement;
@@ -69,6 +71,7 @@ export default class ElementsRenderer {
69
71
  getTree,
70
72
  htmlElement,
71
73
  isNodeSelected,
74
+ multiSelect,
72
75
  onCreateLi,
73
76
  openedIcon = "+",
74
77
  rtl,
@@ -85,6 +88,7 @@ export default class ElementsRenderer {
85
88
  this.getTree = getTree;
86
89
  this.htmlElement = htmlElement;
87
90
  this.isNodeSelected = isNodeSelected;
91
+ this.multiSelect = multiSelect;
88
92
  this.onCreateLi = onCreateLi;
89
93
  this.rtl = rtl;
90
94
  this.setNodeElement = setNodeElement;
@@ -112,14 +116,14 @@ export default class ElementsRenderer {
112
116
  * null when the folder is not rendered or has no children.
113
117
  */
114
118
  public renderChildren(node: Node): HTMLUListElement | null {
115
- return !node.element || !node.hasChildren()
116
- ? null
117
- : this.createDomElements(
119
+ return node.element && node.hasChildren()
120
+ ? this.createDomElements(
118
121
  node.element,
119
122
  node.children,
120
123
  false,
121
124
  node.getLevel() + 1,
122
- );
125
+ )
126
+ : null;
123
127
  }
124
128
 
125
129
  public renderFromNode(node: Node): void {
@@ -208,11 +212,11 @@ export default class ElementsRenderer {
208
212
  this.buttonLeft ? 1 : 0
209
213
  ] as HTMLSpanElement;
210
214
 
211
- this.fillTitleSpan(titleSpan, node.name, isSelected, level);
215
+ this.fillNode(li, titleSpan, node.name, isSelected, level);
212
216
 
213
217
  if (node.is_loading) {
214
218
  li.classList.add(this.classNames.loading);
215
- titleSpan.setAttribute("aria-busy", "true");
219
+ li.setAttribute("aria-busy", "true");
216
220
  }
217
221
 
218
222
  return li;
@@ -251,10 +255,10 @@ export default class ElementsRenderer {
251
255
  div.append(toggler);
252
256
  }
253
257
 
258
+ li.setAttribute("aria-expanded", getBoolString(isOpen));
259
+
254
260
  // title span
255
- const titleSpan = this.createTitleSpanTemplate(true);
256
- titleSpan.setAttribute("aria-expanded", getBoolString(isOpen));
257
- div.append(titleSpan);
261
+ div.append(this.createTitleSpanTemplate(true));
258
262
 
259
263
  if (!this.buttonLeft) {
260
264
  div.append(toggler);
@@ -288,11 +292,22 @@ export default class ElementsRenderer {
288
292
 
289
293
  /*
290
294
  Create the outer part of a <li> template: li > div
295
+ The li is the treeitem, so the ul of its children is inside the treeitem.
291
296
  */
292
297
  private createLiTemplate(liClasses: string): HTMLLIElement {
293
298
  const li = document.createElement("li");
294
299
  li.className = liClasses;
295
- li.setAttribute("role", "none");
300
+ li.setAttribute("role", "treeitem");
301
+
302
+ // Only a multiselect tree marks the nodes that are not selected
303
+ if (this.multiSelect) {
304
+ li.setAttribute("aria-selected", "false");
305
+ }
306
+
307
+ // Every node is focusable, but only the tab stop is in the tab order
308
+ if (this.tabIndex !== undefined) {
309
+ li.setAttribute("tabindex", "-1");
310
+ }
296
311
 
297
312
  const div = document.createElement("div");
298
313
  div.className = `${this.classNames.element} ${this.classNames.common}`;
@@ -318,7 +333,7 @@ export default class ElementsRenderer {
318
333
  const div = li.firstChild as HTMLDivElement;
319
334
  const titleSpan = div.firstChild as HTMLSpanElement;
320
335
 
321
- this.fillTitleSpan(titleSpan, node.name, isSelected, level);
336
+ this.fillNode(li, titleSpan, node.name, isSelected, level);
322
337
 
323
338
  return li;
324
339
  }
@@ -347,13 +362,6 @@ export default class ElementsRenderer {
347
362
  classes += ` ${this.buttonLeft ? this.classNames.titleButtonLeft : this.classNames.titleButtonRight}`;
348
363
 
349
364
  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
- }
357
365
 
358
366
  return titleSpan;
359
367
  }
@@ -391,29 +399,35 @@ export default class ElementsRenderer {
391
399
  if (this.ariaLabelledBy !== undefined) {
392
400
  ul.setAttribute("aria-labelledby", this.ariaLabelledBy);
393
401
  }
402
+
403
+ if (this.multiSelect) {
404
+ ul.setAttribute("aria-multiselectable", "true");
405
+ }
394
406
  }
395
407
 
396
408
  return ul;
397
409
  }
398
410
 
399
411
  /*
400
- Set the parts of a cloned title span that differ per node
412
+ Set the parts of a cloned li and title span that differ per node
401
413
  */
402
- private fillTitleSpan(
414
+ private fillNode(
415
+ li: HTMLLIElement,
403
416
  titleSpan: HTMLSpanElement,
404
417
  nodeName: string,
405
418
  isSelected: boolean,
406
419
  level: number,
407
420
  ): void {
408
- titleSpan.setAttribute("aria-level", String(level));
421
+ li.setAttribute("aria-level", String(level));
409
422
 
410
423
  if (isSelected) {
411
- titleSpan.setAttribute("aria-selected", "true");
424
+ li.setAttribute("aria-selected", "true");
412
425
 
413
426
  const tabIndex = this.tabIndex;
414
427
 
415
- if (tabIndex !== undefined) {
416
- titleSpan.setAttribute("tabindex", String(tabIndex));
428
+ // With multiSelect the tab stop is the active node, see updateTabStop
429
+ if (tabIndex !== undefined && !this.multiSelect) {
430
+ li.setAttribute("tabindex", String(tabIndex));
417
431
  }
418
432
  }
419
433
 
@@ -423,6 +437,10 @@ export default class ElementsRenderer {
423
437
  // eslint-disable-next-line no-unsanitized/property -- the user opted out of escaping with autoEscape: false
424
438
  titleSpan.innerHTML = nodeName;
425
439
  }
440
+
441
+ // The treeitem is named by its title, without the html when autoEscape
442
+ // is false. Otherwise the name would contain everything in the row.
443
+ li.setAttribute("aria-label", titleSpan.textContent);
426
444
  }
427
445
 
428
446
  private getButtonClasses(isOpen: boolean): string {