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