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.
Files changed (60) 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/jest.config.js +4 -0
  8. package/config/jest.polyfills.js +14 -0
  9. package/devserver/test_index.html +9 -0
  10. package/docs/.ruby-version +1 -1
  11. package/docs/_config.yml +1 -1
  12. package/docs/_entries/general/changelog.md +8 -0
  13. package/docs/_entries/multiple_selection/get-selected-nodes.md +1 -1
  14. package/docs/_entries/node/getnextnode.md +3 -6
  15. package/docs/_entries/node/getnextsibling.md +1 -1
  16. package/docs/_entries/node/getnextvisiblenode.md +8 -5
  17. package/docs/_entries/node/getpreviousnode.md +12 -0
  18. package/docs/_entries/node/getprevioussibling.md +1 -1
  19. package/docs/_entries/node/getpreviousvisiblenode.md +6 -5
  20. package/package.json +32 -30
  21. package/src/dataLoader.ts +19 -21
  22. package/src/dragAndDropHandler/dragElement.ts +37 -25
  23. package/src/dragAndDropHandler/generateHitAreas.ts +187 -0
  24. package/src/dragAndDropHandler/index.ts +32 -48
  25. package/src/dragAndDropHandler/iterateVisibleNodes.ts +91 -0
  26. package/src/dragAndDropHandler/types.ts +2 -1
  27. package/src/mouseHandler.ts +385 -0
  28. package/src/mouseUtils.ts +23 -0
  29. package/src/node.ts +1 -29
  30. package/src/nodeElement/folderElement.ts +1 -1
  31. package/src/nodeElement/ghostDropHint.ts +2 -1
  32. package/src/nodeElement/index.ts +2 -1
  33. package/src/playwright/coverage.ts +3 -3
  34. package/src/playwright/playwright.test.ts +150 -49
  35. package/src/playwright/testUtils.ts +28 -5
  36. package/src/position.ts +28 -0
  37. package/src/scrollHandler/containerScrollParent.ts +13 -23
  38. package/src/scrollHandler/createScrollParent.ts +22 -22
  39. package/src/scrollHandler/documentScrollParent.ts +16 -13
  40. package/src/scrollHandler.ts +6 -14
  41. package/src/test/jqTree/events.test.ts +97 -30
  42. package/src/test/jqTree/loadOnDemand.test.ts +22 -15
  43. package/src/test/jqTree/methods.test.ts +8 -11
  44. package/src/test/jqTree/mouse.test.ts +82 -0
  45. package/src/test/jqTree/options.test.ts +9 -8
  46. package/src/test/node.test.ts +2 -1
  47. package/src/test/{nodeUtil.test.ts → position.test.ts} +1 -1
  48. package/src/tree.jquery.ts +108 -184
  49. package/src/util.ts +10 -1
  50. package/src/version.ts +1 -1
  51. package/tree.jquery.debug.js +2167 -2134
  52. package/tree.jquery.debug.js.map +1 -1
  53. package/tree.jquery.js +3 -3
  54. package/tree.jquery.js.map +1 -1
  55. package/tsconfig.json +5 -3
  56. package/docs/_entries/functions/get-selected-nodes.md +0 -10
  57. package/src/dragAndDropHandler/hitAreasGenerator.ts +0 -175
  58. package/src/dragAndDropHandler/visibleNodeIterator.ts +0 -97
  59. package/src/mouse.widget.ts +0 -266
  60. package/src/mouseWidgetTypes.ts +0 -6
