jqtree 1.8.0 → 1.8.2
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.
- package/.eslintrc +13 -3
- package/.github/workflows/ci.yml +6 -6
- package/.github/workflows/codeql-analysis.yml +4 -4
- package/.github/workflows/size.yml +3 -3
- package/.github/workflows/static.yml +1 -1
- package/bower.json +1 -1
- package/config/jest.config.js +4 -0
- package/config/jest.polyfills.js +14 -0
- package/devserver/test_index.html +9 -0
- package/docs/.ruby-version +1 -1
- package/docs/_config.yml +1 -1
- package/docs/_entries/general/changelog.md +8 -0
- package/docs/_entries/multiple_selection/get-selected-nodes.md +1 -1
- package/docs/_entries/node/getnextnode.md +3 -6
- package/docs/_entries/node/getnextsibling.md +1 -1
- package/docs/_entries/node/getnextvisiblenode.md +8 -5
- package/docs/_entries/node/getpreviousnode.md +12 -0
- package/docs/_entries/node/getprevioussibling.md +1 -1
- package/docs/_entries/node/getpreviousvisiblenode.md +6 -5
- package/package.json +32 -30
- package/src/dataLoader.ts +19 -21
- package/src/dragAndDropHandler/dragElement.ts +37 -25
- package/src/dragAndDropHandler/generateHitAreas.ts +187 -0
- package/src/dragAndDropHandler/index.ts +32 -48
- package/src/dragAndDropHandler/iterateVisibleNodes.ts +91 -0
- package/src/dragAndDropHandler/types.ts +2 -1
- package/src/mouseHandler.ts +385 -0
- package/src/mouseUtils.ts +23 -0
- package/src/node.ts +1 -29
- package/src/nodeElement/folderElement.ts +1 -1
- package/src/nodeElement/ghostDropHint.ts +2 -1
- package/src/nodeElement/index.ts +2 -1
- package/src/playwright/coverage.ts +3 -3
- package/src/playwright/playwright.test.ts +150 -49
- package/src/playwright/testUtils.ts +28 -5
- package/src/position.ts +28 -0
- package/src/scrollHandler/containerScrollParent.ts +13 -23
- package/src/scrollHandler/createScrollParent.ts +22 -22
- package/src/scrollHandler/documentScrollParent.ts +16 -13
- package/src/scrollHandler.ts +6 -14
- package/src/test/jqTree/events.test.ts +97 -30
- package/src/test/jqTree/loadOnDemand.test.ts +22 -15
- package/src/test/jqTree/methods.test.ts +8 -11
- package/src/test/jqTree/mouse.test.ts +82 -0
- package/src/test/jqTree/options.test.ts +9 -8
- package/src/test/node.test.ts +2 -1
- package/src/test/{nodeUtil.test.ts → position.test.ts} +1 -1
- package/src/tree.jquery.ts +108 -184
- package/src/util.ts +10 -1
- package/src/version.ts +1 -1
- package/tree.jquery.debug.js +2167 -2134
- package/tree.jquery.debug.js.map +1 -1
- package/tree.jquery.js +3 -3
- package/tree.jquery.js.map +1 -1
- package/tsconfig.json +5 -3
- package/docs/_entries/functions/get-selected-nodes.md +0 -10
- package/src/dragAndDropHandler/hitAreasGenerator.ts +0 -175
- package/src/dragAndDropHandler/visibleNodeIterator.ts +0 -97
- package/src/mouse.widget.ts +0 -266
- package/src/mouseWidgetTypes.ts +0 -6
|
@@ -0,0 +1,187 @@
|
|
|
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
|
+
|
|
84
|
+
// Dnd over the current element is not possible: add a position with type None for the top and the bottom.
|
|
85
|
+
const top = getOffsetTop(element);
|
|
86
|
+
const height = element.clientHeight;
|
|
87
|
+
addPosition(node, Position.None, top);
|
|
88
|
+
|
|
89
|
+
if (height > 5) {
|
|
90
|
+
// Subtract 5 pixels to allow more space for the next element.
|
|
91
|
+
addPosition(node, Position.None, top + height - 5);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Stop iterating
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Cannot move before current item
|
|
99
|
+
if (node.children[0] !== currentNode) {
|
|
100
|
+
addPosition(node, Position.Inside, getOffsetTop(element));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Continue iterating
|
|
104
|
+
return true;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
iterateVisibleNodes(tree, {
|
|
108
|
+
handleAfterOpenFolder,
|
|
109
|
+
handleClosedFolder,
|
|
110
|
+
handleFirstNode,
|
|
111
|
+
handleNode,
|
|
112
|
+
handleOpenFolder,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return positions;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const generateHitAreasForGroup = (
|
|
119
|
+
hitAreas: HitArea[],
|
|
120
|
+
positionsInGroup: HitArea[],
|
|
121
|
+
top: number,
|
|
122
|
+
bottom: number,
|
|
123
|
+
) => {
|
|
124
|
+
// limit positions in group
|
|
125
|
+
const positionCount = Math.min(positionsInGroup.length, 4);
|
|
126
|
+
|
|
127
|
+
const areaHeight = Math.round((bottom - top) / positionCount);
|
|
128
|
+
let areaTop = top;
|
|
129
|
+
|
|
130
|
+
let i = 0;
|
|
131
|
+
while (i < positionCount) {
|
|
132
|
+
const position = positionsInGroup[i];
|
|
133
|
+
|
|
134
|
+
if (position && position.position !== Position.None) {
|
|
135
|
+
hitAreas.push({
|
|
136
|
+
top: areaTop,
|
|
137
|
+
bottom: areaTop + areaHeight,
|
|
138
|
+
node: position.node,
|
|
139
|
+
position: position.position,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
areaTop += areaHeight;
|
|
144
|
+
i += 1;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const generateHitAreasFromPositions = (
|
|
149
|
+
positions: HitArea[],
|
|
150
|
+
treeBottom: number,
|
|
151
|
+
): HitArea[] => {
|
|
152
|
+
let previousTop = positions[0]?.top ?? 0;
|
|
153
|
+
let group = [];
|
|
154
|
+
const hitAreas: HitArea[] = [];
|
|
155
|
+
|
|
156
|
+
for (const position of positions) {
|
|
157
|
+
if (position.top !== previousTop && group.length) {
|
|
158
|
+
generateHitAreasForGroup(
|
|
159
|
+
hitAreas,
|
|
160
|
+
group,
|
|
161
|
+
previousTop,
|
|
162
|
+
position.top,
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
previousTop = position.top;
|
|
166
|
+
group = [];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
group.push(position);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
generateHitAreasForGroup(hitAreas, group, previousTop, treeBottom);
|
|
173
|
+
|
|
174
|
+
return hitAreas;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const generateHitAreas = (
|
|
178
|
+
tree: Node,
|
|
179
|
+
currentNode: Node,
|
|
180
|
+
treeBottom: number,
|
|
181
|
+
) => {
|
|
182
|
+
const positions = generatePositions(tree, currentNode);
|
|
183
|
+
|
|
184
|
+
return generateHitAreasFromPositions(positions, treeBottom);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export default generateHitAreas;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Node } from "../node";
|
|
2
|
+
import { getPositionName, Position } from "../position";
|
|
2
3
|
import { DropHint, HitArea } from "./types";
|
|
3
|
-
import { PositionInfo } from "../
|
|
4
|
+
import { PositionInfo } from "../mouseUtils";
|
|
4
5
|
import NodeElement from "../nodeElement";
|
|
5
6
|
import DragElement from "./dragElement";
|
|
6
|
-
import
|
|
7
|
+
import generateHitAreas from "./generateHitAreas";
|
|
8
|
+
import { getElementPosition } from "../util";
|
|
7
9
|
import {
|
|
8
10
|
OnCanMove,
|
|
9
11
|
OnCanMoveTo,
|
|
@@ -43,7 +45,7 @@ interface DragAndDropHandlerParams {
|
|
|
43
45
|
openNode: OpenNode;
|
|
44
46
|
refreshElements: RefreshElements;
|
|
45
47
|
slide: boolean;
|
|
46
|
-
|
|
48
|
+
treeElement: HTMLElement;
|
|
47
49
|
triggerEvent: TriggerEvent;
|
|
48
50
|
}
|
|
49
51
|
|
|
@@ -70,7 +72,7 @@ export class DragAndDropHandler {
|
|
|
70
72
|
private previousGhost: DropHint | null;
|
|
71
73
|
private refreshElements: RefreshElements;
|
|
72
74
|
private slide: boolean;
|
|
73
|
-
private
|
|
75
|
+
private treeElement: HTMLElement;
|
|
74
76
|
private triggerEvent: TriggerEvent;
|
|
75
77
|
|
|
76
78
|
constructor({
|
|
@@ -87,7 +89,7 @@ export class DragAndDropHandler {
|
|
|
87
89
|
openNode,
|
|
88
90
|
refreshElements,
|
|
89
91
|
slide,
|
|
90
|
-
|
|
92
|
+
treeElement,
|
|
91
93
|
triggerEvent,
|
|
92
94
|
}: DragAndDropHandlerParams) {
|
|
93
95
|
this.autoEscape = autoEscape;
|
|
@@ -103,7 +105,7 @@ export class DragAndDropHandler {
|
|
|
103
105
|
this.openNode = openNode;
|
|
104
106
|
this.refreshElements = refreshElements;
|
|
105
107
|
this.slide = slide;
|
|
106
|
-
this
|
|
108
|
+
this.treeElement = treeElement;
|
|
107
109
|
this.triggerEvent = triggerEvent;
|
|
108
110
|
|
|
109
111
|
this.hoveredArea = null;
|
|
@@ -136,29 +138,23 @@ export class DragAndDropHandler {
|
|
|
136
138
|
}
|
|
137
139
|
|
|
138
140
|
public mouseStart(positionInfo: PositionInfo): boolean {
|
|
139
|
-
if (
|
|
140
|
-
!this.currentItem ||
|
|
141
|
-
positionInfo.pageX === undefined ||
|
|
142
|
-
positionInfo.pageY === undefined
|
|
143
|
-
) {
|
|
141
|
+
if (!this.currentItem) {
|
|
144
142
|
return false;
|
|
145
143
|
}
|
|
146
144
|
|
|
147
145
|
this.refresh();
|
|
148
146
|
|
|
149
|
-
const
|
|
150
|
-
const left = offset ? offset.left : 0;
|
|
151
|
-
const top = offset ? offset.top : 0;
|
|
147
|
+
const { left, top } = getElementPosition(positionInfo.target);
|
|
152
148
|
|
|
153
149
|
const node = this.currentItem.node;
|
|
154
150
|
|
|
155
|
-
this.dragElement = new DragElement(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
positionInfo.
|
|
159
|
-
|
|
160
|
-
this.
|
|
161
|
-
);
|
|
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
|
+
});
|
|
162
158
|
|
|
163
159
|
this.isDragging = true;
|
|
164
160
|
this.currentItem.element.classList.add("jqtree-moving");
|
|
@@ -167,12 +163,7 @@ export class DragAndDropHandler {
|
|
|
167
163
|
}
|
|
168
164
|
|
|
169
165
|
public mouseDrag(positionInfo: PositionInfo): boolean {
|
|
170
|
-
if (
|
|
171
|
-
!this.currentItem ||
|
|
172
|
-
!this.dragElement ||
|
|
173
|
-
positionInfo.pageX === undefined ||
|
|
174
|
-
positionInfo.pageY === undefined
|
|
175
|
-
) {
|
|
166
|
+
if (!this.currentItem || !this.dragElement) {
|
|
176
167
|
return false;
|
|
177
168
|
}
|
|
178
169
|
|
|
@@ -265,12 +256,11 @@ export class DragAndDropHandler {
|
|
|
265
256
|
if (!this.currentItem || !tree) {
|
|
266
257
|
this.hitAreas = [];
|
|
267
258
|
} else {
|
|
268
|
-
|
|
259
|
+
this.hitAreas = generateHitAreas(
|
|
269
260
|
tree,
|
|
270
261
|
this.currentItem.node,
|
|
271
262
|
this.getTreeDimensions().bottom,
|
|
272
263
|
);
|
|
273
|
-
this.hitAreas = hitAreasGenerator.generate();
|
|
274
264
|
}
|
|
275
265
|
}
|
|
276
266
|
|
|
@@ -424,7 +414,8 @@ export class DragAndDropHandler {
|
|
|
424
414
|
|
|
425
415
|
if (tree) {
|
|
426
416
|
tree.moveNode(movedNode, targetNode, position);
|
|
427
|
-
|
|
417
|
+
|
|
418
|
+
this.treeElement.textContent = "";
|
|
428
419
|
this.refreshElements(null);
|
|
429
420
|
}
|
|
430
421
|
};
|
|
@@ -449,22 +440,15 @@ export class DragAndDropHandler {
|
|
|
449
440
|
private getTreeDimensions(): Dimensions {
|
|
450
441
|
// Return the dimensions of the tree. Add a margin to the bottom to allow
|
|
451
442
|
// to drag-and-drop after the last element.
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
return {
|
|
463
|
-
left,
|
|
464
|
-
top: offset.top,
|
|
465
|
-
right: left + width,
|
|
466
|
-
bottom: offset.top + height + 16,
|
|
467
|
-
};
|
|
468
|
-
}
|
|
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
|
+
};
|
|
469
453
|
}
|
|
470
454
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Node } from "../node";
|
|
2
|
+
|
|
3
|
+
interface Options {
|
|
4
|
+
handleAfterOpenFolder: (node: Node, nextNode: Node | null) => void;
|
|
5
|
+
handleClosedFolder: (
|
|
6
|
+
node: Node,
|
|
7
|
+
nextNode: Node | null,
|
|
8
|
+
element: HTMLElement,
|
|
9
|
+
) => void;
|
|
10
|
+
handleFirstNode: (node: Node) => void;
|
|
11
|
+
handleNode: (
|
|
12
|
+
node: Node,
|
|
13
|
+
nextNode: Node | null,
|
|
14
|
+
element: HTMLElement,
|
|
15
|
+
) => void;
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
override
|
|
19
|
+
return
|
|
20
|
+
- true: continue iterating
|
|
21
|
+
- false: stop iterating
|
|
22
|
+
*/
|
|
23
|
+
handleOpenFolder: (node: Node, element: HTMLElement) => boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const iterateVisibleNodes = (
|
|
27
|
+
tree: Node,
|
|
28
|
+
{
|
|
29
|
+
handleAfterOpenFolder,
|
|
30
|
+
handleClosedFolder,
|
|
31
|
+
handleFirstNode,
|
|
32
|
+
handleNode,
|
|
33
|
+
handleOpenFolder,
|
|
34
|
+
}: Options,
|
|
35
|
+
) => {
|
|
36
|
+
let isFirstNode = true;
|
|
37
|
+
|
|
38
|
+
const iterate = (node: Node, nextNode: Node | null): void => {
|
|
39
|
+
let mustIterateInside =
|
|
40
|
+
(node.is_open || !node.element) && node.hasChildren();
|
|
41
|
+
|
|
42
|
+
let element: HTMLElement | null = null;
|
|
43
|
+
|
|
44
|
+
// Is the element visible?
|
|
45
|
+
if (node.element?.offsetParent) {
|
|
46
|
+
element = node.element;
|
|
47
|
+
|
|
48
|
+
if (isFirstNode) {
|
|
49
|
+
handleFirstNode(node);
|
|
50
|
+
isFirstNode = false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!node.hasChildren()) {
|
|
54
|
+
handleNode(node, nextNode, node.element);
|
|
55
|
+
} else if (node.is_open) {
|
|
56
|
+
if (!handleOpenFolder(node, node.element)) {
|
|
57
|
+
mustIterateInside = false;
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
handleClosedFolder(node, nextNode, element);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (mustIterateInside) {
|
|
65
|
+
const childrenLength = node.children.length;
|
|
66
|
+
node.children.forEach((_, i) => {
|
|
67
|
+
const child = node.children[i];
|
|
68
|
+
|
|
69
|
+
if (child) {
|
|
70
|
+
if (i === childrenLength - 1) {
|
|
71
|
+
iterate(child, null);
|
|
72
|
+
} else {
|
|
73
|
+
const nextChild = node.children[i + 1];
|
|
74
|
+
|
|
75
|
+
if (nextChild) {
|
|
76
|
+
iterate(child, nextChild);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (node.is_open && element) {
|
|
83
|
+
handleAfterOpenFolder(node, nextNode);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
iterate(tree, null);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export default iterateVisibleNodes;
|