@node-edit-utils/core 2.3.2 → 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 (136) hide show
  1. package/dist/lib/canvas/helpers/getCanvasContainerOrBody.d.ts +1 -0
  2. package/dist/lib/canvas/helpers/getCanvasWindowValue.d.ts +1 -1
  3. package/dist/lib/helpers/adjustForZoom.d.ts +1 -0
  4. package/dist/lib/helpers/createDragHandler.d.ts +69 -0
  5. package/dist/lib/helpers/getNodeProvider.d.ts +1 -0
  6. package/dist/lib/helpers/getNodeTools.d.ts +2 -0
  7. package/dist/lib/helpers/getViewportDimensions.d.ts +4 -0
  8. package/dist/lib/helpers/index.d.ts +9 -1
  9. package/dist/lib/helpers/parseTransform.d.ts +8 -0
  10. package/dist/lib/helpers/toggleClass.d.ts +1 -0
  11. package/dist/lib/viewport/label/getViewportLabelOverlay.d.ts +1 -0
  12. package/dist/lib/viewport/label/helpers/selectFirstViewportNode.d.ts +5 -0
  13. package/dist/lib/viewport/label/index.d.ts +5 -3
  14. package/dist/lib/viewport/label/isViewportDragging.d.ts +2 -0
  15. package/dist/lib/viewport/label/refreshViewportLabel.d.ts +8 -0
  16. package/dist/lib/viewport/label/removeViewportLabel.d.ts +5 -0
  17. package/dist/lib/viewport/label/setupViewportDrag.d.ts +1 -0
  18. package/dist/node-edit-utils.cjs.js +342 -280
  19. package/dist/node-edit-utils.esm.js +342 -280
  20. package/dist/node-edit-utils.umd.js +342 -280
  21. package/dist/node-edit-utils.umd.min.js +1 -1
  22. package/dist/styles.css +1 -1
  23. package/package.json +7 -2
  24. package/src/lib/canvas/createCanvasObserver.test.ts +242 -0
  25. package/src/lib/canvas/createCanvasObserver.ts +2 -2
  26. package/src/lib/canvas/disableCanvasKeyboard.test.ts +53 -0
  27. package/src/lib/canvas/disableCanvasKeyboard.ts +1 -1
  28. package/src/lib/canvas/disableCanvasTextMode.test.ts +53 -0
  29. package/src/lib/canvas/disableCanvasTextMode.ts +1 -1
  30. package/src/lib/canvas/enableCanvasKeyboard.test.ts +53 -0
  31. package/src/lib/canvas/enableCanvasKeyboard.ts +1 -1
  32. package/src/lib/canvas/enableCanvasTextMode.test.ts +53 -0
  33. package/src/lib/canvas/enableCanvasTextMode.ts +1 -1
  34. package/src/lib/canvas/helpers/applyCanvasState.test.ts +119 -0
  35. package/src/lib/canvas/helpers/applyCanvasState.ts +1 -1
  36. package/src/lib/canvas/helpers/getCanvasContainer.test.ts +62 -0
  37. package/src/lib/canvas/helpers/getCanvasContainerOrBody.test.ts +51 -0
  38. package/src/lib/canvas/helpers/getCanvasContainerOrBody.ts +6 -0
  39. package/src/lib/canvas/helpers/getCanvasWindowValue.test.ts +116 -0
  40. package/src/lib/canvas/helpers/getCanvasWindowValue.ts +2 -3
  41. package/src/lib/helpers/adjustForZoom.test.ts +65 -0
  42. package/src/lib/helpers/adjustForZoom.ts +4 -0
  43. package/src/lib/helpers/createDragHandler.test.ts +325 -0
  44. package/src/lib/helpers/createDragHandler.ts +171 -0
  45. package/src/lib/helpers/getNodeProvider.test.ts +71 -0
  46. package/src/lib/helpers/getNodeProvider.ts +4 -0
  47. package/src/lib/helpers/getNodeTools.test.ts +50 -0
  48. package/src/lib/helpers/getNodeTools.ts +6 -0
  49. package/src/lib/helpers/getViewportDimensions.test.ts +93 -0
  50. package/src/lib/helpers/getViewportDimensions.ts +7 -0
  51. package/src/lib/helpers/index.ts +9 -1
  52. package/src/lib/helpers/observer/connectMutationObserver.test.ts +127 -0
  53. package/src/lib/helpers/observer/connectResizeObserver.test.ts +147 -0
  54. package/src/lib/helpers/parseTransform.test.ts +117 -0
  55. package/src/lib/helpers/parseTransform.ts +9 -0
  56. package/src/lib/helpers/toggleClass.test.ts +71 -0
  57. package/src/lib/helpers/toggleClass.ts +9 -0
  58. package/src/lib/helpers/withRAF.test.ts +439 -0
  59. package/src/lib/node-tools/createNodeTools.test.ts +373 -0
  60. package/src/lib/node-tools/createNodeTools.ts +0 -1
  61. package/src/lib/node-tools/events/click/handleNodeClick.test.ts +109 -0
  62. package/src/lib/node-tools/events/setupEventListener.test.ts +136 -0
  63. package/src/lib/node-tools/highlight/clearHighlightFrame.test.ts +88 -0
  64. package/src/lib/node-tools/highlight/clearHighlightFrame.ts +2 -3
  65. package/src/lib/node-tools/highlight/createCornerHandles.test.ts +150 -0
  66. package/src/lib/node-tools/highlight/createHighlightFrame.test.ts +237 -0
  67. package/src/lib/node-tools/highlight/createHighlightFrame.ts +5 -9
  68. package/src/lib/node-tools/highlight/createTagLabel.test.ts +135 -0
  69. package/src/lib/node-tools/highlight/createToolsContainer.test.ts +97 -0
  70. package/src/lib/node-tools/highlight/createToolsContainer.ts +3 -6
  71. package/src/lib/node-tools/highlight/helpers/getElementBounds.test.ts +158 -0
  72. package/src/lib/node-tools/highlight/helpers/getElementBounds.ts +6 -5
  73. package/src/lib/node-tools/highlight/helpers/getHighlightFrameElement.test.ts +78 -0
  74. package/src/lib/node-tools/highlight/helpers/getHighlightFrameElement.ts +2 -3
  75. package/src/lib/node-tools/highlight/helpers/getScreenBounds.test.ts +133 -0
  76. package/src/lib/node-tools/highlight/highlightNode.test.ts +213 -0
  77. package/src/lib/node-tools/highlight/highlightNode.ts +7 -15
  78. package/src/lib/node-tools/highlight/refreshHighlightFrame.test.ts +323 -0
  79. package/src/lib/node-tools/highlight/refreshHighlightFrame.ts +12 -42
  80. package/src/lib/node-tools/highlight/updateHighlightFrameVisibility.test.ts +110 -0
  81. package/src/lib/node-tools/highlight/updateHighlightFrameVisibility.ts +2 -3
  82. package/src/lib/node-tools/select/helpers/getElementsFromPoint.test.ts +109 -0
  83. package/src/lib/node-tools/select/helpers/isInsideComponent.test.ts +81 -0
  84. package/src/lib/node-tools/select/helpers/isInsideViewport.test.ts +82 -0
  85. package/src/lib/node-tools/select/helpers/targetSameCandidates.test.ts +81 -0
  86. package/src/lib/node-tools/select/selectNode.test.ts +238 -0
  87. package/src/lib/node-tools/text/events/setupKeydownHandler.test.ts +91 -0
  88. package/src/lib/node-tools/text/events/setupMutationObserver.test.ts +213 -0
  89. package/src/lib/node-tools/text/events/setupNodeListeners.test.ts +133 -0
  90. package/src/lib/node-tools/text/helpers/enterTextEditMode.test.ts +50 -0
  91. package/src/lib/node-tools/text/helpers/handleTextChange.test.ts +201 -0
  92. package/src/lib/node-tools/text/helpers/hasTextContent.test.ts +101 -0
  93. package/src/lib/node-tools/text/helpers/insertLineBreak.test.ts +96 -0
  94. package/src/lib/node-tools/text/helpers/makeNodeEditable.test.ts +56 -0
  95. package/src/lib/node-tools/text/helpers/makeNodeNonEditable.test.ts +57 -0
  96. package/src/lib/node-tools/text/helpers/shouldEnterTextEditMode.test.ts +61 -0
  97. package/src/lib/node-tools/text/nodeText.test.ts +233 -0
  98. package/src/lib/post-message/processPostMessage.test.ts +218 -0
  99. package/src/lib/post-message/sendPostMessage.test.ts +120 -0
  100. package/src/lib/styles/styles.css +3 -3
  101. package/src/lib/viewport/createViewport.test.ts +267 -0
  102. package/src/lib/viewport/createViewport.ts +51 -51
  103. package/src/lib/viewport/events/setupEventListener.test.ts +103 -0
  104. package/src/lib/viewport/label/getViewportLabelOverlay.test.ts +77 -0
  105. package/src/lib/viewport/label/{getViewportLabelsOverlay.ts → getViewportLabelOverlay.ts} +6 -6
  106. package/src/lib/viewport/label/helpers/getLabelPosition.test.ts +51 -0
  107. package/src/lib/viewport/label/helpers/getLabelPosition.ts +3 -5
  108. package/src/lib/viewport/label/helpers/getTransformValues.test.ts +59 -0
  109. package/src/lib/viewport/label/helpers/getTransformValues.ts +3 -5
  110. package/src/lib/viewport/label/helpers/getZoomValue.test.ts +53 -0
  111. package/src/lib/viewport/label/helpers/selectFirstViewportNode.test.ts +105 -0
  112. package/src/lib/viewport/label/helpers/selectFirstViewportNode.ts +26 -0
  113. package/src/lib/viewport/label/index.ts +5 -3
  114. package/src/lib/viewport/label/isViewportDragging.test.ts +35 -0
  115. package/src/lib/viewport/label/isViewportDragging.ts +9 -0
  116. package/src/lib/viewport/label/refreshViewportLabel.test.ts +105 -0
  117. package/src/lib/viewport/label/refreshViewportLabel.ts +50 -0
  118. package/src/lib/viewport/label/refreshViewportLabels.test.ts +107 -0
  119. package/src/lib/viewport/label/refreshViewportLabels.ts +19 -52
  120. package/src/lib/viewport/label/removeViewportLabel.test.ts +67 -0
  121. package/src/lib/viewport/label/removeViewportLabel.ts +20 -0
  122. package/src/lib/viewport/label/setupViewportDrag.test.ts +249 -0
  123. package/src/lib/viewport/label/setupViewportDrag.ts +70 -0
  124. package/src/lib/viewport/resize/createResizeHandle.test.ts +37 -0
  125. package/src/lib/viewport/resize/createResizePresets.test.ts +75 -0
  126. package/src/lib/viewport/resize/updateActivePreset.test.ts +92 -0
  127. package/src/lib/viewport/width/calcConstrainedWidth.test.ts +47 -0
  128. package/src/lib/viewport/width/calcWidth.test.ts +68 -0
  129. package/src/lib/viewport/width/updateWidth.test.ts +78 -0
  130. package/src/lib/window/bindToWindow.test.ts +166 -0
  131. package/src/lib/window/bindToWindow.ts +1 -2
  132. package/dist/lib/viewport/label/getViewportLabelsOverlay.d.ts +0 -1
  133. package/dist/lib/viewport/label/isViewportLabelDragging.d.ts +0 -2
  134. package/dist/lib/viewport/label/setupViewportLabelDrag.d.ts +0 -1
  135. package/src/lib/viewport/label/isViewportLabelDragging.ts +0 -9
  136. package/src/lib/viewport/label/setupViewportLabelDrag.ts +0 -98
