jqtree 1.7.4 → 1.7.5

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 (59) hide show
  1. package/.eslintrc +5 -1
  2. package/bower.json +1 -1
  3. package/docs/_config.yml +1 -1
  4. package/docs/_entries/general/changelog.md +5 -0
  5. package/docs/package.json +1 -1
  6. package/docs/pnpm-lock.yaml +30 -30
  7. package/lib/dataLoader.js +2 -3
  8. package/lib/dragAndDropHandler.js +5 -13
  9. package/lib/elementsRenderer.js +2 -3
  10. package/lib/keyHandler.js +1 -5
  11. package/lib/mouse.widget.js +1 -2
  12. package/lib/node.js +30 -39
  13. package/lib/nodeElement.js +3 -6
  14. package/lib/nodeUtils.js +10 -0
  15. package/lib/playwright/coverage.js +14 -11
  16. package/lib/playwright/playwright.test.js +482 -104
  17. package/lib/playwright/testUtils.js +75 -49
  18. package/lib/saveStateHandler.js +2 -3
  19. package/lib/scrollHandler/containerScrollParent.js +160 -0
  20. package/lib/scrollHandler/createScrollParent.js +57 -0
  21. package/lib/scrollHandler/documentScrollParent.js +169 -0
  22. package/lib/scrollHandler/scrollParent.js +58 -0
  23. package/lib/scrollHandler/types.js +1 -0
  24. package/lib/scrollHandler.js +28 -207
  25. package/lib/selectNodeHandler.js +2 -3
  26. package/lib/simple.widget.js +1 -2
  27. package/lib/test/jqTree/loadOnDemand.test.js +3 -3
  28. package/lib/test/jqTree/methods.test.js +2 -1
  29. package/lib/test/jqTree/scrollHandler/containerScrollParent.test.js +94 -0
  30. package/lib/test/node.test.js +49 -7
  31. package/lib/test/nodeUtils.test.js +20 -0
  32. package/lib/test/support/exampleData.js +1 -2
  33. package/lib/test/support/testUtil.js +3 -6
  34. package/lib/test/support/treeStructure.js +1 -2
  35. package/lib/tree.jquery.js +6 -7
  36. package/lib/util.js +4 -7
  37. package/lib/version.js +2 -3
  38. package/package.json +27 -27
  39. package/src/dragAndDropHandler.ts +27 -33
  40. package/src/keyHandler.ts +0 -8
  41. package/src/node.ts +32 -48
  42. package/src/nodeUtils.ts +10 -0
  43. package/src/playwright/playwright.test.ts +207 -15
  44. package/src/playwright/testUtils.ts +23 -15
  45. package/src/scrollHandler/containerScrollParent.ts +177 -0
  46. package/src/scrollHandler/createScrollParent.ts +50 -0
  47. package/src/scrollHandler/documentScrollParent.ts +182 -0
  48. package/src/scrollHandler/types.ts +7 -0
  49. package/src/scrollHandler.ts +25 -243
  50. package/src/test/jqTree/loadOnDemand.test.ts +2 -3
  51. package/src/test/jqTree/methods.test.ts +1 -1
  52. package/src/test/node.test.ts +84 -25
  53. package/src/test/nodeUtils.test.ts +21 -0
  54. package/src/tree.jquery.ts +27 -30
  55. package/src/version.ts +1 -1
  56. package/tree.jquery.debug.js +391 -229
  57. package/tree.jquery.debug.js.map +1 -1
  58. package/tree.jquery.js +2 -2
  59. package/tree.jquery.js.map +1 -1
@@ -3,17 +3,12 @@ import {
3
3
  dragAndDrop,
4
4
  findNodeElement,
5
5
  getTreeStructure,
6
+ moveMouseToNode,
6
7
  selectNode,
7
8
  } from "./testUtils";
8
9
  import { initCoverage, saveCoverage } from "./coverage";
9
10
 
