@node-edit-utils/core 2.3.2 → 2.3.3
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/canvas/helpers/getCanvasContainerOrBody.d.ts +1 -0
- package/dist/lib/canvas/helpers/getCanvasWindowValue.d.ts +1 -1
- package/dist/lib/helpers/adjustForZoom.d.ts +1 -0
- package/dist/lib/helpers/createDragHandler.d.ts +69 -0
- package/dist/lib/helpers/getNodeProvider.d.ts +1 -0
- package/dist/lib/helpers/getNodeTools.d.ts +2 -0
- package/dist/lib/helpers/getViewportDimensions.d.ts +4 -0
- package/dist/lib/helpers/index.d.ts +9 -1
- package/dist/lib/helpers/parseTransform.d.ts +8 -0
- package/dist/lib/helpers/toggleClass.d.ts +1 -0
- package/dist/lib/viewport/label/helpers/selectFirstViewportNode.d.ts +5 -0
- package/dist/node-edit-utils.cjs.js +259 -235
- package/dist/node-edit-utils.esm.js +259 -235
- package/dist/node-edit-utils.umd.js +259 -235
- package/dist/node-edit-utils.umd.min.js +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/lib/canvas/createCanvasObserver.ts +2 -2
- package/src/lib/canvas/helpers/getCanvasContainerOrBody.ts +6 -0
- package/src/lib/canvas/helpers/getCanvasWindowValue.ts +2 -3
- package/src/lib/helpers/adjustForZoom.ts +4 -0
- package/src/lib/helpers/createDragHandler.ts +171 -0
- package/src/lib/helpers/getNodeProvider.ts +4 -0
- package/src/lib/helpers/getNodeTools.ts +6 -0
- package/src/lib/helpers/getViewportDimensions.ts +7 -0
- package/src/lib/helpers/index.ts +9 -1
- package/src/lib/helpers/parseTransform.ts +9 -0
- package/src/lib/helpers/toggleClass.ts +9 -0
- package/src/lib/node-tools/createNodeTools.ts +0 -1
- package/src/lib/node-tools/highlight/clearHighlightFrame.ts +2 -3
- package/src/lib/node-tools/highlight/createHighlightFrame.ts +5 -9
- package/src/lib/node-tools/highlight/createToolsContainer.ts +3 -6
- package/src/lib/node-tools/highlight/helpers/getElementBounds.ts +6 -5
- package/src/lib/node-tools/highlight/helpers/getHighlightFrameElement.ts +2 -3
- package/src/lib/node-tools/highlight/highlightNode.ts +7 -15
- package/src/lib/node-tools/highlight/refreshHighlightFrame.ts +12 -42
- package/src/lib/node-tools/highlight/updateHighlightFrameVisibility.ts +2 -3
- package/src/lib/styles/styles.css +1 -1
- package/src/lib/viewport/createViewport.ts +44 -47
- package/src/lib/viewport/label/getViewportLabelsOverlay.ts +4 -5
- package/src/lib/viewport/label/helpers/getLabelPosition.ts +3 -5
- package/src/lib/viewport/label/helpers/getTransformValues.ts +3 -5
- package/src/lib/viewport/label/helpers/selectFirstViewportNode.ts +18 -0
- package/src/lib/viewport/label/refreshViewportLabels.ts +2 -2
- package/src/lib/viewport/label/setupViewportLabelDrag.ts +58 -86
- package/src/lib/window/bindToWindow.ts +1 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Markup Canvas
|
|
3
3
|
* High-performance markup canvas with zoom and pan capabilities
|
|
4
|
-
* @version 2.3.
|
|
4
|
+
* @version 2.3.3
|
|
5
5
|
*/
|
|
6
6
|
(function (global, factory) {
|
|
7
7
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
@@ -9,6 +9,17 @@
|
|
|
9
9
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.MarkupCanvas = {}));
|
|
10
10
|
})(this, (function (exports) { 'use strict';
|
|
11
11
|
|
|
12
|
+
const getNodeTools = () => {
|
|
13
|
+
return window.nodeTools;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const getViewportDimensions = () => {
|
|
17
|
+
return {
|
|
18
|
+
width: document.documentElement.clientWidth || window.innerWidth,
|
|
19
|
+
height: document.documentElement.clientHeight || window.innerHeight,
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
12
23
|
function getScreenBounds(element) {
|
|
13
24
|
const rect = element.getBoundingClientRect();
|
|
14
25
|
return {
|
|
@@ -23,9 +34,12 @@
|
|
|
23
34
|
return document.querySelector(".canvas-container");
|
|
24
35
|
};
|
|
25
36
|
|
|
37
|
+
const getCanvasContainerOrBody = () => {
|
|
38
|
+
return getCanvasContainer() || document.body;
|
|
39
|
+
};
|
|
40
|
+
|
|
26
41
|
const getViewportLabelsOverlay = () => {
|
|
27
|
-
const
|
|
28
|
-
const container = canvasContainer || document.body;
|
|
42
|
+
const container = getCanvasContainerOrBody();
|
|
29
43
|
// Check if overlay already exists
|
|
30
44
|
let overlay = container.querySelector(".viewport-labels-overlay");
|
|
31
45
|
if (!overlay) {
|
|
@@ -40,8 +54,7 @@
|
|
|
40
54
|
overlay.style.height = "100vh";
|
|
41
55
|
overlay.style.pointerEvents = "none";
|
|
42
56
|
overlay.style.zIndex = "500";
|
|
43
|
-
const viewportWidth
|
|
44
|
-
const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
|
|
57
|
+
const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
|
|
45
58
|
overlay.setAttribute("width", viewportWidth.toString());
|
|
46
59
|
overlay.setAttribute("height", viewportHeight.toString());
|
|
47
60
|
container.appendChild(overlay);
|
|
@@ -56,6 +69,97 @@
|
|
|
56
69
|
globalIsDragging = isDragging;
|
|
57
70
|
};
|
|
58
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Creates a reusable drag handler for mouse drag operations
|
|
74
|
+
* @param element - Element that triggers the drag (mousedown listener attached here)
|
|
75
|
+
* @param callbacks - Callbacks for drag lifecycle events
|
|
76
|
+
* @param options - Configuration options
|
|
77
|
+
* @returns Cleanup function to remove all event listeners
|
|
78
|
+
*/
|
|
79
|
+
function createDragHandler(element, callbacks, options = {}) {
|
|
80
|
+
const { preventDefault = true, stopPropagation = true } = options;
|
|
81
|
+
const state = {
|
|
82
|
+
isDragging: false,
|
|
83
|
+
hasDragged: false,
|
|
84
|
+
startX: 0,
|
|
85
|
+
startY: 0,
|
|
86
|
+
};
|
|
87
|
+
const startDrag = (event) => {
|
|
88
|
+
if (preventDefault) {
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
}
|
|
91
|
+
if (stopPropagation) {
|
|
92
|
+
event.stopPropagation();
|
|
93
|
+
}
|
|
94
|
+
state.isDragging = true;
|
|
95
|
+
state.hasDragged = false;
|
|
96
|
+
state.startX = event.clientX;
|
|
97
|
+
state.startY = event.clientY;
|
|
98
|
+
callbacks.onStart?.(event, state);
|
|
99
|
+
};
|
|
100
|
+
const handleDrag = (event) => {
|
|
101
|
+
if (!state.isDragging)
|
|
102
|
+
return;
|
|
103
|
+
const deltaX = event.clientX - state.startX;
|
|
104
|
+
const deltaY = event.clientY - state.startY;
|
|
105
|
+
state.hasDragged = true;
|
|
106
|
+
callbacks.onDrag(event, {
|
|
107
|
+
...state,
|
|
108
|
+
deltaX,
|
|
109
|
+
deltaY,
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
const stopDrag = (event) => {
|
|
113
|
+
if (!state.isDragging)
|
|
114
|
+
return;
|
|
115
|
+
if (preventDefault) {
|
|
116
|
+
event.preventDefault();
|
|
117
|
+
}
|
|
118
|
+
if (stopPropagation) {
|
|
119
|
+
event.stopPropagation();
|
|
120
|
+
}
|
|
121
|
+
state.isDragging = false;
|
|
122
|
+
callbacks.onStop?.(event, state);
|
|
123
|
+
};
|
|
124
|
+
const cancelDrag = () => {
|
|
125
|
+
if (!state.isDragging)
|
|
126
|
+
return;
|
|
127
|
+
state.isDragging = false;
|
|
128
|
+
callbacks.onCancel?.(state);
|
|
129
|
+
};
|
|
130
|
+
const preventClick = (event) => {
|
|
131
|
+
if (preventDefault) {
|
|
132
|
+
event.preventDefault();
|
|
133
|
+
}
|
|
134
|
+
if (stopPropagation) {
|
|
135
|
+
event.stopPropagation();
|
|
136
|
+
}
|
|
137
|
+
callbacks.onPreventClick?.(event, state);
|
|
138
|
+
// Reset hasDragged flag after handling the click
|
|
139
|
+
if (state.hasDragged) {
|
|
140
|
+
state.hasDragged = false;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
// Attach event listeners
|
|
144
|
+
element.addEventListener("mousedown", startDrag);
|
|
145
|
+
if (callbacks.onPreventClick) {
|
|
146
|
+
element.addEventListener("click", preventClick);
|
|
147
|
+
}
|
|
148
|
+
document.addEventListener("mousemove", handleDrag);
|
|
149
|
+
document.addEventListener("mouseup", stopDrag);
|
|
150
|
+
window.addEventListener("blur", cancelDrag);
|
|
151
|
+
// Return cleanup function
|
|
152
|
+
return () => {
|
|
153
|
+
element.removeEventListener("mousedown", startDrag);
|
|
154
|
+
if (callbacks.onPreventClick) {
|
|
155
|
+
element.removeEventListener("click", preventClick);
|
|
156
|
+
}
|
|
157
|
+
document.removeEventListener("mousemove", handleDrag);
|
|
158
|
+
document.removeEventListener("mouseup", stopDrag);
|
|
159
|
+
window.removeEventListener("blur", cancelDrag);
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
59
163
|
function sendPostMessage(action, data) {
|
|
60
164
|
window.parent.postMessage({
|
|
61
165
|
source: "node-edit-utils",
|
|
@@ -65,22 +169,23 @@
|
|
|
65
169
|
}, "*");
|
|
66
170
|
}
|
|
67
171
|
|
|
172
|
+
const parseTransform3d = (transform) => {
|
|
173
|
+
const match = transform.match(/translate3d\((-?\d+(?:\.\d+)?)px,\s*(-?\d+(?:\.\d+)?)px,\s*(-?\d+(?:\.\d+)?)px\)/);
|
|
174
|
+
return match ? { x: parseFloat(match[1]), y: parseFloat(match[2]) } : { x: 0, y: 0 };
|
|
175
|
+
};
|
|
176
|
+
const parseTransform2d = (transform) => {
|
|
177
|
+
const match = transform?.match(/translate\((-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)\)/);
|
|
178
|
+
return match ? { x: parseFloat(match[1]), y: parseFloat(match[2]) } : { x: 0, y: 0 };
|
|
179
|
+
};
|
|
180
|
+
|
|
68
181
|
const getLabelPosition = (labelGroup) => {
|
|
69
182
|
const transform = labelGroup.getAttribute("transform");
|
|
70
|
-
|
|
71
|
-
if (match) {
|
|
72
|
-
return { x: parseFloat(match[1]), y: parseFloat(match[2]) };
|
|
73
|
-
}
|
|
74
|
-
return { x: 0, y: 0 };
|
|
183
|
+
return parseTransform2d(transform);
|
|
75
184
|
};
|
|
76
185
|
|
|
77
186
|
const getTransformValues = (element) => {
|
|
78
187
|
const style = element.style.transform;
|
|
79
|
-
|
|
80
|
-
if (match) {
|
|
81
|
-
return { x: parseFloat(match[1]), y: parseFloat(match[2]) };
|
|
82
|
-
}
|
|
83
|
-
return { x: 0, y: 0 };
|
|
188
|
+
return parseTransform3d(style);
|
|
84
189
|
};
|
|
85
190
|
|
|
86
191
|
const getZoomValue = () => {
|
|
@@ -88,80 +193,71 @@
|
|
|
88
193
|
return zoomValue ? parseFloat(zoomValue) : 1;
|
|
89
194
|
};
|
|
90
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Selects the first child node inside a viewport element.
|
|
198
|
+
* Skips the resize-handle element if present.
|
|
199
|
+
*/
|
|
200
|
+
const selectFirstViewportNode = (viewportElement) => {
|
|
201
|
+
const firstChild = Array.from(viewportElement.children).find((child) => !child.classList.contains("resize-handle"));
|
|
202
|
+
if (firstChild) {
|
|
203
|
+
const nodeTools = getNodeTools();
|
|
204
|
+
if (nodeTools?.selectNode) {
|
|
205
|
+
nodeTools.selectNode(firstChild);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
91
210
|
const setupViewportLabelDrag = (labelElement, viewportElement, viewportName) => {
|
|
92
|
-
let isDragging = false;
|
|
93
|
-
let startX = 0;
|
|
94
|
-
let startY = 0;
|
|
95
|
-
let initialTransform = { x: 0, y: 0 };
|
|
96
|
-
let initialLabelPosition = { x: 0, y: 0 };
|
|
97
211
|
// Get the parent group element that contains the label
|
|
98
212
|
const labelGroup = labelElement.parentElement;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
});
|
|
148
|
-
};
|
|
149
|
-
const cancelDrag = () => {
|
|
150
|
-
isDragging = false;
|
|
151
|
-
setViewportLabelDragging(false);
|
|
152
|
-
};
|
|
153
|
-
// Attach event listeners
|
|
154
|
-
labelElement.addEventListener("mousedown", startDrag);
|
|
155
|
-
document.addEventListener("mousemove", handleDrag);
|
|
156
|
-
document.addEventListener("mouseup", stopDrag);
|
|
157
|
-
window.addEventListener("blur", cancelDrag);
|
|
158
|
-
// Return cleanup function
|
|
159
|
-
return () => {
|
|
160
|
-
labelElement.removeEventListener("mousedown", startDrag);
|
|
161
|
-
document.removeEventListener("mousemove", handleDrag);
|
|
162
|
-
document.removeEventListener("mouseup", stopDrag);
|
|
163
|
-
window.removeEventListener("blur", cancelDrag);
|
|
164
|
-
};
|
|
213
|
+
// Track initial positions for calculations
|
|
214
|
+
let initialTransform = { x: 0, y: 0 };
|
|
215
|
+
let initialLabelPosition = { x: 0, y: 0 };
|
|
216
|
+
return createDragHandler(labelElement, {
|
|
217
|
+
onStart: () => {
|
|
218
|
+
setViewportLabelDragging(true);
|
|
219
|
+
initialTransform = getTransformValues(viewportElement);
|
|
220
|
+
initialLabelPosition = getLabelPosition(labelGroup);
|
|
221
|
+
selectFirstViewportNode(viewportElement);
|
|
222
|
+
},
|
|
223
|
+
onDrag: (_event, { deltaX, deltaY }) => {
|
|
224
|
+
const zoom = getZoomValue();
|
|
225
|
+
// Adjust delta for zoom level (viewport is in canvas space)
|
|
226
|
+
const deltaXZoomed = deltaX / zoom;
|
|
227
|
+
const deltaYZoomed = deltaY / zoom;
|
|
228
|
+
// Calculate new positions
|
|
229
|
+
const newX = initialTransform.x + deltaXZoomed;
|
|
230
|
+
const newY = initialTransform.y + deltaYZoomed;
|
|
231
|
+
// Update label position with raw delta (labels are in screen space)
|
|
232
|
+
const newLabelX = initialLabelPosition.x + deltaX;
|
|
233
|
+
const newLabelY = initialLabelPosition.y + deltaY;
|
|
234
|
+
labelGroup.setAttribute("transform", `translate(${newLabelX}, ${newLabelY})`);
|
|
235
|
+
// Update viewport position with zoom-adjusted delta
|
|
236
|
+
viewportElement.style.transform = `translate3d(${newX}px, ${newY}px, 0)`;
|
|
237
|
+
},
|
|
238
|
+
onStop: (_event, { hasDragged }) => {
|
|
239
|
+
setViewportLabelDragging(false);
|
|
240
|
+
// If it was a drag, handle drag completion
|
|
241
|
+
if (hasDragged) {
|
|
242
|
+
const finalTransform = getTransformValues(viewportElement);
|
|
243
|
+
// Trigger refresh after drag completes to update highlight frame and labels
|
|
244
|
+
const nodeTools = getNodeTools();
|
|
245
|
+
if (nodeTools?.refreshHighlightFrame) {
|
|
246
|
+
nodeTools.refreshHighlightFrame();
|
|
247
|
+
}
|
|
248
|
+
// Notify parent about the new position
|
|
249
|
+
sendPostMessage("viewport-position-changed", {
|
|
250
|
+
viewportName,
|
|
251
|
+
x: finalTransform.x,
|
|
252
|
+
y: finalTransform.y,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
onCancel: () => {
|
|
257
|
+
setViewportLabelDragging(false);
|
|
258
|
+
},
|
|
259
|
+
onPreventClick: () => { },
|
|
260
|
+
});
|
|
165
261
|
};
|
|
166
262
|
|
|
167
263
|
// Store cleanup functions for drag listeners
|
|
@@ -173,8 +269,7 @@
|
|
|
173
269
|
}
|
|
174
270
|
const overlay = getViewportLabelsOverlay();
|
|
175
271
|
// Update SVG dimensions to match current viewport
|
|
176
|
-
const viewportWidth
|
|
177
|
-
const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
|
|
272
|
+
const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
|
|
178
273
|
overlay.setAttribute("width", viewportWidth.toString());
|
|
179
274
|
overlay.setAttribute("height", viewportHeight.toString());
|
|
180
275
|
// Find all viewports with names
|
|
@@ -218,7 +313,6 @@
|
|
|
218
313
|
};
|
|
219
314
|
|
|
220
315
|
const getCanvasWindowValue = (path, canvasName = "canvas") => {
|
|
221
|
-
// biome-ignore lint/suspicious/noExplicitAny: global window extension
|
|
222
316
|
const canvas = window[canvasName];
|
|
223
317
|
return path.reduce((obj, prop) => obj?.[prop], canvas);
|
|
224
318
|
};
|
|
@@ -241,8 +335,7 @@
|
|
|
241
335
|
const observer = new MutationObserver(() => {
|
|
242
336
|
applyCanvasState(canvasName);
|
|
243
337
|
// Refresh highlight frame (throttled via withRAFThrottle)
|
|
244
|
-
|
|
245
|
-
const nodeTools = window.nodeTools;
|
|
338
|
+
const nodeTools = getNodeTools();
|
|
246
339
|
if (nodeTools?.refreshHighlightFrame) {
|
|
247
340
|
nodeTools.refreshHighlightFrame();
|
|
248
341
|
}
|
|
@@ -281,7 +374,6 @@
|
|
|
281
374
|
|
|
282
375
|
const bindToWindow = (key, value) => {
|
|
283
376
|
if (typeof window !== "undefined") {
|
|
284
|
-
// biome-ignore lint/suspicious/noExplicitAny: global window extension requires flexibility
|
|
285
377
|
window[key] = value;
|
|
286
378
|
}
|
|
287
379
|
};
|
|
@@ -307,8 +399,7 @@
|
|
|
307
399
|
};
|
|
308
400
|
|
|
309
401
|
const clearHighlightFrame = () => {
|
|
310
|
-
const
|
|
311
|
-
const container = canvasContainer || document.body;
|
|
402
|
+
const container = getCanvasContainerOrBody();
|
|
312
403
|
const frame = container.querySelector(".highlight-frame-overlay");
|
|
313
404
|
if (frame) {
|
|
314
405
|
frame.remove();
|
|
@@ -437,7 +528,7 @@
|
|
|
437
528
|
onNodeSelected(selectedNode);
|
|
438
529
|
};
|
|
439
530
|
|
|
440
|
-
const setupEventListener
|
|
531
|
+
const setupEventListener = (nodeProvider, onNodeSelected, onEscapePressed, text) => {
|
|
441
532
|
const messageHandler = (event) => {
|
|
442
533
|
processPostMessage(event, onNodeSelected);
|
|
443
534
|
};
|
|
@@ -461,6 +552,17 @@
|
|
|
461
552
|
};
|
|
462
553
|
};
|
|
463
554
|
|
|
555
|
+
const toggleClass = (element, className, condition) => {
|
|
556
|
+
if (!element)
|
|
557
|
+
return;
|
|
558
|
+
if (condition) {
|
|
559
|
+
element.classList.add(className);
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
element.classList.remove(className);
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
|
|
464
566
|
const isComponentInstance = (element) => {
|
|
465
567
|
return element.getAttribute("data-instance") === "true";
|
|
466
568
|
};
|
|
@@ -526,8 +628,7 @@
|
|
|
526
628
|
svg.style.height = "100vh";
|
|
527
629
|
svg.style.pointerEvents = "none";
|
|
528
630
|
svg.style.zIndex = "500";
|
|
529
|
-
const viewportWidth
|
|
530
|
-
const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
|
|
631
|
+
const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
|
|
531
632
|
svg.setAttribute("width", viewportWidth.toString());
|
|
532
633
|
svg.setAttribute("height", viewportHeight.toString());
|
|
533
634
|
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
@@ -550,13 +651,8 @@
|
|
|
550
651
|
group.appendChild(rect);
|
|
551
652
|
createCornerHandles(group, minWidth, height, isInstance, isTextEdit);
|
|
552
653
|
svg.appendChild(group);
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
canvasContainer.appendChild(svg);
|
|
556
|
-
}
|
|
557
|
-
else {
|
|
558
|
-
document.body.appendChild(svg);
|
|
559
|
-
}
|
|
654
|
+
const container = getCanvasContainerOrBody();
|
|
655
|
+
container.appendChild(svg);
|
|
560
656
|
return svg;
|
|
561
657
|
};
|
|
562
658
|
|
|
@@ -593,19 +689,14 @@
|
|
|
593
689
|
const createToolsContainer = (node, highlightFrame, isInstance = false, isTextEdit = false) => {
|
|
594
690
|
const nodeTools = document.createElement("div");
|
|
595
691
|
nodeTools.className = "node-tools";
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
}
|
|
599
|
-
if (isTextEdit) {
|
|
600
|
-
nodeTools.classList.add("is-text-edit");
|
|
601
|
-
}
|
|
692
|
+
toggleClass(nodeTools, "is-instance", isInstance);
|
|
693
|
+
toggleClass(nodeTools, "is-text-edit", isTextEdit);
|
|
602
694
|
highlightFrame.appendChild(nodeTools);
|
|
603
695
|
createTagLabel(node, nodeTools);
|
|
604
696
|
};
|
|
605
697
|
|
|
606
698
|
function getHighlightFrameElement() {
|
|
607
|
-
const
|
|
608
|
-
const container = canvasContainer || document.body;
|
|
699
|
+
const container = getCanvasContainerOrBody();
|
|
609
700
|
return container.querySelector(".highlight-frame-overlay");
|
|
610
701
|
}
|
|
611
702
|
|
|
@@ -613,8 +704,8 @@
|
|
|
613
704
|
if (!node)
|
|
614
705
|
return;
|
|
615
706
|
const existingHighlightFrame = getHighlightFrameElement();
|
|
616
|
-
const
|
|
617
|
-
const existingToolsWrapper =
|
|
707
|
+
const container = getCanvasContainerOrBody();
|
|
708
|
+
const existingToolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
|
|
618
709
|
if (existingHighlightFrame) {
|
|
619
710
|
existingHighlightFrame.remove();
|
|
620
711
|
}
|
|
@@ -633,24 +724,15 @@
|
|
|
633
724
|
// Create tools wrapper using CSS transform (GPU-accelerated)
|
|
634
725
|
const toolsWrapper = document.createElement("div");
|
|
635
726
|
toolsWrapper.classList.add("highlight-frame-tools-wrapper");
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
}
|
|
639
|
-
if (isTextEdit) {
|
|
640
|
-
toolsWrapper.classList.add("is-text-edit");
|
|
641
|
-
}
|
|
727
|
+
toggleClass(toolsWrapper, "is-instance", isInstance);
|
|
728
|
+
toggleClass(toolsWrapper, "is-text-edit", isTextEdit);
|
|
642
729
|
toolsWrapper.style.position = "absolute";
|
|
643
730
|
toolsWrapper.style.transform = `translate(${left}px, ${bottomY}px)`;
|
|
644
731
|
toolsWrapper.style.transformOrigin = "left center";
|
|
645
732
|
toolsWrapper.style.pointerEvents = "none";
|
|
646
733
|
toolsWrapper.style.zIndex = "500";
|
|
647
734
|
createToolsContainer(node, toolsWrapper, isInstance, isTextEdit);
|
|
648
|
-
|
|
649
|
-
canvasContainer.appendChild(toolsWrapper);
|
|
650
|
-
}
|
|
651
|
-
else {
|
|
652
|
-
document.body.appendChild(toolsWrapper);
|
|
653
|
-
}
|
|
735
|
+
container.appendChild(toolsWrapper);
|
|
654
736
|
};
|
|
655
737
|
|
|
656
738
|
const getComponentColor = () => {
|
|
@@ -668,24 +750,13 @@
|
|
|
668
750
|
const isTextEdit = node.contentEditable === "true";
|
|
669
751
|
// Update SVG dimensions to match current viewport (handles window resize and ensures coordinate system is correct)
|
|
670
752
|
// Use clientWidth/Height to match getBoundingClientRect() coordinate system (excludes scrollbars)
|
|
671
|
-
const viewportWidth
|
|
672
|
-
const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
|
|
753
|
+
const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
|
|
673
754
|
frame.setAttribute("width", viewportWidth.toString());
|
|
674
755
|
frame.setAttribute("height", viewportHeight.toString());
|
|
675
756
|
// Update instance class
|
|
676
|
-
|
|
677
|
-
frame.classList.add("is-instance");
|
|
678
|
-
}
|
|
679
|
-
else {
|
|
680
|
-
frame.classList.remove("is-instance");
|
|
681
|
-
}
|
|
757
|
+
toggleClass(frame, "is-instance", isInstance);
|
|
682
758
|
// Update text edit class
|
|
683
|
-
|
|
684
|
-
frame.classList.add("is-text-edit");
|
|
685
|
-
}
|
|
686
|
-
else {
|
|
687
|
-
frame.classList.remove("is-text-edit");
|
|
688
|
-
}
|
|
759
|
+
toggleClass(frame, "is-text-edit", isTextEdit);
|
|
689
760
|
const group = frame.querySelector(".highlight-frame-group");
|
|
690
761
|
if (!group)
|
|
691
762
|
return;
|
|
@@ -702,8 +773,7 @@
|
|
|
702
773
|
else {
|
|
703
774
|
rect.removeAttribute("stroke"); // Use CSS default
|
|
704
775
|
}
|
|
705
|
-
const
|
|
706
|
-
const container = canvasContainer || document.body;
|
|
776
|
+
const container = getCanvasContainerOrBody();
|
|
707
777
|
const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
|
|
708
778
|
const nodeTools = toolsWrapper?.querySelector(".node-tools");
|
|
709
779
|
const zoom = getCanvasWindowValue(["zoom", "current"], canvasName) ?? 1;
|
|
@@ -714,36 +784,10 @@
|
|
|
714
784
|
const minWidth = Math.max(width, 3);
|
|
715
785
|
const bottomY = top + height;
|
|
716
786
|
// Update instance classes on tools wrapper and node tools
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
else {
|
|
722
|
-
toolsWrapper.classList.remove("is-instance");
|
|
723
|
-
}
|
|
724
|
-
// Update text edit class
|
|
725
|
-
if (isTextEdit) {
|
|
726
|
-
toolsWrapper.classList.add("is-text-edit");
|
|
727
|
-
}
|
|
728
|
-
else {
|
|
729
|
-
toolsWrapper.classList.remove("is-text-edit");
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
if (nodeTools) {
|
|
733
|
-
if (isInstance) {
|
|
734
|
-
nodeTools.classList.add("is-instance");
|
|
735
|
-
}
|
|
736
|
-
else {
|
|
737
|
-
nodeTools.classList.remove("is-instance");
|
|
738
|
-
}
|
|
739
|
-
// Update text edit class
|
|
740
|
-
if (isTextEdit) {
|
|
741
|
-
nodeTools.classList.add("is-text-edit");
|
|
742
|
-
}
|
|
743
|
-
else {
|
|
744
|
-
nodeTools.classList.remove("is-text-edit");
|
|
745
|
-
}
|
|
746
|
-
}
|
|
787
|
+
toggleClass(toolsWrapper, "is-instance", isInstance);
|
|
788
|
+
toggleClass(toolsWrapper, "is-text-edit", isTextEdit);
|
|
789
|
+
toggleClass(nodeTools, "is-instance", isInstance);
|
|
790
|
+
toggleClass(nodeTools, "is-text-edit", isTextEdit);
|
|
747
791
|
// Batch all DOM writes (single paint pass)
|
|
748
792
|
// Update group transform to move entire group (rect + handles) at once
|
|
749
793
|
group.setAttribute("transform", `translate(${left}, ${top})`);
|
|
@@ -809,8 +853,7 @@
|
|
|
809
853
|
const displayValue = hasHiddenClass ? "none" : "";
|
|
810
854
|
// Batch DOM writes
|
|
811
855
|
frame.style.display = displayValue;
|
|
812
|
-
const
|
|
813
|
-
const container = canvasContainer || document.body;
|
|
856
|
+
const container = getCanvasContainerOrBody();
|
|
814
857
|
const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
|
|
815
858
|
if (toolsWrapper) {
|
|
816
859
|
toolsWrapper.style.display = displayValue;
|
|
@@ -1116,7 +1159,6 @@
|
|
|
1116
1159
|
highlightNode(node);
|
|
1117
1160
|
if (nodeProvider) {
|
|
1118
1161
|
updateHighlightFrameVisibility(node);
|
|
1119
|
-
updateHighlightFrameVisibility(node);
|
|
1120
1162
|
}
|
|
1121
1163
|
}
|
|
1122
1164
|
else {
|
|
@@ -1124,7 +1166,7 @@
|
|
|
1124
1166
|
}
|
|
1125
1167
|
};
|
|
1126
1168
|
// Setup event listener
|
|
1127
|
-
const removeListeners = setupEventListener
|
|
1169
|
+
const removeListeners = setupEventListener(nodeProvider, selectNode, handleEscape, text);
|
|
1128
1170
|
const cleanup = () => {
|
|
1129
1171
|
removeListeners();
|
|
1130
1172
|
resizeObserver?.disconnect();
|
|
@@ -1159,6 +1201,10 @@
|
|
|
1159
1201
|
return nodeTools;
|
|
1160
1202
|
};
|
|
1161
1203
|
|
|
1204
|
+
const getNodeProvider = () => {
|
|
1205
|
+
return document.querySelector('[data-role="node-provider"]');
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1162
1208
|
const DEFAULT_WIDTH = 400;
|
|
1163
1209
|
const RESIZE_CONFIG = {
|
|
1164
1210
|
minWidth: 4,
|
|
@@ -1192,27 +1238,6 @@
|
|
|
1192
1238
|
},
|
|
1193
1239
|
];
|
|
1194
1240
|
|
|
1195
|
-
const setupEventListener = (resizeHandle, startResize, handleResize, stopResize, blurResize) => {
|
|
1196
|
-
const handleMouseLeave = (event) => {
|
|
1197
|
-
// Check if mouse is leaving the window/document
|
|
1198
|
-
if (!event.relatedTarget && (event.target === document || event.target === document.documentElement)) {
|
|
1199
|
-
blurResize();
|
|
1200
|
-
}
|
|
1201
|
-
};
|
|
1202
|
-
resizeHandle.addEventListener("mousedown", startResize);
|
|
1203
|
-
document.addEventListener("mousemove", handleResize);
|
|
1204
|
-
document.addEventListener("mouseup", stopResize);
|
|
1205
|
-
document.addEventListener("mouseleave", handleMouseLeave);
|
|
1206
|
-
window.addEventListener("blur", blurResize);
|
|
1207
|
-
return () => {
|
|
1208
|
-
resizeHandle.removeEventListener("mousedown", startResize);
|
|
1209
|
-
document.removeEventListener("mousemove", handleResize);
|
|
1210
|
-
document.removeEventListener("mouseup", stopResize);
|
|
1211
|
-
document.removeEventListener("mouseleave", handleMouseLeave);
|
|
1212
|
-
window.removeEventListener("blur", blurResize);
|
|
1213
|
-
};
|
|
1214
|
-
};
|
|
1215
|
-
|
|
1216
1241
|
const createResizeHandle = (container) => {
|
|
1217
1242
|
const handle = document.createElement("div");
|
|
1218
1243
|
handle.className = "resize-handle";
|
|
@@ -1279,58 +1304,57 @@
|
|
|
1279
1304
|
const width = initialWidth ?? DEFAULT_WIDTH;
|
|
1280
1305
|
container.style.setProperty("--container-width", `${width}px`);
|
|
1281
1306
|
createResizePresets(resizeHandle, container, updateWidth);
|
|
1282
|
-
|
|
1307
|
+
// Track initial values for resize calculation
|
|
1283
1308
|
let startX = 0;
|
|
1284
1309
|
let startWidth = 0;
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
const handleResize = (event) => {
|
|
1293
|
-
if (!isDragging)
|
|
1294
|
-
return;
|
|
1295
|
-
if (canvas) {
|
|
1296
|
-
canvas.style.cursor = "ew-resize";
|
|
1297
|
-
}
|
|
1298
|
-
const width = calcWidth(event, startX, startWidth);
|
|
1299
|
-
updateWidth(container, width);
|
|
1300
|
-
};
|
|
1301
|
-
const stopResize = (event) => {
|
|
1302
|
-
event.preventDefault();
|
|
1303
|
-
event.stopPropagation();
|
|
1304
|
-
if (canvas) {
|
|
1305
|
-
canvas.style.cursor = "default";
|
|
1306
|
-
}
|
|
1307
|
-
isDragging = false;
|
|
1308
|
-
};
|
|
1309
|
-
const blurResize = () => {
|
|
1310
|
-
if (canvas) {
|
|
1311
|
-
canvas.style.cursor = "default";
|
|
1310
|
+
// Handle mouse leave for resize (specific to resize use case)
|
|
1311
|
+
const handleMouseLeave = (event) => {
|
|
1312
|
+
// Check if mouse is leaving the window/document
|
|
1313
|
+
if (!event.relatedTarget && (event.target === document || event.target === document.documentElement)) {
|
|
1314
|
+
if (canvas) {
|
|
1315
|
+
canvas.style.cursor = "default";
|
|
1316
|
+
}
|
|
1312
1317
|
}
|
|
1313
|
-
isDragging = false;
|
|
1314
1318
|
};
|
|
1315
|
-
const
|
|
1316
|
-
|
|
1319
|
+
const removeDragListeners = createDragHandler(resizeHandle, {
|
|
1320
|
+
onStart: (_event, { startX: dragStartX }) => {
|
|
1321
|
+
startX = dragStartX;
|
|
1322
|
+
startWidth = container.offsetWidth;
|
|
1323
|
+
},
|
|
1324
|
+
onDrag: (event) => {
|
|
1325
|
+
if (canvas) {
|
|
1326
|
+
canvas.style.cursor = "ew-resize";
|
|
1327
|
+
}
|
|
1328
|
+
const width = calcWidth(event, startX, startWidth);
|
|
1329
|
+
updateWidth(container, width);
|
|
1330
|
+
},
|
|
1331
|
+
onStop: () => {
|
|
1332
|
+
if (canvas) {
|
|
1333
|
+
canvas.style.cursor = "default";
|
|
1334
|
+
}
|
|
1335
|
+
},
|
|
1336
|
+
onCancel: () => {
|
|
1337
|
+
if (canvas) {
|
|
1338
|
+
canvas.style.cursor = "default";
|
|
1339
|
+
}
|
|
1340
|
+
},
|
|
1341
|
+
onPreventClick: () => { },
|
|
1342
|
+
});
|
|
1343
|
+
document.addEventListener("mouseleave", handleMouseLeave);
|
|
1317
1344
|
refreshViewportLabels();
|
|
1318
1345
|
const cleanup = () => {
|
|
1319
|
-
|
|
1320
|
-
|
|
1346
|
+
removeDragListeners();
|
|
1347
|
+
document.removeEventListener("mouseleave", handleMouseLeave);
|
|
1321
1348
|
resizeHandle.remove();
|
|
1322
|
-
// Refresh labels after cleanup to remove this viewport's label if needed
|
|
1323
1349
|
refreshViewportLabels();
|
|
1324
1350
|
};
|
|
1325
1351
|
return {
|
|
1326
1352
|
setWidth: (width) => {
|
|
1327
1353
|
updateWidth(container, width);
|
|
1328
1354
|
refreshViewportLabels();
|
|
1329
|
-
|
|
1330
|
-
// biome-ignore lint/suspicious/noExplicitAny: global window extension
|
|
1331
|
-
const nodeTools = window.nodeTools;
|
|
1355
|
+
const nodeTools = getNodeTools();
|
|
1332
1356
|
const selectedNode = nodeTools?.getSelectedNode?.();
|
|
1333
|
-
const nodeProvider =
|
|
1357
|
+
const nodeProvider = getNodeProvider();
|
|
1334
1358
|
if (selectedNode && nodeProvider) {
|
|
1335
1359
|
refreshHighlightFrame(selectedNode, nodeProvider);
|
|
1336
1360
|
}
|