@node-edit-utils/core 2.3.3 → 2.3.4

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 (101) hide show
  1. package/dist/lib/viewport/label/getViewportLabelOverlay.d.ts +1 -0
  2. package/dist/lib/viewport/label/index.d.ts +5 -3
  3. package/dist/lib/viewport/label/isViewportDragging.d.ts +2 -0
  4. package/dist/lib/viewport/label/refreshViewportLabel.d.ts +8 -0
  5. package/dist/lib/viewport/label/removeViewportLabel.d.ts +5 -0
  6. package/dist/lib/viewport/label/setupViewportDrag.d.ts +1 -0
  7. package/dist/node-edit-utils.cjs.js +100 -62
  8. package/dist/node-edit-utils.esm.js +100 -62
  9. package/dist/node-edit-utils.umd.js +100 -62
  10. package/dist/node-edit-utils.umd.min.js +1 -1
  11. package/dist/styles.css +1 -1
  12. package/package.json +7 -2
  13. package/src/lib/canvas/createCanvasObserver.test.ts +242 -0
  14. package/src/lib/canvas/disableCanvasKeyboard.test.ts +53 -0
  15. package/src/lib/canvas/disableCanvasKeyboard.ts +1 -1
  16. package/src/lib/canvas/disableCanvasTextMode.test.ts +53 -0
  17. package/src/lib/canvas/disableCanvasTextMode.ts +1 -1
  18. package/src/lib/canvas/enableCanvasKeyboard.test.ts +53 -0
  19. package/src/lib/canvas/enableCanvasKeyboard.ts +1 -1
  20. package/src/lib/canvas/enableCanvasTextMode.test.ts +53 -0
  21. package/src/lib/canvas/enableCanvasTextMode.ts +1 -1
  22. package/src/lib/canvas/helpers/applyCanvasState.test.ts +119 -0
  23. package/src/lib/canvas/helpers/applyCanvasState.ts +1 -1
  24. package/src/lib/canvas/helpers/getCanvasContainer.test.ts +62 -0
  25. package/src/lib/canvas/helpers/getCanvasContainerOrBody.test.ts +51 -0
  26. package/src/lib/canvas/helpers/getCanvasWindowValue.test.ts +116 -0
  27. package/src/lib/helpers/adjustForZoom.test.ts +65 -0
  28. package/src/lib/helpers/createDragHandler.test.ts +325 -0
  29. package/src/lib/helpers/getNodeProvider.test.ts +71 -0
  30. package/src/lib/helpers/getNodeTools.test.ts +50 -0
  31. package/src/lib/helpers/getViewportDimensions.test.ts +93 -0
  32. package/src/lib/helpers/observer/connectMutationObserver.test.ts +127 -0
  33. package/src/lib/helpers/observer/connectResizeObserver.test.ts +147 -0
  34. package/src/lib/helpers/parseTransform.test.ts +117 -0
  35. package/src/lib/helpers/toggleClass.test.ts +71 -0
  36. package/src/lib/helpers/withRAF.test.ts +439 -0
  37. package/src/lib/node-tools/createNodeTools.test.ts +373 -0
  38. package/src/lib/node-tools/events/click/handleNodeClick.test.ts +109 -0
  39. package/src/lib/node-tools/events/setupEventListener.test.ts +136 -0
  40. package/src/lib/node-tools/highlight/clearHighlightFrame.test.ts +88 -0
  41. package/src/lib/node-tools/highlight/createCornerHandles.test.ts +150 -0
  42. package/src/lib/node-tools/highlight/createHighlightFrame.test.ts +237 -0
  43. package/src/lib/node-tools/highlight/createTagLabel.test.ts +135 -0
  44. package/src/lib/node-tools/highlight/createToolsContainer.test.ts +97 -0
  45. package/src/lib/node-tools/highlight/helpers/getElementBounds.test.ts +158 -0
  46. package/src/lib/node-tools/highlight/helpers/getHighlightFrameElement.test.ts +78 -0
  47. package/src/lib/node-tools/highlight/helpers/getScreenBounds.test.ts +133 -0
  48. package/src/lib/node-tools/highlight/highlightNode.test.ts +213 -0
  49. package/src/lib/node-tools/highlight/refreshHighlightFrame.test.ts +323 -0
  50. package/src/lib/node-tools/highlight/updateHighlightFrameVisibility.test.ts +110 -0
  51. package/src/lib/node-tools/select/helpers/getElementsFromPoint.test.ts +109 -0
  52. package/src/lib/node-tools/select/helpers/isInsideComponent.test.ts +81 -0
  53. package/src/lib/node-tools/select/helpers/isInsideViewport.test.ts +82 -0
  54. package/src/lib/node-tools/select/helpers/targetSameCandidates.test.ts +81 -0
  55. package/src/lib/node-tools/select/selectNode.test.ts +238 -0
  56. package/src/lib/node-tools/text/events/setupKeydownHandler.test.ts +91 -0
  57. package/src/lib/node-tools/text/events/setupMutationObserver.test.ts +213 -0
  58. package/src/lib/node-tools/text/events/setupNodeListeners.test.ts +133 -0
  59. package/src/lib/node-tools/text/helpers/enterTextEditMode.test.ts +50 -0
  60. package/src/lib/node-tools/text/helpers/handleTextChange.test.ts +201 -0
  61. package/src/lib/node-tools/text/helpers/hasTextContent.test.ts +101 -0
  62. package/src/lib/node-tools/text/helpers/insertLineBreak.test.ts +96 -0
  63. package/src/lib/node-tools/text/helpers/makeNodeEditable.test.ts +56 -0
  64. package/src/lib/node-tools/text/helpers/makeNodeNonEditable.test.ts +57 -0
  65. package/src/lib/node-tools/text/helpers/shouldEnterTextEditMode.test.ts +61 -0
  66. package/src/lib/node-tools/text/nodeText.test.ts +233 -0
  67. package/src/lib/post-message/processPostMessage.test.ts +218 -0
  68. package/src/lib/post-message/sendPostMessage.test.ts +120 -0
  69. package/src/lib/styles/styles.css +2 -2
  70. package/src/lib/viewport/createViewport.test.ts +267 -0
  71. package/src/lib/viewport/createViewport.ts +7 -4
  72. package/src/lib/viewport/events/setupEventListener.test.ts +103 -0
  73. package/src/lib/viewport/label/getViewportLabelOverlay.test.ts +77 -0
  74. package/src/lib/viewport/label/{getViewportLabelsOverlay.ts → getViewportLabelOverlay.ts} +2 -1
  75. package/src/lib/viewport/label/helpers/getLabelPosition.test.ts +51 -0
  76. package/src/lib/viewport/label/helpers/getTransformValues.test.ts +59 -0
  77. package/src/lib/viewport/label/helpers/getZoomValue.test.ts +53 -0
  78. package/src/lib/viewport/label/helpers/selectFirstViewportNode.test.ts +105 -0
  79. package/src/lib/viewport/label/helpers/selectFirstViewportNode.ts +8 -0
  80. package/src/lib/viewport/label/index.ts +5 -3
  81. package/src/lib/viewport/label/isViewportDragging.test.ts +35 -0
  82. package/src/lib/viewport/label/isViewportDragging.ts +9 -0
  83. package/src/lib/viewport/label/refreshViewportLabel.test.ts +105 -0
  84. package/src/lib/viewport/label/refreshViewportLabel.ts +50 -0
  85. package/src/lib/viewport/label/refreshViewportLabels.test.ts +107 -0
  86. package/src/lib/viewport/label/refreshViewportLabels.ts +17 -50
  87. package/src/lib/viewport/label/removeViewportLabel.test.ts +67 -0
  88. package/src/lib/viewport/label/removeViewportLabel.ts +20 -0
  89. package/src/lib/viewport/label/setupViewportDrag.test.ts +249 -0
  90. package/src/lib/viewport/label/{setupViewportLabelDrag.ts → setupViewportDrag.ts} +14 -14
  91. package/src/lib/viewport/resize/createResizeHandle.test.ts +37 -0
  92. package/src/lib/viewport/resize/createResizePresets.test.ts +75 -0
  93. package/src/lib/viewport/resize/updateActivePreset.test.ts +92 -0
  94. package/src/lib/viewport/width/calcConstrainedWidth.test.ts +47 -0
  95. package/src/lib/viewport/width/calcWidth.test.ts +68 -0
  96. package/src/lib/viewport/width/updateWidth.test.ts +78 -0
  97. package/src/lib/window/bindToWindow.test.ts +166 -0
  98. package/dist/lib/viewport/label/getViewportLabelsOverlay.d.ts +0 -1
  99. package/dist/lib/viewport/label/isViewportLabelDragging.d.ts +0 -2
  100. package/dist/lib/viewport/label/setupViewportLabelDrag.d.ts +0 -1
  101. package/src/lib/viewport/label/isViewportLabelDragging.ts +0 -9
