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