tree-element 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/README.md +12 -4
  2. package/lib/animation.d.ts +2 -2
  3. package/lib/animation.d.ts.map +1 -1
  4. package/lib/animation.js +13 -9
  5. package/lib/animation.js.map +1 -1
  6. package/lib/dataLoader.d.ts +1 -0
  7. package/lib/dataLoader.d.ts.map +1 -1
  8. package/lib/dataLoader.js +8 -0
  9. package/lib/dataLoader.js.map +1 -1
  10. package/lib/dragAndDropHandler/dragElement.d.ts.map +1 -1
  11. package/lib/dragAndDropHandler/dragElement.js +2 -1
  12. package/lib/dragAndDropHandler/dragElement.js.map +1 -1
  13. package/lib/dragAndDropHandler/index.d.ts.map +1 -1
  14. package/lib/dragAndDropHandler/index.js +56 -65
  15. package/lib/dragAndDropHandler/index.js.map +1 -1
  16. package/lib/dragAndDropHandler/iterateVisibleNodes.d.ts.map +1 -1
  17. package/lib/dragAndDropHandler/iterateVisibleNodes.js +16 -15
  18. package/lib/dragAndDropHandler/iterateVisibleNodes.js.map +1 -1
  19. package/lib/elementsRenderer.d.ts +5 -1
  20. package/lib/elementsRenderer.d.ts.map +1 -1
  21. package/lib/elementsRenderer.js +62 -39
  22. package/lib/elementsRenderer.js.map +1 -1
  23. package/lib/events.d.ts +44 -11
  24. package/lib/events.d.ts.map +1 -1
  25. package/lib/index.d.ts +31 -8
  26. package/lib/index.d.ts.map +1 -1
  27. package/lib/index.js +162 -122
  28. package/lib/index.js.map +1 -1
  29. package/lib/keyHandler.d.ts +11 -5
  30. package/lib/keyHandler.d.ts.map +1 -1
  31. package/lib/keyHandler.js +87 -33
  32. package/lib/keyHandler.js.map +1 -1
  33. package/lib/methodTypes.d.ts +2 -2
  34. package/lib/methodTypes.d.ts.map +1 -1
  35. package/lib/mouseHandler.d.ts +1 -1
  36. package/lib/mouseHandler.d.ts.map +1 -1
  37. package/lib/mouseHandler.js +10 -7
  38. package/lib/mouseHandler.js.map +1 -1
  39. package/lib/node.d.ts +45 -15
  40. package/lib/node.d.ts.map +1 -1
  41. package/lib/node.js +136 -139
  42. package/lib/node.js.map +1 -1
  43. package/lib/nodeElement/folderElement.d.ts +1 -1
  44. package/lib/nodeElement/folderElement.d.ts.map +1 -1
  45. package/lib/nodeElement/folderElement.js +46 -57
  46. package/lib/nodeElement/folderElement.js.map +1 -1
  47. package/lib/nodeElement/ghostDropHint.d.ts.map +1 -1
  48. package/lib/nodeElement/ghostDropHint.js +8 -4
  49. package/lib/nodeElement/ghostDropHint.js.map +1 -1
  50. package/lib/nodeElement/index.d.ts.map +1 -1
  51. package/lib/nodeElement/index.js +6 -6
  52. package/lib/nodeElement/index.js.map +1 -1
  53. package/lib/nodeUtils.d.ts.map +1 -1
  54. package/lib/nodeUtils.js +1 -1
  55. package/lib/nodeUtils.js.map +1 -1
  56. package/lib/options.d.ts +19 -3
  57. package/lib/options.d.ts.map +1 -1
  58. package/lib/requestUrl.d.ts.map +1 -1
  59. package/lib/requestUrl.js +2 -11
  60. package/lib/requestUrl.js.map +1 -1
  61. package/lib/saveStateHandler.d.ts.map +1 -1
  62. package/lib/saveStateHandler.js +21 -41
  63. package/lib/saveStateHandler.js.map +1 -1
  64. package/lib/scrollHandler/containerScrollParent.d.ts.map +1 -1
  65. package/lib/scrollHandler/containerScrollParent.js +3 -8
  66. package/lib/scrollHandler/containerScrollParent.js.map +1 -1
  67. package/lib/scrollHandler/createScrollParent.d.ts.map +1 -1
  68. package/lib/scrollHandler/createScrollParent.js +4 -5
  69. package/lib/scrollHandler/createScrollParent.js.map +1 -1
  70. package/lib/scrollHandler/documentScrollParent.d.ts.map +1 -1
  71. package/lib/scrollHandler/documentScrollParent.js +3 -9
  72. package/lib/scrollHandler/documentScrollParent.js.map +1 -1
  73. package/lib/scrollHandler/scrollParent.d.ts.map +1 -1
  74. package/lib/scrollHandler/scrollParent.js +19 -17
  75. package/lib/scrollHandler/scrollParent.js.map +1 -1
  76. package/lib/selectNodeHandler.d.ts +3 -1
  77. package/lib/selectNodeHandler.d.ts.map +1 -1
  78. package/lib/selectNodeHandler.js +27 -37
  79. package/lib/selectNodeHandler.js.map +1 -1
  80. package/lib/setDefaultOptions.d.ts.map +1 -1
  81. package/lib/setDefaultOptions.js +11 -10
  82. package/lib/setDefaultOptions.js.map +1 -1
  83. package/lib/util.d.ts.map +1 -1
  84. package/lib/util.js +1 -1
  85. package/lib/util.js.map +1 -1
  86. package/lib/version.d.ts +1 -1
  87. package/lib/version.js +1 -1
  88. package/lib/version.js.map +1 -1
  89. package/package.json +42 -33
  90. package/src/animation.ts +13 -11
  91. package/src/dataLoader.ts +11 -0
  92. package/src/dragAndDropHandler/dragElement.ts +2 -1
  93. package/src/dragAndDropHandler/index.ts +70 -75
  94. package/src/dragAndDropHandler/iterateVisibleNodes.ts +18 -16
  95. package/src/elementsRenderer.ts +76 -46
  96. package/src/events.ts +44 -11
  97. package/src/index.ts +210 -150
  98. package/src/keyHandler.ts +95 -41
  99. package/src/methodTypes.ts +3 -3
  100. package/src/mouseHandler.ts +10 -7
  101. package/src/node.ts +155 -148
  102. package/src/nodeElement/folderElement.ts +54 -63
  103. package/src/nodeElement/ghostDropHint.ts +4 -2
  104. package/src/nodeElement/index.ts +9 -15
  105. package/src/nodeUtils.ts +1 -1
  106. package/src/options.ts +19 -3
  107. package/src/requestUrl.ts +4 -11
  108. package/src/saveStateHandler.ts +29 -43
  109. package/src/scrollHandler/containerScrollParent.ts +3 -9
  110. package/src/scrollHandler/createScrollParent.ts +1 -2
  111. package/src/scrollHandler/documentScrollParent.ts +5 -11
  112. package/src/scrollHandler/scrollParent.ts +29 -25
  113. package/src/selectNodeHandler.ts +34 -38
  114. package/src/setDefaultOptions.ts +19 -10
  115. package/src/util.ts +1 -2
  116. package/src/version.ts +1 -1
  117. package/tree_element.css +87 -69
  118. package/tree_element.debug.js +706 -639
  119. package/tree_element.debug.js.map +1 -1
  120. package/tree_element.js +2 -2
  121. package/tree_element.js.map +1 -1
