jqtree 1.7.5 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/.eslintrc +13 -3
  2. package/.github/workflows/ci.yml +6 -6
  3. package/.github/workflows/codeql-analysis.yml +4 -4
  4. package/.github/workflows/size.yml +3 -3
  5. package/.github/workflows/static.yml +1 -1
  6. package/bower.json +1 -1
  7. package/config/babel.config.json +1 -1
  8. package/config/jest.config.js +4 -0
  9. package/config/jest.polyfills.js +14 -0
  10. package/config/production +2 -0
  11. package/devserver/devserver_scroll.js +8 -0
  12. package/devserver/test_index.html +9 -0
  13. package/devserver/test_scroll.html +28 -0
  14. package/devserver/test_scroll_container.html +39 -0
  15. package/docs/.ruby-version +1 -1
  16. package/docs/_config.yml +1 -1
  17. package/docs/_entries/general/changelog.md +11 -0
  18. package/docs/_entries/multiple_selection/get-selected-nodes.md +1 -1
  19. package/docs/_entries/node/getnextnode.md +3 -6
  20. package/docs/_entries/node/getnextsibling.md +1 -1
  21. package/docs/_entries/node/getnextvisiblenode.md +8 -5
  22. package/docs/_entries/node/getpreviousnode.md +12 -0
  23. package/docs/_entries/node/getprevioussibling.md +1 -1
  24. package/docs/_entries/node/getpreviousvisiblenode.md +6 -5
  25. package/package.json +35 -29
  26. package/src/dataLoader.ts +57 -34
  27. package/src/dragAndDropHandler/dragElement.ts +54 -0
  28. package/src/dragAndDropHandler/generateHitAreas.ts +176 -0
  29. package/src/dragAndDropHandler/index.ts +454 -0
  30. package/src/dragAndDropHandler/iterateVisibleNodes.ts +91 -0
  31. package/src/dragAndDropHandler/types.ts +13 -0
  32. package/src/elementsRenderer.ts +75 -40
  33. package/src/jqtreeMethodTypes.ts +40 -0
  34. package/src/jqtreeOptions.ts +43 -25
  35. package/src/keyHandler.ts +59 -30
  36. package/src/mouseHandler.ts +385 -0
  37. package/src/mouseUtils.ts +23 -0
  38. package/src/node.ts +1 -29
  39. package/src/nodeElement/borderDropHint.ts +32 -0
  40. package/src/nodeElement/folderElement.ts +133 -0
  41. package/src/nodeElement/ghostDropHint.ts +69 -0
  42. package/src/nodeElement/index.ts +102 -0
  43. package/src/playwright/coverage.ts +4 -7
  44. package/src/playwright/playwright.test.ts +150 -53
  45. package/src/playwright/testUtils.ts +28 -5
  46. package/src/position.ts +28 -0
  47. package/src/saveStateHandler.ts +75 -26
  48. package/src/scrollHandler/containerScrollParent.ts +13 -23
  49. package/src/scrollHandler/createScrollParent.ts +22 -22
  50. package/src/scrollHandler/documentScrollParent.ts +16 -13
  51. package/src/scrollHandler.ts +13 -15
  52. package/src/selectNodeHandler.ts +10 -16
  53. package/src/test/jqTree/events.test.ts +97 -30
  54. package/src/test/jqTree/keyboard.test.ts +18 -23
  55. package/src/test/jqTree/loadOnDemand.test.ts +22 -15
  56. package/src/test/jqTree/methods.test.ts +40 -14
  57. package/src/test/jqTree/mouse.test.ts +82 -0
  58. package/src/test/jqTree/options.test.ts +24 -12
  59. package/src/test/node.test.ts +3 -2
  60. package/src/test/{nodeUtil.test.ts → position.test.ts} +1 -1
  61. package/src/tree.jquery.ts +314 -208
  62. package/src/util.ts +12 -0
  63. package/src/version.ts +1 -1
  64. package/tree.jquery.debug.js +2594 -3419
  65. package/tree.jquery.debug.js.map +1 -1
  66. package/tree.jquery.js +3 -3
  67. package/tree.jquery.js.map +1 -1
  68. package/tsconfig.json +5 -3
  69. package/docs/_entries/functions/get-selected-nodes.md +0 -10
  70. package/lib/dataLoader.js +0 -123
  71. package/lib/dragAndDropHandler.js +0 -588
  72. package/lib/elementsRenderer.js +0 -267
  73. package/lib/jqtreeOptions.js +0 -1
  74. package/lib/keyHandler.js +0 -111
  75. package/lib/mouse.widget.js +0 -255
  76. package/lib/node.js +0 -708
  77. package/lib/nodeElement.js +0 -274
  78. package/lib/nodeUtils.js +0 -10
  79. package/lib/playwright/coverage.js +0 -99
  80. package/lib/playwright/playwright.test.js +0 -606
  81. package/lib/playwright/testUtils.js +0 -210
  82. package/lib/saveStateHandler.js +0 -277
  83. package/lib/scrollHandler/containerScrollParent.js +0 -160
  84. package/lib/scrollHandler/createScrollParent.js +0 -57
  85. package/lib/scrollHandler/documentScrollParent.js +0 -169
  86. package/lib/scrollHandler/scrollParent.js +0 -58
  87. package/lib/scrollHandler/types.js +0 -1
  88. package/lib/scrollHandler.js +0 -71
  89. package/lib/selectNodeHandler.js +0 -128
  90. package/lib/simple.widget.js +0 -158
  91. package/lib/test/global.d.js +0 -3
  92. package/lib/test/jqTree/accessibility.test.js +0 -37
  93. package/lib/test/jqTree/create.test.js +0 -48
  94. package/lib/test/jqTree/events.test.js +0 -210
  95. package/lib/test/jqTree/keyboard.test.js +0 -225
  96. package/lib/test/jqTree/loadOnDemand.test.js +0 -218
  97. package/lib/test/jqTree/methods.test.js +0 -1348
  98. package/lib/test/jqTree/options.test.js +0 -548
  99. package/lib/test/jqTree/scrollHandler/containerScrollParent.test.js +0 -94
  100. package/lib/test/node.test.js +0 -1202
  101. package/lib/test/nodeUtil.test.js +0 -27
  102. package/lib/test/nodeUtils.test.js +0 -20
  103. package/lib/test/support/exampleData.js +0 -35
  104. package/lib/test/support/jqTreeMatchers.js +0 -70
  105. package/lib/test/support/matchers.d.js +0 -1
  106. package/lib/test/support/setupTests.js +0 -7
  107. package/lib/test/support/testUtil.js +0 -29
  108. package/lib/test/support/treeStructure.js +0 -38
  109. package/lib/test/util.test.js +0 -26
  110. package/lib/tree.jquery.d.js +0 -1
  111. package/lib/tree.jquery.js +0 -1105
  112. package/lib/types.js +0 -1
  113. package/lib/typings.d.js +0 -2
  114. package/lib/util.js +0 -15
  115. package/lib/version.js +0 -8
  116. package/src/dragAndDropHandler.ts +0 -713
  117. package/src/mouse.widget.ts +0 -266
  118. package/src/nodeElement.ts +0 -272
  119. package/src/types.ts +0 -19
