tree-element 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/README.md +43 -31
  2. package/lib/animation.d.ts +2 -2
  3. package/lib/animation.d.ts.map +1 -1
  4. package/lib/animation.js +16 -12
  5. package/lib/animation.js.map +1 -1
  6. package/lib/classNames.d.ts.map +1 -1
  7. package/lib/classNames.js +2 -2
  8. package/lib/classNames.js.map +1 -1
  9. package/lib/dataLoader.d.ts +1 -0
  10. package/lib/dataLoader.d.ts.map +1 -1
  11. package/lib/dataLoader.js +22 -20
  12. package/lib/dataLoader.js.map +1 -1
  13. package/lib/dragAndDropHandler/binarySearch.js +3 -3
  14. package/lib/dragAndDropHandler/binarySearch.js.map +1 -1
  15. package/lib/dragAndDropHandler/dragElement.d.ts.map +1 -1
  16. package/lib/dragAndDropHandler/dragElement.js +11 -13
  17. package/lib/dragAndDropHandler/dragElement.js.map +1 -1
  18. package/lib/dragAndDropHandler/generateHitAreas.d.ts.map +1 -1
  19. package/lib/dragAndDropHandler/generateHitAreas.js +25 -25
  20. package/lib/dragAndDropHandler/generateHitAreas.js.map +1 -1
  21. package/lib/dragAndDropHandler/index.d.ts.map +1 -1
  22. package/lib/dragAndDropHandler/index.js +125 -158
  23. package/lib/dragAndDropHandler/index.js.map +1 -1
  24. package/lib/dragAndDropHandler/iterateVisibleNodes.d.ts.map +1 -1
  25. package/lib/dragAndDropHandler/iterateVisibleNodes.js +23 -22
  26. package/lib/dragAndDropHandler/iterateVisibleNodes.js.map +1 -1
  27. package/lib/dragAndDropHandler/types.d.ts.map +1 -1
  28. package/lib/elementsRenderer.d.ts +20 -7
  29. package/lib/elementsRenderer.d.ts.map +1 -1
  30. package/lib/elementsRenderer.js +195 -136
  31. package/lib/elementsRenderer.js.map +1 -1
  32. package/lib/events.d.ts +44 -11
  33. package/lib/events.d.ts.map +1 -1
  34. package/lib/index.d.ts +36 -13
  35. package/lib/index.d.ts.map +1 -1
  36. package/lib/index.js +386 -349
  37. package/lib/index.js.map +1 -1
  38. package/lib/keyHandler.d.ts +13 -8
  39. package/lib/keyHandler.d.ts.map +1 -1
  40. package/lib/keyHandler.js +106 -51
  41. package/lib/keyHandler.js.map +1 -1
  42. package/lib/methodTypes.d.ts +3 -3
  43. package/lib/methodTypes.d.ts.map +1 -1
  44. package/lib/mouseHandler.d.ts +1 -1
  45. package/lib/mouseHandler.d.ts.map +1 -1
  46. package/lib/mouseHandler.js +36 -49
  47. package/lib/mouseHandler.js.map +1 -1
  48. package/lib/mouseUtils.d.ts.map +1 -1
  49. package/lib/mouseUtils.js +2 -2
  50. package/lib/mouseUtils.js.map +1 -1
  51. package/lib/node.d.ts +47 -16
  52. package/lib/node.d.ts.map +1 -1
  53. package/lib/node.js +169 -171
  54. package/lib/node.js.map +1 -1
  55. package/lib/nodeElement/borderDropHint.d.ts.map +1 -1
  56. package/lib/nodeElement/borderDropHint.js +4 -5
  57. package/lib/nodeElement/borderDropHint.js.map +1 -1
  58. package/lib/nodeElement/folderElement.d.ts +8 -5
  59. package/lib/nodeElement/folderElement.d.ts.map +1 -1
  60. package/lib/nodeElement/folderElement.js +75 -67
  61. package/lib/nodeElement/folderElement.js.map +1 -1
  62. package/lib/nodeElement/ghostDropHint.d.ts.map +1 -1
  63. package/lib/nodeElement/ghostDropHint.js +13 -13
  64. package/lib/nodeElement/ghostDropHint.js.map +1 -1
  65. package/lib/nodeElement/index.d.ts +2 -1
  66. package/lib/nodeElement/index.d.ts.map +1 -1
  67. package/lib/nodeElement/index.js +38 -30
  68. package/lib/nodeElement/index.js.map +1 -1
  69. package/lib/nodeUtils.d.ts.map +1 -1
  70. package/lib/nodeUtils.js +1 -1
  71. package/lib/nodeUtils.js.map +1 -1
  72. package/lib/options.d.ts +19 -3
  73. package/lib/options.d.ts.map +1 -1
  74. package/lib/positionUtils.js +3 -3
  75. package/lib/positionUtils.js.map +1 -1
  76. package/lib/requestUrl.d.ts +1 -1
  77. package/lib/requestUrl.d.ts.map +1 -1
  78. package/lib/requestUrl.js +6 -17
  79. package/lib/requestUrl.js.map +1 -1
  80. package/lib/saveStateHandler.d.ts.map +1 -1
  81. package/lib/saveStateHandler.js +59 -89
  82. package/lib/saveStateHandler.js.map +1 -1
  83. package/lib/scrollHandler/containerScrollParent.d.ts.map +1 -1
  84. package/lib/scrollHandler/containerScrollParent.js +11 -18
  85. package/lib/scrollHandler/containerScrollParent.js.map +1 -1
  86. package/lib/scrollHandler/createScrollParent.d.ts.map +1 -1
  87. package/lib/scrollHandler/createScrollParent.js +12 -13
  88. package/lib/scrollHandler/createScrollParent.js.map +1 -1
  89. package/lib/scrollHandler/documentScrollParent.d.ts.map +1 -1
  90. package/lib/scrollHandler/documentScrollParent.js +18 -27
  91. package/lib/scrollHandler/documentScrollParent.js.map +1 -1
  92. package/lib/scrollHandler/scrollParent.d.ts.map +1 -1
  93. package/lib/scrollHandler/scrollParent.js +30 -34
  94. package/lib/scrollHandler/scrollParent.js.map +1 -1
  95. package/lib/scrollHandler.d.ts.map +1 -1
  96. package/lib/scrollHandler.js +11 -14
  97. package/lib/scrollHandler.js.map +1 -1
  98. package/lib/selectNodeHandler.d.ts +4 -2
  99. package/lib/selectNodeHandler.d.ts.map +1 -1
  100. package/lib/selectNodeHandler.js +64 -83
  101. package/lib/selectNodeHandler.js.map +1 -1
  102. package/lib/setDefaultOptions.d.ts.map +1 -1
  103. package/lib/setDefaultOptions.js +34 -38
  104. package/lib/setDefaultOptions.js.map +1 -1
  105. package/lib/triggerCustomEvent.d.ts.map +1 -1
  106. package/lib/triggerCustomEvent.js +2 -2
  107. package/lib/triggerCustomEvent.js.map +1 -1
  108. package/lib/util.d.ts.map +1 -1
  109. package/lib/util.js +2 -2
  110. package/lib/util.js.map +1 -1
  111. package/lib/version.d.ts +1 -1
  112. package/lib/version.js +1 -1
  113. package/lib/version.js.map +1 -1
  114. package/package.json +43 -32
  115. package/src/animation.ts +14 -12
  116. package/src/classNames.ts +52 -52
  117. package/src/dataLoader.ts +79 -71
  118. package/src/dragAndDropHandler/binarySearch.ts +17 -17
  119. package/src/dragAndDropHandler/dragElement.ts +51 -50
  120. package/src/dragAndDropHandler/generateHitAreas.ts +156 -161
  121. package/src/dragAndDropHandler/index.ts +334 -348
  122. package/src/dragAndDropHandler/iterateVisibleNodes.ts +61 -63
  123. package/src/dragAndDropHandler/types.ts +5 -5
  124. package/src/elementsRenderer.ts +377 -294
  125. package/src/events.ts +241 -208
  126. package/src/header.txt +1 -1
  127. package/src/index.ts +259 -229
  128. package/src/keyHandler.ts +179 -115
  129. package/src/methodTypes.ts +7 -10
  130. package/src/mouseHandler.ts +316 -316
  131. package/src/mouseUtils.ts +14 -14
  132. package/src/node.ts +773 -771
  133. package/src/nodeElement/borderDropHint.ts +25 -25
  134. package/src/nodeElement/folderElement.ts +116 -102
  135. package/src/nodeElement/ghostDropHint.ts +64 -62
  136. package/src/nodeElement/index.ts +81 -73
  137. package/src/nodeUtils.ts +5 -5
  138. package/src/options.ts +25 -5
  139. package/src/requestUrl.ts +7 -14
  140. package/src/saveStateHandler.ts +191 -207
  141. package/src/scrollHandler/containerScrollParent.ts +40 -49
  142. package/src/scrollHandler/createScrollParent.ts +26 -27
  143. package/src/scrollHandler/documentScrollParent.ts +66 -72
  144. package/src/scrollHandler/scrollParent.ts +90 -86
  145. package/src/scrollHandler.ts +45 -45
  146. package/src/selectNodeHandler.ts +171 -165
  147. package/src/setDefaultOptions.ts +44 -32
  148. package/src/triggerCustomEvent.ts +7 -3
  149. package/src/util.ts +2 -3
  150. package/src/version.ts +1 -1
  151. package/tree_element.css +87 -69
  152. package/tree_element.debug.js +3866 -3831
  153. package/tree_element.debug.js.map +1 -1
  154. package/tree_element.js +2 -2
  155. package/tree_element.js.map +1 -1