@@ -20,13 +20,15 @@ class GhostDropHint implements DropHint {
20
20
  this.ghost = this.createGhostElement();
21
21
 
22
22
  switch (position) {
23
- case "after":
23
+ case "after": {
24
24
  this.moveAfter();
25
25
  break;
26
+ }
26
27
 
27
- case "before":
28
+ case "before": {
28
29
  this.moveBefore();
29
30
  break;
31
+ }
30
32
 
31
33
  case "inside": {
32
34
  if (node.isFolder() && node.is_open) {
@@ -40,20 +40,9 @@ class NodeElement {
40
40
  }
41
41
 
42
42
  public addDropHint(position: Position): DropHint {
43
- if (this.mustShowBorderDropHint(position)) {
44
- return new BorderDropHint(
45
- this.element,
46
- this.getScrollLeft(),
47
- this.classNames,
48
- );
49
- } else {
50
- return new GhostDropHint(
51
- this.node,
52
- this.element,
53
- position,
54
- this.classNames,
55
- );
56
- }
43
+ return this.mustShowBorderDropHint(position)
44
+ ? new BorderDropHint(this.element, this.getScrollLeft(), this.classNames)
45
+ : new GhostDropHint(this.node, this.element, position, this.classNames);
57
46
  }
58
47
 
59
48
  public deselect(): void {
@@ -64,7 +53,12 @@ class NodeElement {
64
53
  this.element.classList.remove(this.classNames.selected);
65
54
 
66
55
  const titleSpan = this.getTitleSpan();
67
- titleSpan.removeAttribute("tabindex");
56
+
57
+ // Check for null or undefined
58
+ if (this.tabIndex != null) {
59
+ titleSpan.setAttribute("tabindex", "-1");
60
+ }
61
+
68
62
  titleSpan.setAttribute("aria-selected", "false");
69
63
 
70
64
  titleSpan.blur();
package/src/nodeUtils.ts CHANGED
@@ -9,4 +9,4 @@ export const isNodeRecordWithChildren = (
9
9
  ): data is NodeRecordWithChildren =>
10
10
  typeof data === "object" &&
11
11
  "children" in data &&
12
- data.children instanceof Array;
12
+ Array.isArray(data.children);
package/src/options.ts CHANGED
@@ -36,10 +36,25 @@ export interface TreeElementOptions {
36
36
  * The duration of the slide animation: `"fast"` is 200ms, `"slow"` is
37
37
  * 600ms, a number is milliseconds.
38
38
  *
39
- * @defaultValue `"fast"`
39
+ * @defaultValue `"fast"`, or `0` when the user prefers reduced motion
40
+ * (`prefers-reduced-motion: reduce`).
40
41
  * @group Appearance
41
42
  */
42
43
  animationSpeed: AnimationSpeed;
44
+ /**
45
+ * The accessible name of the tree, set as `aria-label` on the root `ul`.
46
+ * Use `ariaLabelledBy` instead when the name is visible on the page.
47
+ *
48
+ * @group Appearance
49
+ */
50
+ ariaLabel?: string;
51
+ /**
52
+ * The id of the element that names the tree, set as `aria-labelledby` on
53
+ * the root `ul`.
54
+ *
55
+ * @group Appearance
56
+ */
57
+ ariaLabelledBy?: string;
43
58
  /**
44
59
  * Escape node names. With `false`, names are inserted as html — only do
45
60
  * that for data you control.
@@ -129,7 +144,7 @@ export interface TreeElementOptions {
129
144
  */
130
145
  dragAndDrop: boolean;
131
146
  /**
132
- * Handle the arrow keys when the focus is on the tree. See
147
+ * Handle the keyboard when the focus is on the tree. See
133
148
  * [Selection](/guide/selection#keyboard-navigation).
134
149
  *
135
150
  * @defaultValue `true`
@@ -302,7 +317,8 @@ export interface TreeElementOptions {
302
317
  startDndDelay?: number;
303
318
  /**
304
319
  * The `tabindex` of the selected node's title, which is what makes the
305
- * tree focusable.
320
+ * tree focusable. When no node is selected, the first node gets it. The
321
+ * other nodes get `-1`.
306
322
  *
307
323
  * @defaultValue `0`
308
324
  * @group Selection
package/src/requestUrl.ts CHANGED
@@ -1,12 +1,7 @@
1
1
  // Url class for absolute and relative urls.
2
2
 
3
3
  const isAbsoluteUrl = (inputUrl: string) => {
4
- try {
5
- new URL(inputUrl);
6
- return true;
7
- } catch {
8
- return false;
9
- }
4
+ return URL.canParse(inputUrl);
10
5
  };
11
6
 
12
7
  const LOCALHOST = "http://localhost";
@@ -30,11 +25,9 @@ class RequestUrl {
30
25
  }
31
26
 
32
27
  value() {
33
- if (this.isAbsolute) {
34
- return this.url.href;
35
- } else {
36
- return this.url.href.slice(LOCALHOST.length);
37
- }
28
+ return this.isAbsolute
29
+ ? this.url.href
30
+ : this.url.href.slice(LOCALHOST.length);
38
31
  }
39
32
  }
40
33
 
@@ -73,11 +73,7 @@ export default class SaveStateHandler {
73
73
 
74
74
  const state = this.getStateFromStorage();
75
75
 
76
- if (state?.selected_node) {
77
- return state.selected_node[0] ?? null;
78
- } else {
79
- return null;
80
- }
76
+ return state?.selected_node ? (state.selected_node[0] ?? null) : null;
81
77
  }
82
78
 
83
79
  public getState(): SavedState {
@@ -97,11 +93,11 @@ export default class SaveStateHandler {
97
93
  const getSelectedNodeIds = (): NodeId[] => {
98
94
  const selectedNodeIds: NodeId[] = [];
99
95
 
100
- this.getSelectedNodes().forEach((node) => {
96
+ for (const node of this.getSelectedNodes()) {
101
97
  if (node.id != null) {
102
98
  selectedNodeIds.push(node.id);
103
99
  }
104
- });
100
+ }
105
101
 
106
102
  return selectedNodeIds;
107
103
  };
@@ -119,11 +115,7 @@ export default class SaveStateHandler {
119
115
 
120
116
  const jsonData = this.loadFromStorage();
121
117
 
122
- if (jsonData) {
123
- return this.parseState(jsonData);
124
- } else {
125
- return null;
126
- }
118
+ return jsonData ? this.parseState(jsonData) : null;
127
119
  }
128
120
 
129
121
  public saveState(): void {
@@ -147,11 +139,9 @@ export default class SaveStateHandler {
147
139
  result: must load on demand (boolean)
148
140
  */
149
141
  public setInitialState(state: SavedState): boolean {
150
- let mustLoadOnDemand = false;
151
-
152
- if (state.open_nodes) {
153
- mustLoadOnDemand = this.openInitialNodes(state.open_nodes);
154
- }
142
+ const mustLoadOnDemand = state.open_nodes
143
+ ? this.openInitialNodes(state.open_nodes)
144
+ : false;
155
145
 
156
146
  this.resetSelection();
157
147
 
@@ -175,9 +165,7 @@ export default class SaveStateHandler {
175
165
  for (const nodeId of nodeIds) {
176
166
  const node = this.getNodeById(nodeId);
177
167
 
178
- if (!node) {
179
- newNodesIds.push(nodeId);
180
- } else {
168
+ if (node) {
181
169
  if (!node.is_loading) {
182
170
  if (node.load_on_demand) {
183
171
  await loadAndOpenNode(node);
@@ -185,15 +173,15 @@ export default class SaveStateHandler {
185
173
  await this.openNode(node, false);
186
174
  }
187
175
  }
176
+ } else {
177
+ newNodesIds.push(nodeId);
188
178
  }
189
179
  }
190
180
 
191
181
  nodeIds = newNodesIds;
192
182
 
193
- if (state.selected_node) {
194
- if (this.selectInitialNodes(state.selected_node)) {
195
- this.refreshElements(null);
196
- }
183
+ if (state.selected_node && this.selectInitialNodes(state.selected_node)) {
184
+ this.refreshElements(null);
197
185
  }
198
186
  };
199
187
 
@@ -206,19 +194,15 @@ export default class SaveStateHandler {
206
194
  }
207
195
 
208
196
  private getKeyName(): string {
209
- if (typeof this.saveStateOption === "string") {
210
- return this.saveStateOption;
211
- } else {
212
- return "tree";
213
- }
197
+ return typeof this.saveStateOption === "string"
198
+ ? this.saveStateOption
199
+ : "tree";
214
200
  }
215
201
 
216
202
  private loadFromStorage(): null | string {
217
- if (this.onGetStateFromStorage) {
218
- return this.onGetStateFromStorage();
219
- } else {
220
- return localStorage.getItem(this.getKeyName());
221
- }
203
+ return this.onGetStateFromStorage
204
+ ? this.onGetStateFromStorage()
205
+ : localStorage.getItem(this.getKeyName());
222
206
  }
223
207
 
224
208
  private openInitialNodes(nodeIds: NodeId[]): boolean {
@@ -228,10 +212,10 @@ export default class SaveStateHandler {
228
212
  const node = this.getNodeById(nodeId);
229
213
 
230
214
  if (node) {
231
- if (!node.load_on_demand) {
232
- node.is_open = true;
233
- } else {
215
+ if (node.load_on_demand) {
234
216
  mustLoadOnDemand = true;
217
+ } else {
218
+ node.is_open = true;
235
219
  }
236
220
  }
237
221
  }
@@ -254,9 +238,9 @@ export default class SaveStateHandler {
254
238
  private resetSelection(): void {
255
239
  const selectedNodes = this.getSelectedNodes();
256
240
 
257
- selectedNodes.forEach((node) => {
241
+ for (const node of selectedNodes) {
258
242
  this.removeFromSelection(node);
259
- });
243
+ }
260
244
  }
261
245
 
262
246
  private selectInitialNodes(nodeIds: NodeId[]): boolean {
@@ -265,11 +249,13 @@ export default class SaveStateHandler {
265
249
  for (const nodeId of nodeIds) {
266
250
  const node = this.getNodeById(nodeId);
267
251
 
268
- if (node) {
269
- selectCount += 1;
270
-
271
- this.addToSelection(node);
252
+ if (!node) {
253
+ continue;
272
254
  }
255
+
256
+ selectCount += 1;
257
+
258
+ this.addToSelection(node);
273
259
  }
274
260
 
275
261
  return selectCount !== 0;
@@ -26,15 +26,13 @@ export default class ContainerScrollParent extends ScrollParent {
26
26
  const rightEdge = scrollParentOffset.left + containerWidth;
27
27
  const leftEdge = scrollParentOffset.left;
28
28
  const isNearRightEdge = pageX > rightEdge - 20;
29
- const isNearLeftEdge = pageX < leftEdge + 20;
30
29
 
31
30
  if (isNearRightEdge) {
32
31
  return "right";
33
- } else if (isNearLeftEdge) {
34
- return "left";
35
32
  }
36
33
 
37
- return undefined;
34
+ const isNearLeftEdge = pageX < leftEdge + 20;
35
+ return isNearLeftEdge ? "left" : undefined;
38
36
  }
39
37
 
40
38
  protected getNewVerticalScrollDirection(
@@ -44,11 +42,7 @@ export default class ContainerScrollParent extends ScrollParent {
44
42
  return "top";
45
43
  }
46
44
 
47
- if (pageY > this.getScrollParentBottom()) {
48
- return "bottom";
49
- }
50
-
51
- return undefined;
45
+ return pageY > this.getScrollParentBottom() ? "bottom" : undefined;
52
46
  }
53
47
 
54
48
  private getScrollParentBottom() {
@@ -43,9 +43,8 @@ const createScrollParent = (
43
43
  container,
44
44
  refreshHitAreas,
45
45
  });
46
- } else {
47
- return new DocumentScrollParent({ refreshHitAreas, treeElement });
48
46
  }
47
+ return new DocumentScrollParent({ refreshHitAreas, treeElement });
49
48
  };
50
49
 
51
50
  export default createScrollParent;
@@ -42,17 +42,13 @@ export default class DocumentScrollParent extends ScrollParent {
42
42
  const windowWidth = window.innerWidth;
43
43
 
44
44
  const isNearRightEdge = pageX > windowWidth - 20;
45
- const isNearLeftEdge = pageX - scrollLeft < 20;
46
45
 
47
46
  if (isNearRightEdge && this.canScrollRight()) {
48
47
  return "right";
49
48
  }
50
49
 
51
- if (isNearLeftEdge) {
52
- return "left";
53
- }
54
-
55
- return undefined;
50
+ const isNearLeftEdge = pageX - scrollLeft < 20;
51
+ return isNearLeftEdge ? "left" : undefined;
56
52
  }
57
53
 
58
54
  protected getNewVerticalScrollDirection(
@@ -67,11 +63,9 @@ export default class DocumentScrollParent extends ScrollParent {
67
63
 
68
64
  const windowHeight = window.innerHeight;
69
65
 
70
- if (windowHeight - (pageY - scrollTop) < 20 && this.canScrollDown()) {
71
- return "bottom";
72
- }
73
-
74
- return undefined;
66
+ return windowHeight - (pageY - scrollTop) < 20 && this.canScrollDown()
67
+ ? "bottom"
68
+ : undefined;
75
69
  }
76
70
 
77
71
  private canScrollDown() {
@@ -24,19 +24,21 @@ export abstract class ScrollParent {
24
24
  const newHorizontalScrollDirection =
25
25
  this.getNewHorizontalScrollDirection(pageX);
26
26
 
27
- if (this.horizontalScrollDirection !== newHorizontalScrollDirection) {
28
- this.horizontalScrollDirection = newHorizontalScrollDirection;
29
-
30
- if (this.horizontalScrollTimeout != null) {
31
- window.clearTimeout(this.horizontalScrollTimeout);
32
- }
33
-
34
- if (newHorizontalScrollDirection) {
35
- this.horizontalScrollTimeout = window.setTimeout(
36
- this.scrollHorizontally.bind(this),
37
- 40,
38
- );
39
- }
27
+ if (this.horizontalScrollDirection === newHorizontalScrollDirection) {
28
+ return;
29
+ }
30
+
31
+ this.horizontalScrollDirection = newHorizontalScrollDirection;
32
+
33
+ if (this.horizontalScrollTimeout != null) {
34
+ window.clearTimeout(this.horizontalScrollTimeout);
35
+ }
36
+
37
+ if (newHorizontalScrollDirection) {
38
+ this.horizontalScrollTimeout = window.setTimeout(
39
+ this.scrollHorizontally.bind(this),
40
+ 40,
41
+ );
40
42
  }
41
43
  }
42
44
 
@@ -44,20 +46,22 @@ export abstract class ScrollParent {
44
46
  const newVerticalScrollDirection =
45
47
  this.getNewVerticalScrollDirection(pageY);
46
48
 
47
- if (this.verticalScrollDirection !== newVerticalScrollDirection) {
48
- this.verticalScrollDirection = newVerticalScrollDirection;
49
+ if (this.verticalScrollDirection === newVerticalScrollDirection) {
50
+ return;
51
+ }
49
52
 
50
- if (this.verticalScrollTimeout != null) {
51
- window.clearTimeout(this.verticalScrollTimeout);
52
- this.verticalScrollTimeout = undefined;
53
- }
53
+ this.verticalScrollDirection = newVerticalScrollDirection;
54
+
55
+ if (this.verticalScrollTimeout != null) {
56
+ window.clearTimeout(this.verticalScrollTimeout);
57
+ this.verticalScrollTimeout = undefined;
58
+ }
54
59
 
55
- if (newVerticalScrollDirection) {
56
- this.verticalScrollTimeout = window.setTimeout(
57
- this.scrollVertically.bind(this),
58
- 40,
59
- );
60
- }
60
+ if (newVerticalScrollDirection) {
61
+ this.verticalScrollTimeout = window.setTimeout(
62
+ this.scrollVertically.bind(this),
63
+ 40,
64
+ );
61
65
  }
62
66
  }
63
67
 
@@ -20,6 +20,7 @@ interface SelectNodeHandlerParameters {
20
20
  openParents: OpenParents;
21
21
  saveState: () => void;
22
22
  triggerEvent: TriggerEvent;
23
+ updateTabStop: () => void;
23
24
  }
24
25
 
25
26
  export default class SelectNodeHandler {
@@ -32,6 +33,7 @@ export default class SelectNodeHandler {
32
33
  private selectedNodes: Set<NodeId>;
33
34
  private selectedSingleNode: Node | null;
34
35
  private triggerEvent: TriggerEvent;
36
+ private updateTabStop: () => void;
35
37
 
36
38
  constructor({
37
39
  getNodeById,
@@ -41,6 +43,7 @@ export default class SelectNodeHandler {
41
43
  openParents,
42
44
  saveState,
43
45
  triggerEvent,
46
+ updateTabStop,
44
47
  }: SelectNodeHandlerParameters) {
45
48
  this.getNodeById = getNodeById;
46
49
  this.getNodeElementForNode = getNodeElementForNode;
@@ -51,13 +54,14 @@ export default class SelectNodeHandler {
51
54
  this.selectedNodes = new Set<NodeId>();
52
55
  this.selectedSingleNode = null;
53
56
  this.triggerEvent = triggerEvent;
57
+ this.updateTabStop = updateTabStop;
54
58
  }
55
59
 
56
60
  public addToSelection(node: Node): void {
57
- if (node.id != null) {
58
- this.selectedNodes.add(node.id);
59
- } else {
61
+ if (node.id == null) {
60
62
  this.selectedSingleNode = node;
63
+ } else {
64
+ this.selectedNodes.add(node.id);
61
65
  }
62
66
  }
63
67
 
@@ -69,59 +73,50 @@ export default class SelectNodeHandler {
69
73
  public getSelectedNode(): Node | null {
70
74
  const selectedNodes = this.getSelectedNodes();
71
75
 
72
- if (selectedNodes.length) {
73
- return selectedNodes[0] ?? null;
74
- } else {
75
- return null;
76
- }
76
+ return selectedNodes.length ? (selectedNodes[0] ?? null) : null;
77
77
  }
78
78
 
79
79
  public getSelectedNodes(): Node[] {
80
80
  if (this.selectedSingleNode) {
81
81
  return [this.selectedSingleNode];
82
- } else {
83
- const selectedNodes: Node[] = [];
82
+ }
84
83
 
85
- this.selectedNodes.forEach((id) => {
86
- const node = this.getNodeById(id);
87
- if (node) {
88
- selectedNodes.push(node);
89
- }
90
- });
84
+ const selectedNodes: Node[] = [];
91
85
 
92
- return selectedNodes;
93
- }
86
+ this.selectedNodes.forEach((id) => {
87
+ const node = this.getNodeById(id);
88
+ if (node) {
89
+ selectedNodes.push(node);
90
+ }
91
+ });
92
+
93
+ return selectedNodes;
94
94
  }
95
95
 
96
96
  public getSelectedNodesUnder(parent: Node): Node[] {
97
97
  if (this.selectedSingleNode) {
98
- if (parent.isParentOf(this.selectedSingleNode)) {
99
- return [this.selectedSingleNode];
100
- } else {
101
- return [];
102
- }
103
- } else {
104
- const selectedNodes: Node[] = [];
98
+ return parent.isParentOf(this.selectedSingleNode)
99
+ ? [this.selectedSingleNode]
100
+ : [];
101
+ }
105
102
 
106
- this.selectedNodes.forEach((id) => {
107
- const node = this.getNodeById(id);
108
- if (node && parent.isParentOf(node)) {
109
- selectedNodes.push(node);
110
- }
111
- });
103
+ const selectedNodes: Node[] = [];
112
104
 
113
- return selectedNodes;
114
- }
105
+ this.selectedNodes.forEach((id) => {
106
+ const node = this.getNodeById(id);
107
+ if (node && parent.isParentOf(node)) {
108
+ selectedNodes.push(node);
109
+ }
110
+ });
111
+
112
+ return selectedNodes;
115
113
  }
116
114
 
117
115
  public isNodeSelected(node: Node): boolean {
118
116
  if (node.id != null) {
119
117
  return this.selectedNodes.has(node.id);
120
- } else if (this.selectedSingleNode) {
121
- return this.selectedSingleNode === node;
122
- } else {
123
- return false;
124
118
  }
119
+ return this.selectedSingleNode ? this.selectedSingleNode === node : false;
125
120
  }
126
121
 
127
122
  public removeFromSelection(node: Node, includeChildren = false): void {
@@ -155,7 +150,7 @@ export default class SelectNodeHandler {
155
150
  */
156
151
  public selectSingleNode(node: Node, optionsParam?: SelectNodeOptions) {
157
152
  const defaultOptions = { mustSetFocus: true, mustToggle: true };
158
- const selectOptions = { ...defaultOptions, ...(optionsParam ?? {}) };
153
+ const selectOptions = { ...defaultOptions, ...optionsParam };
159
154
 
160
155
  const canSelect = (): boolean => {
161
156
  if (!this.getSelectable()) {
@@ -197,6 +192,7 @@ export default class SelectNodeHandler {
197
192
  });
198
193
  }
199
194
 
195
+ this.updateTabStop();
200
196
  this.saveState();
201
197
  }
202
198
  }
@@ -1,3 +1,4 @@
1
+ import type { AnimationSpeed } from "./animation";
1
2
  import type { TreeElementOptions } from "./options";
2
3
 
3
4
  import { DEFAULT_CLASS_PREFIX } from "./classNames";
@@ -10,7 +11,7 @@ const defaults: TreeElementOptions = {
10
11
  buttonLeft: true,
11
12
  classPrefix: DEFAULT_CLASS_PREFIX, // the prefix of all css classes
12
13
  // The symbol to use for a closed node - ► BLACK RIGHT-POINTING POINTER
13
- // http://www.fileformat.info/info/unicode/char/25ba/index.htm
14
+ // https://www.fileformat.info/info/unicode/char/25ba/index.htm
14
15
  // closedIcon: undefined,
15
16
  // commonClassName: undefined, // the class of every element; the default is "<classPrefix>-common"
16
17
  // data: undefined,
@@ -31,7 +32,7 @@ const defaults: TreeElementOptions = {
31
32
  // openedIcon: undefined,
32
33
  openFolderDelay: 500, // The delay for opening a folder during drag and drop; the value is in milliseconds
33
34
  // The symbol to use for an open node - ▼ BLACK DOWN-POINTING TRIANGLE
34
- // http://www.fileformat.info/info/unicode/char/25bc/index.htm
35
+ // https://www.fileformat.info/info/unicode/char/25bc/index.htm
35
36
  // rtl: undefined, // right-to-left support; true / false (default)
36
37
  saveState: false, // true / false / string (local storage key; the default key is "tree")
37
38
  selectable: true,
@@ -47,7 +48,11 @@ const setDefaultOptions = (
47
48
  htmlElement: HTMLElement,
48
49
  inputOptions: Partial<TreeElementOptions>,
49
50
  ): TreeElementOptions => {
50
- const options = { ...defaults, ...inputOptions };
51
+ const options = {
52
+ ...defaults,
53
+ ...inputOptions,
54
+ animationSpeed: inputOptions.animationSpeed ?? getDefaultAnimationSpeed(),
55
+ };
51
56
 
52
57
  options.dataUrl ??= htmlElement.dataset.url;
53
58
  options.rtl ??= getRtlOptionFromHTMLElement(htmlElement);
@@ -57,14 +62,21 @@ const setDefaultOptions = (
57
62
  return options;
58
63
  };
59
64
 
65
+ // No animation when the user asked the system for reduced motion
66
+ const getDefaultAnimationSpeed = (): AnimationSpeed =>
67
+ prefersReducedMotion() ? 0 : defaults.animationSpeed;
68
+
69
+ const prefersReducedMotion = (): boolean =>
70
+ typeof window.matchMedia === "function" &&
71
+ window.matchMedia("(prefers-reduced-motion: reduce)").matches;
72
+
60
73
  const getDefaultClosedIcon = (options: TreeElementOptions): string => {
61
74
  if (options.rtl) {
62
75
  // triangle to the left
63
76
  return "&#x25c0;";
64
- } else {
65
- // triangle to the right
66
- return "&#x25ba;";
67
77
  }
78
+ // triangle to the right
79
+ return "&#x25ba;";
68
80
  };
69
81
 
70
82
  const getRtlOptionFromHTMLElement = (htmlElement: HTMLElement): boolean => {
@@ -72,11 +84,8 @@ const getRtlOptionFromHTMLElement = (htmlElement: HTMLElement): boolean => {
72
84
 
73
85
  if (dataRtl == "") {
74
86
  return true;
75
- } else if (dataRtl === "false") {
76
- return false;
77
- } else {
78
- return Boolean(dataRtl);
79
87
  }
88
+ return dataRtl === "false" ? false : Boolean(dataRtl);
80
89
  };
81
90
 
82
91
  export default setDefaultOptions;
package/src/util.ts CHANGED
@@ -1,5 +1,4 @@
1
- export const isInt = (n: unknown): boolean =>
2
- typeof n === "number" && n % 1 === 0;
1
+ export const isInt = (n: unknown): boolean => Number.isInteger(n);
3
2
 
4
3
  export const getBoolString = (value: unknown): string =>
5
4
  value ? "true" : "false";
package/src/version.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  // Generated by config/generateVersion.mjs. Do not edit.
2
- const version = "0.2.0";
2
+ const version = "0.3.0";
3
3
 
4
4
  export default version;