@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.
- package/dist/lib/viewport/label/getViewportLabelOverlay.d.ts +1 -0
- package/dist/lib/viewport/label/index.d.ts +5 -3
- package/dist/lib/viewport/label/isViewportDragging.d.ts +2 -0
- package/dist/lib/viewport/label/refreshViewportLabel.d.ts +8 -0
- package/dist/lib/viewport/label/removeViewportLabel.d.ts +5 -0
- package/dist/lib/viewport/label/setupViewportDrag.d.ts +1 -0
- package/dist/node-edit-utils.cjs.js +100 -62
- package/dist/node-edit-utils.esm.js +100 -62
- package/dist/node-edit-utils.umd.js +100 -62
- package/dist/node-edit-utils.umd.min.js +1 -1
- package/dist/styles.css +1 -1
- package/package.json +7 -2
- package/src/lib/canvas/createCanvasObserver.test.ts +242 -0
- package/src/lib/canvas/disableCanvasKeyboard.test.ts +53 -0
- package/src/lib/canvas/disableCanvasKeyboard.ts +1 -1
- package/src/lib/canvas/disableCanvasTextMode.test.ts +53 -0
- package/src/lib/canvas/disableCanvasTextMode.ts +1 -1
- package/src/lib/canvas/enableCanvasKeyboard.test.ts +53 -0
- package/src/lib/canvas/enableCanvasKeyboard.ts +1 -1
- package/src/lib/canvas/enableCanvasTextMode.test.ts +53 -0
- package/src/lib/canvas/enableCanvasTextMode.ts +1 -1
- package/src/lib/canvas/helpers/applyCanvasState.test.ts +119 -0
- package/src/lib/canvas/helpers/applyCanvasState.ts +1 -1
- package/src/lib/canvas/helpers/getCanvasContainer.test.ts +62 -0
- package/src/lib/canvas/helpers/getCanvasContainerOrBody.test.ts +51 -0
- package/src/lib/canvas/helpers/getCanvasWindowValue.test.ts +116 -0
- package/src/lib/helpers/adjustForZoom.test.ts +65 -0
- package/src/lib/helpers/createDragHandler.test.ts +325 -0
- package/src/lib/helpers/getNodeProvider.test.ts +71 -0
- package/src/lib/helpers/getNodeTools.test.ts +50 -0
- package/src/lib/helpers/getViewportDimensions.test.ts +93 -0
- package/src/lib/helpers/observer/connectMutationObserver.test.ts +127 -0
- package/src/lib/helpers/observer/connectResizeObserver.test.ts +147 -0
- package/src/lib/helpers/parseTransform.test.ts +117 -0
- package/src/lib/helpers/toggleClass.test.ts +71 -0
- package/src/lib/helpers/withRAF.test.ts +439 -0
- package/src/lib/node-tools/createNodeTools.test.ts +373 -0
- package/src/lib/node-tools/events/click/handleNodeClick.test.ts +109 -0
- package/src/lib/node-tools/events/setupEventListener.test.ts +136 -0
- package/src/lib/node-tools/highlight/clearHighlightFrame.test.ts +88 -0
- package/src/lib/node-tools/highlight/createCornerHandles.test.ts +150 -0
- package/src/lib/node-tools/highlight/createHighlightFrame.test.ts +237 -0
- package/src/lib/node-tools/highlight/createTagLabel.test.ts +135 -0
- package/src/lib/node-tools/highlight/createToolsContainer.test.ts +97 -0
- package/src/lib/node-tools/highlight/helpers/getElementBounds.test.ts +158 -0
- package/src/lib/node-tools/highlight/helpers/getHighlightFrameElement.test.ts +78 -0
- package/src/lib/node-tools/highlight/helpers/getScreenBounds.test.ts +133 -0
- package/src/lib/node-tools/highlight/highlightNode.test.ts +213 -0
- package/src/lib/node-tools/highlight/refreshHighlightFrame.test.ts +323 -0
- package/src/lib/node-tools/highlight/updateHighlightFrameVisibility.test.ts +110 -0
- package/src/lib/node-tools/select/helpers/getElementsFromPoint.test.ts +109 -0
- package/src/lib/node-tools/select/helpers/isInsideComponent.test.ts +81 -0
- package/src/lib/node-tools/select/helpers/isInsideViewport.test.ts +82 -0
- package/src/lib/node-tools/select/helpers/targetSameCandidates.test.ts +81 -0
- package/src/lib/node-tools/select/selectNode.test.ts +238 -0
- package/src/lib/node-tools/text/events/setupKeydownHandler.test.ts +91 -0
- package/src/lib/node-tools/text/events/setupMutationObserver.test.ts +213 -0
- package/src/lib/node-tools/text/events/setupNodeListeners.test.ts +133 -0
- package/src/lib/node-tools/text/helpers/enterTextEditMode.test.ts +50 -0
- package/src/lib/node-tools/text/helpers/handleTextChange.test.ts +201 -0
- package/src/lib/node-tools/text/helpers/hasTextContent.test.ts +101 -0
- package/src/lib/node-tools/text/helpers/insertLineBreak.test.ts +96 -0
- package/src/lib/node-tools/text/helpers/makeNodeEditable.test.ts +56 -0
- package/src/lib/node-tools/text/helpers/makeNodeNonEditable.test.ts +57 -0
- package/src/lib/node-tools/text/helpers/shouldEnterTextEditMode.test.ts +61 -0
- package/src/lib/node-tools/text/nodeText.test.ts +233 -0
- package/src/lib/post-message/processPostMessage.test.ts +218 -0
- package/src/lib/post-message/sendPostMessage.test.ts +120 -0
- package/src/lib/styles/styles.css +2 -2
- package/src/lib/viewport/createViewport.test.ts +267 -0
- package/src/lib/viewport/createViewport.ts +7 -4
- package/src/lib/viewport/events/setupEventListener.test.ts +103 -0
- package/src/lib/viewport/label/getViewportLabelOverlay.test.ts +77 -0
- package/src/lib/viewport/label/{getViewportLabelsOverlay.ts → getViewportLabelOverlay.ts} +2 -1
- package/src/lib/viewport/label/helpers/getLabelPosition.test.ts +51 -0
- package/src/lib/viewport/label/helpers/getTransformValues.test.ts +59 -0
- package/src/lib/viewport/label/helpers/getZoomValue.test.ts +53 -0
- package/src/lib/viewport/label/helpers/selectFirstViewportNode.test.ts +105 -0
- package/src/lib/viewport/label/helpers/selectFirstViewportNode.ts +8 -0
- package/src/lib/viewport/label/index.ts +5 -3
- package/src/lib/viewport/label/isViewportDragging.test.ts +35 -0
- package/src/lib/viewport/label/isViewportDragging.ts +9 -0
- package/src/lib/viewport/label/refreshViewportLabel.test.ts +105 -0
- package/src/lib/viewport/label/refreshViewportLabel.ts +50 -0
- package/src/lib/viewport/label/refreshViewportLabels.test.ts +107 -0
- package/src/lib/viewport/label/refreshViewportLabels.ts +17 -50
- package/src/lib/viewport/label/removeViewportLabel.test.ts +67 -0
- package/src/lib/viewport/label/removeViewportLabel.ts +20 -0
- package/src/lib/viewport/label/setupViewportDrag.test.ts +249 -0
- package/src/lib/viewport/label/{setupViewportLabelDrag.ts → setupViewportDrag.ts} +14 -14
- package/src/lib/viewport/resize/createResizeHandle.test.ts +37 -0
- package/src/lib/viewport/resize/createResizePresets.test.ts +75 -0
- package/src/lib/viewport/resize/updateActivePreset.test.ts +92 -0
- package/src/lib/viewport/width/calcConstrainedWidth.test.ts +47 -0
- package/src/lib/viewport/width/calcWidth.test.ts +68 -0
- package/src/lib/viewport/width/updateWidth.test.ts +78 -0
- package/src/lib/window/bindToWindow.test.ts +166 -0
- package/dist/lib/viewport/label/getViewportLabelsOverlay.d.ts +0 -1
- package/dist/lib/viewport/label/isViewportLabelDragging.d.ts +0 -2
- package/dist/lib/viewport/label/setupViewportLabelDrag.d.ts +0 -1
- package/src/lib/viewport/label/isViewportLabelDragging.ts +0 -9
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { DragState } from "../../helpers/createDragHandler";
|
|
3
|
+
import * as createDragHandlerModule from "../../helpers/createDragHandler";
|
|
4
|
+
import * as getNodeToolsModule from "../../helpers/getNodeTools";
|
|
5
|
+
import * as sendPostMessageModule from "../../post-message/sendPostMessage";
|
|
6
|
+
import * as getLabelPositionModule from "./helpers/getLabelPosition";
|
|
7
|
+
import * as getTransformValuesModule from "./helpers/getTransformValues";
|
|
8
|
+
import * as getZoomValueModule from "./helpers/getZoomValue";
|
|
9
|
+
import * as selectFirstViewportNodeModule from "./helpers/selectFirstViewportNode";
|
|
10
|
+
import * as setViewportDraggingModule from "./isViewportDragging";
|
|
11
|
+
import { setupViewportDrag } from "./setupViewportDrag";
|
|
12
|
+
|
|
13
|
+
vi.mock("../../helpers/createDragHandler");
|
|
14
|
+
vi.mock("../../helpers/getNodeTools");
|
|
15
|
+
vi.mock("../../post-message/sendPostMessage");
|
|
16
|
+
vi.mock("./helpers/getLabelPosition");
|
|
17
|
+
vi.mock("./helpers/getTransformValues");
|
|
18
|
+
vi.mock("./helpers/getZoomValue");
|
|
19
|
+
vi.mock("./helpers/selectFirstViewportNode");
|
|
20
|
+
vi.mock("./isViewportDragging");
|
|
21
|
+
|
|
22
|
+
describe("setupViewportDrag", () => {
|
|
23
|
+
let labelElement: SVGTextElement;
|
|
24
|
+
let labelGroup: SVGGElement;
|
|
25
|
+
let viewportElement: HTMLElement;
|
|
26
|
+
let mockCleanup: ReturnType<typeof vi.fn>;
|
|
27
|
+
let mockNodeTools: {
|
|
28
|
+
refreshHighlightFrame: ReturnType<typeof vi.fn>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
labelGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
33
|
+
labelElement = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
34
|
+
labelGroup.appendChild(labelElement);
|
|
35
|
+
|
|
36
|
+
viewportElement = document.createElement("div");
|
|
37
|
+
viewportElement.style.transform = "translate3d(100px, 200px, 0px)";
|
|
38
|
+
document.body.appendChild(viewportElement);
|
|
39
|
+
|
|
40
|
+
mockCleanup = vi.fn();
|
|
41
|
+
mockNodeTools = {
|
|
42
|
+
refreshHighlightFrame: vi.fn(),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
vi.mocked(createDragHandlerModule.createDragHandler).mockReturnValue(mockCleanup);
|
|
46
|
+
vi.mocked(getNodeToolsModule.getNodeTools).mockReturnValue(
|
|
47
|
+
mockNodeTools as unknown as ReturnType<typeof getNodeToolsModule.getNodeTools>
|
|
48
|
+
);
|
|
49
|
+
vi.mocked(sendPostMessageModule.sendPostMessage).mockImplementation(() => {});
|
|
50
|
+
vi.mocked(getLabelPositionModule.getLabelPosition).mockReturnValue({ x: 50, y: 100 });
|
|
51
|
+
vi.mocked(getTransformValuesModule.getTransformValues).mockReturnValue({ x: 100, y: 200 });
|
|
52
|
+
vi.mocked(getZoomValueModule.getZoomValue).mockReturnValue(1);
|
|
53
|
+
vi.mocked(selectFirstViewportNodeModule.selectFirstViewportNode).mockImplementation(() => {});
|
|
54
|
+
vi.mocked(setViewportDraggingModule.setViewportDragging).mockImplementation(() => {});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
document.body.removeChild(viewportElement);
|
|
59
|
+
vi.clearAllMocks();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should return cleanup function from createDragHandler", () => {
|
|
63
|
+
const cleanup = setupViewportDrag(labelElement, viewportElement, "test-viewport");
|
|
64
|
+
|
|
65
|
+
expect(cleanup).toBe(mockCleanup);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should set dragging to true on drag start", () => {
|
|
69
|
+
let onStartCallback: ((event: MouseEvent, state: DragState) => void) | undefined;
|
|
70
|
+
|
|
71
|
+
vi.mocked(createDragHandlerModule.createDragHandler).mockImplementation((_element, callbacks) => {
|
|
72
|
+
onStartCallback = callbacks.onStart;
|
|
73
|
+
return mockCleanup;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
setupViewportDrag(labelElement, viewportElement, "test-viewport");
|
|
77
|
+
|
|
78
|
+
const mockEvent = new MouseEvent("mousedown");
|
|
79
|
+
const mockState: DragState = { isDragging: true, hasDragged: false, startX: 0, startY: 0 };
|
|
80
|
+
onStartCallback?.(mockEvent, mockState);
|
|
81
|
+
|
|
82
|
+
expect(setViewportDraggingModule.setViewportDragging).toHaveBeenCalledWith(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should select first viewport node on drag start", () => {
|
|
86
|
+
let onStartCallback: ((event: MouseEvent, state: DragState) => void) | undefined;
|
|
87
|
+
|
|
88
|
+
vi.mocked(createDragHandlerModule.createDragHandler).mockImplementation((_element, callbacks) => {
|
|
89
|
+
onStartCallback = callbacks.onStart;
|
|
90
|
+
return mockCleanup;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
setupViewportDrag(labelElement, viewportElement, "test-viewport");
|
|
94
|
+
|
|
95
|
+
const mockEvent = new MouseEvent("mousedown");
|
|
96
|
+
const mockState: DragState = { isDragging: true, hasDragged: false, startX: 0, startY: 0 };
|
|
97
|
+
onStartCallback?.(mockEvent, mockState);
|
|
98
|
+
|
|
99
|
+
expect(selectFirstViewportNodeModule.selectFirstViewportNode).toHaveBeenCalledWith(viewportElement);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should update viewport and label positions during drag", () => {
|
|
103
|
+
let onStartCallback: ((event: MouseEvent, state: DragState) => void) | undefined;
|
|
104
|
+
let onDragCallback: ((event: MouseEvent, state: DragState & { deltaX: number; deltaY: number }) => void) | undefined;
|
|
105
|
+
|
|
106
|
+
vi.mocked(createDragHandlerModule.createDragHandler).mockImplementation((_element, callbacks) => {
|
|
107
|
+
onStartCallback = callbacks.onStart;
|
|
108
|
+
onDragCallback = callbacks.onDrag;
|
|
109
|
+
return mockCleanup;
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
setupViewportDrag(labelElement, viewportElement, "test-viewport");
|
|
113
|
+
|
|
114
|
+
// Call onStart first to capture initial positions
|
|
115
|
+
const startEvent = new MouseEvent("mousedown");
|
|
116
|
+
const startState: DragState = { isDragging: true, hasDragged: false, startX: 0, startY: 0 };
|
|
117
|
+
onStartCallback?.(startEvent, startState);
|
|
118
|
+
|
|
119
|
+
const mockEvent = new MouseEvent("mousemove");
|
|
120
|
+
const mockState: DragState & { deltaX: number; deltaY: number } = {
|
|
121
|
+
isDragging: true,
|
|
122
|
+
hasDragged: true,
|
|
123
|
+
startX: 0,
|
|
124
|
+
startY: 0,
|
|
125
|
+
deltaX: 50,
|
|
126
|
+
deltaY: 100,
|
|
127
|
+
};
|
|
128
|
+
onDragCallback?.(mockEvent, mockState);
|
|
129
|
+
|
|
130
|
+
// Label position should use raw delta (screen space)
|
|
131
|
+
expect(labelGroup.getAttribute("transform")).toBe("translate(100, 200)");
|
|
132
|
+
// Viewport position should use zoom-adjusted delta (canvas space)
|
|
133
|
+
expect(viewportElement.style.transform).toBe("translate3d(150px, 300px, 0)");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("should account for zoom level during drag", () => {
|
|
137
|
+
vi.mocked(getZoomValueModule.getZoomValue).mockReturnValue(2);
|
|
138
|
+
|
|
139
|
+
let onStartCallback: ((event: MouseEvent, state: DragState) => void) | undefined;
|
|
140
|
+
let onDragCallback: ((event: MouseEvent, state: DragState & { deltaX: number; deltaY: number }) => void) | undefined;
|
|
141
|
+
|
|
142
|
+
vi.mocked(createDragHandlerModule.createDragHandler).mockImplementation((_element, callbacks) => {
|
|
143
|
+
onStartCallback = callbacks.onStart;
|
|
144
|
+
onDragCallback = callbacks.onDrag;
|
|
145
|
+
return mockCleanup;
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
setupViewportDrag(labelElement, viewportElement, "test-viewport");
|
|
149
|
+
|
|
150
|
+
// Call onStart first to capture initial positions
|
|
151
|
+
const startEvent = new MouseEvent("mousedown");
|
|
152
|
+
const startState: DragState = { isDragging: true, hasDragged: false, startX: 0, startY: 0 };
|
|
153
|
+
onStartCallback?.(startEvent, startState);
|
|
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: 100,
|
|
162
|
+
deltaY: 200,
|
|
163
|
+
};
|
|
164
|
+
onDragCallback?.(mockEvent, mockState);
|
|
165
|
+
|
|
166
|
+
// Delta should be divided by zoom (100/2 = 50, 200/2 = 100)
|
|
167
|
+
expect(viewportElement.style.transform).toBe("translate3d(150px, 300px, 0)");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("should set dragging to false and refresh highlight on drag stop with hasDragged", () => {
|
|
171
|
+
let onStopCallback: ((event: MouseEvent, state: DragState) => void) | undefined;
|
|
172
|
+
|
|
173
|
+
vi.mocked(createDragHandlerModule.createDragHandler).mockImplementation((_element, callbacks) => {
|
|
174
|
+
onStopCallback = callbacks.onStop;
|
|
175
|
+
return mockCleanup;
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
setupViewportDrag(labelElement, viewportElement, "test-viewport");
|
|
179
|
+
|
|
180
|
+
const mockEvent = new MouseEvent("mouseup");
|
|
181
|
+
const mockState: DragState = { isDragging: false, hasDragged: true, startX: 0, startY: 0 };
|
|
182
|
+
onStopCallback?.(mockEvent, mockState);
|
|
183
|
+
|
|
184
|
+
expect(setViewportDraggingModule.setViewportDragging).toHaveBeenCalledWith(false);
|
|
185
|
+
expect(mockNodeTools.refreshHighlightFrame).toHaveBeenCalled();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("should send postMessage with final position on drag stop when hasDragged is true", () => {
|
|
189
|
+
vi.mocked(getTransformValuesModule.getTransformValues).mockReturnValue({ x: 150, y: 250 });
|
|
190
|
+
|
|
191
|
+
let onStopCallback: ((event: MouseEvent, state: DragState) => void) | undefined;
|
|
192
|
+
|
|
193
|
+
vi.mocked(createDragHandlerModule.createDragHandler).mockImplementation((_element, callbacks) => {
|
|
194
|
+
onStopCallback = callbacks.onStop;
|
|
195
|
+
return mockCleanup;
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
setupViewportDrag(labelElement, viewportElement, "test-viewport");
|
|
199
|
+
|
|
200
|
+
const mockEvent = new MouseEvent("mouseup");
|
|
201
|
+
const mockState: DragState = { isDragging: false, hasDragged: true, startX: 0, startY: 0 };
|
|
202
|
+
onStopCallback?.(mockEvent, mockState);
|
|
203
|
+
|
|
204
|
+
expect(sendPostMessageModule.sendPostMessage).toHaveBeenCalledWith("viewport-position-changed", {
|
|
205
|
+
viewportName: "test-viewport",
|
|
206
|
+
x: 150,
|
|
207
|
+
y: 250,
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("should send postMessage with final position on drag stop even when hasDragged is false", () => {
|
|
212
|
+
vi.mocked(getTransformValuesModule.getTransformValues).mockReturnValue({ x: 100, y: 200 });
|
|
213
|
+
|
|
214
|
+
let onStopCallback: ((event: MouseEvent, state: DragState) => void) | undefined;
|
|
215
|
+
|
|
216
|
+
vi.mocked(createDragHandlerModule.createDragHandler).mockImplementation((_element, callbacks) => {
|
|
217
|
+
onStopCallback = callbacks.onStop;
|
|
218
|
+
return mockCleanup;
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
setupViewportDrag(labelElement, viewportElement, "test-viewport");
|
|
222
|
+
|
|
223
|
+
const mockEvent = new MouseEvent("mouseup");
|
|
224
|
+
const mockState: DragState = { isDragging: false, hasDragged: false, startX: 0, startY: 0 };
|
|
225
|
+
onStopCallback?.(mockEvent, mockState);
|
|
226
|
+
|
|
227
|
+
expect(sendPostMessageModule.sendPostMessage).toHaveBeenCalledWith("viewport-position-changed", {
|
|
228
|
+
viewportName: "test-viewport",
|
|
229
|
+
x: 100,
|
|
230
|
+
y: 200,
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("should set dragging to false on cancel", () => {
|
|
235
|
+
let onCancelCallback: ((state: DragState) => void) | undefined;
|
|
236
|
+
|
|
237
|
+
vi.mocked(createDragHandlerModule.createDragHandler).mockImplementation((_element, callbacks) => {
|
|
238
|
+
onCancelCallback = callbacks.onCancel;
|
|
239
|
+
return mockCleanup;
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
setupViewportDrag(labelElement, viewportElement, "test-viewport");
|
|
243
|
+
|
|
244
|
+
const mockState: DragState = { isDragging: false, hasDragged: false, startX: 0, startY: 0 };
|
|
245
|
+
onCancelCallback?.(mockState);
|
|
246
|
+
|
|
247
|
+
expect(setViewportDraggingModule.setViewportDragging).toHaveBeenCalledWith(false);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
@@ -5,9 +5,9 @@ import { getLabelPosition } from "./helpers/getLabelPosition";
|
|
|
5
5
|
import { getTransformValues } from "./helpers/getTransformValues";
|
|
6
6
|
import { getZoomValue } from "./helpers/getZoomValue";
|
|
7
7
|
import { selectFirstViewportNode } from "./helpers/selectFirstViewportNode";
|
|
8
|
-
import {
|
|
8
|
+
import { setViewportDragging } from "./isViewportDragging";
|
|
9
9
|
|
|
10
|
-
export const
|
|
10
|
+
export const setupViewportDrag = (labelElement: SVGTextElement, viewportElement: HTMLElement, viewportName: string): (() => void) => {
|
|
11
11
|
// Get the parent group element that contains the label
|
|
12
12
|
const labelGroup = labelElement.parentElement as unknown as SVGGElement;
|
|
13
13
|
|
|
@@ -17,7 +17,7 @@ export const setupViewportLabelDrag = (labelElement: SVGTextElement, viewportEle
|
|
|
17
17
|
|
|
18
18
|
return createDragHandler(labelElement, {
|
|
19
19
|
onStart: () => {
|
|
20
|
-
|
|
20
|
+
setViewportDragging(true);
|
|
21
21
|
initialTransform = getTransformValues(viewportElement);
|
|
22
22
|
initialLabelPosition = getLabelPosition(labelGroup);
|
|
23
23
|
selectFirstViewportNode(viewportElement);
|
|
@@ -42,28 +42,28 @@ export const setupViewportLabelDrag = (labelElement: SVGTextElement, viewportEle
|
|
|
42
42
|
viewportElement.style.transform = `translate3d(${newX}px, ${newY}px, 0)`;
|
|
43
43
|
},
|
|
44
44
|
onStop: (_event, { hasDragged }) => {
|
|
45
|
-
|
|
45
|
+
setViewportDragging(false);
|
|
46
|
+
|
|
47
|
+
const finalTransform = getTransformValues(viewportElement);
|
|
46
48
|
|
|
47
49
|
// If it was a drag, handle drag completion
|
|
48
50
|
if (hasDragged) {
|
|
49
|
-
const finalTransform = getTransformValues(viewportElement);
|
|
50
|
-
|
|
51
51
|
// Trigger refresh after drag completes to update highlight frame and labels
|
|
52
52
|
const nodeTools = getNodeTools();
|
|
53
53
|
if (nodeTools?.refreshHighlightFrame) {
|
|
54
54
|
nodeTools.refreshHighlightFrame();
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
// Notify parent about the new position
|
|
58
|
-
sendPostMessage("viewport-position-changed", {
|
|
59
|
-
viewportName,
|
|
60
|
-
x: finalTransform.x,
|
|
61
|
-
y: finalTransform.y,
|
|
62
|
-
});
|
|
63
56
|
}
|
|
57
|
+
|
|
58
|
+
// Always notify parent about the new position on drag stop
|
|
59
|
+
sendPostMessage("viewport-position-changed", {
|
|
60
|
+
viewportName,
|
|
61
|
+
x: finalTransform.x,
|
|
62
|
+
y: finalTransform.y,
|
|
63
|
+
});
|
|
64
64
|
},
|
|
65
65
|
onCancel: () => {
|
|
66
|
-
|
|
66
|
+
setViewportDragging(false);
|
|
67
67
|
},
|
|
68
68
|
onPreventClick: () => {},
|
|
69
69
|
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { createResizeHandle } from "./createResizeHandle";
|
|
3
|
+
|
|
4
|
+
describe("createResizeHandle", () => {
|
|
5
|
+
let container: HTMLElement;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
container = document.createElement("div");
|
|
9
|
+
document.body.appendChild(container);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
document.body.removeChild(container);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should create a resize handle element", () => {
|
|
17
|
+
const handle = createResizeHandle(container);
|
|
18
|
+
expect(handle).toBeInstanceOf(HTMLElement);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should have the resize-handle class", () => {
|
|
22
|
+
const handle = createResizeHandle(container);
|
|
23
|
+
expect(handle.classList.contains("resize-handle")).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should append handle to container", () => {
|
|
27
|
+
const handle = createResizeHandle(container);
|
|
28
|
+
expect(container.contains(handle)).toBe(true);
|
|
29
|
+
expect(container.children).toContain(handle);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should return the created handle element", () => {
|
|
33
|
+
const handle = createResizeHandle(container);
|
|
34
|
+
const foundHandle = container.querySelector(".resize-handle");
|
|
35
|
+
expect(handle).toBe(foundHandle);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { RESIZE_PRESETS } from "../constants";
|
|
3
|
+
import { createResizePresets } from "./createResizePresets";
|
|
4
|
+
|
|
5
|
+
describe("createResizePresets", () => {
|
|
6
|
+
let container: HTMLElement;
|
|
7
|
+
let resizeHandle: HTMLElement;
|
|
8
|
+
const mockUpdateWidth = vi.fn();
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
container = document.createElement("div");
|
|
12
|
+
resizeHandle = document.createElement("div");
|
|
13
|
+
container.appendChild(resizeHandle);
|
|
14
|
+
document.body.appendChild(container);
|
|
15
|
+
mockUpdateWidth.mockClear();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
document.body.removeChild(container);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should create presets container", () => {
|
|
23
|
+
const presets = createResizePresets(resizeHandle, container, mockUpdateWidth);
|
|
24
|
+
expect(presets).toBeInstanceOf(HTMLElement);
|
|
25
|
+
expect(presets.classList.contains("resize-presets")).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should append presets to resize handle", () => {
|
|
29
|
+
const presets = createResizePresets(resizeHandle, container, mockUpdateWidth);
|
|
30
|
+
expect(resizeHandle.contains(presets)).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should create buttons for all presets", () => {
|
|
34
|
+
createResizePresets(resizeHandle, container, mockUpdateWidth);
|
|
35
|
+
const buttons = resizeHandle.querySelectorAll(".resize-preset-button");
|
|
36
|
+
expect(buttons).toHaveLength(RESIZE_PRESETS.length);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should set correct text content for each button", () => {
|
|
40
|
+
createResizePresets(resizeHandle, container, mockUpdateWidth);
|
|
41
|
+
const buttons = resizeHandle.querySelectorAll<HTMLButtonElement>(".resize-preset-button");
|
|
42
|
+
RESIZE_PRESETS.forEach((preset, index) => {
|
|
43
|
+
expect(buttons[index].textContent).toBe(preset.name);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should call updateWidth with correct rawValue when button is clicked", () => {
|
|
48
|
+
createResizePresets(resizeHandle, container, mockUpdateWidth);
|
|
49
|
+
const buttons = resizeHandle.querySelectorAll<HTMLButtonElement>(".resize-preset-button");
|
|
50
|
+
|
|
51
|
+
const firstPreset = RESIZE_PRESETS[0];
|
|
52
|
+
buttons[0].click();
|
|
53
|
+
|
|
54
|
+
expect(mockUpdateWidth).toHaveBeenCalledTimes(1);
|
|
55
|
+
expect(mockUpdateWidth).toHaveBeenCalledWith(container, firstPreset.rawValue);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should handle clicks on all preset buttons", () => {
|
|
59
|
+
createResizePresets(resizeHandle, container, mockUpdateWidth);
|
|
60
|
+
const buttons = resizeHandle.querySelectorAll<HTMLButtonElement>(".resize-preset-button");
|
|
61
|
+
|
|
62
|
+
buttons.forEach((button, index) => {
|
|
63
|
+
button.click();
|
|
64
|
+
expect(mockUpdateWidth).toHaveBeenCalledWith(container, RESIZE_PRESETS[index].rawValue);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(mockUpdateWidth).toHaveBeenCalledTimes(RESIZE_PRESETS.length);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should return the presets element", () => {
|
|
71
|
+
const presets = createResizePresets(resizeHandle, container, mockUpdateWidth);
|
|
72
|
+
const foundPresets = resizeHandle.querySelector(".resize-presets");
|
|
73
|
+
expect(presets).toBe(foundPresets);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { RESIZE_PRESETS } from "../constants";
|
|
3
|
+
import { updateActivePreset } from "./updateActivePreset";
|
|
4
|
+
|
|
5
|
+
describe("updateActivePreset", () => {
|
|
6
|
+
let container: HTMLElement;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
container = document.createElement("div");
|
|
10
|
+
document.body.appendChild(container);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
document.body.removeChild(container);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should add is-active class to matching preset button", () => {
|
|
18
|
+
const presets = document.createElement("div");
|
|
19
|
+
RESIZE_PRESETS.forEach(() => {
|
|
20
|
+
const button = document.createElement("button");
|
|
21
|
+
button.className = "resize-preset-button";
|
|
22
|
+
presets.appendChild(button);
|
|
23
|
+
});
|
|
24
|
+
container.appendChild(presets);
|
|
25
|
+
|
|
26
|
+
const mobilePreset = RESIZE_PRESETS[0];
|
|
27
|
+
updateActivePreset(container, mobilePreset.rawValue);
|
|
28
|
+
|
|
29
|
+
const buttons = container.querySelectorAll<HTMLButtonElement>(".resize-preset-button");
|
|
30
|
+
expect(buttons[0].classList.contains("is-active")).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should remove is-active class from non-matching buttons", () => {
|
|
34
|
+
const presets = document.createElement("div");
|
|
35
|
+
RESIZE_PRESETS.forEach(() => {
|
|
36
|
+
const button = document.createElement("button");
|
|
37
|
+
button.className = "resize-preset-button";
|
|
38
|
+
presets.appendChild(button);
|
|
39
|
+
});
|
|
40
|
+
container.appendChild(presets);
|
|
41
|
+
|
|
42
|
+
const buttons = container.querySelectorAll<HTMLButtonElement>(".resize-preset-button");
|
|
43
|
+
buttons[0].classList.add("is-active");
|
|
44
|
+
buttons[1].classList.add("is-active");
|
|
45
|
+
|
|
46
|
+
const tabletPreset = RESIZE_PRESETS[1];
|
|
47
|
+
updateActivePreset(container, tabletPreset.rawValue);
|
|
48
|
+
|
|
49
|
+
expect(buttons[0].classList.contains("is-active")).toBe(false);
|
|
50
|
+
expect(buttons[1].classList.contains("is-active")).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should handle width that doesn't match any preset", () => {
|
|
54
|
+
const presets = document.createElement("div");
|
|
55
|
+
RESIZE_PRESETS.forEach(() => {
|
|
56
|
+
const button = document.createElement("button");
|
|
57
|
+
button.className = "resize-preset-button";
|
|
58
|
+
presets.appendChild(button);
|
|
59
|
+
});
|
|
60
|
+
container.appendChild(presets);
|
|
61
|
+
|
|
62
|
+
const buttons = container.querySelectorAll<HTMLButtonElement>(".resize-preset-button");
|
|
63
|
+
buttons[0].classList.add("is-active");
|
|
64
|
+
|
|
65
|
+
updateActivePreset(container, 999);
|
|
66
|
+
|
|
67
|
+
buttons.forEach((button) => {
|
|
68
|
+
expect(button.classList.contains("is-active")).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should handle empty container", () => {
|
|
73
|
+
expect(() => {
|
|
74
|
+
updateActivePreset(container, 100);
|
|
75
|
+
}).not.toThrow();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("should handle container with fewer buttons than presets", () => {
|
|
79
|
+
const presets = document.createElement("div");
|
|
80
|
+
// Create only 2 buttons when there are 5 presets
|
|
81
|
+
for (let i = 0; i < 2; i++) {
|
|
82
|
+
const button = document.createElement("button");
|
|
83
|
+
button.className = "resize-preset-button";
|
|
84
|
+
presets.appendChild(button);
|
|
85
|
+
}
|
|
86
|
+
container.appendChild(presets);
|
|
87
|
+
|
|
88
|
+
expect(() => {
|
|
89
|
+
updateActivePreset(container, RESIZE_PRESETS[0].rawValue);
|
|
90
|
+
}).not.toThrow();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { RESIZE_CONFIG } from "../constants";
|
|
3
|
+
import { calcConstrainedWidth } from "./calcConstrainedWidth";
|
|
4
|
+
|
|
5
|
+
describe("calcConstrainedWidth", () => {
|
|
6
|
+
it("should return startWidth + deltaX when within bounds", () => {
|
|
7
|
+
const startWidth = 100;
|
|
8
|
+
const deltaX = 50;
|
|
9
|
+
const result = calcConstrainedWidth(startWidth, deltaX);
|
|
10
|
+
expect(result).toBe(150);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should return minWidth when result is below minimum", () => {
|
|
14
|
+
const startWidth = 10;
|
|
15
|
+
const deltaX = -10;
|
|
16
|
+
const result = calcConstrainedWidth(startWidth, deltaX);
|
|
17
|
+
expect(result).toBe(RESIZE_CONFIG.minWidth);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should return maxWidth when result is above maximum", () => {
|
|
21
|
+
const startWidth = 2500;
|
|
22
|
+
const deltaX = 100;
|
|
23
|
+
const result = calcConstrainedWidth(startWidth, deltaX);
|
|
24
|
+
expect(result).toBe(RESIZE_CONFIG.maxWidth);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should round the result", () => {
|
|
28
|
+
const startWidth = 100;
|
|
29
|
+
const deltaX = 50.7;
|
|
30
|
+
const result = calcConstrainedWidth(startWidth, deltaX);
|
|
31
|
+
expect(result).toBe(151);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should handle negative deltaX correctly", () => {
|
|
35
|
+
const startWidth = 200;
|
|
36
|
+
const deltaX = -50;
|
|
37
|
+
const result = calcConstrainedWidth(startWidth, deltaX);
|
|
38
|
+
expect(result).toBe(150);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should handle zero deltaX", () => {
|
|
42
|
+
const startWidth = 100;
|
|
43
|
+
const deltaX = 0;
|
|
44
|
+
const result = calcConstrainedWidth(startWidth, deltaX);
|
|
45
|
+
expect(result).toBe(100);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { calcWidth } from "./calcWidth";
|
|
3
|
+
|
|
4
|
+
describe("calcWidth", () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
// Reset zoom data attribute
|
|
7
|
+
document.body.dataset.zoom = "1";
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("should calculate width based on mouse event and start position", () => {
|
|
11
|
+
const startX = 100;
|
|
12
|
+
const startWidth = 200;
|
|
13
|
+
const event = {
|
|
14
|
+
clientX: 150,
|
|
15
|
+
} as MouseEvent;
|
|
16
|
+
|
|
17
|
+
const result = calcWidth(event, startX, startWidth);
|
|
18
|
+
expect(result).toBe(250);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should account for zoom level", () => {
|
|
22
|
+
document.body.dataset.zoom = "2";
|
|
23
|
+
const startX = 100;
|
|
24
|
+
const startWidth = 200;
|
|
25
|
+
const event = {
|
|
26
|
+
clientX: 150,
|
|
27
|
+
} as MouseEvent;
|
|
28
|
+
|
|
29
|
+
const result = calcWidth(event, startX, startWidth);
|
|
30
|
+
// deltaX = (150 - 100) / 2 = 25
|
|
31
|
+
expect(result).toBe(225);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should handle negative deltaX", () => {
|
|
35
|
+
const startX = 150;
|
|
36
|
+
const startWidth = 200;
|
|
37
|
+
const event = {
|
|
38
|
+
clientX: 100,
|
|
39
|
+
} as MouseEvent;
|
|
40
|
+
|
|
41
|
+
const result = calcWidth(event, startX, startWidth);
|
|
42
|
+
expect(result).toBe(150);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should default to zoom 1 if dataset.zoom is not set", () => {
|
|
46
|
+
delete document.body.dataset.zoom;
|
|
47
|
+
const startX = 100;
|
|
48
|
+
const startWidth = 200;
|
|
49
|
+
const event = {
|
|
50
|
+
clientX: 150,
|
|
51
|
+
} as MouseEvent;
|
|
52
|
+
|
|
53
|
+
const result = calcWidth(event, startX, startWidth);
|
|
54
|
+
expect(result).toBe(250);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should respect min and max width constraints", () => {
|
|
58
|
+
const startX = 100;
|
|
59
|
+
const startWidth = 10;
|
|
60
|
+
const event = {
|
|
61
|
+
clientX: 50,
|
|
62
|
+
} as MouseEvent;
|
|
63
|
+
|
|
64
|
+
const result = calcWidth(event, startX, startWidth);
|
|
65
|
+
// Should be clamped to minWidth (4)
|
|
66
|
+
expect(result).toBe(4);
|
|
67
|
+
});
|
|
68
|
+
});
|