@@ -0,0 +1,62 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+ import { getCanvasContainer } from "./getCanvasContainer";
3
+
4
+ describe("getCanvasContainer", () => {
5
+ let canvasContainer: HTMLElement;
6
+
7
+ beforeEach(() => {
8
+ canvasContainer = document.createElement("div");
9
+ canvasContainer.classList.add("canvas-container");
10
+ });
11
+
12
+ afterEach(() => {
13
+ if (document.body.contains(canvasContainer)) {
14
+ document.body.removeChild(canvasContainer);
15
+ }
16
+ // Remove any other canvas containers that might exist
17
+ const existingContainers = document.querySelectorAll(".canvas-container");
18
+ existingContainers.forEach((container) => {
19
+ if (document.body.contains(container)) {
20
+ document.body.removeChild(container);
21
+ }
22
+ });
23
+ });
24
+
25
+ it("should return canvas container element when it exists", () => {
26
+ document.body.appendChild(canvasContainer);
27
+
28
+ const result = getCanvasContainer();
29
+
30
+ expect(result).toBe(canvasContainer);
31
+ });
32
+
33
+ it("should return null when canvas container does not exist", () => {
34
+ const result = getCanvasContainer();
35
+
36
+ expect(result).toBeNull();
37
+ });
38
+
39
+ it("should return first canvas container when multiple exist", () => {
40
+ const firstContainer = document.createElement("div");
41
+ firstContainer.classList.add("canvas-container");
42
+ const secondContainer = document.createElement("div");
43
+ secondContainer.classList.add("canvas-container");
44
+
45
+ document.body.appendChild(firstContainer);
46
+ document.body.appendChild(secondContainer);
47
+
48
+ const result = getCanvasContainer();
49
+
50
+ expect(result).toBe(firstContainer);
51
+ });
52
+
53
+ it("should return null after container is removed", () => {
54
+ document.body.appendChild(canvasContainer);
55
+ const result1 = getCanvasContainer();
56
+ expect(result1).toBe(canvasContainer);
57
+
58
+ document.body.removeChild(canvasContainer);
59
+ const result2 = getCanvasContainer();
60
+ expect(result2).toBeNull();
61
+ });
62
+ });
@@ -0,0 +1,51 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import * as getCanvasContainerModule from "./getCanvasContainer";
3
+ import { getCanvasContainerOrBody } from "./getCanvasContainerOrBody";
4
+
5
+ vi.mock("./getCanvasContainer");
6
+
7
+ describe("getCanvasContainerOrBody", () => {
8
+ let canvasContainer: HTMLElement;
9
+
10
+ beforeEach(() => {
11
+ canvasContainer = document.createElement("div");
12
+ canvasContainer.classList.add("canvas-container");
13
+ });
14
+
15
+ afterEach(() => {
16
+ vi.clearAllMocks();
17
+ });
18
+
19
+ it("should return canvas container when it exists", () => {
20
+ vi.mocked(getCanvasContainerModule.getCanvasContainer).mockReturnValue(canvasContainer);
21
+
22
+ const result = getCanvasContainerOrBody();
23
+
24
+ expect(result).toBe(canvasContainer);
25
+ });
26
+
27
+ it("should return document.body when canvas container does not exist", () => {
28
+ vi.mocked(getCanvasContainerModule.getCanvasContainer).mockReturnValue(null);
29
+
30
+ const result = getCanvasContainerOrBody();
31
+
32
+ expect(result).toBe(document.body);
33
+ });
34
+
35
+ it("should always return an HTMLElement", () => {
36
+ vi.mocked(getCanvasContainerModule.getCanvasContainer).mockReturnValue(canvasContainer);
37
+
38
+ const result = getCanvasContainerOrBody();
39
+
40
+ expect(result).toBeInstanceOf(HTMLElement);
41
+ });
42
+
43
+ it("should return document.body when canvas container is null", () => {
44
+ vi.mocked(getCanvasContainerModule.getCanvasContainer).mockReturnValue(null);
45
+
46
+ const result = getCanvasContainerOrBody();
47
+
48
+ expect(result).toBe(document.body);
49
+ expect(result).toBeInstanceOf(HTMLElement);
50
+ });
51
+ });
@@ -0,0 +1,116 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { getCanvasWindowValue } from "./getCanvasWindowValue";
3
+
4
+ describe("getCanvasWindowValue", () => {
5
+ beforeEach(() => {
6
+ // Clear any existing canvas properties
7
+ delete (window as unknown as Record<string, unknown>).canvas;
8
+ delete (window as unknown as Record<string, unknown>).customCanvas;
9
+ });
10
+
11
+ afterEach(() => {
12
+ // Clean up
13
+ delete (window as unknown as Record<string, unknown>).canvas;
14
+ delete (window as unknown as Record<string, unknown>).customCanvas;
15
+ });
16
+
17
+ it("should return value from nested path", () => {
18
+ const mockValue = { keyboard: { enable: vi.fn() } };
19
+ (window as unknown as Record<string, unknown>).canvas = mockValue;
20
+
21
+ const result = getCanvasWindowValue(["keyboard", "enable"], "canvas");
22
+
23
+ expect(result).toBe(mockValue.keyboard.enable);
24
+ });
25
+
26
+ it("should return undefined when canvas does not exist", () => {
27
+ const result = getCanvasWindowValue(["keyboard", "enable"], "nonExistentCanvas");
28
+
29
+ expect(result).toBeUndefined();
30
+ });
31
+
32
+ it("should return undefined when path does not exist", () => {
33
+ (window as unknown as Record<string, unknown>).canvas = {};
34
+
35
+ const result = getCanvasWindowValue(["nonExistent", "path"], "canvas");
36
+
37
+ expect(result).toBeUndefined();
38
+ });
39
+
40
+ it("should return undefined when intermediate path is null", () => {
41
+ (window as unknown as Record<string, unknown>).canvas = { keyboard: null };
42
+
43
+ const result = getCanvasWindowValue(["keyboard", "enable"], "canvas");
44
+
45
+ expect(result).toBeUndefined();
46
+ });
47
+
48
+ it("should return undefined when intermediate path is undefined", () => {
49
+ (window as unknown as Record<string, unknown>).canvas = { keyboard: undefined };
50
+
51
+ const result = getCanvasWindowValue(["keyboard", "enable"], "canvas");
52
+
53
+ expect(result).toBeUndefined();
54
+ });
55
+
56
+ it("should use default canvas name when not provided", () => {
57
+ const mockValue = { keyboard: { enable: vi.fn() } };
58
+ (window as unknown as Record<string, unknown>).canvas = mockValue;
59
+
60
+ const result = getCanvasWindowValue(["keyboard", "enable"]);
61
+
62
+ expect(result).toBe(mockValue.keyboard.enable);
63
+ });
64
+
65
+ it("should handle custom canvas name", () => {
66
+ const mockValue = { keyboard: { enable: vi.fn() } };
67
+ (window as unknown as Record<string, unknown>).customCanvas = mockValue;
68
+
69
+ const result = getCanvasWindowValue(["keyboard", "enable"], "customCanvas");
70
+
71
+ expect(result).toBe(mockValue.keyboard.enable);
72
+ });
73
+
74
+ it("should handle single level path", () => {
75
+ const mockValue = { property: "value" };
76
+ (window as unknown as Record<string, unknown>).canvas = mockValue;
77
+
78
+ const result = getCanvasWindowValue(["property"], "canvas");
79
+
80
+ expect(result).toBe("value");
81
+ });
82
+
83
+ it("should handle deep nested path", () => {
84
+ const mockValue = {
85
+ level1: {
86
+ level2: {
87
+ level3: {
88
+ level4: "deep-value",
89
+ },
90
+ },
91
+ },
92
+ };
93
+ (window as unknown as Record<string, unknown>).canvas = mockValue;
94
+
95
+ const result = getCanvasWindowValue(["level1", "level2", "level3", "level4"], "canvas");
96
+
97
+ expect(result).toBe("deep-value");
98
+ });
99
+
100
+ it("should handle empty path array", () => {
101
+ const mockValue = { property: "value" };
102
+ (window as unknown as Record<string, unknown>).canvas = mockValue;
103
+
104
+ const result = getCanvasWindowValue([], "canvas");
105
+
106
+ expect(result).toBe(mockValue);
107
+ });
108
+
109
+ it("should handle when canvas is not an object", () => {
110
+ (window as unknown as Record<string, unknown>).canvas = "not-an-object";
111
+
112
+ const result = getCanvasWindowValue(["keyboard", "enable"], "canvas");
113
+
114
+ expect(result).toBeUndefined();
115
+ });
116
+ });
@@ -0,0 +1,65 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { adjustForZoom } from "./adjustForZoom";
3
+
4
+ describe("adjustForZoom", () => {
5
+ it("should divide value by zoom", () => {
6
+ const result = adjustForZoom(100, 2);
7
+
8
+ expect(result).toBe(50);
9
+ });
10
+
11
+ it("should handle zoom of 1", () => {
12
+ const result = adjustForZoom(100, 1);
13
+
14
+ expect(result).toBe(100);
15
+ });
16
+
17
+ it("should handle fractional zoom", () => {
18
+ const result = adjustForZoom(100, 0.5);
19
+
20
+ expect(result).toBe(200);
21
+ });
22
+
23
+ it("should handle decimal values", () => {
24
+ const result = adjustForZoom(123.456, 2.5);
25
+
26
+ expect(result).toBe(49.3824);
27
+ });
28
+
29
+ it("should use default precision of 5", () => {
30
+ const result = adjustForZoom(100, 3);
31
+
32
+ expect(result).toBe(33.33333);
33
+ });
34
+
35
+ it("should use custom precision", () => {
36
+ const result = adjustForZoom(100, 3, 2);
37
+
38
+ expect(result).toBe(33.33);
39
+ });
40
+
41
+ it("should handle zero value", () => {
42
+ const result = adjustForZoom(0, 2);
43
+
44
+ expect(result).toBe(0);
45
+ });
46
+
47
+ it("should handle negative values", () => {
48
+ const result = adjustForZoom(-100, 2);
49
+
50
+ expect(result).toBe(-50);
51
+ });
52
+
53
+ it("should handle precision of 0", () => {
54
+ const result = adjustForZoom(100, 3, 0);
55
+
56
+ expect(result).toBe(33);
57
+ });
58
+
59
+ it("should handle very small zoom values", () => {
60
+ const result = adjustForZoom(100, 0.1);
61
+
62
+ expect(result).toBe(1000);
63
+ });
64
+ });
65
+
@@ -0,0 +1,325 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import type { DragCallbacks } from "./createDragHandler";
3
+ import { createDragHandler } from "./createDragHandler";
4
+
5
+ describe("createDragHandler", () => {
6
+ let element: HTMLElement;
7
+ let callbacks: DragCallbacks;
8
+ let cleanup: () => void;
9
+
10
+ beforeEach(() => {
11
+ element = document.createElement("div");
12
+ document.body.appendChild(element);
13
+
14
+ callbacks = {
15
+ onStart: vi.fn(),
16
+ onDrag: vi.fn(),
17
+ onStop: vi.fn(),
18
+ onCancel: vi.fn(),
19
+ onPreventClick: vi.fn(),
20
+ };
21
+ });
22
+
23
+ afterEach(() => {
24
+ if (cleanup) {
25
+ cleanup();
26
+ }
27
+ if (document.body.contains(element)) {
28
+ document.body.removeChild(element);
29
+ }
30
+ vi.clearAllMocks();
31
+ });
32
+
33
+ it("should return cleanup function", () => {
34
+ cleanup = createDragHandler(element, callbacks);
35
+
36
+ expect(typeof cleanup).toBe("function");
37
+ });
38
+
39
+ it("should call onStart on mousedown", () => {
40
+ cleanup = createDragHandler(element, callbacks);
41
+
42
+ const event = new MouseEvent("mousedown", { clientX: 100, clientY: 200, bubbles: true });
43
+ element.dispatchEvent(event);
44
+
45
+ expect(callbacks.onStart).toHaveBeenCalledTimes(1);
46
+ expect(callbacks.onStart).toHaveBeenCalledWith(
47
+ expect.any(MouseEvent),
48
+ expect.objectContaining({
49
+ isDragging: true,
50
+ hasDragged: false,
51
+ startX: 100,
52
+ startY: 200,
53
+ })
54
+ );
55
+ });
56
+
57
+ it("should call onDrag on mousemove after drag starts", () => {
58
+ cleanup = createDragHandler(element, callbacks);
59
+
60
+ // Start drag
61
+ const startEvent = new MouseEvent("mousedown", { clientX: 100, clientY: 200, bubbles: true });
62
+ element.dispatchEvent(startEvent);
63
+
64
+ // Move mouse
65
+ const moveEvent = new MouseEvent("mousemove", { clientX: 150, clientY: 250, bubbles: true });
66
+ document.dispatchEvent(moveEvent);
67
+
68
+ expect(callbacks.onDrag).toHaveBeenCalledTimes(1);
69
+ expect(callbacks.onDrag).toHaveBeenCalledWith(
70
+ expect.any(MouseEvent),
71
+ expect.objectContaining({
72
+ isDragging: true,
73
+ hasDragged: true,
74
+ startX: 100,
75
+ startY: 200,
76
+ deltaX: 50,
77
+ deltaY: 50,
78
+ })
79
+ );
80
+ });
81
+
82
+ it("should not call onDrag before drag starts", () => {
83
+ cleanup = createDragHandler(element, callbacks);
84
+
85
+ const moveEvent = new MouseEvent("mousemove", { clientX: 150, clientY: 250, bubbles: true });
86
+ document.dispatchEvent(moveEvent);
87
+
88
+ expect(callbacks.onDrag).not.toHaveBeenCalled();
89
+ });
90
+
91
+ it("should call onStop on mouseup after drag starts", () => {
92
+ cleanup = createDragHandler(element, callbacks);
93
+
94
+ // Start drag
95
+ const startEvent = new MouseEvent("mousedown", { clientX: 100, clientY: 200, bubbles: true });
96
+ element.dispatchEvent(startEvent);
97
+
98
+ // Stop drag
99
+ const stopEvent = new MouseEvent("mouseup", { bubbles: true });
100
+ document.dispatchEvent(stopEvent);
101
+
102
+ expect(callbacks.onStop).toHaveBeenCalledTimes(1);
103
+ expect(callbacks.onStop).toHaveBeenCalledWith(
104
+ expect.any(MouseEvent),
105
+ expect.objectContaining({
106
+ isDragging: false,
107
+ hasDragged: false,
108
+ startX: 100,
109
+ startY: 200,
110
+ })
111
+ );
112
+ });
113
+
114
+ it("should not call onStop if drag hasn't started", () => {
115
+ cleanup = createDragHandler(element, callbacks);
116
+
117
+ const stopEvent = new MouseEvent("mouseup", { bubbles: true });
118
+ document.dispatchEvent(stopEvent);
119
+
120
+ expect(callbacks.onStop).not.toHaveBeenCalled();
121
+ });
122
+
123
+ it("should call onCancel on window blur during drag", () => {
124
+ cleanup = createDragHandler(element, callbacks);
125
+
126
+ // Start drag
127
+ const startEvent = new MouseEvent("mousedown", { clientX: 100, clientY: 200, bubbles: true });
128
+ element.dispatchEvent(startEvent);
129
+
130
+ // Blur window
131
+ window.dispatchEvent(new Event("blur"));
132
+
133
+ expect(callbacks.onCancel).toHaveBeenCalledTimes(1);
134
+ expect(callbacks.onCancel).toHaveBeenCalledWith(
135
+ expect.objectContaining({
136
+ isDragging: false,
137
+ hasDragged: false,
138
+ startX: 100,
139
+ startY: 200,
140
+ })
141
+ );
142
+ });
143
+
144
+ it("should not call onCancel if drag hasn't started", () => {
145
+ cleanup = createDragHandler(element, callbacks);
146
+
147
+ window.dispatchEvent(new Event("blur"));
148
+
149
+ expect(callbacks.onCancel).not.toHaveBeenCalled();
150
+ });
151
+
152
+ it("should call onPreventClick on click after drag", () => {
153
+ cleanup = createDragHandler(element, callbacks);
154
+
155
+ // Start drag
156
+ const startEvent = new MouseEvent("mousedown", { clientX: 100, clientY: 200, bubbles: true });
157
+ element.dispatchEvent(startEvent);
158
+
159
+ // Move mouse to set hasDragged
160
+ const moveEvent = new MouseEvent("mousemove", { clientX: 150, clientY: 250, bubbles: true });
161
+ document.dispatchEvent(moveEvent);
162
+
163
+ // Stop drag
164
+ const stopEvent = new MouseEvent("mouseup", { bubbles: true });
165
+ document.dispatchEvent(stopEvent);
166
+
167
+ // Click
168
+ const clickEvent = new MouseEvent("click", { bubbles: true });
169
+ element.dispatchEvent(clickEvent);
170
+
171
+ expect(callbacks.onPreventClick).toHaveBeenCalledTimes(1);
172
+ });
173
+
174
+ it("should calculate correct deltaX and deltaY", () => {
175
+ cleanup = createDragHandler(element, callbacks);
176
+
177
+ // Start at (100, 200)
178
+ const startEvent = new MouseEvent("mousedown", { clientX: 100, clientY: 200, bubbles: true });
179
+ element.dispatchEvent(startEvent);
180
+
181
+ // Move to (250, 350)
182
+ const moveEvent = new MouseEvent("mousemove", { clientX: 250, clientY: 350, bubbles: true });
183
+ document.dispatchEvent(moveEvent);
184
+
185
+ expect(callbacks.onDrag).toHaveBeenCalledWith(
186
+ expect.any(MouseEvent),
187
+ expect.objectContaining({
188
+ deltaX: 150,
189
+ deltaY: 150,
190
+ })
191
+ );
192
+ });
193
+
194
+ it("should handle negative deltas", () => {
195
+ cleanup = createDragHandler(element, callbacks);
196
+
197
+ // Start at (200, 200)
198
+ const startEvent = new MouseEvent("mousedown", { clientX: 200, clientY: 200, bubbles: true });
199
+ element.dispatchEvent(startEvent);
200
+
201
+ // Move to (100, 100)
202
+ const moveEvent = new MouseEvent("mousemove", { clientX: 100, clientY: 100, bubbles: true });
203
+ document.dispatchEvent(moveEvent);
204
+
205
+ expect(callbacks.onDrag).toHaveBeenCalledWith(
206
+ expect.any(MouseEvent),
207
+ expect.objectContaining({
208
+ deltaX: -100,
209
+ deltaY: -100,
210
+ })
211
+ );
212
+ });
213
+
214
+ it("should prevent default and stop propagation by default", () => {
215
+ cleanup = createDragHandler(element, callbacks);
216
+
217
+ const startEvent = new MouseEvent("mousedown", { clientX: 100, clientY: 200, bubbles: true, cancelable: true });
218
+ const preventDefaultSpy = vi.spyOn(startEvent, "preventDefault");
219
+ const stopPropagationSpy = vi.spyOn(startEvent, "stopPropagation");
220
+
221
+ element.dispatchEvent(startEvent);
222
+
223
+ expect(preventDefaultSpy).toHaveBeenCalled();
224
+ expect(stopPropagationSpy).toHaveBeenCalled();
225
+ });
226
+
227
+ it("should not prevent default when preventDefault option is false", () => {
228
+ cleanup = createDragHandler(element, callbacks, { preventDefault: false });
229
+
230
+ const startEvent = new MouseEvent("mousedown", { clientX: 100, clientY: 200, bubbles: true, cancelable: true });
231
+ const preventDefaultSpy = vi.spyOn(startEvent, "preventDefault");
232
+
233
+ element.dispatchEvent(startEvent);
234
+
235
+ expect(preventDefaultSpy).not.toHaveBeenCalled();
236
+ });
237
+
238
+ it("should not stop propagation when stopPropagation option is false", () => {
239
+ cleanup = createDragHandler(element, callbacks, { stopPropagation: false });
240
+
241
+ const startEvent = new MouseEvent("mousedown", { clientX: 100, clientY: 200, bubbles: true, cancelable: true });
242
+ const stopPropagationSpy = vi.spyOn(startEvent, "stopPropagation");
243
+
244
+ element.dispatchEvent(startEvent);
245
+
246
+ expect(stopPropagationSpy).not.toHaveBeenCalled();
247
+ });
248
+
249
+ it("should remove all event listeners on cleanup", () => {
250
+ cleanup = createDragHandler(element, callbacks);
251
+
252
+ cleanup();
253
+
254
+ // Clear mocks
255
+ vi.clearAllMocks();
256
+
257
+ // Try to trigger events - they should not be called
258
+ const startEvent = new MouseEvent("mousedown", { clientX: 100, clientY: 200, bubbles: true });
259
+ element.dispatchEvent(startEvent);
260
+
261
+ const moveEvent = new MouseEvent("mousemove", { clientX: 150, clientY: 250, bubbles: true });
262
+ document.dispatchEvent(moveEvent);
263
+
264
+ window.dispatchEvent(new Event("blur"));
265
+
266
+ expect(callbacks.onStart).not.toHaveBeenCalled();
267
+ expect(callbacks.onDrag).not.toHaveBeenCalled();
268
+ expect(callbacks.onCancel).not.toHaveBeenCalled();
269
+ });
270
+
271
+ it("should work without optional callbacks", () => {
272
+ const minimalCallbacks: DragCallbacks = {
273
+ onDrag: vi.fn(),
274
+ };
275
+ cleanup = createDragHandler(element, minimalCallbacks);
276
+
277
+ const startEvent = new MouseEvent("mousedown", { clientX: 100, clientY: 200, bubbles: true });
278
+ element.dispatchEvent(startEvent);
279
+
280
+ const moveEvent = new MouseEvent("mousemove", { clientX: 150, clientY: 250, bubbles: true });
281
+ document.dispatchEvent(moveEvent);
282
+
283
+ expect(minimalCallbacks.onDrag).toHaveBeenCalled();
284
+ });
285
+
286
+ it("should reset hasDragged flag after preventClick", () => {
287
+ cleanup = createDragHandler(element, callbacks);
288
+
289
+ // Start drag
290
+ const startEvent = new MouseEvent("mousedown", { clientX: 100, clientY: 200, bubbles: true });
291
+ element.dispatchEvent(startEvent);
292
+
293
+ // Move mouse
294
+ const moveEvent = new MouseEvent("mousemove", { clientX: 150, clientY: 250, bubbles: true });
295
+ document.dispatchEvent(moveEvent);
296
+
297
+ // Stop drag
298
+ const stopEvent = new MouseEvent("mouseup", { bubbles: true });
299
+ document.dispatchEvent(stopEvent);
300
+
301
+ // Click - should reset hasDragged
302
+ const clickEvent = new MouseEvent("click", { bubbles: true });
303
+ element.dispatchEvent(clickEvent);
304
+
305
+ // The hasDragged flag should be reset after preventClick
306
+ // This is tested implicitly by the fact that preventClick is called
307
+ expect(callbacks.onPreventClick).toHaveBeenCalled();
308
+ });
309
+
310
+ it("should work with SVGElement", () => {
311
+ const svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
312
+ document.body.appendChild(svgElement);
313
+
314
+ cleanup = createDragHandler(svgElement, callbacks);
315
+
316
+ const startEvent = new MouseEvent("mousedown", { clientX: 100, clientY: 200, bubbles: true });
317
+ svgElement.dispatchEvent(startEvent);
318
+
319
+ expect(callbacks.onStart).toHaveBeenCalled();
320
+
321
+ cleanup();
322
+ document.body.removeChild(svgElement);
323
+ });
324
+ });
325
+
@@ -0,0 +1,71 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+ import { getNodeProvider } from "./getNodeProvider";
3
+
4
+ describe("getNodeProvider", () => {
5
+ let nodeProvider: HTMLElement;
6
+
7
+ beforeEach(() => {
8
+ nodeProvider = document.createElement("div");
9
+ nodeProvider.setAttribute("data-role", "node-provider");
10
+ });
11
+
12
+ afterEach(() => {
13
+ if (document.body.contains(nodeProvider)) {
14
+ document.body.removeChild(nodeProvider);
15
+ }
16
+ // Remove any other node providers
17
+ const existingProviders = document.querySelectorAll('[data-role="node-provider"]');
18
+ existingProviders.forEach((provider) => {
19
+ if (document.body.contains(provider)) {
20
+ document.body.removeChild(provider);
21
+ }
22
+ });
23
+ });
24
+
25
+ it("should return node provider element when it exists", () => {
26
+ document.body.appendChild(nodeProvider);
27
+
28
+ const result = getNodeProvider();
29
+
30
+ expect(result).toBe(nodeProvider);
31
+ });
32
+
33
+ it("should return null when node provider does not exist", () => {
34
+ const result = getNodeProvider();
35
+
36
+ expect(result).toBeNull();
37
+ });
38
+
39
+ it("should return first node provider when multiple exist", () => {
40
+ const firstProvider = document.createElement("div");
41
+ firstProvider.setAttribute("data-role", "node-provider");
42
+ const secondProvider = document.createElement("div");
43
+ secondProvider.setAttribute("data-role", "node-provider");
44
+
45
+ document.body.appendChild(firstProvider);
46
+ document.body.appendChild(secondProvider);
47
+
48
+ const result = getNodeProvider();
49
+
50
+ expect(result).toBe(firstProvider);
51
+ });
52
+
53
+ it("should return null after provider is removed", () => {
54
+ document.body.appendChild(nodeProvider);
55
+ const result1 = getNodeProvider();
56
+ expect(result1).toBe(nodeProvider);
57
+
58
+ document.body.removeChild(nodeProvider);
59
+ const result2 = getNodeProvider();
60
+ expect(result2).toBeNull();
61
+ });
62
+
63
+ it("should return element with correct data-role attribute", () => {
64
+ document.body.appendChild(nodeProvider);
65
+
66
+ const result = getNodeProvider();
67
+
68
+ expect(result?.getAttribute("data-role")).toBe("node-provider");
69
+ });
70
+ });
71
+