tree-element 0.1.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 (182) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +221 -0
  3. package/lib/animation.d.ts +5 -0
  4. package/lib/animation.d.ts.map +1 -0
  5. package/lib/animation.js +39 -0
  6. package/lib/animation.js.map +1 -0
  7. package/lib/classNames.d.ts +34 -0
  8. package/lib/classNames.d.ts.map +1 -0
  9. package/lib/classNames.js +39 -0
  10. package/lib/classNames.js.map +1 -0
  11. package/lib/dataLoader.d.ts +25 -0
  12. package/lib/dataLoader.d.ts.map +1 -0
  13. package/lib/dataLoader.js +69 -0
  14. package/lib/dataLoader.js.map +1 -0
  15. package/lib/dragAndDropHandler/binarySearch.d.ts +3 -0
  16. package/lib/dragAndDropHandler/binarySearch.d.ts.map +1 -0
  17. package/lib/dragAndDropHandler/binarySearch.js +23 -0
  18. package/lib/dragAndDropHandler/binarySearch.js.map +1 -0
  19. package/lib/dragAndDropHandler/dragElement.d.ts +20 -0
  20. package/lib/dragAndDropHandler/dragElement.d.ts.map +1 -0
  21. package/lib/dragAndDropHandler/dragElement.js +39 -0
  22. package/lib/dragAndDropHandler/dragElement.js.map +1 -0
  23. package/lib/dragAndDropHandler/generateHitAreas.d.ts +13 -0
  24. package/lib/dragAndDropHandler/generateHitAreas.d.ts.map +1 -0
  25. package/lib/dragAndDropHandler/generateHitAreas.js +130 -0
  26. package/lib/dragAndDropHandler/generateHitAreas.js.map +1 -0
  27. package/lib/dragAndDropHandler/index.d.ts +76 -0
  28. package/lib/dragAndDropHandler/index.d.ts.map +1 -0
  29. package/lib/dragAndDropHandler/index.js +311 -0
  30. package/lib/dragAndDropHandler/index.js.map +1 -0
  31. package/lib/dragAndDropHandler/iterateVisibleNodes.d.ts +11 -0
  32. package/lib/dragAndDropHandler/iterateVisibleNodes.d.ts.map +1 -0
  33. package/lib/dragAndDropHandler/iterateVisibleNodes.js +54 -0
  34. package/lib/dragAndDropHandler/iterateVisibleNodes.js.map +1 -0
  35. package/lib/dragAndDropHandler/types.d.ts +11 -0
  36. package/lib/dragAndDropHandler/types.d.ts.map +1 -0
  37. package/lib/elementsRenderer.d.ts +53 -0
  38. package/lib/elementsRenderer.d.ts.map +1 -0
  39. package/lib/elementsRenderer.js +251 -0
  40. package/lib/elementsRenderer.js.map +1 -0
  41. package/lib/events.d.ts +224 -0
  42. package/lib/events.d.ts.map +1 -0
  43. package/lib/index.d.ts +367 -0
  44. package/lib/index.d.ts.map +1 -0
  45. package/lib/index.js +1098 -0
  46. package/lib/index.js.map +1 -0
  47. package/lib/keyHandler.d.ts +29 -0
  48. package/lib/keyHandler.d.ts.map +1 -0
  49. package/lib/keyHandler.js +107 -0
  50. package/lib/keyHandler.js.map +1 -0
  51. package/lib/methodTypes.d.ts +21 -0
  52. package/lib/methodTypes.d.ts.map +1 -0
  53. package/lib/mouseHandler.d.ts +60 -0
  54. package/lib/mouseHandler.d.ts.map +1 -0
  55. package/lib/mouseHandler.js +283 -0
  56. package/lib/mouseHandler.js.map +1 -0
  57. package/lib/mouseUtils.d.ts +9 -0
  58. package/lib/mouseUtils.d.ts.map +1 -0
  59. package/lib/mouseUtils.js +15 -0
  60. package/lib/mouseUtils.js.map +1 -0
  61. package/lib/node.d.ts +320 -0
  62. package/lib/node.d.ts.map +1 -0
  63. package/lib/node.js +737 -0
  64. package/lib/node.js.map +1 -0
  65. package/lib/nodeElement/borderDropHint.d.ts +9 -0
  66. package/lib/nodeElement/borderDropHint.d.ts.map +1 -0
  67. package/lib/nodeElement/borderDropHint.js +24 -0
  68. package/lib/nodeElement/borderDropHint.js.map +1 -0
  69. package/lib/nodeElement/folderElement.d.ts +22 -0
  70. package/lib/nodeElement/folderElement.d.ts.map +1 -0
  71. package/lib/nodeElement/folderElement.js +100 -0
  72. package/lib/nodeElement/folderElement.js.map +1 -0
  73. package/lib/nodeElement/ghostDropHint.d.ts +18 -0
  74. package/lib/nodeElement/ghostDropHint.d.ts.map +1 -0
  75. package/lib/nodeElement/ghostDropHint.js +67 -0
  76. package/lib/nodeElement/ghostDropHint.js.map +1 -0
  77. package/lib/nodeElement/index.d.ts +28 -0
  78. package/lib/nodeElement/index.d.ts.map +1 -0
  79. package/lib/nodeElement/index.js +66 -0
  80. package/lib/nodeElement/index.js.map +1 -0
  81. package/lib/nodeUtils.d.ts +7 -0
  82. package/lib/nodeUtils.d.ts.map +1 -0
  83. package/lib/nodeUtils.js +4 -0
  84. package/lib/nodeUtils.js.map +1 -0
  85. package/lib/options.d.ts +310 -0
  86. package/lib/options.d.ts.map +1 -0
  87. package/lib/package.json +3 -0
  88. package/lib/positionUtils.d.ts +8 -0
  89. package/lib/positionUtils.d.ts.map +1 -0
  90. package/lib/positionUtils.js +14 -0
  91. package/lib/positionUtils.js.map +1 -0
  92. package/lib/requestUrl.d.ts +9 -0
  93. package/lib/requestUrl.d.ts.map +1 -0
  94. package/lib/requestUrl.js +37 -0
  95. package/lib/requestUrl.js.map +1 -0
  96. package/lib/saveStateHandler.d.ts +46 -0
  97. package/lib/saveStateHandler.d.ts.map +1 -0
  98. package/lib/saveStateHandler.js +204 -0
  99. package/lib/saveStateHandler.js.map +1 -0
  100. package/lib/scrollHandler/containerScrollParent.d.ts +12 -0
  101. package/lib/scrollHandler/containerScrollParent.d.ts.map +1 -0
  102. package/lib/scrollHandler/containerScrollParent.js +49 -0
  103. package/lib/scrollHandler/containerScrollParent.js.map +1 -0
  104. package/lib/scrollHandler/createScrollParent.d.ts +4 -0
  105. package/lib/scrollHandler/createScrollParent.d.ts.map +1 -0
  106. package/lib/scrollHandler/createScrollParent.js +38 -0
  107. package/lib/scrollHandler/createScrollParent.js.map +1 -0
  108. package/lib/scrollHandler/documentScrollParent.d.ts +22 -0
  109. package/lib/scrollHandler/documentScrollParent.d.ts.map +1 -0
  110. package/lib/scrollHandler/documentScrollParent.js +71 -0
  111. package/lib/scrollHandler/documentScrollParent.js.map +1 -0
  112. package/lib/scrollHandler/scrollParent.d.ts +26 -0
  113. package/lib/scrollHandler/scrollParent.d.ts.map +1 -0
  114. package/lib/scrollHandler/scrollParent.js +79 -0
  115. package/lib/scrollHandler/scrollParent.js.map +1 -0
  116. package/lib/scrollHandler.d.ts +20 -0
  117. package/lib/scrollHandler.d.ts.map +1 -0
  118. package/lib/scrollHandler.js +41 -0
  119. package/lib/scrollHandler.js.map +1 -0
  120. package/lib/selectNodeHandler.d.ts +38 -0
  121. package/lib/selectNodeHandler.d.ts.map +1 -0
  122. package/lib/selectNodeHandler.js +166 -0
  123. package/lib/selectNodeHandler.js.map +1 -0
  124. package/lib/setDefaultOptions.d.ts +4 -0
  125. package/lib/setDefaultOptions.d.ts.map +1 -0
  126. package/lib/setDefaultOptions.js +86 -0
  127. package/lib/setDefaultOptions.js.map +1 -0
  128. package/lib/triggerCustomEvent.d.ts +3 -0
  129. package/lib/triggerCustomEvent.d.ts.map +1 -0
  130. package/lib/triggerCustomEvent.js +13 -0
  131. package/lib/triggerCustomEvent.js.map +1 -0
  132. package/lib/util.d.ts +3 -0
  133. package/lib/util.d.ts.map +1 -0
  134. package/lib/util.js +5 -0
  135. package/lib/util.js.map +1 -0
  136. package/lib/version.d.ts +3 -0
  137. package/lib/version.d.ts.map +1 -0
  138. package/lib/version.js +5 -0
  139. package/lib/version.js.map +1 -0
  140. package/package.json +110 -0
  141. package/src/animation.ts +48 -0
  142. package/src/classNames.ts +70 -0
  143. package/src/dataLoader.ts +92 -0
  144. package/src/dragAndDropHandler/binarySearch.ts +27 -0
  145. package/src/dragAndDropHandler/dragElement.ts +62 -0
  146. package/src/dragAndDropHandler/generateHitAreas.ts +195 -0
  147. package/src/dragAndDropHandler/index.ts +442 -0
  148. package/src/dragAndDropHandler/iterateVisibleNodes.ts +91 -0
  149. package/src/dragAndDropHandler/types.ts +12 -0
  150. package/src/elementsRenderer.ts +367 -0
  151. package/src/events.ts +237 -0
  152. package/src/header.txt +16 -0
  153. package/src/index.ts +1290 -0
  154. package/src/keyHandler.ts +139 -0
  155. package/src/methodTypes.ts +44 -0
  156. package/src/mouseHandler.ts +391 -0
  157. package/src/mouseUtils.ts +23 -0
  158. package/src/node.ts +831 -0
  159. package/src/nodeElement/borderDropHint.ts +37 -0
  160. package/src/nodeElement/folderElement.ts +140 -0
  161. package/src/nodeElement/ghostDropHint.ts +84 -0
  162. package/src/nodeElement/index.ts +103 -0
  163. package/src/nodeUtils.ts +12 -0
  164. package/src/options.ts +325 -0
  165. package/src/positionUtils.ts +18 -0
  166. package/src/requestUrl.ts +41 -0
  167. package/src/saveStateHandler.ts +279 -0
  168. package/src/scrollHandler/containerScrollParent.ts +71 -0
  169. package/src/scrollHandler/createScrollParent.ts +51 -0
  170. package/src/scrollHandler/documentScrollParent.ts +104 -0
  171. package/src/scrollHandler/scrollParent.ts +117 -0
  172. package/src/scrollHandler.ts +55 -0
  173. package/src/selectNodeHandler.ts +192 -0
  174. package/src/setDefaultOptions.ts +79 -0
  175. package/src/triggerCustomEvent.ts +14 -0
  176. package/src/util.ts +5 -0
  177. package/src/version.ts +4 -0
  178. package/tree_element.css +195 -0
  179. package/tree_element.debug.js +4153 -0
  180. package/tree_element.debug.js.map +1 -0
  181. package/tree_element.js +21 -0
  182. package/tree_element.js.map +1 -0