@@ -0,0 +1,267 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import * as getCanvasContainerModule from "../canvas/helpers/getCanvasContainer";
3
+ import type { DragState } from "../helpers/createDragHandler";
4
+ import * as createDragHandlerModule from "../helpers/createDragHandler";
5
+ import * as getNodeProviderModule from "../helpers/getNodeProvider";
6
+ import * as getNodeToolsModule from "../helpers/getNodeTools";
7
+ import * as refreshHighlightFrameModule from "../node-tools/highlight/refreshHighlightFrame";
8
+ import { DEFAULT_WIDTH } from "./constants";
9
+ import { createViewport } from "./createViewport";
10
+ import * as refreshViewportLabelModule from "./label/refreshViewportLabel";
11
+ import * as removeViewportLabelModule from "./label/removeViewportLabel";
12
+ import * as createResizeHandleModule from "./resize/createResizeHandle";
13
+ import * as createResizePresetsModule from "./resize/createResizePresets";
14
+ import * as calcWidthModule from "./width/calcWidth";
15
+ import * as updateWidthModule from "./width/updateWidth";
16
+
17
+ vi.mock("../canvas/helpers/getCanvasContainer");
18
+ vi.mock("../helpers/createDragHandler");
19
+ vi.mock("../helpers/getNodeProvider");
20
+ vi.mock("../helpers/getNodeTools");
21
+ vi.mock("../node-tools/highlight/refreshHighlightFrame");
22
+ vi.mock("./label/refreshViewportLabel");
23
+ vi.mock("./label/removeViewportLabel");
24
+ vi.mock("./resize/createResizeHandle");
25
+ vi.mock("./resize/createResizePresets");
26
+ vi.mock("./width/calcWidth");
27
+ vi.mock("./width/updateWidth");
28
+
29
+ describe("createViewport", () => {
30
+ let container: HTMLElement;
31
+ let canvas: HTMLElement;
32
+ let resizeHandle: HTMLElement;
33
+ let mockDragCleanup: ReturnType<typeof vi.fn>;
34
+ let mockNodeTools: {
35
+ getSelectedNode: ReturnType<typeof vi.fn>;
36
+ };
37
+ let mockNodeProvider: HTMLElement;
38
+
39
+ beforeEach(() => {
40
+ container = document.createElement("div");
41
+ container.classList.add("viewport");
42
+ document.body.appendChild(container);
43
+
44
+ canvas = document.createElement("div");
45
+ canvas.id = "canvas";
46
+ document.body.appendChild(canvas);
47
+
48
+ resizeHandle = document.createElement("div");
49
+ resizeHandle.classList.add("resize-handle");
50
+
51
+ mockDragCleanup = vi.fn();
52
+ mockNodeTools = {
53
+ getSelectedNode: vi.fn().mockReturnValue(null),
54
+ };
55
+ mockNodeProvider = document.createElement("div");
56
+
57
+ vi.mocked(getCanvasContainerModule.getCanvasContainer).mockReturnValue(canvas);
58
+ vi.mocked(createDragHandlerModule.createDragHandler).mockReturnValue(mockDragCleanup);
59
+ vi.mocked(getNodeProviderModule.getNodeProvider).mockReturnValue(mockNodeProvider);
60
+ vi.mocked(getNodeToolsModule.getNodeTools).mockReturnValue(
61
+ mockNodeTools as unknown as ReturnType<typeof getNodeToolsModule.getNodeTools>
62
+ );
63
+ vi.mocked(refreshHighlightFrameModule.refreshHighlightFrame).mockImplementation(() => {});
64
+ vi.mocked(refreshViewportLabelModule.refreshViewportLabel).mockImplementation(() => {});
65
+ vi.mocked(removeViewportLabelModule.removeViewportLabel).mockImplementation(() => {});
66
+ vi.mocked(createResizeHandleModule.createResizeHandle).mockReturnValue(resizeHandle);
67
+ vi.mocked(createResizePresetsModule.createResizePresets).mockReturnValue(document.createElement("div"));
68
+ vi.mocked(calcWidthModule.calcWidth).mockReturnValue(500);
69
+ vi.mocked(updateWidthModule.updateWidth).mockImplementation(() => {});
70
+ });
71
+
72
+ afterEach(() => {
73
+ document.body.removeChild(container);
74
+ document.body.removeChild(canvas);
75
+ vi.clearAllMocks();
76
+ });
77
+
78
+ it("should create viewport with default width", () => {
79
+ const viewport = createViewport(container);
80
+
81
+ expect(container.style.getPropertyValue("--container-width")).toBe(`${DEFAULT_WIDTH}px`);
82
+ expect(viewport).toHaveProperty("setWidth");
83
+ expect(viewport).toHaveProperty("cleanup");
84
+ });
85
+
86
+ it("should create viewport with initial width", () => {
87
+ const initialWidth = 600;
88
+ createViewport(container, initialWidth);
89
+
90
+ expect(container.style.getPropertyValue("--container-width")).toBe(`${initialWidth}px`);
91
+ });
92
+
93
+ it("should remove existing resize handle before creating new one", () => {
94
+ const existingHandle = document.createElement("div");
95
+ existingHandle.classList.add("resize-handle");
96
+ container.appendChild(existingHandle);
97
+
98
+ createViewport(container);
99
+
100
+ expect(container.querySelector(".resize-handle")).not.toBe(existingHandle);
101
+ });
102
+
103
+ it("should create resize handle", () => {
104
+ createViewport(container);
105
+
106
+ expect(createResizeHandleModule.createResizeHandle).toHaveBeenCalledWith(container);
107
+ });
108
+
109
+ it("should create resize presets", () => {
110
+ createViewport(container);
111
+
112
+ expect(createResizePresetsModule.createResizePresets).toHaveBeenCalledWith(resizeHandle, container, updateWidthModule.updateWidth);
113
+ });
114
+
115
+ it("should refresh viewport label on creation", () => {
116
+ createViewport(container);
117
+
118
+ expect(refreshViewportLabelModule.refreshViewportLabel).toHaveBeenCalledWith(container);
119
+ });
120
+
121
+ it("should set cursor to ew-resize during drag", () => {
122
+ let onDragCallback: ((event: MouseEvent, state: DragState & { deltaX: number; deltaY: number }) => void) | undefined;
123
+
124
+ vi.mocked(createDragHandlerModule.createDragHandler).mockImplementation((_element, callbacks) => {
125
+ onDragCallback = callbacks.onDrag;
126
+ return mockDragCleanup;
127
+ });
128
+
129
+ createViewport(container);
130
+
131
+ const mockEvent = new MouseEvent("mousemove");
132
+ const mockState: DragState & { deltaX: number; deltaY: number } = {
133
+ isDragging: true,
134
+ hasDragged: true,
135
+ startX: 0,
136
+ startY: 0,
137
+ deltaX: 0,
138
+ deltaY: 0,
139
+ };
140
+ onDragCallback?.(mockEvent, mockState);
141
+
142
+ expect(canvas.style.cursor).toBe("ew-resize");
143
+ });
144
+
145
+ it("should calculate and update width during drag", () => {
146
+ let onDragCallback: ((event: MouseEvent, state: DragState & { deltaX: number; deltaY: number }) => void) | undefined;
147
+
148
+ vi.mocked(createDragHandlerModule.createDragHandler).mockImplementation((_element, callbacks) => {
149
+ onDragCallback = callbacks.onDrag;
150
+ return mockDragCleanup;
151
+ });
152
+
153
+ createViewport(container);
154
+
155
+ const mockEvent = new MouseEvent("mousemove");
156
+ const mockState: DragState & { deltaX: number; deltaY: number } = {
157
+ isDragging: true,
158
+ hasDragged: true,
159
+ startX: 0,
160
+ startY: 0,
161
+ deltaX: 0,
162
+ deltaY: 0,
163
+ };
164
+ onDragCallback?.(mockEvent, mockState);
165
+
166
+ expect(calcWidthModule.calcWidth).toHaveBeenCalled();
167
+ expect(updateWidthModule.updateWidth).toHaveBeenCalled();
168
+ });
169
+
170
+ it("should reset cursor to default on drag stop", () => {
171
+ let onStopCallback: ((event: MouseEvent, state: DragState) => void) | undefined;
172
+
173
+ vi.mocked(createDragHandlerModule.createDragHandler).mockImplementation((_element, callbacks) => {
174
+ onStopCallback = callbacks.onStop;
175
+ return mockDragCleanup;
176
+ });
177
+
178
+ createViewport(container);
179
+ canvas.style.cursor = "ew-resize";
180
+
181
+ const mockEvent = new MouseEvent("mouseup");
182
+ const mockState: DragState = { isDragging: false, hasDragged: false, startX: 0, startY: 0 };
183
+ onStopCallback?.(mockEvent, mockState);
184
+
185
+ expect(canvas.style.cursor).toBe("default");
186
+ });
187
+
188
+ it("should reset cursor to default on drag cancel", () => {
189
+ let onCancelCallback: ((state: DragState) => void) | undefined;
190
+
191
+ vi.mocked(createDragHandlerModule.createDragHandler).mockImplementation((_element, callbacks) => {
192
+ onCancelCallback = callbacks.onCancel;
193
+ return mockDragCleanup;
194
+ });
195
+
196
+ createViewport(container);
197
+ canvas.style.cursor = "ew-resize";
198
+
199
+ const mockState: DragState = { isDragging: false, hasDragged: false, startX: 0, startY: 0 };
200
+ onCancelCallback?.(mockState);
201
+
202
+ expect(canvas.style.cursor).toBe("default");
203
+ });
204
+
205
+ it("should handle mouseleave when leaving window", () => {
206
+ createViewport(container);
207
+ canvas.style.cursor = "ew-resize";
208
+
209
+ const event = new MouseEvent("mouseleave", {
210
+ bubbles: true,
211
+ relatedTarget: null,
212
+ });
213
+ Object.defineProperty(event, "target", { value: document, writable: false });
214
+ document.dispatchEvent(event);
215
+
216
+ expect(canvas.style.cursor).toBe("default");
217
+ });
218
+
219
+ it("should cleanup all listeners and remove handle on cleanup", () => {
220
+ const viewport = createViewport(container);
221
+
222
+ viewport.cleanup();
223
+
224
+ expect(mockDragCleanup).toHaveBeenCalled();
225
+ expect(removeViewportLabelModule.removeViewportLabel).toHaveBeenCalledWith(container);
226
+ expect(container.querySelector(".resize-handle")).toBeNull();
227
+ });
228
+
229
+ it("should update width via setWidth", () => {
230
+ const viewport = createViewport(container);
231
+ const newWidth = 800;
232
+
233
+ viewport.setWidth(newWidth);
234
+
235
+ expect(updateWidthModule.updateWidth).toHaveBeenCalledWith(container, newWidth);
236
+ expect(refreshViewportLabelModule.refreshViewportLabel).toHaveBeenCalledWith(container);
237
+ });
238
+
239
+ it("should refresh highlight frame when node is selected and setWidth is called", () => {
240
+ const selectedNode = document.createElement("div");
241
+ mockNodeTools.getSelectedNode.mockReturnValue(selectedNode);
242
+
243
+ const viewport = createViewport(container);
244
+ viewport.setWidth(800);
245
+
246
+ expect(refreshHighlightFrameModule.refreshHighlightFrame).toHaveBeenCalledWith(selectedNode, mockNodeProvider);
247
+ });
248
+
249
+ it("should not refresh highlight frame when no node is selected", () => {
250
+ mockNodeTools.getSelectedNode.mockReturnValue(null);
251
+
252
+ const viewport = createViewport(container);
253
+ viewport.setWidth(800);
254
+
255
+ expect(refreshHighlightFrameModule.refreshHighlightFrame).not.toHaveBeenCalled();
256
+ });
257
+
258
+ it("should handle cleanup when canvas is null", () => {
259
+ vi.mocked(getCanvasContainerModule.getCanvasContainer).mockReturnValue(null);
260
+
261
+ const viewport = createViewport(container);
262
+
263
+ expect(() => {
264
+ viewport.cleanup();
265
+ }).not.toThrow();
266
+ });
267
+ });
@@ -1,8 +1,11 @@
1
1
  import { getCanvasContainer } from "../canvas/helpers/getCanvasContainer";