@@ -0,0 +1,385 @@
1
+ import {
2
+ getPositionInfoFromMouseEvent,
3
+ getPositionInfoFromTouch,
4
+ PositionInfo,
5
+ } from "./mouseUtils";
6
+ import { Node } from "./node";
7
+ import { TriggerEvent } from "./jqtreeMethodTypes";
8
+
9
+ interface ClickTarget {
10
+ node: Node;
11
+ type: "button" | "label";
12
+ }
13
+
14
+ type GetNode = (element: HTMLElement) => null | Node;
15
+
16
+ interface MouseHandlerParams {
17
+ element: HTMLElement;
18
+ getMouseDelay: () => number;
19
+ getNode: GetNode;
20
+ onClickButton: (node: Node) => void;
21
+ onClickTitle: (node: Node) => void;
22
+ onMouseCapture: (positionInfo: PositionInfo) => boolean | null;
23
+ onMouseDrag: (positionInfo: PositionInfo) => void;
24
+ onMouseStart: (positionInfo: PositionInfo) => boolean;
25
+ onMouseStop: (positionInfo: PositionInfo) => void;
26
+ triggerEvent: TriggerEvent;
27
+ useContextMenu: boolean;
28
+ }
29
+
30
+ class MouseHandler {
31
+ private element: HTMLElement;
32
+ private getMouseDelay: () => number;
33
+ private getNode: GetNode;
34
+ private isMouseDelayMet: boolean;
35
+ private isMouseStarted: boolean;
36
+ private mouseDelayTimer: number | null;
37
+ private mouseDownInfo: PositionInfo | null;
38
+ private onClickButton: (node: Node) => void;
39
+ private onClickTitle: (node: Node) => void;
40
+ private onMouseCapture: (positionInfo: PositionInfo) => boolean | null;
41
+ private onMouseDrag: (positionInfo: PositionInfo) => void;
42
+ private onMouseStart: (positionInfo: PositionInfo) => boolean;
43
+ private onMouseStop: (positionInfo: PositionInfo) => void;
44
+ private triggerEvent: TriggerEvent;
45
+ private useContextMenu: boolean;
46
+
47
+ constructor({
48
+ element,
49
+ getMouseDelay,
50
+ getNode,
51
+ onClickButton,
52
+ onClickTitle,
53
+ onMouseCapture,
54
+ onMouseDrag,
55
+ onMouseStart,
56
+ onMouseStop,
57
+ triggerEvent,
58
+ useContextMenu,
59
+ }: MouseHandlerParams) {
60
+ this.element = element;
61
+ this.getMouseDelay = getMouseDelay;
62
+ this.getNode = getNode;
63
+ this.onClickButton = onClickButton;
64
+ this.onClickTitle = onClickTitle;
65
+ this.onMouseCapture = onMouseCapture;
66
+ this.onMouseDrag = onMouseDrag;
67
+ this.onMouseStart = onMouseStart;
68
+ this.onMouseStop = onMouseStop;
69
+ this.triggerEvent = triggerEvent;
70
+ this.useContextMenu = useContextMenu;
71
+
72
+ element.addEventListener("click", this.handleClick);
73
+ element.addEventListener("dblclick", this.handleDblclick);
74
+ element.addEventListener("mousedown", this.mouseDown, {
75
+ passive: false,
76
+ });
77
+ element.addEventListener("touchstart", this.touchStart, {
78
+ passive: false,
79
+ });
80
+
81
+ if (useContextMenu) {
82
+ element.addEventListener("contextmenu", this.handleContextmenu);
83
+ }
84
+
85
+ this.isMouseStarted = false;
86
+ this.mouseDelayTimer = null;
87
+ this.isMouseDelayMet = false;
88
+ this.mouseDownInfo = null;
89
+ }
90
+
91
+ public deinit(): void {
92
+ this.element.removeEventListener("click", this.handleClick);
93
+ this.element.removeEventListener("dblclick", this.handleDblclick);
94
+
95
+ if (this.useContextMenu) {
96
+ this.element.removeEventListener(
97
+ "contextmenu",
98
+ this.handleContextmenu,
99
+ );
100
+ }
101
+
102
+ this.element.removeEventListener("mousedown", this.mouseDown);
103
+ this.element.removeEventListener("touchstart", this.touchStart);
104
+ this.removeMouseMoveEventListeners();
105
+ }
106
+
107
+ private mouseDown = (e: MouseEvent): void => {
108
+ // Left mouse button?
109
+ if (e.button !== 0) {
110
+ return;
111
+ }
112
+
113
+ const result = this.handleMouseDown(getPositionInfoFromMouseEvent(e));
114
+
115
+ if (result && e.cancelable) {
116
+ e.preventDefault();
117
+ }
118
+ };
119
+
120
+ private handleMouseDown(positionInfo: PositionInfo): boolean {
121
+ // We may have missed mouseup (out of window)
122
+ if (this.isMouseStarted) {
123
+ this.handleMouseUp(positionInfo);
124
+ }
125
+
126
+ this.mouseDownInfo = positionInfo;
127
+
128
+ if (!this.onMouseCapture(positionInfo)) {
129
+ return false;
130
+ }
131
+
132
+ this.handleStartMouse();
133
+
134
+ return true;
135
+ }
136
+
137
+ private handleStartMouse(): void {
138
+ document.addEventListener("mousemove", this.mouseMove, {
139
+ passive: false,
140
+ });
141
+ document.addEventListener("touchmove", this.touchMove, {
142
+ passive: false,
143
+ });
144
+ document.addEventListener("mouseup", this.mouseUp, { passive: false });
145
+ document.addEventListener("touchend", this.touchEnd, {
146
+ passive: false,
147
+ });
148
+
149
+ const mouseDelay = this.getMouseDelay();
150
+
151
+ if (mouseDelay) {
152
+ this.startMouseDelayTimer(mouseDelay);
153
+ } else {
154
+ this.isMouseDelayMet = true;
155
+ }
156
+ }
157
+
158
+ private startMouseDelayTimer(mouseDelay: number): void {
159
+ if (this.mouseDelayTimer) {
160
+ clearTimeout(this.mouseDelayTimer);
161
+ }
162
+
163
+ this.mouseDelayTimer = window.setTimeout(() => {
164
+ if (this.mouseDownInfo) {
165
+ this.isMouseDelayMet = true;
166
+ }
167
+ }, mouseDelay);
168
+
169
+ this.isMouseDelayMet = false;
170
+ }
171
+
172
+ private mouseMove = (e: MouseEvent): void => {
173
+ this.handleMouseMove(e, getPositionInfoFromMouseEvent(e));
174
+ };
175
+
176
+ private handleMouseMove(
177
+ e: MouseEvent | TouchEvent,
178
+ positionInfo: PositionInfo,
179
+ ): void {
180
+ if (this.isMouseStarted) {
181
+ this.onMouseDrag(positionInfo);
182
+
183
+ if (e.cancelable) {
184
+ e.preventDefault();
185
+ }
186
+ return;
187
+ }
188
+
189
+ if (!this.isMouseDelayMet) {
190
+ return;
191
+ }
192
+
193
+ if (this.mouseDownInfo) {
194
+ this.isMouseStarted =
195
+ this.onMouseStart(this.mouseDownInfo) !== false;
196
+ }
197
+
198
+ if (this.isMouseStarted) {
199
+ this.onMouseDrag(positionInfo);
200
+
201
+ if (e.cancelable) {
202
+ e.preventDefault();
203
+ }
204
+ } else {
205
+ this.handleMouseUp(positionInfo);
206
+ }
207
+ }
208
+
209
+ private mouseUp = (e: MouseEvent): void => {
210
+ this.handleMouseUp(getPositionInfoFromMouseEvent(e));
211
+ };
212
+
213
+ private handleMouseUp(positionInfo: PositionInfo): void {
214
+ this.removeMouseMoveEventListeners();
215
+ this.isMouseDelayMet = false;
216
+ this.mouseDownInfo = null;
217
+
218
+ if (this.isMouseStarted) {
219
+ this.isMouseStarted = false;
220
+ this.onMouseStop(positionInfo);
221
+ }
222
+ }
223
+
224
+ private removeMouseMoveEventListeners() {
225
+ document.removeEventListener("mousemove", this.mouseMove);
226
+ document.removeEventListener("touchmove", this.touchMove);
227
+ document.removeEventListener("mouseup", this.mouseUp);
228
+ document.removeEventListener("touchend", this.touchEnd);
229
+ }
230
+
231
+ private touchStart = (e: TouchEvent): void => {
232
+ if (!e) {
233
+ return;
234
+ }
235
+
236
+ if (e.touches.length > 1) {
237
+ return;
238
+ }
239
+
240
+ const touch = e.touches[0];
241
+
242
+ if (!touch) {
243
+ return;
244
+ }
245
+
246
+ this.handleMouseDown(getPositionInfoFromTouch(touch, e));
247
+ };
248
+
249
+ private touchMove = (e: TouchEvent): void => {
250
+ if (!e) {
251
+ return;
252
+ }
253
+
254
+ if (e.touches.length > 1) {
255
+ return;
256
+ }
257
+
258
+ const touch = e.touches[0];
259
+
260
+ if (!touch) {
261
+ return;
262
+ }
263
+
264
+ this.handleMouseMove(e, getPositionInfoFromTouch(touch, e));
265
+ };
266
+
267
+ private touchEnd = (e: TouchEvent): void => {
268
+ if (!e) {
269
+ return;
270
+ }
271
+
272
+ if (e.touches.length > 1) {
273
+ return;
274
+ }
275
+
276
+ const touch = e.touches[0];
277
+
278
+ if (!touch) {
279
+ return;
280
+ }
281
+
282
+ this.handleMouseUp(getPositionInfoFromTouch(touch, e));
283
+ };
284
+
285
+ private handleClick = (e: MouseEvent): void => {
286
+ if (!e.target) {
287
+ return;
288
+ }
289
+
290
+ const clickTarget = this.getClickTarget(e.target as HTMLElement);
291
+
292
+ if (!clickTarget) {
293
+ return;
294
+ }
295
+
296
+ if (clickTarget.type === "button") {
297
+ this.onClickButton(clickTarget.node);
298
+
299
+ e.preventDefault();
300
+ e.stopPropagation();
301
+ } else if (clickTarget.type === "label") {
302
+ const event = this.triggerEvent("tree.click", {
303
+ node: clickTarget.node,
304
+ click_event: e,
305
+ });
306
+
307
+ if (!event.isDefaultPrevented()) {
308
+ this.onClickTitle(clickTarget.node);
309
+ }
310
+ }
311
+ };
312
+
313
+ private handleDblclick = (e: MouseEvent): void => {
314
+ if (!e.target) {
315
+ return;
316
+ }
317
+
318
+ const clickTarget = this.getClickTarget(e.target as HTMLElement);
319
+
320
+ if (clickTarget?.type === "label") {
321
+ this.triggerEvent("tree.dblclick", {
322
+ node: clickTarget.node,
323
+ click_event: e,
324
+ });
325
+ }
326
+ };
327
+
328
+ private handleContextmenu = (e: MouseEvent) => {
329
+ if (!e.target) {
330
+ return;
331
+ }
332
+
333
+ const div = (e.target as HTMLElement).closest<HTMLElement>(
334
+ "ul.jqtree-tree .jqtree-element",
335
+ );
336
+
337
+ if (div) {
338
+ const node = this.getNode(div);
339
+ if (node) {
340
+ e.preventDefault();
341
+ e.stopPropagation();
342
+
343
+ this.triggerEvent("tree.contextmenu", {
344
+ node,
345
+ click_event: e,
346
+ });
347
+ return false;
348
+ }
349
+ }
350
+
351
+ return null;
352
+ };
353
+
354
+ private getClickTarget(element: HTMLElement): ClickTarget | null {
355
+ const button = element.closest<HTMLElement>(".jqtree-toggler");
356
+
357
+ if (button) {
358
+ const node = this.getNode(button);
359
+
360
+ if (node) {
361
+ return {
362
+ type: "button",
363
+ node,
364
+ };
365
+ }
366
+ } else {
367
+ const jqTreeElement =
368
+ element.closest<HTMLElement>(".jqtree-element");
369
+
370
+ if (jqTreeElement) {
371
+ const node = this.getNode(jqTreeElement);
372
+ if (node) {
373
+ return {
374
+ type: "label",
375
+ node,
376
+ };
377
+ }
378
+ }
379
+ }
380
+
381
+ return null;
382
+ }
383
+ }
384
+
385
+ export default MouseHandler;
@@ -0,0 +1,23 @@
1
+ export interface PositionInfo {
2
+ originalEvent: Event;
3
+ pageX: number;
4
+ pageY: number;
5
+ target: HTMLElement;
6
+ }
7
+
8
+ export const getPositionInfoFromMouseEvent = (e: MouseEvent): PositionInfo => ({
9
+ originalEvent: e,
10
+ pageX: e.pageX,
11
+ pageY: e.pageY,
12
+ target: e.target as HTMLElement,
13
+ });
14
+
15
+ export const getPositionInfoFromTouch = (
16
+ touch: Touch,
17
+ e: TouchEvent,
18
+ ): PositionInfo => ({
19
+ originalEvent: e,
20
+ pageX: touch.pageX,
21
+ pageY: touch.pageY,
22
+ target: touch.target as HTMLElement,
23
+ });
package/src/node.ts CHANGED
@@ -1,36 +1,8 @@
1
1
  import { isNodeRecordWithChildren } from "./nodeUtils";