@@ -0,0 +1,54 @@
1
+ interface DragElementParams {
2
+ autoEscape: boolean;
3
+ nodeName: string;
4
+ offsetX: number;
5
+ offsetY: number;
6
+ treeElement: HTMLElement;
7
+ }
8
+
9
+ class DragElement {
10
+ private offsetX: number;
11
+ private offsetY: number;
12
+ private element: HTMLElement;
13
+
14
+ constructor({
15
+ autoEscape,
16
+ nodeName,
17
+ offsetX,
18
+ offsetY,
19
+ treeElement,
20
+ }: DragElementParams) {
21
+ this.offsetX = offsetX;
22
+ this.offsetY = offsetY;
23
+
24
+ this.element = this.createElement(nodeName, autoEscape);
25
+
26
+ treeElement.appendChild(this.element);
27
+ }
28
+
29
+ public move(pageX: number, pageY: number): void {
30
+ this.element.style.left = `${pageX - this.offsetX}px`;
31
+ this.element.style.top = `${pageY - this.offsetY}px`;
32
+ }
33
+
34
+ public remove(): void {
35
+ this.element.remove();
36
+ }
37
+
38
+ private createElement(nodeName: string, autoEscape: boolean) {
39
+ const element = document.createElement("span");
40
+ element.classList.add("jqtree-title", "jqtree-dragging");
41
+
42
+ if (autoEscape) {
43
+ element.textContent = nodeName;
44
+ } else {
45
+ element.innerHTML = nodeName;
46
+ }
47
+
48
+ element.style.position = "absolute";
49
+
50
+ return element;
51
+ }
52
+ }
53
+
54
+ export default DragElement;
@@ -0,0 +1,176 @@
1
+ import { HitArea } from "./types";
2
+ import { Node } from "../node";
3
+ import { Position } from "../position";
4
+ import { getOffsetTop } from "../util";
5
+ import iterateVisibleNodes from "./iterateVisibleNodes";
6
+
7
+ const generatePositions = (tree: Node, currentNode: Node): HitArea[] => {
8
+ const positions: HitArea[] = [];
9
+ let lastTop = 0;
10
+
11
+ const addPosition = (node: Node, position: number, top: number) => {
12
+ const area = {
13
+ top,
14
+ bottom: 0,
15
+ node,
16
+ position,
17
+ };
18
+
19
+ positions.push(area);
20
+ lastTop = top;
21
+ };
22
+
23
+ const handleAfterOpenFolder = (node: Node, nextNode: Node | null) => {
24
+ if (node === currentNode || nextNode === currentNode) {
25
+ // Cannot move before or after current item
26
+ addPosition(node, Position.None, lastTop);
27
+ } else {
28
+ addPosition(node, Position.After, lastTop);
29
+ }
30
+ };
31
+
32
+ const handleClosedFolder = (
33
+ node: Node,
34
+ nextNode: Node | null,
35
+ element: HTMLElement,
36
+ ) => {
37
+ const top = getOffsetTop(element);
38
+
39
+ if (node === currentNode) {
40
+ // Cannot move after current item
41
+ addPosition(node, Position.None, top);
42
+ } else {
43
+ addPosition(node, Position.Inside, top);
44
+
45
+ // Cannot move before current item
46
+ if (nextNode !== currentNode) {
47
+ addPosition(node, Position.After, top);
48
+ }
49
+ }
50
+ };
51
+
52
+ const handleFirstNode = (node: Node) => {
53
+ if (node !== currentNode) {
54
+ addPosition(node, Position.Before, getOffsetTop(node.element));
55
+ }
56
+ };
57
+
58
+ const handleNode = (
59
+ node: Node,
60
+ nextNode: Node | null,
61
+ element: HTMLElement,
62
+ ) => {
63
+ const top = getOffsetTop(element);
64
+
65
+ if (node === currentNode) {
66
+ // Cannot move inside current item
67
+ addPosition(node, Position.None, top);
68
+ } else {
69
+ addPosition(node, Position.Inside, top);
70
+ }
71
+
72
+ if (nextNode === currentNode || node === currentNode) {
73
+ // Cannot move before or after current item
74
+ addPosition(node, Position.None, top);
75
+ } else {
76
+ addPosition(node, Position.After, top);
77
+ }
78
+ };
79
+
80
+ const handleOpenFolder = (node: Node, element: HTMLElement) => {
81
+ if (node === currentNode) {
82
+ // Cannot move inside current item
83
+ // Stop iterating
84
+ return false;
85
+ }
86
+
87
+ // Cannot move before current item
88
+ if (node.children[0] !== currentNode) {
89
+ addPosition(node, Position.Inside, getOffsetTop(element));
90
+ }
91
+
92
+ // Continue iterating
93
+ return true;
94
+ };
95
+
96
+ iterateVisibleNodes(tree, {
97
+ handleAfterOpenFolder,
98
+ handleClosedFolder,
99
+ handleFirstNode,
100
+ handleNode,
101
+ handleOpenFolder,
102
+ });
103
+
104
+ return positions;
105
+ };
106
+
107
+ const generateHitAreasForGroup = (
108
+ hitAreas: HitArea[],
109
+ positionsInGroup: HitArea[],
110
+ top: number,
111
+ bottom: number,
112
+ ) => {
113
+ // limit positions in group
114
+ const positionCount = Math.min(positionsInGroup.length, 4);
115
+
116
+ const areaHeight = Math.round((bottom - top) / positionCount);
117
+ let areaTop = top;
118
+
119
+ let i = 0;
120
+ while (i < positionCount) {
121
+ const position = positionsInGroup[i];
122
+
123
+ if (position) {
124
+ hitAreas.push({
125
+ top: areaTop,
126
+ bottom: areaTop + areaHeight,
127
+ node: position.node,
128
+ position: position.position,
129
+ });
130
+ }
131
+
132
+ areaTop += areaHeight;
133
+ i += 1;
134
+ }
135
+ };
136
+
137
+ const generateHitAreasFromPositions = (
138
+ positions: HitArea[],
139
+ treeBottom: number,
140
+ ): HitArea[] => {
141
+ let previousTop = positions[0]?.top ?? 0;
142
+ let group = [];
143
+ const hitAreas: HitArea[] = [];
144
+
145
+ for (const position of positions) {
146
+ if (position.top !== previousTop && group.length) {
147
+ generateHitAreasForGroup(
148
+ hitAreas,
149
+ group,
150
+ previousTop,
151
+ position.top,
152
+ );
153
+
154
+ previousTop = position.top;
155
+ group = [];
156
+ }
157
+
158
+ group.push(position);
159
+ }
160
+
161
+ generateHitAreasForGroup(hitAreas, group, previousTop, treeBottom);
162
+
163
+ return hitAreas;
164
+ };
165
+
166
+ const generateHitAreas = (
167
+ tree: Node,
168
+ currentNode: Node,
169
+ treeBottom: number,
170
+ ) => {
171
+ const positions = generatePositions(tree, currentNode);
172
+
173
+ return generateHitAreasFromPositions(positions, treeBottom);
174
+ };
175
+
176
+ export default generateHitAreas;
@@ -0,0 +1,454 @@
1
+ import { Node } from "../node";
2
+ import { getPositionName, Position } from "../position";
3
+ import { DropHint, HitArea } from "./types";
4
+ import { PositionInfo } from "../mouseUtils";
5
+ import NodeElement from "../nodeElement";
6
+ import DragElement from "./dragElement";
7
+ import generateHitAreas from "./generateHitAreas";
8
+ import { getElementPosition } from "../util";
9
+ import {
10
+ OnCanMove,
11
+ OnCanMoveTo,
12
+ OnIsMoveHandle,
13
+ DragMethod,
14
+ } from "../jqtreeOptions";
15
+ import {
16
+ GetScrollLeft,
17
+ GetTree,
18
+ OpenNode,
19
+ RefreshElements,
20
+ TriggerEvent,
21
+ } from "../jqtreeMethodTypes";
22
+
23
+ interface Dimensions {
24
+ left: number;
25
+ top: number;
26
+ right: number;
27
+ bottom: number;
28
+ }
29
+
30
+ type GetNodeElement = (element: HTMLElement) => NodeElement | null;
31
+ type GetNodeElementForNode = (node: Node) => NodeElement;
32
+
33
+ interface DragAndDropHandlerParams {
34
+ autoEscape?: boolean;
35
+ getNodeElement: GetNodeElement;
36
+ getNodeElementForNode: GetNodeElementForNode;
37
+ getScrollLeft: GetScrollLeft;
38
+ getTree: GetTree;
39
+ onCanMove?: OnCanMove;
40
+ onCanMoveTo?: OnCanMoveTo;
41
+ onDragMove?: DragMethod;
42
+ onDragStop?: DragMethod;
43
+ onIsMoveHandle?: OnIsMoveHandle;
44
+ openFolderDelay: number | false;
45
+ openNode: OpenNode;
46
+ refreshElements: RefreshElements;
47
+ slide: boolean;
48
+ treeElement: HTMLElement;
49
+ triggerEvent: TriggerEvent;
50
+ }
51
+
52
+ export class DragAndDropHandler {
53
+ public hitAreas: HitArea[];
54
+ public isDragging: boolean;
55
+ public currentItem: NodeElement | null;
56
+ public hoveredArea: HitArea | null;
57
+
58
+ private autoEscape?: boolean;
59
+ private dragElement: DragElement | null;
60
+ private getNodeElement: GetNodeElement;
61
+ private getNodeElementForNode: GetNodeElementForNode;
62
+ private getScrollLeft: GetScrollLeft;
63
+ private getTree: GetTree;
64
+ private onCanMove?: OnCanMove;
65
+ private onCanMoveTo?: OnCanMoveTo;
66
+ private onDragMove?: DragMethod;
67
+ private onDragStop?: DragMethod;
68
+ private onIsMoveHandle?: OnIsMoveHandle;
69
+ private openFolderDelay: number | false;
70
+ private openFolderTimer: number | null;
71
+ private openNode: OpenNode;
72
+ private previousGhost: DropHint | null;
73
+ private refreshElements: RefreshElements;
74
+ private slide: boolean;
75
+ private treeElement: HTMLElement;
76
+ private triggerEvent: TriggerEvent;
77
+
78
+ constructor({
79
+ autoEscape,
80
+ getNodeElement,
81
+ getNodeElementForNode,
82
+ getScrollLeft,
83
+ getTree,
84
+ onCanMove,
85
+ onCanMoveTo,
86
+ onDragMove,
87
+ onDragStop,
88
+ onIsMoveHandle,
89
+ openNode,
90
+ refreshElements,
91
+ slide,
92
+ treeElement,
93
+ triggerEvent,
94
+ }: DragAndDropHandlerParams) {
95
+ this.autoEscape = autoEscape;
96
+ this.getNodeElement = getNodeElement;
97
+ this.getNodeElementForNode = getNodeElementForNode;
98
+ this.getScrollLeft = getScrollLeft;
99
+ this.getTree = getTree;
100
+ this.onCanMove = onCanMove;
101
+ this.onCanMoveTo = onCanMoveTo;
102
+ this.onDragMove = onDragMove;
103
+ this.onDragStop = onDragStop;
104
+ this.onIsMoveHandle = onIsMoveHandle;
105
+ this.openNode = openNode;
106
+ this.refreshElements = refreshElements;
107
+ this.slide = slide;
108
+ this.treeElement = treeElement;
109
+ this.triggerEvent = triggerEvent;
110
+
111
+ this.hoveredArea = null;
112
+ this.hitAreas = [];
113
+ this.isDragging = false;
114
+ this.currentItem = null;
115
+ }
116
+
117
+ public mouseCapture(positionInfo: PositionInfo): boolean | null {
118
+ const element = positionInfo.target;
119
+
120
+ if (!this.mustCaptureElement(element)) {
121
+ return null;
122
+ }
123
+
124
+ if (this.onIsMoveHandle && !this.onIsMoveHandle(jQuery(element))) {
125
+ return null;
126
+ }
127
+
128
+ let nodeElement = this.getNodeElement(element);
129
+
130
+ if (nodeElement && this.onCanMove) {
131
+ if (!this.onCanMove(nodeElement.node)) {
132
+ nodeElement = null;
133
+ }
134
+ }
135
+
136
+ this.currentItem = nodeElement;
137
+ return this.currentItem != null;
138
+ }
139
+
140
+ public mouseStart(positionInfo: PositionInfo): boolean {
141
+ if (!this.currentItem) {
142
+ return false;
143
+ }
144
+
145
+ this.refresh();
146
+
147
+ const { left, top } = getElementPosition(positionInfo.target);
148
+
149
+ const node = this.currentItem.node;
150
+
151
+ this.dragElement = new DragElement({
152
+ autoEscape: this.autoEscape ?? true,
153
+ nodeName: node.name,
154
+ offsetX: positionInfo.pageX - left,
155
+ offsetY: positionInfo.pageY - top,
156
+ treeElement: this.treeElement,
157
+ });
158
+
159
+ this.isDragging = true;
160
+ this.currentItem.element.classList.add("jqtree-moving");
161
+
162
+ return true;
163
+ }
164
+
165
+ public mouseDrag(positionInfo: PositionInfo): boolean {
166
+ if (!this.currentItem || !this.dragElement) {
167
+ return false;
168
+ }
169
+
170
+ this.dragElement.move(positionInfo.pageX, positionInfo.pageY);
171
+
172
+ const area = this.findHoveredArea(
173
+ positionInfo.pageX,
174
+ positionInfo.pageY,
175
+ );
176
+
177
+ if (area && this.canMoveToArea(area)) {
178
+ if (!area.node.isFolder()) {
179
+ this.stopOpenFolderTimer();
180
+ }
181
+
182
+ if (this.hoveredArea !== area) {
183
+ this.hoveredArea = area;
184
+
185
+ // If this is a closed folder, start timer to open it
186
+ if (this.mustOpenFolderTimer(area)) {
187
+ this.startOpenFolderTimer(area.node);
188
+ } else {
189
+ this.stopOpenFolderTimer();
190
+ }
191
+
192
+ this.updateDropHint();
193
+ }
194
+ } else {
195
+ this.removeDropHint();
196
+ this.stopOpenFolderTimer();
197
+ this.hoveredArea = area;
198
+ }
199
+
200
+ if (!area) {
201
+ if (this.onDragMove) {
202
+ this.onDragMove(
203
+ this.currentItem.node,
204
+ positionInfo.originalEvent,
205
+ );
206
+ }
207
+ }
208
+
209
+ return true;
210
+ }
211
+
212
+ public mouseStop(positionInfo: PositionInfo): boolean {
213
+ this.moveItem(positionInfo);
214
+ this.clear();
215
+ this.removeHover();
216
+ this.removeDropHint();
217
+ this.removeHitAreas();
218
+
219
+ const currentItem = this.currentItem;
220
+
221
+ if (this.currentItem) {
222
+ this.currentItem.element.classList.remove("jqtree-moving");
223
+ this.currentItem = null;
224
+ }
225
+
226
+ this.isDragging = false;
227
+
228
+ if (!this.hoveredArea && currentItem) {
229
+ if (this.onDragStop) {
230
+ this.onDragStop(currentItem.node, positionInfo.originalEvent);
231
+ }
232
+ }
233
+
234
+ return false;
235
+ }
236
+
237
+ public refresh(): void {
238
+ this.removeHitAreas();
239
+
240
+ if (this.currentItem) {
241
+ this.generateHitAreas();
242
+
243
+ this.currentItem = this.getNodeElementForNode(
244
+ this.currentItem.node,
245
+ );
246
+
247
+ if (this.isDragging) {
248
+ this.currentItem.element.classList.add("jqtree-moving");
249
+ }
250
+ }
251
+ }
252
+
253
+ private generateHitAreas(): void {
254
+ const tree = this.getTree();
255
+
256
+ if (!this.currentItem || !tree) {
257
+ this.hitAreas = [];
258
+ } else {
259
+ this.hitAreas = generateHitAreas(
260
+ tree,
261
+ this.currentItem.node,
262
+ this.getTreeDimensions().bottom,
263
+ );
264
+ }
265
+ }
266
+
267
+ private mustCaptureElement(element: HTMLElement): boolean {
268
+ const nodeName = element.nodeName;
269
+
270
+ return (
271
+ nodeName !== "INPUT" &&
272
+ nodeName !== "SELECT" &&
273
+ nodeName !== "TEXTAREA"
274
+ );
275
+ }
276
+
277
+ private canMoveToArea(area: HitArea): boolean {
278
+ if (!this.onCanMoveTo) {
279
+ return true;
280
+ }
281
+
282
+ if (!this.currentItem) {
283
+ return false;
284
+ }
285
+
286
+ const positionName = getPositionName(area.position);
287
+
288
+ return this.onCanMoveTo(this.currentItem.node, area.node, positionName);
289
+ }
290
+
291
+ private removeHitAreas(): void {
292
+ this.hitAreas = [];
293
+ }
294
+
295
+ private clear(): void {
296
+ if (this.dragElement) {
297
+ this.dragElement.remove();
298
+ this.dragElement = null;
299
+ }
300
+ }
301
+
302
+ private removeDropHint(): void {
303
+ if (this.previousGhost) {
304
+ this.previousGhost.remove();
305
+ }
306
+ }
307
+
308
+ private removeHover(): void {
309
+ this.hoveredArea = null;
310
+ }
311
+
312
+ private findHoveredArea(x: number, y: number): HitArea | null {
313
+ const dimensions = this.getTreeDimensions();
314
+
315
+ if (
316
+ x < dimensions.left ||
317
+ y < dimensions.top ||
318
+ x > dimensions.right ||
319
+ y > dimensions.bottom
320
+ ) {
321
+ return null;
322
+ }
323
+
324
+ let low = 0;
325
+ let high = this.hitAreas.length;
326
+ while (low < high) {
327
+ const mid = (low + high) >> 1;
328
+ const area = this.hitAreas[mid];
329
+
330
+ if (!area) {
331
+ return null;
332
+ }
333
+
334
+ if (y < area.top) {
335
+ high = mid;
336
+ } else if (y > area.bottom) {
337
+ low = mid + 1;
338
+ } else {
339
+ return area;
340
+ }
341
+ }
342
+
343
+ return null;
344
+ }
345
+
346
+ private mustOpenFolderTimer(area: HitArea): boolean {
347
+ const node = area.node;
348
+
349
+ return (
350
+ node.isFolder() &&
351
+ !node.is_open &&
352
+ area.position === Position.Inside
353
+ );
354
+ }
355
+
356
+ private updateDropHint(): void {
357
+ if (!this.hoveredArea) {
358
+ return;
359
+ }
360
+
361
+ // remove previous drop hint
362
+ this.removeDropHint();
363
+
364
+ // add new drop hint
365
+ const nodeElement = this.getNodeElementForNode(this.hoveredArea.node);
366
+ this.previousGhost = nodeElement.addDropHint(this.hoveredArea.position);
367
+ }
368
+
369
+ private startOpenFolderTimer(folder: Node): void {
370
+ const openFolder = (): void => {
371
+ this.openNode(folder, this.slide, () => {
372
+ this.refresh();
373
+ this.updateDropHint();
374
+ });
375
+ };
376
+
377
+ this.stopOpenFolderTimer();
378
+
379
+ const openFolderDelay = this.openFolderDelay;
380
+
381
+ if (openFolderDelay !== false) {
382
+ this.openFolderTimer = window.setTimeout(
383
+ openFolder,
384
+ openFolderDelay,
385
+ );
386
+ }
387
+ }
388
+
389
+ private stopOpenFolderTimer(): void {
390
+ if (this.openFolderTimer) {
391
+ clearTimeout(this.openFolderTimer);
392
+ this.openFolderTimer = null;
393
+ }
394
+ }
395
+
396
+ private moveItem(positionInfo: PositionInfo): void {
397
+ if (
398
+ this.currentItem &&
399
+ this.hoveredArea &&
400
+ this.hoveredArea.position !== Position.None &&
401
+ this.canMoveToArea(this.hoveredArea)
402
+ ) {
403
+ const movedNode = this.currentItem.node;
404
+ const targetNode = this.hoveredArea.node;
405
+ const position = this.hoveredArea.position;
406
+ const previousParent = movedNode.parent;
407
+
408
+ if (position === Position.Inside) {
409
+ this.hoveredArea.node.is_open = true;
410
+ }
411
+
412
+ const doMove = (): void => {
413
+ const tree = this.getTree();
414
+
415
+ if (tree) {
416
+ tree.moveNode(movedNode, targetNode, position);
417
+
418
+ this.treeElement.textContent = "";
419
+ this.refreshElements(null);
420
+ }
421
+ };
422
+
423
+ const event = this.triggerEvent("tree.move", {
424
+ move_info: {
425
+ moved_node: movedNode,
426
+ target_node: targetNode,
427
+ position: getPositionName(position),
428
+ previous_parent: previousParent,
429
+ do_move: doMove,
430
+ original_event: positionInfo.originalEvent,
431
+ },
432
+ });
433
+
434
+ if (!event.isDefaultPrevented()) {
435
+ doMove();
436
+ }
437
+ }
438
+ }
439
+
440
+ private getTreeDimensions(): Dimensions {
441
+ // Return the dimensions of the tree. Add a margin to the bottom to allow
442
+ // to drag-and-drop after the last element.
443
+ const treePosition = getElementPosition(this.treeElement);
444
+ const left = treePosition.left + this.getScrollLeft();
445
+ const top = treePosition.top;
446
+
447
+ return {
448
+ left,
449
+ top,
450
+ right: left + this.treeElement.clientWidth,
451
+ bottom: top + this.treeElement.clientHeight + 16,
452
+ };
453
+ }
454
+ }