@@ -0,0 +1,18 @@
1
+ interface TopLeftPosition {
2
+ left: number;
3
+ top: number;
4
+ }
5
+
6
+ // Get the top position of the HTML element.
7
+ export const getOffsetTop = (element: HTMLElement): number =>
8
+ getElementPosition(element).top;
9
+
10
+ // Get the top left position of the HTML element.
11
+ export const getElementPosition = (element: HTMLElement): TopLeftPosition => {
12
+ const rect = element.getBoundingClientRect();
13
+
14
+ return {
15
+ left: rect.x + window.scrollX,
16
+ top: rect.y + window.scrollY,
17
+ };
18
+ };
@@ -0,0 +1,41 @@
1
+ // Url class for absolute and relative urls.
2
+
3
+ const isAbsoluteUrl = (inputUrl: string) => {
4
+ try {
5
+ new URL(inputUrl);
6
+ return true;
7
+ } catch {
8
+ return false;
9
+ }
10
+ }
11
+
12
+ const LOCALHOST = "http://localhost";
13
+
14
+ class RequestUrl {
15
+ private isAbsolute: boolean;
16
+ private url: URL;
17
+
18
+ constructor(inputUrl: string) {
19
+ if (isAbsoluteUrl(inputUrl)) {
20
+ this.url = new URL(inputUrl);
21
+ this.isAbsolute = true;
22
+ } else {
23
+ this.url = new URL(inputUrl, LOCALHOST);
24
+ this.isAbsolute = false;
25
+ }
26
+ }
27
+
28
+ setSearchParam(key: string, value: string) {
29
+ this.url.searchParams.set(key, value);
30
+ }
31
+
32
+ toString() {
33
+ if (this.isAbsolute) {
34
+ return this.url.href;
35
+ } else {
36
+ return this.url.href.slice(LOCALHOST.length);
37
+ }
38
+ }
39
+ }
40
+
41
+ export default RequestUrl;
@@ -0,0 +1,279 @@
1
+ import type {
2
+ AddToSelection,
3
+ GetNodeById,
4
+ GetSelectedNodes,
5
+ GetTree,
6
+ OpenNode,
7
+ RefreshElements,
8
+ RemoveFromSelection,
9
+ } from "./methodTypes";
10
+ import type { Node, NodeId } from "./node";
11
+ import type { OnGetStateFromStorage, OnSetStateFromStorage } from "./options";
12
+
13
+ import { isInt } from "./util";
14
+
15
+ export interface SavedState {
16
+ open_nodes?: NodeId[];
17
+ selected_node?: NodeId[];
18
+ }
19
+
20
+ interface SaveStateHandlerParams {
21
+ addToSelection: AddToSelection;
22
+ getNodeById: GetNodeById;
23
+ getSelectedNodes: GetSelectedNodes;
24
+ getTree: GetTree;
25
+ onGetStateFromStorage?: OnGetStateFromStorage;
26
+ onSetStateFromStorage?: OnSetStateFromStorage;
27
+ openNode: OpenNode;
28
+ refreshElements: RefreshElements;
29
+ removeFromSelection: RemoveFromSelection;
30
+ saveState: boolean | string;
31
+ }
32
+
33
+ export default class SaveStateHandler {
34
+ private addToSelection: AddToSelection;
35
+ private getNodeById: GetNodeById;
36
+ private getSelectedNodes: GetSelectedNodes;
37
+ private getTree: GetTree;
38
+ private onGetStateFromStorage?: OnGetStateFromStorage;
39
+ private onSetStateFromStorage?: OnSetStateFromStorage;
40
+ private openNode: OpenNode;
41
+ private refreshElements: RefreshElements;
42
+ private removeFromSelection: RemoveFromSelection;
43
+ private saveStateOption: boolean | string;
44
+
45
+ constructor({
46
+ addToSelection,
47
+ getNodeById,
48
+ getSelectedNodes,
49
+ getTree,
50
+ onGetStateFromStorage,
51
+ onSetStateFromStorage,
52
+ openNode,
53
+ refreshElements,
54
+ removeFromSelection,
55
+ saveState,
56
+ }: SaveStateHandlerParams) {
57
+ this.addToSelection = addToSelection;
58
+ this.getNodeById = getNodeById;
59
+ this.getSelectedNodes = getSelectedNodes;
60
+ this.getTree = getTree;
61
+ this.onGetStateFromStorage = onGetStateFromStorage;
62
+ this.onSetStateFromStorage = onSetStateFromStorage;
63
+ this.openNode = openNode;
64
+ this.refreshElements = refreshElements;
65
+ this.removeFromSelection = removeFromSelection;
66
+ this.saveStateOption = saveState;
67
+ }
68
+
69
+ public getNodeIdToBeSelected(): NodeId | null {
70
+ if (!this.saveStateOption) {
71
+ return null;
72
+ }
73
+
74
+ const state = this.getStateFromStorage();
75
+
76
+ if (state?.selected_node) {
77
+ return state.selected_node[0] ?? null;
78
+ } else {
79
+ return null;
80
+ }
81
+ }
82
+
83
+ public getState(): SavedState {
84
+ const getOpenNodeIds = (): NodeId[] => {
85
+ const openNodes: NodeId[] = [];
86
+
87
+ this.getTree()?.iterate((node: Node) => {
88
+ if (node.is_open && node.id && node.hasChildren()) {
89
+ openNodes.push(node.id);
90
+ }
91
+ return true;
92
+ });
93
+
94
+ return openNodes;
95
+ };
96
+
97
+ const getSelectedNodeIds = (): NodeId[] => {
98
+ const selectedNodeIds: NodeId[] = [];
99
+
100
+ this.getSelectedNodes().forEach((node) => {
101
+ if (node.id != null) {
102
+ selectedNodeIds.push(node.id);
103
+ }
104
+ });
105
+
106
+ return selectedNodeIds;
107
+ };
108
+
109
+ return {
110
+ open_nodes: getOpenNodeIds(),
111
+ selected_node: getSelectedNodeIds(),
112
+ };
113
+ }
114
+
115
+ public getStateFromStorage(): null | SavedState {
116
+ if (!this.saveStateOption) {
117
+ return null;
118
+ }
119
+
120
+ const jsonData = this.loadFromStorage();
121
+
122
+ if (jsonData) {
123
+ return this.parseState(jsonData);
124
+ } else {
125
+ return null;
126
+ }
127
+ }
128
+
129
+ public saveState(): void {
130
+ if (!this.saveStateOption) {
131
+ return;
132
+ }
133
+
134
+ const state = JSON.stringify(this.getState());
135
+
136
+ if (this.onSetStateFromStorage) {
137
+ this.onSetStateFromStorage(state);
138
+ } else {
139
+ localStorage.setItem(this.getKeyName(), state);
140
+ }
141
+ }
142
+
143
+ /*
144
+ Set initial state
145
+ Don't handle nodes that are loaded on demand
146
+
147
+ result: must load on demand (boolean)
148
+ */
149
+ public setInitialState(state: SavedState): boolean {
150
+ let mustLoadOnDemand = false;
151
+
152
+ if (state.open_nodes) {
153
+ mustLoadOnDemand = this.openInitialNodes(state.open_nodes);
154
+ }
155
+
156
+ this.resetSelection();
157
+
158
+ if (state.selected_node) {
159
+ this.selectInitialNodes(state.selected_node);
160
+ }
161
+
162
+ return mustLoadOnDemand;
163
+ }
164
+
165
+ public async setInitialStateOnDemand(
166
+ state: SavedState,
167
+ ): Promise<void> {
168
+ let nodeIds = state.open_nodes;
169
+
170
+ const openNodes = async () => {
171
+ if (!nodeIds) {
172
+ return;
173
+ }
174
+
175
+ const newNodesIds = [];
176
+
177
+ for (const nodeId of nodeIds) {
178
+ const node = this.getNodeById(nodeId);
179
+
180
+ if (!node) {
181
+ newNodesIds.push(nodeId);
182
+ } else {
183
+ if (!node.is_loading) {
184
+ if (node.load_on_demand) {
185
+ await loadAndOpenNode(node);
186
+ } else {
187
+ await this.openNode(node, false);
188
+ }
189
+ }
190
+ }
191
+ }
192
+
193
+ nodeIds = newNodesIds;
194
+
195
+ if (state.selected_node) {
196
+ if (this.selectInitialNodes(state.selected_node)) {
197
+ this.refreshElements(null);
198
+ }
199
+ }
200
+ };
201
+
202
+ const loadAndOpenNode = async (node: Node) => {
203
+ await this.openNode(node, false);
204
+ await openNodes();
205
+ };
206
+
207
+ await openNodes();
208
+ }
209
+
210
+ private getKeyName(): string {
211
+ if (typeof this.saveStateOption === "string") {
212
+ return this.saveStateOption;
213
+ } else {
214
+ return "tree";
215
+ }
216
+ }
217
+
218
+ private loadFromStorage(): null | string {
219
+ if (this.onGetStateFromStorage) {
220
+ return this.onGetStateFromStorage();
221
+ } else {
222
+ return localStorage.getItem(this.getKeyName());
223
+ }
224
+ }
225
+
226
+ private openInitialNodes(nodeIds: NodeId[]): boolean {
227
+ let mustLoadOnDemand = false;
228
+
229
+ for (const nodeId of nodeIds) {
230
+ const node = this.getNodeById(nodeId);
231
+
232
+ if (node) {
233
+ if (!node.load_on_demand) {
234
+ node.is_open = true;
235
+ } else {
236
+ mustLoadOnDemand = true;
237
+ }
238
+ }
239
+ }
240
+
241
+ return mustLoadOnDemand;
242
+ }
243
+
244
+ private parseState(jsonData: string): SavedState {
245
+ const state = JSON.parse(jsonData) as Record<string, unknown>;
246
+
247
+ // Check if selected_node is an int (instead of an array)
248
+ if (state.selected_node && isInt(state.selected_node)) {
249
+ // Convert to array
250
+ state.selected_node = [state.selected_node];
251
+ }
252
+
253
+ return state;
254
+ }
255
+
256
+ private resetSelection(): void {
257
+ const selectedNodes = this.getSelectedNodes();
258
+
259
+ selectedNodes.forEach((node) => {
260
+ this.removeFromSelection(node);
261
+ });
262
+ }
263
+
264
+ private selectInitialNodes(nodeIds: NodeId[]): boolean {
265
+ let selectCount = 0;
266
+
267
+ for (const nodeId of nodeIds) {
268
+ const node = this.getNodeById(nodeId);
269
+
270
+ if (node) {
271
+ selectCount += 1;
272
+
273
+ this.addToSelection(node);
274
+ }
275
+ }
276
+
277
+ return selectCount !== 0;
278
+ }
279
+ }
@@ -0,0 +1,71 @@
1
+ import type {
2
+ HorizontalScrollDirection,
3
+ VerticalScrollDirection,
4
+ } from "./scrollParent";
5
+
6
+ import { getElementPosition, getOffsetTop } from "../positionUtils";
7
+ import { ScrollParent } from "./scrollParent";
8
+
9
+
10
+ export default class ContainerScrollParent extends ScrollParent {
11
+ private scrollParentBottom?: number;
12
+ private scrollParentTop?: number;
13
+
14
+ public stopScrolling() {
15
+ super.stopScrolling();
16
+
17
+ this.horizontalScrollDirection = undefined;
18
+ this.verticalScrollDirection = undefined;
19
+ }
20
+
21
+ protected getNewHorizontalScrollDirection(
22
+ pageX: number,
23
+ ): HorizontalScrollDirection | undefined {
24
+ const scrollParentOffset = getElementPosition(this.container);
25
+ const containerWidth = this.container.getBoundingClientRect().width;
26
+
27
+ const rightEdge = scrollParentOffset.left + containerWidth;
28
+ const leftEdge = scrollParentOffset.left;
29
+ const isNearRightEdge = pageX > rightEdge - 20;
30
+ const isNearLeftEdge = pageX < leftEdge + 20;
31
+
32
+ if (isNearRightEdge) {
33
+ return "right";
34
+ } else if (isNearLeftEdge) {
35
+ return "left";
36
+ }
37
+
38
+ return undefined;
39
+ }
40
+
41
+ protected getNewVerticalScrollDirection(
42
+ pageY: number,
43
+ ): undefined | VerticalScrollDirection {
44
+ if (pageY < this.getScrollParentTop()) {
45
+ return "top";
46
+ }
47
+
48
+ if (pageY > this.getScrollParentBottom()) {
49
+ return "bottom";
50
+ }
51
+
52
+ return undefined;
53
+ }
54
+
55
+ private getScrollParentBottom() {
56
+ if (this.scrollParentBottom == null) {
57
+ const containerHeight =
58
+ this.container.getBoundingClientRect().height;
59
+ this.scrollParentBottom =
60
+ this.getScrollParentTop() + containerHeight;
61
+ }
62
+
63
+ return this.scrollParentBottom;
64
+ }
65
+
66
+ private getScrollParentTop() {
67
+ this.scrollParentTop ??= getOffsetTop(this.container);
68
+
69
+ return this.scrollParentTop;
70
+ }
71
+ }
@@ -0,0 +1,51 @@
1
+ import type { ScrollParent } from "./scrollParent";
2
+
3
+ import ContainerScrollParent from "./containerScrollParent";
4
+ import DocumentScrollParent from "./documentScrollParent";
5
+
6
+ const isOverflow = (overflowValue: string) =>
7
+ overflowValue === "auto" || overflowValue === "scroll";
8
+
9
+ const hasOverFlow = (element: HTMLElement): boolean => {
10
+ const style = getComputedStyle(element);
11
+
12
+ return isOverflow(style.overflowX) || isOverflow(style.overflowY);
13
+ };
14
+
15
+ const getParentWithOverflow = (
16
+ treeElement: HTMLElement,
17
+ ): HTMLElement | null => {
18
+ if (hasOverFlow(treeElement)) {
19
+ return treeElement;
20
+ }
21
+
22
+ let parent = treeElement.parentElement;
23
+
24
+ while (parent) {
25
+ if (hasOverFlow(parent)) {
26
+ return parent;
27
+ }
28
+
29
+ parent = parent.parentElement;
30
+ }
31
+
32
+ return null;
33
+ };
34
+
35
+ const createScrollParent = (
36
+ treeElement: HTMLElement,
37
+ refreshHitAreas: () => void,
38
+ ): ScrollParent => {
39
+ const container = getParentWithOverflow(treeElement);
40
+
41
+ if (container && container.tagName !== "HTML") {
42
+ return new ContainerScrollParent({
43
+ container,
44
+ refreshHitAreas,
45
+ });
46
+ } else {
47
+ return new DocumentScrollParent({ refreshHitAreas, treeElement });
48
+ }
49
+ };
50
+
51
+ export default createScrollParent;
@@ -0,0 +1,104 @@
1
+ import type {
2
+ HorizontalScrollDirection,
3
+ VerticalScrollDirection,
4
+ } from "./scrollParent";
5
+
6
+ import { getOffsetTop } from "../positionUtils";
7
+ import { ScrollParent } from "./scrollParent";
8
+
9
+ interface Params {
10
+ refreshHitAreas: () => void;
11
+ treeElement: HTMLElement;
12
+ }
13
+
14
+ export default class DocumentScrollParent extends ScrollParent {
15
+ private documentScrollHeight?: number;
16
+ private documentScrollWidth?: number;
17
+ private treeElement: HTMLElement;
18
+
19
+ constructor({ refreshHitAreas, treeElement }: Params) {
20
+ super({ container: document.documentElement, refreshHitAreas });
21
+
22
+ this.treeElement = treeElement;
23
+ }
24
+
25
+ public scrollToY(top: number): void {
26
+ const treeTop = getOffsetTop(this.treeElement);
27
+
28
+ super.scrollToY(top + treeTop);
29
+ }
30
+
31
+ public stopScrolling() {
32
+ super.stopScrolling();
33
+
34
+ this.documentScrollHeight = undefined;
35
+ this.documentScrollWidth = undefined;
36
+ }
37
+
38
+ protected getNewHorizontalScrollDirection(
39
+ pageX: number,
40
+ ): HorizontalScrollDirection | undefined {
41
+ const scrollLeft = this.container.scrollLeft;
42
+ const windowWidth = window.innerWidth;
43
+
44
+ const isNearRightEdge = pageX > windowWidth - 20;
45
+ const isNearLeftEdge = pageX - scrollLeft < 20;
46
+
47
+ if (isNearRightEdge && this.canScrollRight()) {
48
+ return "right";
49
+ }
50
+
51
+ if (isNearLeftEdge) {
52
+ return "left";
53
+ }
54
+
55
+ return undefined;
56
+ }
57
+
58
+ protected getNewVerticalScrollDirection(
59
+ pageY: number,
60
+ ): undefined | VerticalScrollDirection {
61
+ const scrollTop = this.container.scrollTop;
62
+ const distanceTop = pageY - scrollTop;
63
+
64
+ if (distanceTop < 20) {
65
+ return "top";
66
+ }
67
+
68
+ const windowHeight = window.innerHeight;
69
+
70
+ if (windowHeight - (pageY - scrollTop) < 20 && this.canScrollDown()) {
71
+ return "bottom";
72
+ }
73
+
74
+ return undefined;
75
+ }
76
+
77
+ private canScrollDown() {
78
+ return (
79
+ this.container.scrollTop + this.container.clientHeight <
80
+ this.getDocumentScrollHeight()
81
+ );
82
+ }
83
+
84
+ private canScrollRight() {
85
+ return (
86
+ this.container.scrollLeft + this.container.clientWidth <
87
+ this.getDocumentScrollWidth()
88
+ );
89
+ }
90
+
91
+ private getDocumentScrollHeight() {
92
+ // Store the original scroll height because the scroll height can increase when the drag element is moved beyond the scroll height.
93
+ this.documentScrollHeight ??= this.container.scrollHeight;
94
+
95
+ return this.documentScrollHeight;
96
+ }
97
+
98
+ private getDocumentScrollWidth() {
99
+ // Store the original scroll width because the scroll width can increase when the drag element is moved beyond the scroll width.
100
+ this.documentScrollWidth ??= this.container.scrollWidth;
101
+
102
+ return this.documentScrollWidth;
103
+ }
104
+ }
@@ -0,0 +1,117 @@
1
+ export type HorizontalScrollDirection = "left" | "right";
2
+ export type VerticalScrollDirection = "bottom" | "top";
3
+
4
+ interface ConstructorParams {
5
+ container: HTMLElement;
6
+ refreshHitAreas: () => void;
7
+ }
8
+
9
+ export abstract class ScrollParent {
10
+ protected container: HTMLElement;
11
+ protected horizontalScrollDirection?: HorizontalScrollDirection;
12
+ protected horizontalScrollTimeout?: number;
13
+
14
+ protected refreshHitAreas: () => void;
15
+ protected verticalScrollDirection?: VerticalScrollDirection;
16
+ protected verticalScrollTimeout?: number;
17
+
18
+ constructor({ container, refreshHitAreas }: ConstructorParams) {
19
+ this.container = container;
20
+ this.refreshHitAreas = refreshHitAreas;
21
+ }
22
+
23
+ public checkHorizontalScrolling(pageX: number): void {
24
+ const newHorizontalScrollDirection =
25
+ this.getNewHorizontalScrollDirection(pageX);
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
+ }
40
+ }
41
+ }
42
+
43
+ public checkVerticalScrolling(pageY: number) {
44
+ const newVerticalScrollDirection =
45
+ this.getNewVerticalScrollDirection(pageY);
46
+
47
+ if (this.verticalScrollDirection !== newVerticalScrollDirection) {
48
+ this.verticalScrollDirection = newVerticalScrollDirection;
49
+
50
+ if (this.verticalScrollTimeout != null) {
51
+ window.clearTimeout(this.verticalScrollTimeout);
52
+ this.verticalScrollTimeout = undefined;
53
+ }
54
+
55
+ if (newVerticalScrollDirection) {
56
+ this.verticalScrollTimeout = window.setTimeout(
57
+ this.scrollVertically.bind(this),
58
+ 40,
59
+ );
60
+ }
61
+ }
62
+ }
63
+
64
+ public getScrollLeft(): number {
65
+ return this.container.scrollLeft;
66
+ }
67
+
68
+ public scrollToY(top: number): void {
69
+ this.container.scrollTop = top;
70
+ }
71
+
72
+ public stopScrolling() {
73
+ this.horizontalScrollDirection = undefined;
74
+ this.verticalScrollDirection = undefined;
75
+ }
76
+
77
+ protected abstract getNewHorizontalScrollDirection(
78
+ pageX: number,
79
+ ): HorizontalScrollDirection | undefined;
80
+ protected abstract getNewVerticalScrollDirection(
81
+ pageY: number,
82
+ ): undefined | VerticalScrollDirection;
83
+
84
+ protected scrollHorizontally() {
85
+ if (!this.horizontalScrollDirection) {
86
+ return;
87
+ }
88
+
89
+ const distance = this.horizontalScrollDirection === "left" ? -20 : 20;
90
+ this.container.scrollBy({
91
+ behavior: "instant",
92
+ left: distance,
93
+ top: 0,
94
+ });
95
+
96
+ this.refreshHitAreas();
97
+
98
+ setTimeout(this.scrollHorizontally.bind(this), 40);
99
+ }
100
+
101
+ protected scrollVertically() {
102
+ if (!this.verticalScrollDirection) {
103
+ return;
104
+ }
105
+
106
+ const distance = this.verticalScrollDirection === "top" ? -20 : 20;
107
+ this.container.scrollBy({
108
+ behavior: "instant",
109
+ left: 0,
110
+ top: distance,
111
+ });
112
+
113
+ this.refreshHitAreas();
114
+
115
+ setTimeout(this.scrollVertically.bind(this), 40);
116
+ }
117
+ }