2
-
3
- export enum Position {
4
- Before = 1,
5
- After,
6
- Inside,
7
- None,
8
- }
9
-
10
- const positionNames: Record<string, Position> = {
11
- before: Position.Before,
12
- after: Position.After,
13
- inside: Position.Inside,
14
- none: Position.None,
15
- };
2
+ import { Position } from "./position";
16
3
 
17
4
  type IterateCallback = (node: Node, level: number) => boolean;
18
5
 
19
- export const getPositionName = (position: Position): string => {
20
- for (const name in positionNames) {
21
- if (Object.prototype.hasOwnProperty.call(positionNames, name)) {
22
- if (positionNames[name] === position) {
23
- return name;
24
- }
25
- }
26
- }
27
-
28
- return "";
29
- };
30
-
31
- export const getPosition = (name: string): Position | undefined =>
32
- positionNames[name];
33
-
34
6
  export class Node implements INode {
35
7
  public id?: NodeId;
36
8
  public name: string;
@@ -1,4 +1,4 @@
1
- import { Position } from "../node";
1
+ import { Position } from "../position";
2
2
  import NodeElement, { NodeElementParams } from "./index";
3
3
  import { OnFinishOpenNode, TriggerEvent } from "../jqtreeMethodTypes";
4
4
 
@@ -1,4 +1,5 @@
1
- import { Position, Node } from "../node";
1
+ import { Node } from "../node";
2
+ import { Position } from "../position";
2
3
  import { DropHint } from "../dragAndDropHandler/types";
3
4
 
4
5
  class GhostDropHint implements DropHint {
@@ -1,4 +1,5 @@
1
- import { Position, Node } from "../node";
1
+ import { Node } from "../node";
2
+ import { Position } from "../position";
2
3
  import { DropHint } from "../dragAndDropHandler/types";
3
4
  import BorderDropHint from "./borderDropHint";
4
5
  import GhostDropHint from "./ghostDropHint";
@@ -16,11 +16,11 @@ export const initCoverage = async (context: BrowserContext) => {
16
16
  if (coverageJSON) {
17
17
  const filename = path.join(
18
18
  istanbulCLIOutput,
19
- `playwright_coverage_${generateUUID()}.json`
19
+ `playwright_coverage_${generateUUID()}.json`,
20
20
  );
21
21
  fs.writeFileSync(filename, coverageJSON);
22
22
  }
23
- }
23
+ },
24
24
  );
25
25
  };
26
26
 
@@ -31,7 +31,7 @@ export const saveCoverage = async (context: BrowserContext) => {
31
31
  const anyWindow = window as any;
32
32
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
33
33
  const coverageData = anyWindow.__coverage__;
34
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
34
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, playwright/no-unsafe-references
35
35
  anyWindow.collectIstanbulCoverage(JSON.stringify(coverageData));
36
36
  });
37
37
  }