@@ -2,36 +2,36 @@ import type { ClassNames } from "../classNames";
2
2
  import type { DropHint } from "../dragAndDropHandler/types";
3
3
 
4
4
  class BorderDropHint implements DropHint {
5
- private hint?: HTMLElement;
6
-
7
- constructor(
8
- element: HTMLElement,
9
- scrollLeft: number,
10
- classNames: ClassNames,
11
- ) {
12
- const div = element.querySelector(`:scope > .${classNames.element}`);
5
+ private hint?: HTMLElement;
6
+
7
+ constructor(
8
+ element: HTMLElement,
9
+ scrollLeft: number,
10
+ classNames: ClassNames,
11
+ ) {
12
+ const div = element.querySelector(`:scope > .${classNames.element}`);
13
+
14
+ if (!div) {
15
+ this.hint = undefined;
16
+ return;
17
+ }
13
18
 
14
- if (!div) {
15
- this.hint = undefined;
16
- return;
17
- }
19
+ const width = Math.max(element.offsetWidth + scrollLeft - 4, 0);
20
+ const height = Math.max(element.clientHeight - 4, 0);
18
21
 
19
- const width = Math.max(element.offsetWidth + scrollLeft - 4, 0);
20
- const height = Math.max(element.clientHeight - 4, 0);
22
+ const hint = document.createElement("span");
23
+ hint.className = classNames.border;
24
+ hint.style.width = `${width}px`;
25
+ hint.style.height = `${height}px`;
21
26
 
22
- const hint = document.createElement("span");
23
- hint.className = classNames.border;
24
- hint.style.width = `${width}px`;
25
- hint.style.height = `${height}px`;
27
+ this.hint = hint;
26
28
 
27
- this.hint = hint;
29
+ div.append(this.hint);
30
+ }
28
31
 
29
- div.append(this.hint);
30
- }
31
-
32
- public remove(): void {
33
- this.hint?.remove();
34
- }
32
+ public remove(): void {
33
+ this.hint?.remove();
34
+ }
35
35
  }