2
+ import { createDragHandler } from "../helpers/createDragHandler";
3
+ import { getNodeProvider } from "../helpers/getNodeProvider";
4
+ import { getNodeTools } from "../helpers/getNodeTools";
2
5
  import { refreshHighlightFrame } from "../node-tools/highlight/refreshHighlightFrame";
3
6
  import { DEFAULT_WIDTH } from "./constants";
4
- import { setupEventListener } from "./events/setupEventListener";
5
- import { refreshViewportLabels } from "./label/refreshViewportLabels";
7
+ import { refreshViewportLabel } from "./label/refreshViewportLabel";
8
+ import { removeViewportLabel } from "./label/removeViewportLabel";
6
9
  import { createResizeHandle } from "./resize/createResizeHandle";
7
10
  import { createResizePresets } from "./resize/createResizePresets";
8
11
  import type { Viewport } from "./types";
@@ -14,6 +17,7 @@ export const createViewport = (container: HTMLElement, initialWidth?: number): V
14
17
 
15
18
  // Remove any existing resize handle to prevent duplicates
16
19
  const existingHandle = container.querySelector(".resize-handle");
20
+
17
21
  if (existingHandle) {
18
22
  existingHandle.remove();
19
23
  }
@@ -24,72 +28,68 @@ export const createViewport = (container: HTMLElement, initialWidth?: number): V
24
28
 