10
- interface InitPageParameters {
11
- baseURL?: string;
12
- dragAndDrop: boolean;
13
- page: Page;
14
- }
15
-
16
- const initPage = async ({ baseURL, dragAndDrop, page }: InitPageParameters) => {
11
+ const initPage = async (page: Page, baseURL: string | undefined) => {
17
12
  if (!baseURL) {
18
13
  throw new Error("Missing baseURL");
19
14
  }
@@ -23,21 +18,31 @@ const initPage = async ({ baseURL, dragAndDrop, page }: InitPageParameters) => {
23
18
 
24
19
  page.on("console", (msg) => console.log(`console: ${msg.text()}`));
25
20
 
21
+ await page.evaluate(`
22
+ console.log(window.__coverage__ ? 'Coverage enabled' : 'Coverage not enabled');
23
+ `);
24
+ };
25
+
26
+ interface InitTreeOptions {
27
+ autoOpen?: number;
28
+ dragAndDrop?: boolean;
29
+ }
30
+
31
+ const initTree = async (
32
+ page: Page,
33
+ { autoOpen, dragAndDrop }: InitTreeOptions,
34
+ ) => {
26
35
  await page.evaluate(`
27
36
  const $tree = jQuery("#tree1");
28
37
 
29
38
  $tree.tree({
30
39
  animationSpeed: 0,
31
- autoOpen: 0,
40
+ autoOpen: ${autoOpen || 0},
32
41
  data: ExampleData.exampleData,
33
- dragAndDrop: ${dragAndDrop},
42
+ dragAndDrop: ${dragAndDrop || false},
34
43
  startDndDelay: 100,
35
44
  });
36
45
  `);
37
-
38
- await page.evaluate(`
39
- console.log(window.__coverage__ ? 'Coverage enabled' : 'Coverage not enabled');
40
- `);
41
46
  };
42
47
 
43
48
  test.beforeEach(async ({ context }) => {
@@ -50,7 +55,8 @@ test.afterEach(async ({ context }) => {
50
55
 
51
56
  test.describe("without dragAndDrop", () => {
52
57
  test.beforeEach(async ({ baseURL, page }) => {
53
- await initPage({ baseURL, page, dragAndDrop: false });
58
+ await initPage(page, baseURL);
59
+ await initTree(page, { dragAndDrop: false });
54
60
  });
55
61
 
56
62
  test("displays a tree", async ({ page }) => {
@@ -75,7 +81,8 @@ test.describe("without dragAndDrop", () => {
75
81
 
76
82
  test.describe("with dragAndDrop", () => {
77
83
  test.beforeEach(async ({ baseURL, page }) => {
78
- await initPage({ baseURL, page, dragAndDrop: true });
84
+ await initPage(page, baseURL);
85
+ await initTree(page, { dragAndDrop: true });
79
86
  });
80
87
 
81
88
  test("moves a node", async ({ page }) => {
@@ -110,3 +117,188 @@ test.describe("with dragAndDrop", () => {
110
117
  expect(screenshot).toMatchSnapshot();
111
118
  });
112
119
  });
120
+
121
+ test.describe("autoscroll when the window is scrollable", () => {
122
+ test("it scrolls vertically when the users drags an element to the bottom ", async ({
123
+ baseURL,
124
+ page,
125
+ }) => {
126
+ await page.setViewportSize({ width: 200, height: 100 });
127
+ await initPage(page, baseURL);
128
+ await initTree(page, { autoOpen: 3, dragAndDrop: true });
129
+
130
+ expect(
131
+ await page
132
+ .getByRole("document")
133
+ .evaluate((element) => element.scrollTop),
134
+ ).toEqual(0);
135
+
136
+ await moveMouseToNode(page, "Saurischia");
137
+ await page.mouse.down();
138
+
139
+ // eslint-disable-next-line playwright/no-wait-for-timeout
140
+ await page.waitForTimeout(200);
141
+
142
+ await page.mouse.move(20, 190);
143
+ // eslint-disable-next-line playwright/no-wait-for-timeout
144
+ await page.waitForTimeout(50);
145
+
146
+ expect(
147
+ await page
148
+ .getByRole("document")
149
+ .evaluate((element) => element.scrollTop),
150
+ ).toBeGreaterThan(0);
151
+ });
152
+
153
+ test("it scrolls horizontally when the users drags an element to the right", async ({
154
+ baseURL,
155
+ page,
156
+ }) => {
157
+ await page.setViewportSize({ width: 60, height: 400 });
158
+ await initPage(page, baseURL);
159
+ await initTree(page, { autoOpen: 3, dragAndDrop: true });
160
+
161
+ expect(
162
+ await page
163
+ .getByRole("document")
164
+ .evaluate((element) => element.scrollLeft),
165
+ ).toEqual(0);
166
+
167
+ await moveMouseToNode(page, "Saurischia");
168
+ await page.mouse.down();
169
+
170
+ // eslint-disable-next-line playwright/no-wait-for-timeout
171
+ await page.waitForTimeout(200);
172
+
173
+ await page.mouse.move(55, 10);
174
+ // eslint-disable-next-line playwright/no-wait-for-timeout
175
+ await page.waitForTimeout(50);
176
+
177
+ expect(
178
+ await page
179
+ .getByRole("document")
180
+ .evaluate((element) => element.scrollLeft),
181
+ ).toBeGreaterThan(0);
182
+ });
183
+
184
+ test("scrollToNode scrolls to a node", async ({ baseURL, page }) => {
185
+ await page.setViewportSize({ width: 200, height: 100 });
186
+ await initPage(page, baseURL);
187
+ await initTree(page, { autoOpen: 3, dragAndDrop: true });
188
+
189
+ expect(
190
+ await page
191
+ .getByRole("document")
192
+ .evaluate((element) => element.scrollTop),
193
+ ).toEqual(0);
194
+
195
+ await page.evaluate(`
196
+ const $tree = jQuery("#tree1");
197
+ const node = $tree.tree("getNodeByName", "Sauropodomorphs");
198
+ $tree.tree("scrollToNode",node);
199
+ `);
200
+
201
+ expect(
202
+ await page
203
+ .getByRole("document")
204
+ .evaluate((element) => element.scrollTop),
205
+ ).toBeGreaterThan(0);
206
+ });
207
+ });
208
+
209
+ test.describe("autoscroll when the container is scrollable", () => {
210
+ test.beforeEach(async ({ page, baseURL }) => {
211
+ await initPage(page, baseURL);
212
+
213
+ // Add a container and make it the parent of the tree element
214
+ await page.evaluate(`
215
+ document.body.style.marginLeft = "40px";
216
+ document.body.style.marginTop = "40px";
217
+
218
+ const treeElement = document.querySelector("#tree1");
219
+
220
+ const container = document.createElement("div");
221
+ container.id = "container";
222
+ container.style.height = "200px";
223
+ container.style.width = "60px";
224
+ container.style.overflowY = "scroll";
225
+
226
+ document.body.replaceChild(container, treeElement);
227
+ container.appendChild(treeElement);
228
+ `);
229
+
230
+ await initTree(page, { autoOpen: 3, dragAndDrop: true });
231
+ });
232
+
233
+ test("it scrolls vertically when the users drags an element to the bottom", async ({
234
+ page,
235
+ }) => {
236
+ expect(
237
+ await page
238
+ .locator("#container")
239
+ .evaluate((element) => element.scrollTop),
240
+ ).toEqual(0);
241
+
242
+ await moveMouseToNode(page, "Saurischia");
243
+ await page.mouse.down();
244
+
245
+ // eslint-disable-next-line playwright/no-wait-for-timeout
246
+ await page.waitForTimeout(200);
247
+
248
+ await page.mouse.move(20, 245);
249
+ // eslint-disable-next-line playwright/no-wait-for-timeout
250
+ await page.waitForTimeout(50);
251
+
252
+ expect(
253
+ await page
254
+ .locator("#container")
255
+ .evaluate((element) => element.scrollTop),
256
+ ).toBeGreaterThan(0);
257
+ });
258
+
259
+ test("it scrolls horizontally when the users drags an element to the right", async ({
260
+ page,
261
+ }) => {
262
+ expect(
263
+ await page
264
+ .locator("#container")
265
+ .evaluate((element) => element.scrollLeft),
266
+ ).toEqual(0);
267
+
268
+ await moveMouseToNode(page, "Saurischia");
269
+ await page.mouse.down();
270
+
271
+ // eslint-disable-next-line playwright/no-wait-for-timeout
272
+ await page.waitForTimeout(200);
273
+
274
+ await page.mouse.move(100, 50);
275
+ // eslint-disable-next-line playwright/no-wait-for-timeout
276
+ await page.waitForTimeout(50);
277
+
278
+ expect(
279
+ await page
280
+ .locator("#container")
281
+ .evaluate((element) => element.scrollLeft),
282
+ ).toBeGreaterThan(0);
283
+ });
284
+
285
+ test("scrollToNode scrolls to a node", async ({ page }) => {
286
+ expect(
287
+ await page
288
+ .locator("#container")
289
+ .evaluate((element) => element.scrollTop),
290
+ ).toEqual(0);
291
+
292
+ await page.evaluate(`
293
+ const $tree = jQuery("#tree1");
294
+ const node = $tree.tree("getNodeByName", "Sauropodomorphs");
295
+ $tree.tree("scrollToNode",node);
296
+ `);
297
+
298
+ expect(
299
+ await page
300
+ .locator("#container")
301
+ .evaluate((element) => element.scrollTop),
302
+ ).toBeGreaterThan(0);
303
+ });
304
+ });
@@ -1,5 +1,12 @@
1
1
  import { Page, ElementHandle } from "@playwright/test";
2
2
 
3
+ interface BoundingBox {
4
+ x: number;
5
+ y: number;
6
+ width: number;
7
+ height: number;
8
+ }
9
+
3
10
  const locateTitle = (page: Page, title: string) =>
4
11
  page.locator(".jqtree-title", {
5
12
  hasText: title,
@@ -35,7 +42,9 @@ export const selectNode = async (nodeElement: ElementHandle) => {
35
42
  await titleHandle.click();
36
43
  };
37
44
 
38
- const getRect = async (elementHandle: ElementHandle<HTMLElement>) => {
45
+ const getRect = async (
46
+ elementHandle: ElementHandle<HTMLElement>,
47
+ ): Promise<BoundingBox> => {
39
48
  const boundingBox = await elementHandle.boundingBox();
40
49
 
41
50
  if (!boundingBox) {
@@ -88,7 +97,7 @@ export const getTreeStructure = async (page: Page) => {
88
97
  return JSON.parse(structure) as JQTreeMatchers.TreeStructure;
89
98
  };
90
99
 
91
- const getNodeRect = async (page: Page, title: string) => {
100
+ const getNodeRect = async (page: Page, title: string): Promise<BoundingBox> => {
92
101
  const titleElement = await locateTitle(page, title).elementHandle();
93
102
 
94
103
  if (!titleElement) {
@@ -99,24 +108,23 @@ const getNodeRect = async (page: Page, title: string) => {
99
108
  return rect;
100
109
  };
101
110
 
111
+ export const moveMouseToNode = async (page: Page, title: string) => {
112
+ const rect = await getNodeRect(page, title);
113
+
114
+ await page.mouse.move(rect.x + 10, rect.y + rect.height / 2);
115
+ };
116
+
102
117
  export const dragAndDrop = async (
103
118
  page: Page,
104
- from: string,
105
- to: string
119
+ fromTitle: string,
120
+ toTitle: string,
106
121
  ): Promise<void> => {
107
- const fromRect = await getNodeRect(page, from);
108
- const toRect = await getNodeRect(page, to);
109
-
110
- await page.mouse.move(
111
- fromRect.x + fromRect.width / 2,
112
- fromRect.y + fromRect.height / 2
113
- );
122
+ await moveMouseToNode(page, fromTitle);
114
123
  await page.mouse.down();
124
+
115
125
  // eslint-disable-next-line playwright/no-wait-for-timeout
116
126
  await page.waitForTimeout(200);
117
- await page.mouse.move(
118
- toRect.x + toRect.width / 2,
119
- toRect.y + toRect.height / 2
120
- );
127
+
128
+ await moveMouseToNode(page, toTitle);
121
129
  await page.mouse.up();
122
130
  };
@@ -0,0 +1,177 @@
1
+ import type { ScrollParent } from "./types";
2
+
3
+ type HorizontalScrollDirection = "left" | "right";
4
+ type VerticalScrollDirection = "bottom" | "top";
5
+
6
+ interface Params {
7
+ $container: JQuery<HTMLElement>;
8
+ refreshHitAreas: () => void;
9
+ $treeElement: JQuery<HTMLElement>;
10
+ }
11
+
12
+ export default class ContainerScrollParent implements ScrollParent {
13
+ private $container: JQuery<HTMLElement>;
14
+ private horizontalScrollDirection?: HorizontalScrollDirection;
15
+ private horizontalScrollTimeout?: number;
16
+ private refreshHitAreas: () => void;
17
+ private scrollParentBottom?: number;
18
+ private scrollParentTop?: number;
19
+ private verticalScrollTimeout?: number;
20
+ private verticalScrollDirection?: VerticalScrollDirection;
21
+
22
+ constructor({ $container, refreshHitAreas }: Params) {
23
+ this.$container = $container;
24
+ this.refreshHitAreas = refreshHitAreas;
25
+ }
26
+
27
+ public checkHorizontalScrolling(pageX: number): void {
28
+ const newHorizontalScrollDirection =
29
+ this.getNewHorizontalScrollDirection(pageX);
30
+
31
+ if (this.horizontalScrollDirection !== newHorizontalScrollDirection) {
32
+ this.horizontalScrollDirection = newHorizontalScrollDirection;
33
+
34
+ if (this.horizontalScrollTimeout != null) {
35
+ window.clearTimeout(this.verticalScrollTimeout);
36
+ }
37
+
38
+ if (newHorizontalScrollDirection) {
39
+ this.horizontalScrollTimeout = window.setTimeout(
40
+ this.scrollHorizontally.bind(this),
41
+ 40,
42
+ );
43
+ }
44
+ }
45
+ }
46
+
47
+ public checkVerticalScrolling(pageY: number) {
48
+ const newVerticalScrollDirection =
49
+ this.getNewVerticalScrollDirection(pageY);
50
+
51
+ if (this.verticalScrollDirection !== newVerticalScrollDirection) {
52
+ this.verticalScrollDirection = newVerticalScrollDirection;
53
+
54
+ if (this.verticalScrollTimeout != null) {
55
+ window.clearTimeout(this.verticalScrollTimeout);
56
+ this.verticalScrollTimeout = undefined;
57
+ }
58
+
59
+ if (newVerticalScrollDirection) {
60
+ this.verticalScrollTimeout = window.setTimeout(
61
+ this.scrollVertically.bind(this),
62
+ 40,
63
+ );
64
+ }
65
+ }
66
+ }
67
+
68
+ public getScrollLeft(): number {
69
+ return this.$container.scrollLeft() || 0;
70
+ }
71
+
72
+ public scrollToY(top: number): void {
73
+ const container = this.$container.get(0) as HTMLElement;
74
+ container.scrollTop = top;
75
+ }
76
+
77
+ public stopScrolling() {
78
+ this.horizontalScrollDirection = undefined;
79
+ this.verticalScrollDirection = undefined;
80
+ this.scrollParentTop = undefined;
81
+ this.scrollParentBottom = undefined;
82
+ }
83
+
84
+ private getNewHorizontalScrollDirection(
85
+ pageX: number,
86
+ ): HorizontalScrollDirection | undefined {
87
+ const scrollParentOffset = this.$container.offset();
88
+ if (!scrollParentOffset) {
89
+ return undefined;
90
+ }
91
+
92
+ const container = this.$container.get(0) as HTMLElement;
93
+
94
+ const rightEdge = scrollParentOffset.left + container.clientWidth;
95
+ const leftEdge = scrollParentOffset.left;
96
+ const isNearRightEdge = pageX > rightEdge - 20;
97
+ const isNearLeftEdge = pageX < leftEdge + 20;
98
+
99
+ if (isNearRightEdge) {
100
+ return "right";
101
+ } else if (isNearLeftEdge) {
102
+ return "left";
103
+ }
104
+
105
+ return undefined;
106
+ }
107
+
108
+ private getNewVerticalScrollDirection(
109
+ pageY: number,
110
+ ): VerticalScrollDirection | undefined {
111
+ if (pageY < this.getScrollParentTop()) {
112
+ return "top";
113
+ }
114
+
115
+ if (pageY > this.getScrollParentBottom()) {
116
+ return "bottom";
117
+ }
118
+
119
+ return undefined;
120
+ }
121
+
122
+ private scrollHorizontally() {
123
+ if (!this.horizontalScrollDirection) {
124
+ return;
125
+ }
126
+
127
+ const distance = this.horizontalScrollDirection === "left" ? -20 : 20;
128
+ const container = this.$container.get(0) as HTMLElement;
129
+
130
+ container.scrollBy({
131
+ left: distance,
132
+ top: 0,
133
+ behavior: "instant",
134
+ });
135
+
136
+ this.refreshHitAreas();
137
+
138
+ setTimeout(this.scrollHorizontally.bind(this), 40);
139
+ }
140
+
141
+ private scrollVertically() {
142
+ if (!this.verticalScrollDirection) {
143
+ return;
144
+ }
145
+
146
+ const distance = this.verticalScrollDirection === "top" ? -20 : 20;
147
+ const container = this.$container.get(0) as HTMLElement;
148
+
149
+ container.scrollBy({
150
+ left: 0,
151
+ top: distance,
152
+ behavior: "instant",
153
+ });
154
+
155
+ this.refreshHitAreas();
156
+
157
+ setTimeout(this.scrollVertically.bind(this), 40);
158
+ }
159
+
160
+ private getScrollParentTop() {
161
+ if (this.scrollParentTop == null) {
162
+ this.scrollParentTop = this.$container.offset()?.top || 0;
163
+ }
164
+
165
+ return this.scrollParentTop;
166
+ }
167
+
168
+ private getScrollParentBottom() {
169
+ if (this.scrollParentBottom == null) {
170
+ this.scrollParentBottom =
171
+ this.getScrollParentTop() +
172
+ (this.$container.innerHeight() ?? 0);
173
+ }
174
+
175
+ return this.scrollParentBottom;
176
+ }
177
+ }
@@ -0,0 +1,50 @@
1
+ import type { ScrollParent } from "./types";
2
+ import ContainerScrollParent from "./containerScrollParent";
3
+ import DocumentScrollParent from "./documentScrollParent";
4
+
5
+ const hasOverFlow = ($element: JQuery<HTMLElement>): boolean => {
6
+ for (const attr of ["overflow", "overflow-y"]) {
7
+ const overflowValue = $element.css(attr);
8
+ if (overflowValue === "auto" || overflowValue === "scroll") {
9
+ return true;
10
+ }
11
+ }
12
+
13
+ return false;
14
+ };
15
+
16
+ const getParentWithOverflow = (
17
+ $treeElement: JQuery<HTMLElement>,
18
+ ): JQuery<HTMLElement> | null => {
19
+ if (hasOverFlow($treeElement)) {
20
+ return $treeElement;
21
+ }
22
+
23
+ for (const element of $treeElement.parents().get()) {
24
+ const $element = jQuery(element);
25
+ if (hasOverFlow($element)) {
26
+ return $element;
27
+ }
28
+ }
29
+
30
+ return null;
31
+ };
32
+
33
+ const createScrollParent = (
34
+ $treeElement: JQuery<HTMLElement>,
35
+ refreshHitAreas: () => void,
36
+ ): ScrollParent => {
37
+ const $container = getParentWithOverflow($treeElement);
38
+
39
+ if ($container?.length && $container[0]?.tagName !== "HTML") {
40
+ return new ContainerScrollParent({
41
+ $container,
42
+ refreshHitAreas,
43
+ $treeElement,
44
+ });
45
+ } else {
46
+ return new DocumentScrollParent($treeElement, refreshHitAreas);
47
+ }
48
+ };
49
+
50
+ export default createScrollParent;