36
36
 
37
37
  export default BorderDropHint;
@@ -1,140 +1,154 @@
1
1
  import type { AnimationSpeed } from "../animation";
2
2
  import type { TriggerEvent } from "../methodTypes";
3
- import type { Position } from "../node";
3
+ import type { Node, Position } from "../node";
4
4
  import type { NodeElementParams } from "./index";
5
5
 
6
6
  import { slideDown, slideUp } from "../animation";
7
7
  import NodeElement from "./index";
8
8
 
9
+ export type RenderChildren = (node: Node) => HTMLUListElement | null;
10
+
9
11
  interface FolderElementParams extends NodeElementParams {
10
- closedIconElement?: HTMLElement | Text;
11
- openedIconElement?: HTMLElement | Text;
12
- triggerEvent: TriggerEvent;
12
+ closedIconElement?: globalThis.Node;
13
+ openedIconElement?: globalThis.Node;
14
+ renderChildren: RenderChildren;
15
+ triggerEvent: TriggerEvent;
13
16
  }
14
17
 
15
18
  class FolderElement extends NodeElement {
16
- private closedIconElement?: HTMLElement | Text;
17
- private openedIconElement?: HTMLElement | Text;
18
- private triggerEvent: TriggerEvent;
19
-
20
- constructor({
21
- classNames,
22
- closedIconElement,
23
- getScrollLeft,
24
- node,
25
- openedIconElement,
26
- tabIndex,
27
- treeElement,
28
- triggerEvent,
29
- }: FolderElementParams) {
30
- super({
31
- classNames,
32
- getScrollLeft,
33
- node,
34
- tabIndex,
35
- treeElement,
36
- });
37
-
38
- this.closedIconElement = closedIconElement;
39
- this.openedIconElement = openedIconElement;
40
- this.triggerEvent = triggerEvent;
19
+ private closedIconElement?: globalThis.Node;
20
+ private openedIconElement?: globalThis.Node;
21
+ private renderChildren: RenderChildren;
22
+ private triggerEvent: TriggerEvent;
23
+
24
+ constructor({
25
+ classNames,
26
+ closedIconElement,
27
+ getScrollLeft,
28
+ node,
29
+ openedIconElement,
30
+ renderChildren,
31
+ tabIndex,
32
+ treeElement,
33
+ triggerEvent,
34
+ }: FolderElementParams) {
35
+ super({
36
+ classNames,
37
+ getScrollLeft,
38
+ node,
39
+ tabIndex,
40
+ treeElement,
41
+ });
42
+
43
+ this.closedIconElement = closedIconElement;
44
+ this.openedIconElement = openedIconElement;
45
+ this.renderChildren = renderChildren;
46
+ this.triggerEvent = triggerEvent;
47
+ }
48
+
49
+ public async close(
50
+ slide: boolean,
51
+ animationSpeed: AnimationSpeed,
52
+ ): Promise<void> {
53
+ if (!this.node.is_open) {
54
+ return;
41
55
  }
42
56
 
43
- public close(slide: boolean, animationSpeed: AnimationSpeed): void {
44
- if (!this.node.is_open) {
45
- return;
46
- }
57
+ this.node.is_open = false;
58
+
59
+ if (!this.isRendered()) {
60
+ this.triggerEvent("tree.close", { node: this.node });
61
+ return;
62
+ }
47
63
 
48
- this.node.is_open = false;
64
+ const button = this.getButton();
65
+ button.classList.add(this.classNames.closed);
66
+ button.replaceChildren();
49
67
 
50
- const button = this.getButton();
51
- button.classList.add(this.classNames.closed);
52
- button.innerHTML = "";
68
+ const closedIconElement = this.closedIconElement;
53
69
 
54
- const closedIconElement = this.closedIconElement;
70
+ if (closedIconElement) {
71
+ const icon = closedIconElement.cloneNode(true);
72
+ button.append(icon);
73
+ }
55
74
 
56
- if (closedIconElement) {
57
- const icon = closedIconElement.cloneNode(true);
58
- button.appendChild(icon);
59
- }
75
+ const ul = this.getUl();
60
76
 
61
- const doClose = (): void => {
62
- this.element.classList.add(this.classNames.closed);
77
+ if (ul) {
78
+ if (slide) {
79
+ await slideUp(ul, animationSpeed);
80
+ } else {
81
+ ul.style.display = "none";
82
+ }
83
+ }
63
84
 
64
- const titleSpan = this.getTitleSpan();
65
- titleSpan.setAttribute("aria-expanded", "false");
85
+ this.element.classList.add(this.classNames.closed);
66
86
 
67
- this.triggerEvent("tree.close", {
68
- node: this.node,
69
- });
70
- };
87
+ const titleSpan = this.getTitleSpan();
88
+ titleSpan.setAttribute("aria-expanded", "false");
71
89
 
72
- const ul = this.getUl();
90
+ this.triggerEvent("tree.close", {
91
+ node: this.node,
92
+ });
93
+ }
73
94
 
74
- if (slide) {
75
- slideUp(ul, animationSpeed, doClose);
76
- } else {
77
- ul.style.display = "none";
78
- doClose();
79
- }
95
+ public async open(
96
+ slide: boolean,
97
+ animationSpeed: AnimationSpeed,
98
+ ): Promise<void> {
99
+ if (this.node.is_open) {
100
+ return;
80
101
  }
81
102
 
82
- public async open(
83
- slide: boolean,
84
- animationSpeed: AnimationSpeed,
85
- ): Promise<void> {
86
- return new Promise(resolve => {
87
- if (this.node.is_open) {
88
- resolve();
89
- return;
90
- }
91
-
92
- this.node.is_open = true;
103
+ this.node.is_open = true;
93
104
 
94
- const button = this.getButton();
95
- button.classList.remove(this.classNames.closed);
96
- button.innerHTML = "";
105
+ if (!this.isRendered()) {
106
+ // The folder is rendered open when its parent is opened
107
+ this.triggerEvent("tree.open", { node: this.node });
108
+ return;
109
+ }
97
110
 
98
- const openedIconElement = this.openedIconElement;
111
+ const button = this.getButton();
112
+ button.classList.remove(this.classNames.closed);
113
+ button.replaceChildren();
99
114
 
100
- if (openedIconElement) {
101
- const icon = openedIconElement.cloneNode(true);
102
- button.appendChild(icon);
103
- }
115
+ const openedIconElement = this.openedIconElement;
104
116
 
105
- const doOpen = (): void => {
106
- this.element.classList.remove(this.classNames.closed);
117
+ if (openedIconElement) {
118
+ const icon = openedIconElement.cloneNode(true);
119
+ button.append(icon);
120
+ }
107
121
 
108
- const titleSpan = this.getTitleSpan();
109
- titleSpan.setAttribute("aria-expanded", "true");
122
+ // The children are rendered the first time the folder is opened
123
+ const ul = this.getUl() ?? this.renderChildren(this.node);
110
124
 
111
- this.triggerEvent("tree.open", {
112
- node: this.node,
113
- });
125
+ if (ul) {
126
+ if (slide) {
127
+ await slideDown(ul, animationSpeed);
128
+ } else {
129
+ ul.style.display = "block";
130
+ }
131
+ }
114
132
 
115
- resolve();
116
- };
133
+ this.element.classList.remove(this.classNames.closed);
117
134
 
118
- const ul = this.getUl();
135
+ const titleSpan = this.getTitleSpan();
136
+ titleSpan.setAttribute("aria-expanded", "true");
119
137
 
120
- if (slide) {
121
- slideDown(ul, animationSpeed, doOpen);
122
- } else {
123
- ul.style.display = "block";
124
- doOpen();
125
- }
126
- });
127
- }
138
+ this.triggerEvent("tree.open", {
139
+ node: this.node,
140
+ });
141
+ }
128
142
 
129
- protected mustShowBorderDropHint(position: Position): boolean {
130
- return !this.node.is_open && position === "inside";
131
- }
143
+ protected mustShowBorderDropHint(position: Position): boolean {
144
+ return !this.node.is_open && position === "inside";
145
+ }
132
146
 
133
- private getButton(): HTMLLinkElement {
134
- return this.element.querySelector(
135
- `:scope > .${this.classNames.element} > a.${this.classNames.toggler}`,
136
- ) as HTMLLinkElement;
137
- }
147
+ private getButton(): HTMLElement {
148
+ return this.element.querySelector(
149
+ `:scope > .${this.classNames.element} > .${this.classNames.toggler}`,
150
+ ) as HTMLElement;
151
+ }
138
152
  }
139
153
 
140
154
  export default FolderElement;
@@ -3,82 +3,84 @@ import type { DropHint } from "../dragAndDropHandler/types";
3
3
  import type { Node, Position } from "../node";
4
4
 
5
5
  class GhostDropHint implements DropHint {
6
- private classNames: ClassNames;
7
- private element: HTMLElement;
8
- private ghost: HTMLElement;
9
- private node: Node;
10
-
11
- constructor(
12
- node: Node,
13
- element: HTMLElement,
14
- position: Position,
15
- classNames: ClassNames,
16
- ) {
17
- this.classNames = classNames;
18
- this.element = element;
19
- this.node = node;
20
- this.ghost = this.createGhostElement();
21
-
22
- switch (position) {
23
- case "after":
24
- this.moveAfter();
25
- break;
26
-
27
- case "before":
28
- this.moveBefore();
29
- break;
30
-
31
- case "inside": {
32
- if (node.isFolder() && node.is_open) {
33
- this.moveInsideOpenFolder();
34
- } else {
35
- this.moveInside();
36
- }
37
- }
6
+ private classNames: ClassNames;
7
+ private element: HTMLElement;
8
+ private ghost: HTMLElement;
9
+ private node: Node;
10
+
11
+ constructor(
12
+ node: Node,
13
+ element: HTMLElement,
14
+ position: Position,
15
+ classNames: ClassNames,
16
+ ) {
17
+ this.classNames = classNames;
18
+ this.element = element;
19
+ this.node = node;
20
+ this.ghost = this.createGhostElement();
21
+
22
+ switch (position) {
23
+ case "after": {
24
+ this.moveAfter();
25
+ break;
26
+ }
27
+
28
+ case "before": {
29
+ this.moveBefore();
30
+ break;
31
+ }
32
+
33
+ case "inside": {
34
+ if (node.isFolder() && node.is_open) {
35
+ this.moveInsideOpenFolder();
36
+ } else {
37
+ this.moveInside();
38
38
  }
39
+ }
39
40
  }
41
+ }
40
42
 
41
- public remove(): void {
42
- this.ghost.remove();
43
- }
43
+ public remove(): void {
44
+ this.ghost.remove();
45
+ }
44
46
 
45
- private createGhostElement() {
46
- const { circle, common, ghost: ghostClass, line } = this.classNames;
47
+ private createGhostElement() {
48
+ const { circle, common, ghost: ghostClass, line } = this.classNames;
47
49
 
48
- const ghost = document.createElement("li");
49
- ghost.className = `${common} ${ghostClass}`;
50
+ const ghost = document.createElement("li");
51
+ ghost.className = `${common} ${ghostClass}`;
50
52
 
51
- const circleSpan = document.createElement("span");
52
- circleSpan.className = `${common} ${circle}`;
53
- ghost.append(circleSpan);
53
+ const circleSpan = document.createElement("span");
54
+ circleSpan.className = `${common} ${circle}`;
55
+ ghost.append(circleSpan);
54
56
 
55
- const lineSpan = document.createElement("span");
56
- lineSpan.className = `${common} ${line}`;
57
- ghost.append(lineSpan);
57
+ const lineSpan = document.createElement("span");
58
+ lineSpan.className = `${common} ${line}`;
59
+ ghost.append(lineSpan);
58
60
 
59
- return ghost;
60
- }
61
+ return ghost;
62
+ }
61
63
 
62
- private moveAfter(): void {
63
- this.element.after(this.ghost);
64
- }
64
+ private moveAfter(): void {
65
+ this.element.after(this.ghost);
66
+ }
65
67
 
66
- private moveBefore(): void {
67
- this.element.before(this.ghost);
68
- }
68
+ private moveBefore(): void {
69
+ this.element.before(this.ghost);
70
+ }
69
71
 
70
- private moveInside(): void {
71
- this.element.after(this.ghost);
72
- this.ghost.classList.add(this.classNames.inside);
73
- }
72
+ private moveInside(): void {
73
+ this.element.after(this.ghost);
74
+ this.ghost.classList.add(this.classNames.inside);
75
+ }
74
76
 
75
- private moveInsideOpenFolder(): void {
76
- const childElement = this.node.children[0]?.element;
77
+ private moveInsideOpenFolder(): void {
78
+ const childElement = this.node.children[0]?.element;
77
79
 
78
- if (childElement) {
79
- childElement.before(this.ghost);
80
- }
80
+ if (childElement) {
81
+ childElement.before(this.ghost);
81
82
  }
83
+ }
82
84
  }
83
85
 
84
86
  export default GhostDropHint;
@@ -7,97 +7,105 @@ import BorderDropHint from "./borderDropHint";
7
7
  import GhostDropHint from "./ghostDropHint";
8
8
 
9
9
  export interface NodeElementParams {
10
- classNames: ClassNames;
11
- getScrollLeft: GetScrollLeft;
12
- node: Node;
13
- tabIndex?: number;
14
- treeElement: HTMLElement;
10
+ classNames: ClassNames;
11
+ getScrollLeft: GetScrollLeft;
12
+ node: Node;
13
+ tabIndex?: number;
14
+ treeElement: HTMLElement;
15
15
  }
16
16
 
17
17
  class NodeElement {
18
- public element: HTMLElement;
19
- public node: Node;
20
- protected classNames: ClassNames;
21
- private getScrollLeft: GetScrollLeft;
22
- private tabIndex?: number;
23
- private treeElement: HTMLElement;
24
-
25
- constructor({
26
- classNames,
27
- getScrollLeft,
28
- node,
29
- tabIndex,
30
- treeElement,
31
- }: NodeElementParams) {
32
- this.classNames = classNames;
33
- this.getScrollLeft = getScrollLeft;
34
- this.node = node;
35
- this.tabIndex = tabIndex;
36
- this.treeElement = treeElement;
37
-
38
- node.element ??= this.treeElement;
39
- this.element = node.element;
40
-
41
- }
42
-
43
- public addDropHint(position: Position): DropHint {
44
- if (this.mustShowBorderDropHint(position)) {
45
- return new BorderDropHint(
46
- this.element,
47
- this.getScrollLeft(),
48
- this.classNames,
49
- );
50
- } else {
51
- return new GhostDropHint(
52
- this.node,
53
- this.element,
54
- position,
55
- this.classNames,
56
- );
57
- }
18
+ public element: HTMLElement;
19
+ public node: Node;
20
+ protected classNames: ClassNames;
21
+ private getScrollLeft: GetScrollLeft;
22
+ private tabIndex?: number;
23
+ private treeElement: HTMLElement;
24
+
25
+ constructor({
26
+ classNames,
27
+ getScrollLeft,
28
+ node,
29
+ tabIndex,
30
+ treeElement,
31
+ }: NodeElementParams) {
32
+ this.classNames = classNames;
33
+ this.getScrollLeft = getScrollLeft;
34
+ this.node = node;
35
+ this.tabIndex = tabIndex;
36
+ this.treeElement = treeElement;
37
+
38
+ // The root node has no element of its own; it uses the tree element.
39
+ this.element = node.element ?? this.treeElement;
40
+ }
41
+
42
+ public addDropHint(position: Position): DropHint {
43
+ return this.mustShowBorderDropHint(position)
44
+ ? new BorderDropHint(this.element, this.getScrollLeft(), this.classNames)
45
+ : new GhostDropHint(this.node, this.element, position, this.classNames);
46
+ }
47
+
48
+ public deselect(): void {
49
+ if (!this.isRendered()) {
50
+ return;
58
51
  }
59
52
 
60
- public deselect(): void {
61
- this.element.classList.remove(this.classNames.selected);
53
+ this.element.classList.remove(this.classNames.selected);
62
54
 
63
- const titleSpan = this.getTitleSpan();
64
- titleSpan.removeAttribute("tabindex");
65
- titleSpan.setAttribute("aria-selected", "false");
55
+ const titleSpan = this.getTitleSpan();
66
56
 
67
- titleSpan.blur();
57
+ // Check for null or undefined
58
+ if (this.tabIndex != null) {
59
+ titleSpan.setAttribute("tabindex", "-1");
68
60
  }
69
61
 
70
- public select(mustSetFocus: boolean): void {
71
- this.element.classList.add(this.classNames.selected);
62
+ titleSpan.setAttribute("aria-selected", "false");
72
63
 
73
- const titleSpan = this.getTitleSpan();
74
- const tabIndex = this.tabIndex;
64
+ titleSpan.blur();
65
+ }
75
66
 
76
- // Check for null or undefined
77
- if (tabIndex != null) {
78
- titleSpan.setAttribute("tabindex", tabIndex.toString());
79
- }
67
+ public select(mustSetFocus: boolean): void {
68
+ if (!this.isRendered()) {
69
+ return;
70
+ }
80
71
 
81
- titleSpan.setAttribute("aria-selected", "true");
72
+ this.element.classList.add(this.classNames.selected);
82
73
 
83
- if (mustSetFocus) {
84
- titleSpan.focus();
85
- }
86
- }
74
+ const titleSpan = this.getTitleSpan();
75
+ const tabIndex = this.tabIndex;
87
76
 
88
- protected getTitleSpan(): HTMLSpanElement {
89
- return this.element.querySelector(
90
- `:scope > .${this.classNames.element} > span.${this.classNames.title}`,
91
- ) as HTMLSpanElement;
77
+ // Check for null or undefined
78
+ if (tabIndex != null) {
79
+ titleSpan.setAttribute("tabindex", tabIndex.toString());
92
80
  }
93
81
 
94
- protected getUl(): HTMLUListElement {
95
- return this.element.querySelector(":scope > ul") as HTMLUListElement;
96
- }
82
+ titleSpan.setAttribute("aria-selected", "true");
97
83
 
98
- protected mustShowBorderDropHint(position: Position): boolean {
99
- return position === "inside";
84
+ if (mustSetFocus) {
85
+ titleSpan.focus();
100
86
  }
87
+ }
88
+
89
+ protected getTitleSpan(): HTMLSpanElement {
90
+ return this.element.querySelector(
91
+ `:scope > .${this.classNames.element} > span.${this.classNames.title}`,
92
+ ) as HTMLSpanElement;
93
+ }
94
+
95
+ protected getUl(): HTMLUListElement | null {
96
+ return this.element.querySelector(":scope > ul");
97
+ }
98
+
99
+ /* A node inside a closed folder is not rendered. Its selected and open
100
+ * state is applied when it is rendered.
101
+ */
102
+ protected isRendered(): boolean {
103
+ return Boolean(this.node.element);
104
+ }
105
+
106
+ protected mustShowBorderDropHint(position: Position): boolean {
107
+ return position === "inside";
108
+ }
101
109
  }
102
110
 
103
111
  export default NodeElement;
package/src/nodeUtils.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import type { NodeData, NodeRecord } from "./node";
2
2
 
3
3
  interface NodeRecordWithChildren extends NodeRecord {
4
- children: NodeData[];
4
+ children: NodeData[];
5
5
  }
6
6
 
7
7
  export const isNodeRecordWithChildren = (
8
- data: NodeData,
8
+ data: NodeData,
9
9
  ): data is NodeRecordWithChildren =>
10
- typeof data === "object" &&
11
- "children" in data &&
12
- data.children instanceof Array;
10
+ typeof data === "object" &&
11
+ "children" in data &&
12
+ Array.isArray(data.children);