25
29
  createResizePresets(resizeHandle, container, updateWidth);
26
30
 
27
- let isDragging: boolean = false;
28
- let startX: number = 0;
29
- let startWidth: number = 0;
30
-
31
- const startResize = (event: MouseEvent): void => {
32
- event.preventDefault();
33
- event.stopPropagation();
34
-
35
- isDragging = true;
36
- startX = event.clientX;
37
- startWidth = container.offsetWidth;
38
- };
39
-
40
- const handleResize = (event: MouseEvent): void => {
41
- if (!isDragging) return;
42
-
43
- if (canvas) {
44
- canvas.style.cursor = "ew-resize";
45
- }
46
-
47
- const width = calcWidth(event, startX, startWidth);
48
- updateWidth(container, width);
49
- };
31
+ // Track initial values for resize calculation
32
+ let startX = 0;
33
+ let startWidth = 0;
50
34
 
51
- const stopResize = (event: MouseEvent): void => {
52
- event.preventDefault();
53
- event.stopPropagation();
54
-
55
- if (canvas) {
56
- canvas.style.cursor = "default";
35
+ // Handle mouse leave for resize (specific to resize use case)
36
+ const handleMouseLeave = (event: MouseEvent): void => {
37
+ // Check if mouse is leaving the window/document
38
+ if (!event.relatedTarget && (event.target === document || event.target === document.documentElement)) {
39
+ if (canvas) {
40
+ canvas.style.cursor = "default";
41
+ }
57
42
  }
58
-
59
- isDragging = false;
60
43
  };
61
44
 
62
- const blurResize = (): void => {
63
- if (canvas) {
64
- canvas.style.cursor = "default";
65
- }
45
+ const removeDragListeners = createDragHandler(resizeHandle, {
46
+ onStart: (_event, { startX: dragStartX }) => {
47
+ startX = dragStartX;
48
+ startWidth = container.offsetWidth;
49
+ },
50
+ onDrag: (event) => {
51
+ if (canvas) {
52
+ canvas.style.cursor = "ew-resize";
53
+ }
66
54
 
67
- isDragging = false;
68
- };
55
+ const width = calcWidth(event, startX, startWidth);
56
+ updateWidth(container, width);
57
+ },
58
+ onStop: () => {
59
+ if (canvas) {
60
+ canvas.style.cursor = "default";
61
+ }
62
+ },
63
+ onCancel: () => {
64
+ if (canvas) {
65
+ canvas.style.cursor = "default";
66
+ }
67
+ },
68
+ onPreventClick: () => {},
69
+ });
69
70
 
70
- const removeListeners = setupEventListener(resizeHandle, startResize, handleResize, stopResize, blurResize);
71
+ document.addEventListener("mouseleave", handleMouseLeave);
71
72
 
72
- // Refresh viewport labels when viewport is created
73
- refreshViewportLabels();
73
+ // Create/refresh the label for this viewport
74
+ refreshViewportLabel(container);
74
75
 
75
76
  const cleanup = (): void => {
76
- isDragging = false;
77
- removeListeners();
77
+ removeDragListeners();
78
+ document.removeEventListener("mouseleave", handleMouseLeave);
78
79
  resizeHandle.remove();
79
- // Refresh labels after cleanup to remove this viewport's label if needed
80
- refreshViewportLabels();
80
+
81
+ // Remove the label for this viewport
82
+ removeViewportLabel(container);
81
83
  };
82
84
 
83
85
  return {
84
86
  setWidth: (width: number): void => {
85
87
  updateWidth(container, width);
86
- refreshViewportLabels();
88
+ refreshViewportLabel(container);
87
89
 
88
- // Refresh highlight frame when viewport width changes to update node positions
89
- // biome-ignore lint/suspicious/noExplicitAny: global window extension
90
- const nodeTools = (window as any).nodeTools;
90
+ const nodeTools = getNodeTools();
91
91
  const selectedNode = nodeTools?.getSelectedNode?.();
92
- const nodeProvider = document.querySelector('[data-role="node-provider"]') as HTMLElement | null;
92
+ const nodeProvider = getNodeProvider();
93
93
 
94
94
  if (selectedNode && nodeProvider) {
95
95
  refreshHighlightFrame(selectedNode, nodeProvider);
@@ -0,0 +1,103 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { setupEventListener } from "./setupEventListener";
3
+
4
+ describe("setupEventListener", () => {
5
+ let resizeHandle: HTMLElement;
6
+ let startResize: ReturnType<typeof vi.fn>;
7
+ let handleResize: ReturnType<typeof vi.fn>;
8
+ let stopResize: ReturnType<typeof vi.fn>;
9
+ let blurResize: ReturnType<typeof vi.fn>;
10
+ let cleanup: () => void;
11
+
12
+ beforeEach(() => {
13
+ resizeHandle = document.createElement("div");
14
+ document.body.appendChild(resizeHandle);
15
+
16
+ startResize = vi.fn();
17
+ handleResize = vi.fn();
18
+ stopResize = vi.fn();
19
+ blurResize = vi.fn();
20
+
21
+ cleanup = setupEventListener(resizeHandle, startResize, handleResize, stopResize, blurResize);
22
+ });
23
+
24
+ afterEach(() => {
25
+ cleanup();
26
+ document.body.removeChild(resizeHandle);
27
+ vi.clearAllMocks();
28
+ });
29
+
30
+ it("should call startResize on mousedown", () => {
31
+ const event = new MouseEvent("mousedown", { bubbles: true });
32
+ resizeHandle.dispatchEvent(event);
33
+
34
+ expect(startResize).toHaveBeenCalledTimes(1);
35
+ expect(startResize).toHaveBeenCalledWith(expect.any(MouseEvent));
36
+ });
37
+
38
+ it("should call handleResize on mousemove", () => {
39
+ const event = new MouseEvent("mousemove", { bubbles: true });
40
+ document.dispatchEvent(event);
41
+
42
+ expect(handleResize).toHaveBeenCalledTimes(1);
43
+ expect(handleResize).toHaveBeenCalledWith(expect.any(MouseEvent));
44
+ });
45
+
46
+ it("should call stopResize on mouseup", () => {
47
+ const event = new MouseEvent("mouseup", { bubbles: true });
48
+ document.dispatchEvent(event);
49
+
50
+ expect(stopResize).toHaveBeenCalledTimes(1);
51
+ expect(stopResize).toHaveBeenCalledWith(expect.any(MouseEvent));
52
+ });
53
+
54
+ it("should call blurResize on window blur", () => {
55
+ const event = new Event("blur");
56
+ window.dispatchEvent(event);
57
+
58
+ expect(blurResize).toHaveBeenCalledTimes(1);
59
+ });
60
+
61
+ it("should call blurResize on mouseleave when leaving window", () => {
62
+ const event = new MouseEvent("mouseleave", {
63
+ bubbles: true,
64
+ relatedTarget: null,
65
+ });
66
+ Object.defineProperty(event, "target", { value: document, writable: false });
67
+ document.dispatchEvent(event);
68
+
69
+ expect(blurResize).toHaveBeenCalledTimes(1);
70
+ });
71
+
72
+ it("should not call blurResize on mouseleave when relatedTarget exists", () => {
73
+ const relatedTarget = document.createElement("div");
74
+ const event = new MouseEvent("mouseleave", {
75
+ bubbles: true,
76
+ relatedTarget,
77
+ });
78
+ Object.defineProperty(event, "target", { value: document, writable: false });
79
+ document.dispatchEvent(event);
80
+
81
+ expect(blurResize).not.toHaveBeenCalled();
82
+ });
83
+
84
+ it("should remove all event listeners when cleanup is called", () => {
85
+ cleanup();
86
+
87
+ const mousedownEvent = new MouseEvent("mousedown", { bubbles: true });
88
+ resizeHandle.dispatchEvent(mousedownEvent);
89
+ expect(startResize).not.toHaveBeenCalled();
90
+
91
+ const mousemoveEvent = new MouseEvent("mousemove", { bubbles: true });
92
+ document.dispatchEvent(mousemoveEvent);
93
+ expect(handleResize).not.toHaveBeenCalled();
94
+
95
+ const mouseupEvent = new MouseEvent("mouseup", { bubbles: true });
96
+ document.dispatchEvent(mouseupEvent);
97
+ expect(stopResize).not.toHaveBeenCalled();
98
+
99
+ const blurEvent = new Event("blur");
100
+ window.dispatchEvent(blurEvent);
101
+ expect(blurResize).not.toHaveBeenCalled();
102
+ });
103
+ });
@@ -0,0 +1,77 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import * as getCanvasContainerOrBodyModule from "../../canvas/helpers/getCanvasContainerOrBody";
3
+ import * as getViewportDimensionsModule from "../../helpers/getViewportDimensions";
4
+ import { getViewportLabelOverlay } from "./getViewportLabelOverlay";
5
+
6
+ vi.mock("../../canvas/helpers/getCanvasContainerOrBody");
7
+ vi.mock("../../helpers/getViewportDimensions");
8
+
9
+ describe("getViewportLabelOverlay", () => {
10
+ let container: HTMLElement;
11
+
12
+ beforeEach(() => {
13
+ container = document.createElement("div");
14
+ document.body.appendChild(container);
15
+ vi.mocked(getCanvasContainerOrBodyModule.getCanvasContainerOrBody).mockReturnValue(container);
16
+ vi.mocked(getViewportDimensionsModule.getViewportDimensions).mockReturnValue({
17
+ width: 1920,
18
+ height: 1080,
19
+ });
20
+ });
21
+
22
+ afterEach(() => {
23
+ // Clean up any overlays
24
+ const overlays = container.querySelectorAll(".viewport-labels-overlay");
25
+ overlays.forEach((overlay) => {
26
+ overlay.remove();
27
+ });
28
+ document.body.removeChild(container);
29
+ vi.clearAllMocks();
30
+ });
31
+
32
+ it("should create a new SVG overlay if it doesn't exist", () => {
33
+ const overlay = getViewportLabelOverlay();
34
+
35
+ expect(overlay).toBeInstanceOf(SVGSVGElement);
36
+ expect(overlay.classList.contains("viewport-labels-overlay")).toBe(true);
37
+ expect(container.contains(overlay)).toBe(true);
38
+ });
39
+
40
+ it("should return existing overlay if it already exists", () => {
41
+ const overlay1 = getViewportLabelOverlay();
42
+ const overlay2 = getViewportLabelOverlay();
43
+
44
+ expect(overlay1).toBe(overlay2);
45
+ });
46
+
47
+ it("should set correct SVG attributes", () => {
48
+ const overlay = getViewportLabelOverlay();
49
+
50
+ expect(overlay.getAttribute("width")).toBe("1920");
51
+ expect(overlay.getAttribute("height")).toBe("1080");
52
+ });
53
+
54
+ it("should set correct CSS styles", () => {
55
+ const overlay = getViewportLabelOverlay();
56
+
57
+ expect(overlay.style.position).toBe("absolute");
58
+ expect(overlay.style.top).toBe("0px");
59
+ expect(overlay.style.left).toBe("0px");
60
+ expect(overlay.style.width).toBe("100vw");
61
+ expect(overlay.style.height).toBe("100vh");
62
+ expect(overlay.style.pointerEvents).toBe("none");
63
+ expect(overlay.style.zIndex).toBe("500");
64
+ });
65
+
66
+ it("should use viewport dimensions from getViewportDimensions", () => {
67
+ vi.mocked(getViewportDimensionsModule.getViewportDimensions).mockReturnValue({
68
+ width: 2560,
69
+ height: 1440,
70
+ });
71
+
72
+ const overlay = getViewportLabelOverlay();
73
+
74
+ expect(overlay.getAttribute("width")).toBe("2560");
75
+ expect(overlay.getAttribute("height")).toBe("1440");
76
+ });
77
+ });
@@ -1,8 +1,8 @@
1
- import { getCanvasContainer } from "../../canvas/helpers/getCanvasContainer";
1
+ import { getCanvasContainerOrBody } from "../../canvas/helpers/getCanvasContainerOrBody";
2
+ import { getViewportDimensions } from "../../helpers/getViewportDimensions";
2
3
 
3
- export const getViewportLabelsOverlay = (): SVGSVGElement => {
4
- const canvasContainer = getCanvasContainer();
5
- const container = canvasContainer || document.body;
4
+ export const getViewportLabelOverlay = (): SVGSVGElement => {
5
+ const container = getCanvasContainerOrBody();
6
6
 
7
7
  // Check if overlay already exists
8
8
  let overlay = container.querySelector(".viewport-labels-overlay") as SVGSVGElement | null;
@@ -21,8 +21,7 @@ export const getViewportLabelsOverlay = (): SVGSVGElement => {
21
21
  overlay.style.pointerEvents = "none";
22
22
  overlay.style.zIndex = "500";
23
23
 
24
- const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
25
- const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
24
+ const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
26
25
  overlay.setAttribute("width", viewportWidth.toString());
27
26
  overlay.setAttribute("height", viewportHeight.toString());
28
27
 
@@ -31,3 +30,4 @@ export const getViewportLabelsOverlay = (): SVGSVGElement => {
31
30
 
32
31
  return overlay;
33
32
  };
33
+
@@ -0,0 +1,51 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getLabelPosition } from "./getLabelPosition";
3
+
4
+ describe("getLabelPosition", () => {
5
+ it("should parse translate transform with positive values", () => {
6
+ const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
7
+ group.setAttribute("transform", "translate(100, 200)");
8
+
9
+ const result = getLabelPosition(group as unknown as SVGGElement);
10
+ expect(result).toEqual({ x: 100, y: 200 });
11
+ });
12
+
13
+ it("should parse translate transform with negative values", () => {
14
+ const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
15
+ group.setAttribute("transform", "translate(-50, -100)");
16
+
17
+ const result = getLabelPosition(group as unknown as SVGGElement);
18
+ expect(result).toEqual({ x: -50, y: -100 });
19
+ });
20
+
21
+ it("should parse translate transform with decimal values", () => {
22
+ const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
23
+ group.setAttribute("transform", "translate(123.45, 678.90)");
24
+
25
+ const result = getLabelPosition(group as unknown as SVGGElement);
26
+ expect(result).toEqual({ x: 123.45, y: 678.9 });
27
+ });
28
+
29
+ it("should return { x: 0, y: 0 } when transform is null", () => {
30
+ const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
31
+
32
+ const result = getLabelPosition(group as unknown as SVGGElement);
33
+ expect(result).toEqual({ x: 0, y: 0 });
34
+ });
35
+
36
+ it("should return { x: 0, y: 0 } when transform doesn't match pattern", () => {
37
+ const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
38
+ group.setAttribute("transform", "rotate(45)");
39
+
40
+ const result = getLabelPosition(group as unknown as SVGGElement);
41
+ expect(result).toEqual({ x: 0, y: 0 });
42
+ });
43
+
44
+ it("should handle transform with spaces", () => {
45
+ const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
46
+ group.setAttribute("transform", "translate(100, 200)");
47
+
48
+ const result = getLabelPosition(group as unknown as SVGGElement);
49
+ expect(result).toEqual({ x: 100, y: 200 });
50
+ });
51
+ });
@@ -1,8 +1,6 @@
1
+ import { parseTransform2d } from "../../../helpers/parseTransform";
2
+
1
3
  export const getLabelPosition = (labelGroup: SVGGElement): { x: number; y: number } => {
2
4
  const transform = labelGroup.getAttribute("transform");
3
- const match = transform?.match(/translate\((-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)\)/);
4
- if (match) {
5
- return { x: parseFloat(match[1]), y: parseFloat(match[2]) };
6
- }
7
- return { x: 0, y: 0 };
5
+ return parseTransform2d(transform);
8
6
  };