@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,10 +1,21 @@
|
|
|
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
|
'use strict';
|
|
7
7
|
|
|
8
|
+
const getNodeTools = () => {
|
|
9
|
+
return window.nodeTools;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const getViewportDimensions = () => {
|
|
13
|
+
return {
|
|
14
|
+
width: document.documentElement.clientWidth || window.innerWidth,
|
|
15
|
+
height: document.documentElement.clientHeight || window.innerHeight,
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
8
19
|
function getScreenBounds(element) {
|
|
9
20
|
const rect = element.getBoundingClientRect();
|
|
10
21
|
return {
|
|
@@ -19,9 +30,12 @@ const getCanvasContainer = () => {
|
|
|
19
30
|
return document.querySelector(".canvas-container");
|
|
20
31
|
};
|
|
21
32
|
|
|
33
|
+
const getCanvasContainerOrBody = () => {
|
|
34
|
+
return getCanvasContainer() || document.body;
|
|
35
|
+
};
|
|
36
|
+
|
|
22
37
|
const getViewportLabelsOverlay = () => {
|
|
23
|
-
const
|
|
24
|
-
const container = canvasContainer || document.body;
|
|
38
|
+
const container = getCanvasContainerOrBody();
|
|
25
39
|
// Check if overlay already exists
|
|
26
40
|
let overlay = container.querySelector(".viewport-labels-overlay");
|
|
27
41
|
if (!overlay) {
|
|
@@ -36,8 +50,7 @@ const getViewportLabelsOverlay = () => {
|
|
|
36
50
|
overlay.style.height = "100vh";
|
|
37
51
|
overlay.style.pointerEvents = "none";
|
|
38
52
|
overlay.style.zIndex = "500";
|
|
39
|
-
const viewportWidth
|
|
40
|
-
const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
|
|
53
|
+
const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
|
|
41
54
|
overlay.setAttribute("width", viewportWidth.toString());
|
|
42
55
|
overlay.setAttribute("height", viewportHeight.toString());
|
|
43
56
|
container.appendChild(overlay);
|
|
@@ -52,6 +65,97 @@ const setViewportLabelDragging = (isDragging) => {
|
|
|
52
65
|
globalIsDragging = isDragging;
|
|
53
66
|
};
|
|
54
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Creates a reusable drag handler for mouse drag operations
|
|
70
|
+
* @param element - Element that triggers the drag (mousedown listener attached here)
|
|
71
|
+
* @param callbacks - Callbacks for drag lifecycle events
|
|
72
|
+
* @param options - Configuration options
|
|
73
|
+
* @returns Cleanup function to remove all event listeners
|
|
74
|
+
*/
|
|
75
|
+
function createDragHandler(element, callbacks, options = {}) {
|
|
76
|
+
const { preventDefault = true, stopPropagation = true } = options;
|
|
77
|
+
const state = {
|
|
78
|
+
isDragging: false,
|
|
79
|
+
hasDragged: false,
|
|
80
|
+
startX: 0,
|
|
81
|
+
startY: 0,
|
|
82
|
+
};
|
|
83
|
+
const startDrag = (event) => {
|
|
84
|
+
if (preventDefault) {
|
|
85
|
+
event.preventDefault();
|
|
86
|
+
}
|
|
87
|
+
if (stopPropagation) {
|
|
88
|
+
event.stopPropagation();
|
|
89
|
+
}
|
|
90
|
+
state.isDragging = true;
|
|
91
|
+
state.hasDragged = false;
|
|
92
|
+
state.startX = event.clientX;
|
|
93
|
+
state.startY = event.clientY;
|
|
94
|
+
callbacks.onStart?.(event, state);
|
|
95
|
+
};
|
|
96
|
+
const handleDrag = (event) => {
|
|
97
|
+
if (!state.isDragging)
|
|
98
|
+
return;
|
|
99
|
+
const deltaX = event.clientX - state.startX;
|
|
100
|
+
const deltaY = event.clientY - state.startY;
|
|
101
|
+
state.hasDragged = true;
|
|
102
|
+
callbacks.onDrag(event, {
|
|
103
|
+
...state,
|
|
104
|
+
deltaX,
|
|
105
|
+
deltaY,
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
const stopDrag = (event) => {
|
|
109
|
+
if (!state.isDragging)
|
|
110
|
+
return;
|
|
111
|
+
if (preventDefault) {
|
|
112
|
+
event.preventDefault();
|
|
113
|
+
}
|
|
114
|
+
if (stopPropagation) {
|
|
115
|
+
event.stopPropagation();
|
|
116
|
+
}
|
|
117
|
+
state.isDragging = false;
|
|
118
|
+
callbacks.onStop?.(event, state);
|
|
119
|
+
};
|
|
120
|
+
const cancelDrag = () => {
|
|
121
|
+
if (!state.isDragging)
|
|
122
|
+
return;
|
|
123
|
+
state.isDragging = false;
|
|
124
|
+
callbacks.onCancel?.(state);
|
|
125
|
+
};
|
|
126
|
+
const preventClick = (event) => {
|
|
127
|
+
if (preventDefault) {
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
}
|
|
130
|
+
if (stopPropagation) {
|
|
131
|
+
event.stopPropagation();
|
|
132
|
+
}
|
|
133
|
+
callbacks.onPreventClick?.(event, state);
|
|
134
|
+
// Reset hasDragged flag after handling the click
|
|
135
|
+
if (state.hasDragged) {
|
|
136
|
+
state.hasDragged = false;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
// Attach event listeners
|
|
140
|
+
element.addEventListener("mousedown", startDrag);
|
|
141
|
+
if (callbacks.onPreventClick) {
|
|
142
|
+
element.addEventListener("click", preventClick);
|
|
143
|
+
}
|
|
144
|
+
document.addEventListener("mousemove", handleDrag);
|
|
145
|
+
document.addEventListener("mouseup", stopDrag);
|
|
146
|
+
window.addEventListener("blur", cancelDrag);
|
|
147
|
+
// Return cleanup function
|
|
148
|
+
return () => {
|
|
149
|
+
element.removeEventListener("mousedown", startDrag);
|
|
150
|
+
if (callbacks.onPreventClick) {
|
|
151
|
+
element.removeEventListener("click", preventClick);
|
|
152
|
+
}
|
|
153
|
+
document.removeEventListener("mousemove", handleDrag);
|
|
154
|
+
document.removeEventListener("mouseup", stopDrag);
|
|
155
|
+
window.removeEventListener("blur", cancelDrag);
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
55
159
|
function sendPostMessage(action, data) {
|
|
56
160
|
window.parent.postMessage({
|
|
57
161
|
source: "node-edit-utils",
|
|
@@ -61,22 +165,23 @@ function sendPostMessage(action, data) {
|
|
|
61
165
|
}, "*");
|
|
62
166
|
}
|
|
63
167
|
|
|
168
|
+
const parseTransform3d = (transform) => {
|
|
169
|
+
const match = transform.match(/translate3d\((-?\d+(?:\.\d+)?)px,\s*(-?\d+(?:\.\d+)?)px,\s*(-?\d+(?:\.\d+)?)px\)/);
|
|
170
|
+
return match ? { x: parseFloat(match[1]), y: parseFloat(match[2]) } : { x: 0, y: 0 };
|
|
171
|
+
};
|
|
172
|
+
const parseTransform2d = (transform) => {
|
|
173
|
+
const match = transform?.match(/translate\((-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)\)/);
|
|
174
|
+
return match ? { x: parseFloat(match[1]), y: parseFloat(match[2]) } : { x: 0, y: 0 };
|
|
175
|
+
};
|
|
176
|
+
|
|
64
177
|
const getLabelPosition = (labelGroup) => {
|
|
65
178
|
const transform = labelGroup.getAttribute("transform");
|
|
66
|
-
|
|
67
|
-
if (match) {
|
|
68
|
-
return { x: parseFloat(match[1]), y: parseFloat(match[2]) };
|
|
69
|
-
}
|
|
70
|
-
return { x: 0, y: 0 };
|
|
179
|
+
return parseTransform2d(transform);
|
|
71
180
|
};
|
|
72
181
|
|
|
73
182
|
const getTransformValues = (element) => {
|
|
74
183
|
const style = element.style.transform;
|
|
75
|
-
|
|
76
|
-
if (match) {
|
|
77
|
-
return { x: parseFloat(match[1]), y: parseFloat(match[2]) };
|
|
78
|
-
}
|
|
79
|
-
return { x: 0, y: 0 };
|
|
184
|
+
return parseTransform3d(style);
|
|
80
185
|
};
|
|
81
186
|
|
|
82
187
|
const getZoomValue = () => {
|
|
@@ -84,80 +189,71 @@ const getZoomValue = () => {
|
|
|
84
189
|
return zoomValue ? parseFloat(zoomValue) : 1;
|
|
85
190
|
};
|
|
86
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Selects the first child node inside a viewport element.
|
|
194
|
+
* Skips the resize-handle element if present.
|
|
195
|
+
*/
|
|
196
|
+
const selectFirstViewportNode = (viewportElement) => {
|
|
197
|
+
const firstChild = Array.from(viewportElement.children).find((child) => !child.classList.contains("resize-handle"));
|
|
198
|
+
if (firstChild) {
|
|
199
|
+
const nodeTools = getNodeTools();
|
|
200
|
+
if (nodeTools?.selectNode) {
|
|
201
|
+
nodeTools.selectNode(firstChild);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
87
206
|
const setupViewportLabelDrag = (labelElement, viewportElement, viewportName) => {
|
|
88
|
-
let isDragging = false;
|
|
89
|
-
let startX = 0;
|
|
90
|
-
let startY = 0;
|
|
91
|
-
let initialTransform = { x: 0, y: 0 };
|
|
92
|
-
let initialLabelPosition = { x: 0, y: 0 };
|
|
93
207
|
// Get the parent group element that contains the label
|
|
94
208
|
const labelGroup = labelElement.parentElement;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
const cancelDrag = () => {
|
|
146
|
-
isDragging = false;
|
|
147
|
-
setViewportLabelDragging(false);
|
|
148
|
-
};
|
|
149
|
-
// Attach event listeners
|
|
150
|
-
labelElement.addEventListener("mousedown", startDrag);
|
|
151
|
-
document.addEventListener("mousemove", handleDrag);
|
|
152
|
-
document.addEventListener("mouseup", stopDrag);
|
|
153
|
-
window.addEventListener("blur", cancelDrag);
|
|
154
|
-
// Return cleanup function
|
|
155
|
-
return () => {
|
|
156
|
-
labelElement.removeEventListener("mousedown", startDrag);
|
|
157
|
-
document.removeEventListener("mousemove", handleDrag);
|
|
158
|
-
document.removeEventListener("mouseup", stopDrag);
|
|
159
|
-
window.removeEventListener("blur", cancelDrag);
|
|
160
|
-
};
|
|
209
|
+
// Track initial positions for calculations
|
|
210
|
+
let initialTransform = { x: 0, y: 0 };
|
|
211
|
+
let initialLabelPosition = { x: 0, y: 0 };
|
|
212
|
+
return createDragHandler(labelElement, {
|
|
213
|
+
onStart: () => {
|
|
214
|
+
setViewportLabelDragging(true);
|
|
215
|
+
initialTransform = getTransformValues(viewportElement);
|
|
216
|
+
initialLabelPosition = getLabelPosition(labelGroup);
|
|
217
|
+
selectFirstViewportNode(viewportElement);
|
|
218
|
+
},
|
|
219
|
+
onDrag: (_event, { deltaX, deltaY }) => {
|
|
220
|
+
const zoom = getZoomValue();
|
|
221
|
+
// Adjust delta for zoom level (viewport is in canvas space)
|
|
222
|
+
const deltaXZoomed = deltaX / zoom;
|
|
223
|
+
const deltaYZoomed = deltaY / zoom;
|
|
224
|
+
// Calculate new positions
|
|
225
|
+
const newX = initialTransform.x + deltaXZoomed;
|
|
226
|
+
const newY = initialTransform.y + deltaYZoomed;
|
|
227
|
+
// Update label position with raw delta (labels are in screen space)
|
|
228
|
+
const newLabelX = initialLabelPosition.x + deltaX;
|
|
229
|
+
const newLabelY = initialLabelPosition.y + deltaY;
|
|
230
|
+
labelGroup.setAttribute("transform", `translate(${newLabelX}, ${newLabelY})`);
|
|
231
|
+
// Update viewport position with zoom-adjusted delta
|
|
232
|
+
viewportElement.style.transform = `translate3d(${newX}px, ${newY}px, 0)`;
|
|
233
|
+
},
|
|
234
|
+
onStop: (_event, { hasDragged }) => {
|
|
235
|
+
setViewportLabelDragging(false);
|
|
236
|
+
// If it was a drag, handle drag completion
|
|
237
|
+
if (hasDragged) {
|
|
238
|
+
const finalTransform = getTransformValues(viewportElement);
|
|
239
|
+
// Trigger refresh after drag completes to update highlight frame and labels
|
|
240
|
+
const nodeTools = getNodeTools();
|
|
241
|
+
if (nodeTools?.refreshHighlightFrame) {
|
|
242
|
+
nodeTools.refreshHighlightFrame();
|
|
243
|
+
}
|
|
244
|
+
// Notify parent about the new position
|
|
245
|
+
sendPostMessage("viewport-position-changed", {
|
|
246
|
+
viewportName,
|
|
247
|
+
x: finalTransform.x,
|
|
248
|
+
y: finalTransform.y,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
onCancel: () => {
|
|
253
|
+
setViewportLabelDragging(false);
|
|
254
|
+
},
|
|
255
|
+
onPreventClick: () => { },
|
|
256
|
+
});
|
|
161
257
|
};
|
|
162
258
|
|
|
163
259
|
// Store cleanup functions for drag listeners
|
|
@@ -169,8 +265,7 @@ const refreshViewportLabels = () => {
|
|
|
169
265
|
}
|
|
170
266
|
const overlay = getViewportLabelsOverlay();
|
|
171
267
|
// Update SVG dimensions to match current viewport
|
|
172
|
-
const viewportWidth
|
|
173
|
-
const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
|
|
268
|
+
const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
|
|
174
269
|
overlay.setAttribute("width", viewportWidth.toString());
|
|
175
270
|
overlay.setAttribute("height", viewportHeight.toString());
|
|
176
271
|
// Find all viewports with names
|
|
@@ -214,7 +309,6 @@ const refreshViewportLabels = () => {
|
|
|
214
309
|
};
|
|
215
310
|
|
|
216
311
|
const getCanvasWindowValue = (path, canvasName = "canvas") => {
|
|
217
|
-
// biome-ignore lint/suspicious/noExplicitAny: global window extension
|
|
218
312
|
const canvas = window[canvasName];
|
|
219
313
|
return path.reduce((obj, prop) => obj?.[prop], canvas);
|
|
220
314
|
};
|
|
@@ -237,8 +331,7 @@ function createCanvasObserver(canvasName = "canvas") {
|
|
|
237
331
|
const observer = new MutationObserver(() => {
|
|
238
332
|
applyCanvasState(canvasName);
|
|
239
333
|
// Refresh highlight frame (throttled via withRAFThrottle)
|
|
240
|
-
|
|
241
|
-
const nodeTools = window.nodeTools;
|
|
334
|
+
const nodeTools = getNodeTools();
|
|
242
335
|
if (nodeTools?.refreshHighlightFrame) {
|
|
243
336
|
nodeTools.refreshHighlightFrame();
|
|
244
337
|
}
|
|
@@ -277,7 +370,6 @@ const connectResizeObserver = (element, handler) => {
|
|
|
277
370
|
|
|
278
371
|
const bindToWindow = (key, value) => {
|
|
279
372
|
if (typeof window !== "undefined") {
|
|
280
|
-
// biome-ignore lint/suspicious/noExplicitAny: global window extension requires flexibility
|
|
281
373
|
window[key] = value;
|
|
282
374
|
}
|
|
283
375
|
};
|
|
@@ -303,8 +395,7 @@ const processPostMessage = (event, onNodeSelected) => {
|
|
|
303
395
|
};
|
|
304
396
|
|
|
305
397
|
const clearHighlightFrame = () => {
|
|
306
|
-
const
|
|
307
|
-
const container = canvasContainer || document.body;
|
|
398
|
+
const container = getCanvasContainerOrBody();
|
|
308
399
|
const frame = container.querySelector(".highlight-frame-overlay");
|
|
309
400
|
if (frame) {
|
|
310
401
|
frame.remove();
|
|
@@ -433,7 +524,7 @@ const handleNodeClick = (event, nodeProvider, text, onNodeSelected) => {
|
|
|
433
524
|
onNodeSelected(selectedNode);
|
|
434
525
|
};
|
|
435
526
|
|
|
436
|
-
const setupEventListener
|
|
527
|
+
const setupEventListener = (nodeProvider, onNodeSelected, onEscapePressed, text) => {
|
|
437
528
|
const messageHandler = (event) => {
|
|
438
529
|
processPostMessage(event, onNodeSelected);
|
|
439
530
|
};
|
|
@@ -457,6 +548,17 @@ const setupEventListener$1 = (nodeProvider, onNodeSelected, onEscapePressed, tex
|
|
|
457
548
|
};
|
|
458
549
|
};
|
|
459
550
|
|
|
551
|
+
const toggleClass = (element, className, condition) => {
|
|
552
|
+
if (!element)
|
|
553
|
+
return;
|
|
554
|
+
if (condition) {
|
|
555
|
+
element.classList.add(className);
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
element.classList.remove(className);
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
|
|
460
562
|
const isComponentInstance = (element) => {
|
|
461
563
|
return element.getAttribute("data-instance") === "true";
|
|
462
564
|
};
|
|
@@ -522,8 +624,7 @@ const createHighlightFrame = (node, isInstance = false, isTextEdit = false) => {
|
|
|
522
624
|
svg.style.height = "100vh";
|
|
523
625
|
svg.style.pointerEvents = "none";
|
|
524
626
|
svg.style.zIndex = "500";
|
|
525
|
-
const viewportWidth
|
|
526
|
-
const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
|
|
627
|
+
const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
|
|
527
628
|
svg.setAttribute("width", viewportWidth.toString());
|
|
528
629
|
svg.setAttribute("height", viewportHeight.toString());
|
|
529
630
|
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
@@ -546,13 +647,8 @@ const createHighlightFrame = (node, isInstance = false, isTextEdit = false) => {
|
|
|
546
647
|
group.appendChild(rect);
|
|
547
648
|
createCornerHandles(group, minWidth, height, isInstance, isTextEdit);
|
|
548
649
|
svg.appendChild(group);
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
canvasContainer.appendChild(svg);
|
|
552
|
-
}
|
|
553
|
-
else {
|
|
554
|
-
document.body.appendChild(svg);
|
|
555
|
-
}
|
|
650
|
+
const container = getCanvasContainerOrBody();
|
|
651
|
+
container.appendChild(svg);
|
|
556
652
|
return svg;
|
|
557
653
|
};
|
|
558
654
|
|
|
@@ -589,19 +685,14 @@ const createTagLabel = (node, nodeTools) => {
|
|
|
589
685
|
const createToolsContainer = (node, highlightFrame, isInstance = false, isTextEdit = false) => {
|
|
590
686
|
const nodeTools = document.createElement("div");
|
|
591
687
|
nodeTools.className = "node-tools";
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
}
|
|
595
|
-
if (isTextEdit) {
|
|
596
|
-
nodeTools.classList.add("is-text-edit");
|
|
597
|
-
}
|
|
688
|
+
toggleClass(nodeTools, "is-instance", isInstance);
|
|
689
|
+
toggleClass(nodeTools, "is-text-edit", isTextEdit);
|
|
598
690
|
highlightFrame.appendChild(nodeTools);
|
|
599
691
|
createTagLabel(node, nodeTools);
|
|
600
692
|
};
|
|
601
693
|
|
|
602
694
|
function getHighlightFrameElement() {
|
|
603
|
-
const
|
|
604
|
-
const container = canvasContainer || document.body;
|
|
695
|
+
const container = getCanvasContainerOrBody();
|
|
605
696
|
return container.querySelector(".highlight-frame-overlay");
|
|
606
697
|
}
|
|
607
698
|
|
|
@@ -609,8 +700,8 @@ const highlightNode = (node) => {
|
|
|
609
700
|
if (!node)
|
|
610
701
|
return;
|
|
611
702
|
const existingHighlightFrame = getHighlightFrameElement();
|
|
612
|
-
const
|
|
613
|
-
const existingToolsWrapper =
|
|
703
|
+
const container = getCanvasContainerOrBody();
|
|
704
|
+
const existingToolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
|
|
614
705
|
if (existingHighlightFrame) {
|
|
615
706
|
existingHighlightFrame.remove();
|
|
616
707
|
}
|
|
@@ -629,24 +720,15 @@ const highlightNode = (node) => {
|
|
|
629
720
|
// Create tools wrapper using CSS transform (GPU-accelerated)
|
|
630
721
|
const toolsWrapper = document.createElement("div");
|
|
631
722
|
toolsWrapper.classList.add("highlight-frame-tools-wrapper");
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
}
|
|
635
|
-
if (isTextEdit) {
|
|
636
|
-
toolsWrapper.classList.add("is-text-edit");
|
|
637
|
-
}
|
|
723
|
+
toggleClass(toolsWrapper, "is-instance", isInstance);
|
|
724
|
+
toggleClass(toolsWrapper, "is-text-edit", isTextEdit);
|
|
638
725
|
toolsWrapper.style.position = "absolute";
|
|
639
726
|
toolsWrapper.style.transform = `translate(${left}px, ${bottomY}px)`;
|
|
640
727
|
toolsWrapper.style.transformOrigin = "left center";
|
|
641
728
|
toolsWrapper.style.pointerEvents = "none";
|
|
642
729
|
toolsWrapper.style.zIndex = "500";
|
|
643
730
|
createToolsContainer(node, toolsWrapper, isInstance, isTextEdit);
|
|
644
|
-
|
|
645
|
-
canvasContainer.appendChild(toolsWrapper);
|
|
646
|
-
}
|
|
647
|
-
else {
|
|
648
|
-
document.body.appendChild(toolsWrapper);
|
|
649
|
-
}
|
|
731
|
+
container.appendChild(toolsWrapper);
|
|
650
732
|
};
|
|
651
733
|
|
|
652
734
|
const getComponentColor = () => {
|
|
@@ -664,24 +746,13 @@ const refreshHighlightFrame = (node, nodeProvider, canvasName = "canvas") => {
|
|
|
664
746
|
const isTextEdit = node.contentEditable === "true";
|
|
665
747
|
// Update SVG dimensions to match current viewport (handles window resize and ensures coordinate system is correct)
|
|
666
748
|
// Use clientWidth/Height to match getBoundingClientRect() coordinate system (excludes scrollbars)
|
|
667
|
-
const viewportWidth
|
|
668
|
-
const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
|
|
749
|
+
const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
|
|
669
750
|
frame.setAttribute("width", viewportWidth.toString());
|
|
670
751
|
frame.setAttribute("height", viewportHeight.toString());
|
|
671
752
|
// Update instance class
|
|
672
|
-
|
|
673
|
-
frame.classList.add("is-instance");
|
|
674
|
-
}
|
|
675
|
-
else {
|
|
676
|
-
frame.classList.remove("is-instance");
|
|
677
|
-
}
|
|
753
|
+
toggleClass(frame, "is-instance", isInstance);
|
|
678
754
|
// Update text edit class
|
|
679
|
-
|
|
680
|
-
frame.classList.add("is-text-edit");
|
|
681
|
-
}
|
|
682
|
-
else {
|
|
683
|
-
frame.classList.remove("is-text-edit");
|
|
684
|
-
}
|
|
755
|
+
toggleClass(frame, "is-text-edit", isTextEdit);
|
|
685
756
|
const group = frame.querySelector(".highlight-frame-group");
|
|
686
757
|
if (!group)
|
|
687
758
|
return;
|
|
@@ -698,8 +769,7 @@ const refreshHighlightFrame = (node, nodeProvider, canvasName = "canvas") => {
|
|
|
698
769
|
else {
|
|
699
770
|
rect.removeAttribute("stroke"); // Use CSS default
|
|
700
771
|
}
|
|
701
|
-
const
|
|
702
|
-
const container = canvasContainer || document.body;
|
|
772
|
+
const container = getCanvasContainerOrBody();
|
|
703
773
|
const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
|
|
704
774
|
const nodeTools = toolsWrapper?.querySelector(".node-tools");
|
|
705
775
|
const zoom = getCanvasWindowValue(["zoom", "current"], canvasName) ?? 1;
|
|
@@ -710,36 +780,10 @@ const refreshHighlightFrame = (node, nodeProvider, canvasName = "canvas") => {
|
|
|
710
780
|
const minWidth = Math.max(width, 3);
|
|
711
781
|
const bottomY = top + height;
|
|
712
782
|
// Update instance classes on tools wrapper and node tools
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
else {
|
|
718
|
-
toolsWrapper.classList.remove("is-instance");
|
|
719
|
-
}
|
|
720
|
-
// Update text edit class
|
|
721
|
-
if (isTextEdit) {
|
|
722
|
-
toolsWrapper.classList.add("is-text-edit");
|
|
723
|
-
}
|
|
724
|
-
else {
|
|
725
|
-
toolsWrapper.classList.remove("is-text-edit");
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
if (nodeTools) {
|
|
729
|
-
if (isInstance) {
|
|
730
|
-
nodeTools.classList.add("is-instance");
|
|
731
|
-
}
|
|
732
|
-
else {
|
|
733
|
-
nodeTools.classList.remove("is-instance");
|
|
734
|
-
}
|
|
735
|
-
// Update text edit class
|
|
736
|
-
if (isTextEdit) {
|
|
737
|
-
nodeTools.classList.add("is-text-edit");
|
|
738
|
-
}
|
|
739
|
-
else {
|
|
740
|
-
nodeTools.classList.remove("is-text-edit");
|
|
741
|
-
}
|
|
742
|
-
}
|
|
783
|
+
toggleClass(toolsWrapper, "is-instance", isInstance);
|
|
784
|
+
toggleClass(toolsWrapper, "is-text-edit", isTextEdit);
|
|
785
|
+
toggleClass(nodeTools, "is-instance", isInstance);
|
|
786
|
+
toggleClass(nodeTools, "is-text-edit", isTextEdit);
|
|
743
787
|
// Batch all DOM writes (single paint pass)
|
|
744
788
|
// Update group transform to move entire group (rect + handles) at once
|
|
745
789
|
group.setAttribute("transform", `translate(${left}, ${top})`);
|
|
@@ -805,8 +849,7 @@ const updateHighlightFrameVisibility = (node) => {
|
|
|
805
849
|
const displayValue = hasHiddenClass ? "none" : "";
|
|
806
850
|
// Batch DOM writes
|
|
807
851
|
frame.style.display = displayValue;
|
|
808
|
-
const
|
|
809
|
-
const container = canvasContainer || document.body;
|
|
852
|
+
const container = getCanvasContainerOrBody();
|
|
810
853
|
const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
|
|
811
854
|
if (toolsWrapper) {
|
|
812
855
|
toolsWrapper.style.display = displayValue;
|
|
@@ -1112,7 +1155,6 @@ const createNodeTools = (element, canvasName = "canvas") => {
|
|
|
1112
1155
|
highlightNode(node);
|
|
1113
1156
|
if (nodeProvider) {
|
|
1114
1157
|
updateHighlightFrameVisibility(node);
|
|
1115
|
-
updateHighlightFrameVisibility(node);
|
|
1116
1158
|
}
|
|
1117
1159
|
}
|
|
1118
1160
|
else {
|
|
@@ -1120,7 +1162,7 @@ const createNodeTools = (element, canvasName = "canvas") => {
|
|
|
1120
1162
|
}
|
|
1121
1163
|
};
|
|
1122
1164
|
// Setup event listener
|
|
1123
|
-
const removeListeners = setupEventListener
|
|
1165
|
+
const removeListeners = setupEventListener(nodeProvider, selectNode, handleEscape, text);
|
|
1124
1166
|
const cleanup = () => {
|
|
1125
1167
|
removeListeners();
|
|
1126
1168
|
resizeObserver?.disconnect();
|
|
@@ -1155,6 +1197,10 @@ const createNodeTools = (element, canvasName = "canvas") => {
|
|
|
1155
1197
|
return nodeTools;
|
|
1156
1198
|
};
|
|
1157
1199
|
|
|
1200
|
+
const getNodeProvider = () => {
|
|
1201
|
+
return document.querySelector('[data-role="node-provider"]');
|
|
1202
|
+
};
|
|
1203
|
+
|
|
1158
1204
|
const DEFAULT_WIDTH = 400;
|
|
1159
1205
|
const RESIZE_CONFIG = {
|
|
1160
1206
|
minWidth: 4,
|
|
@@ -1188,27 +1234,6 @@ const RESIZE_PRESETS = [
|
|
|
1188
1234
|
},
|
|
1189
1235
|
];
|
|
1190
1236
|
|
|
1191
|
-
const setupEventListener = (resizeHandle, startResize, handleResize, stopResize, blurResize) => {
|
|
1192
|
-
const handleMouseLeave = (event) => {
|
|
1193
|
-
// Check if mouse is leaving the window/document
|
|
1194
|
-
if (!event.relatedTarget && (event.target === document || event.target === document.documentElement)) {
|
|
1195
|
-
blurResize();
|
|
1196
|
-
}
|
|
1197
|
-
};
|
|
1198
|
-
resizeHandle.addEventListener("mousedown", startResize);
|
|
1199
|
-
document.addEventListener("mousemove", handleResize);
|
|
1200
|
-
document.addEventListener("mouseup", stopResize);
|
|
1201
|
-
document.addEventListener("mouseleave", handleMouseLeave);
|
|
1202
|
-
window.addEventListener("blur", blurResize);
|
|
1203
|
-
return () => {
|
|
1204
|
-
resizeHandle.removeEventListener("mousedown", startResize);
|
|
1205
|
-
document.removeEventListener("mousemove", handleResize);
|
|
1206
|
-
document.removeEventListener("mouseup", stopResize);
|
|
1207
|
-
document.removeEventListener("mouseleave", handleMouseLeave);
|
|
1208
|
-
window.removeEventListener("blur", blurResize);
|
|
1209
|
-
};
|
|
1210
|
-
};
|
|
1211
|
-
|
|
1212
1237
|
const createResizeHandle = (container) => {
|
|
1213
1238
|
const handle = document.createElement("div");
|
|
1214
1239
|
handle.className = "resize-handle";
|
|
@@ -1275,58 +1300,57 @@ const createViewport = (container, initialWidth) => {
|
|
|
1275
1300
|
const width = initialWidth ?? DEFAULT_WIDTH;
|
|
1276
1301
|
container.style.setProperty("--container-width", `${width}px`);
|
|
1277
1302
|
createResizePresets(resizeHandle, container, updateWidth);
|
|
1278
|
-
|
|
1303
|
+
// Track initial values for resize calculation
|
|
1279
1304
|
let startX = 0;
|
|
1280
1305
|
let startWidth = 0;
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
const handleResize = (event) => {
|
|
1289
|
-
if (!isDragging)
|
|
1290
|
-
return;
|
|
1291
|
-
if (canvas) {
|
|
1292
|
-
canvas.style.cursor = "ew-resize";
|
|
1293
|
-
}
|
|
1294
|
-
const width = calcWidth(event, startX, startWidth);
|
|
1295
|
-
updateWidth(container, width);
|
|
1296
|
-
};
|
|
1297
|
-
const stopResize = (event) => {
|
|
1298
|
-
event.preventDefault();
|
|
1299
|
-
event.stopPropagation();
|
|
1300
|
-
if (canvas) {
|
|
1301
|
-
canvas.style.cursor = "default";
|
|
1302
|
-
}
|
|
1303
|
-
isDragging = false;
|
|
1304
|
-
};
|
|
1305
|
-
const blurResize = () => {
|
|
1306
|
-
if (canvas) {
|
|
1307
|
-
canvas.style.cursor = "default";
|
|
1306
|
+
// Handle mouse leave for resize (specific to resize use case)
|
|
1307
|
+
const handleMouseLeave = (event) => {
|
|
1308
|
+
// Check if mouse is leaving the window/document
|
|
1309
|
+
if (!event.relatedTarget && (event.target === document || event.target === document.documentElement)) {
|
|
1310
|
+
if (canvas) {
|
|
1311
|
+
canvas.style.cursor = "default";
|
|
1312
|
+
}
|
|
1308
1313
|
}
|
|
1309
|
-
isDragging = false;
|
|
1310
1314
|
};
|
|
1311
|
-
const
|
|
1312
|
-
|
|
1315
|
+
const removeDragListeners = createDragHandler(resizeHandle, {
|
|
1316
|
+
onStart: (_event, { startX: dragStartX }) => {
|
|
1317
|
+
startX = dragStartX;
|
|
1318
|
+
startWidth = container.offsetWidth;
|
|
1319
|
+
},
|
|
1320
|
+
onDrag: (event) => {
|
|
1321
|
+
if (canvas) {
|
|
1322
|
+
canvas.style.cursor = "ew-resize";
|
|
1323
|
+
}
|
|
1324
|
+
const width = calcWidth(event, startX, startWidth);
|
|
1325
|
+
updateWidth(container, width);
|
|
1326
|
+
},
|
|
1327
|
+
onStop: () => {
|
|
1328
|
+
if (canvas) {
|
|
1329
|
+
canvas.style.cursor = "default";
|
|
1330
|
+
}
|
|
1331
|
+
},
|
|
1332
|
+
onCancel: () => {
|
|
1333
|
+
if (canvas) {
|
|
1334
|
+
canvas.style.cursor = "default";
|
|
1335
|
+
}
|
|
1336
|
+
},
|
|
1337
|
+
onPreventClick: () => { },
|
|
1338
|
+
});
|
|
1339
|
+
document.addEventListener("mouseleave", handleMouseLeave);
|
|
1313
1340
|
refreshViewportLabels();
|
|
1314
1341
|
const cleanup = () => {
|
|
1315
|
-
|
|
1316
|
-
|
|
1342
|
+
removeDragListeners();
|
|
1343
|
+
document.removeEventListener("mouseleave", handleMouseLeave);
|
|
1317
1344
|
resizeHandle.remove();
|
|
1318
|
-
// Refresh labels after cleanup to remove this viewport's label if needed
|
|
1319
1345
|
refreshViewportLabels();
|
|
1320
1346
|
};
|
|
1321
1347
|
return {
|
|
1322
1348
|
setWidth: (width) => {
|
|
1323
1349
|
updateWidth(container, width);
|
|
1324
1350
|
refreshViewportLabels();
|
|
1325
|
-
|
|
1326
|
-
// biome-ignore lint/suspicious/noExplicitAny: global window extension
|
|
1327
|
-
const nodeTools = window.nodeTools;
|
|
1351
|
+
const nodeTools = getNodeTools();
|
|
1328
1352
|
const selectedNode = nodeTools?.getSelectedNode?.();
|
|
1329
|
-
const nodeProvider =
|
|
1353
|
+
const nodeProvider = getNodeProvider();
|
|
1330
1354
|
if (selectedNode && nodeProvider) {
|
|
1331
1355
|
refreshHighlightFrame(selectedNode, nodeProvider);
|
|
1332
1356
|
}
|