@node-edit-utils/core 2.3.3 → 2.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/viewport/label/getViewportLabelOverlay.d.ts +1 -0
- package/dist/lib/viewport/label/index.d.ts +5 -3
- package/dist/lib/viewport/label/isViewportDragging.d.ts +2 -0
- package/dist/lib/viewport/label/refreshViewportLabel.d.ts +8 -0
- package/dist/lib/viewport/label/removeViewportLabel.d.ts +5 -0
- package/dist/lib/viewport/label/setupViewportDrag.d.ts +1 -0
- package/dist/node-edit-utils.cjs.js +100 -62
- package/dist/node-edit-utils.esm.js +100 -62
- package/dist/node-edit-utils.umd.js +100 -62
- package/dist/node-edit-utils.umd.min.js +1 -1
- package/dist/styles.css +1 -1
- package/package.json +7 -2
- package/src/lib/canvas/createCanvasObserver.test.ts +242 -0
- package/src/lib/canvas/disableCanvasKeyboard.test.ts +53 -0
- package/src/lib/canvas/disableCanvasKeyboard.ts +1 -1
- package/src/lib/canvas/disableCanvasTextMode.test.ts +53 -0
- package/src/lib/canvas/disableCanvasTextMode.ts +1 -1
- package/src/lib/canvas/enableCanvasKeyboard.test.ts +53 -0
- package/src/lib/canvas/enableCanvasKeyboard.ts +1 -1
- package/src/lib/canvas/enableCanvasTextMode.test.ts +53 -0
- package/src/lib/canvas/enableCanvasTextMode.ts +1 -1
- package/src/lib/canvas/helpers/applyCanvasState.test.ts +119 -0
- package/src/lib/canvas/helpers/applyCanvasState.ts +1 -1
- package/src/lib/canvas/helpers/getCanvasContainer.test.ts +62 -0
- package/src/lib/canvas/helpers/getCanvasContainerOrBody.test.ts +51 -0
- package/src/lib/canvas/helpers/getCanvasWindowValue.test.ts +116 -0
- package/src/lib/helpers/adjustForZoom.test.ts +65 -0
- package/src/lib/helpers/createDragHandler.test.ts +325 -0
- package/src/lib/helpers/getNodeProvider.test.ts +71 -0
- package/src/lib/helpers/getNodeTools.test.ts +50 -0
- package/src/lib/helpers/getViewportDimensions.test.ts +93 -0
- package/src/lib/helpers/observer/connectMutationObserver.test.ts +127 -0
- package/src/lib/helpers/observer/connectResizeObserver.test.ts +147 -0
- package/src/lib/helpers/parseTransform.test.ts +117 -0
- package/src/lib/helpers/toggleClass.test.ts +71 -0
- package/src/lib/helpers/withRAF.test.ts +439 -0
- package/src/lib/node-tools/createNodeTools.test.ts +373 -0
- package/src/lib/node-tools/events/click/handleNodeClick.test.ts +109 -0
- package/src/lib/node-tools/events/setupEventListener.test.ts +136 -0
- package/src/lib/node-tools/highlight/clearHighlightFrame.test.ts +88 -0
- package/src/lib/node-tools/highlight/createCornerHandles.test.ts +150 -0
- package/src/lib/node-tools/highlight/createHighlightFrame.test.ts +237 -0
- package/src/lib/node-tools/highlight/createTagLabel.test.ts +135 -0
- package/src/lib/node-tools/highlight/createToolsContainer.test.ts +97 -0
- package/src/lib/node-tools/highlight/helpers/getElementBounds.test.ts +158 -0
- package/src/lib/node-tools/highlight/helpers/getHighlightFrameElement.test.ts +78 -0
- package/src/lib/node-tools/highlight/helpers/getScreenBounds.test.ts +133 -0
- package/src/lib/node-tools/highlight/highlightNode.test.ts +213 -0
- package/src/lib/node-tools/highlight/refreshHighlightFrame.test.ts +323 -0
- package/src/lib/node-tools/highlight/updateHighlightFrameVisibility.test.ts +110 -0
- package/src/lib/node-tools/select/helpers/getElementsFromPoint.test.ts +109 -0
- package/src/lib/node-tools/select/helpers/isInsideComponent.test.ts +81 -0
- package/src/lib/node-tools/select/helpers/isInsideViewport.test.ts +82 -0
- package/src/lib/node-tools/select/helpers/targetSameCandidates.test.ts +81 -0
- package/src/lib/node-tools/select/selectNode.test.ts +238 -0
- package/src/lib/node-tools/text/events/setupKeydownHandler.test.ts +91 -0
- package/src/lib/node-tools/text/events/setupMutationObserver.test.ts +213 -0
- package/src/lib/node-tools/text/events/setupNodeListeners.test.ts +133 -0
- package/src/lib/node-tools/text/helpers/enterTextEditMode.test.ts +50 -0
- package/src/lib/node-tools/text/helpers/handleTextChange.test.ts +201 -0
- package/src/lib/node-tools/text/helpers/hasTextContent.test.ts +101 -0
- package/src/lib/node-tools/text/helpers/insertLineBreak.test.ts +96 -0
- package/src/lib/node-tools/text/helpers/makeNodeEditable.test.ts +56 -0
- package/src/lib/node-tools/text/helpers/makeNodeNonEditable.test.ts +57 -0
- package/src/lib/node-tools/text/helpers/shouldEnterTextEditMode.test.ts +61 -0
- package/src/lib/node-tools/text/nodeText.test.ts +233 -0
- package/src/lib/post-message/processPostMessage.test.ts +218 -0
- package/src/lib/post-message/sendPostMessage.test.ts +120 -0
- package/src/lib/styles/styles.css +2 -2
- package/src/lib/viewport/createViewport.test.ts +267 -0
- package/src/lib/viewport/createViewport.ts +7 -4
- package/src/lib/viewport/events/setupEventListener.test.ts +103 -0
- package/src/lib/viewport/label/getViewportLabelOverlay.test.ts +77 -0
- package/src/lib/viewport/label/{getViewportLabelsOverlay.ts → getViewportLabelOverlay.ts} +2 -1
- package/src/lib/viewport/label/helpers/getLabelPosition.test.ts +51 -0
- package/src/lib/viewport/label/helpers/getTransformValues.test.ts +59 -0
- package/src/lib/viewport/label/helpers/getZoomValue.test.ts +53 -0
- package/src/lib/viewport/label/helpers/selectFirstViewportNode.test.ts +105 -0
- package/src/lib/viewport/label/helpers/selectFirstViewportNode.ts +8 -0
- package/src/lib/viewport/label/index.ts +5 -3
- package/src/lib/viewport/label/isViewportDragging.test.ts +35 -0
- package/src/lib/viewport/label/isViewportDragging.ts +9 -0
- package/src/lib/viewport/label/refreshViewportLabel.test.ts +105 -0
- package/src/lib/viewport/label/refreshViewportLabel.ts +50 -0
- package/src/lib/viewport/label/refreshViewportLabels.test.ts +107 -0
- package/src/lib/viewport/label/refreshViewportLabels.ts +17 -50
- package/src/lib/viewport/label/removeViewportLabel.test.ts +67 -0
- package/src/lib/viewport/label/removeViewportLabel.ts +20 -0
- package/src/lib/viewport/label/setupViewportDrag.test.ts +249 -0
- package/src/lib/viewport/label/{setupViewportLabelDrag.ts → setupViewportDrag.ts} +14 -14
- package/src/lib/viewport/resize/createResizeHandle.test.ts +37 -0
- package/src/lib/viewport/resize/createResizePresets.test.ts +75 -0
- package/src/lib/viewport/resize/updateActivePreset.test.ts +92 -0
- package/src/lib/viewport/width/calcConstrainedWidth.test.ts +47 -0
- package/src/lib/viewport/width/calcWidth.test.ts +68 -0
- package/src/lib/viewport/width/updateWidth.test.ts +78 -0
- package/src/lib/window/bindToWindow.test.ts +166 -0
- package/dist/lib/viewport/label/getViewportLabelsOverlay.d.ts +0 -1
- package/dist/lib/viewport/label/isViewportLabelDragging.d.ts +0 -2
- package/dist/lib/viewport/label/setupViewportLabelDrag.d.ts +0 -1
- package/src/lib/viewport/label/isViewportLabelDragging.ts +0 -9
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getViewportLabelOverlay: () => SVGSVGElement;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
1
|
+
export { getViewportLabelOverlay } from "./getViewportLabelOverlay";
|
|
2
|
+
export { isViewportDragging } from "./isViewportDragging";
|
|
3
|
+
export { refreshViewportLabel } from "./refreshViewportLabel";
|
|
3
4
|
export { refreshViewportLabels } from "./refreshViewportLabels";
|
|
4
|
-
export {
|
|
5
|
+
export { removeViewportLabel } from "./removeViewportLabel";
|
|
6
|
+
export { setupViewportDrag } from "./setupViewportDrag";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refreshes (updates) a viewport label for a single viewport element.
|
|
3
|
+
* Creates the label if it doesn't exist, or updates its position if it does.
|
|
4
|
+
* Similar to refreshHighlightFrame - updates existing elements rather than recreating.
|
|
5
|
+
*
|
|
6
|
+
* @param viewportElement - The viewport element to refresh the label for
|
|
7
|
+
*/
|
|
8
|
+
export declare const refreshViewportLabel: (viewportElement: HTMLElement) => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const setupViewportDrag: (labelElement: SVGTextElement, viewportElement: HTMLElement, viewportName: string) => (() => void);
|
|
@@ -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.4
|
|
5
5
|
*/
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
@@ -16,16 +16,6 @@ const getViewportDimensions = () => {
|
|
|
16
16
|
};
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
function getScreenBounds(element) {
|
|
20
|
-
const rect = element.getBoundingClientRect();
|
|
21
|
-
return {
|
|
22
|
-
top: rect.top,
|
|
23
|
-
left: rect.left,
|
|
24
|
-
width: rect.width,
|
|
25
|
-
height: rect.height,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
19
|
const getCanvasContainer = () => {
|
|
30
20
|
return document.querySelector(".canvas-container");
|
|
31
21
|
};
|
|
@@ -34,7 +24,7 @@ const getCanvasContainerOrBody = () => {
|
|
|
34
24
|
return getCanvasContainer() || document.body;
|
|
35
25
|
};
|
|
36
26
|
|
|
37
|
-
const
|
|
27
|
+
const getViewportLabelOverlay = () => {
|
|
38
28
|
const container = getCanvasContainerOrBody();
|
|
39
29
|
// Check if overlay already exists
|
|
40
30
|
let overlay = container.querySelector(".viewport-labels-overlay");
|
|
@@ -60,11 +50,21 @@ const getViewportLabelsOverlay = () => {
|
|
|
60
50
|
|
|
61
51
|
// Global flag to prevent refreshViewportLabels during drag
|
|
62
52
|
let globalIsDragging = false;
|
|
63
|
-
const
|
|
64
|
-
const
|
|
53
|
+
const isViewportDragging = () => globalIsDragging;
|
|
54
|
+
const setViewportDragging = (isDragging) => {
|
|
65
55
|
globalIsDragging = isDragging;
|
|
66
56
|
};
|
|
67
57
|
|
|
58
|
+
function getScreenBounds(element) {
|
|
59
|
+
const rect = element.getBoundingClientRect();
|
|
60
|
+
return {
|
|
61
|
+
top: rect.top,
|
|
62
|
+
left: rect.left,
|
|
63
|
+
width: rect.width,
|
|
64
|
+
height: rect.height,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
68
|
/**
|
|
69
69
|
* Creates a reusable drag handler for mouse drag operations
|
|
70
70
|
* @param element - Element that triggers the drag (mousedown listener attached here)
|
|
@@ -198,12 +198,18 @@ const selectFirstViewportNode = (viewportElement) => {
|
|
|
198
198
|
if (firstChild) {
|
|
199
199
|
const nodeTools = getNodeTools();
|
|
200
200
|
if (nodeTools?.selectNode) {
|
|
201
|
+
const wasAlreadySelected = nodeTools.getSelectedNode() === firstChild;
|
|
201
202
|
nodeTools.selectNode(firstChild);
|
|
203
|
+
// Always emit postMessage when selecting via viewport label click,
|
|
204
|
+
// even if the node was already selected (to match behavior of direct node clicks)
|
|
205
|
+
if (wasAlreadySelected) {
|
|
206
|
+
sendPostMessage("selectedNodeChanged", firstChild.getAttribute("data-node-id") ?? null);
|
|
207
|
+
}
|
|
202
208
|
}
|
|
203
209
|
}
|
|
204
210
|
};
|
|
205
211
|
|
|
206
|
-
const
|
|
212
|
+
const setupViewportDrag = (labelElement, viewportElement, viewportName) => {
|
|
207
213
|
// Get the parent group element that contains the label
|
|
208
214
|
const labelGroup = labelElement.parentElement;
|
|
209
215
|
// Track initial positions for calculations
|
|
@@ -211,7 +217,7 @@ const setupViewportLabelDrag = (labelElement, viewportElement, viewportName) =>
|
|
|
211
217
|
let initialLabelPosition = { x: 0, y: 0 };
|
|
212
218
|
return createDragHandler(labelElement, {
|
|
213
219
|
onStart: () => {
|
|
214
|
-
|
|
220
|
+
setViewportDragging(true);
|
|
215
221
|
initialTransform = getTransformValues(viewportElement);
|
|
216
222
|
initialLabelPosition = getLabelPosition(labelGroup);
|
|
217
223
|
selectFirstViewportNode(viewportElement);
|
|
@@ -232,66 +238,51 @@ const setupViewportLabelDrag = (labelElement, viewportElement, viewportName) =>
|
|
|
232
238
|
viewportElement.style.transform = `translate3d(${newX}px, ${newY}px, 0)`;
|
|
233
239
|
},
|
|
234
240
|
onStop: (_event, { hasDragged }) => {
|
|
235
|
-
|
|
241
|
+
setViewportDragging(false);
|
|
242
|
+
const finalTransform = getTransformValues(viewportElement);
|
|
236
243
|
// If it was a drag, handle drag completion
|
|
237
244
|
if (hasDragged) {
|
|
238
|
-
const finalTransform = getTransformValues(viewportElement);
|
|
239
245
|
// Trigger refresh after drag completes to update highlight frame and labels
|
|
240
246
|
const nodeTools = getNodeTools();
|
|
241
247
|
if (nodeTools?.refreshHighlightFrame) {
|
|
242
248
|
nodeTools.refreshHighlightFrame();
|
|
243
249
|
}
|
|
244
|
-
// Notify parent about the new position
|
|
245
|
-
sendPostMessage("viewport-position-changed", {
|
|
246
|
-
viewportName,
|
|
247
|
-
x: finalTransform.x,
|
|
248
|
-
y: finalTransform.y,
|
|
249
|
-
});
|
|
250
250
|
}
|
|
251
|
+
// Always notify parent about the new position on drag stop
|
|
252
|
+
sendPostMessage("viewport-position-changed", {
|
|
253
|
+
viewportName,
|
|
254
|
+
x: finalTransform.x,
|
|
255
|
+
y: finalTransform.y,
|
|
256
|
+
});
|
|
251
257
|
},
|
|
252
258
|
onCancel: () => {
|
|
253
|
-
|
|
259
|
+
setViewportDragging(false);
|
|
254
260
|
},
|
|
255
261
|
onPreventClick: () => { },
|
|
256
262
|
});
|
|
257
263
|
};
|
|
258
264
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
265
|
+
/**
|
|
266
|
+
* Refreshes (updates) a viewport label for a single viewport element.
|
|
267
|
+
* Creates the label if it doesn't exist, or updates its position if it does.
|
|
268
|
+
* Similar to refreshHighlightFrame - updates existing elements rather than recreating.
|
|
269
|
+
*
|
|
270
|
+
* @param viewportElement - The viewport element to refresh the label for
|
|
271
|
+
*/
|
|
272
|
+
const refreshViewportLabel = (viewportElement) => {
|
|
273
|
+
const viewportName = viewportElement.getAttribute("data-viewport-name");
|
|
274
|
+
if (!viewportName) {
|
|
264
275
|
return;
|
|
265
276
|
}
|
|
266
|
-
const overlay =
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
overlay.
|
|
270
|
-
|
|
271
|
-
// Find all viewports with names
|
|
272
|
-
const viewports = document.querySelectorAll(".viewport[data-viewport-name]");
|
|
273
|
-
// Clean up existing drag listeners
|
|
274
|
-
dragCleanupFunctions.forEach((cleanup) => {
|
|
275
|
-
cleanup();
|
|
276
|
-
});
|
|
277
|
-
dragCleanupFunctions.clear();
|
|
278
|
-
// Remove existing label groups
|
|
279
|
-
const existingGroups = overlay.querySelectorAll(".viewport-label-group");
|
|
280
|
-
existingGroups.forEach((group) => {
|
|
281
|
-
group.remove();
|
|
282
|
-
});
|
|
283
|
-
// Create/update labels for each viewport
|
|
284
|
-
viewports.forEach((viewport) => {
|
|
285
|
-
const viewportElement = viewport;
|
|
286
|
-
const viewportName = viewportElement.getAttribute("data-viewport-name");
|
|
287
|
-
if (!viewportName)
|
|
288
|
-
return;
|
|
289
|
-
const bounds = getScreenBounds(viewportElement);
|
|
277
|
+
const overlay = getViewportLabelOverlay();
|
|
278
|
+
const bounds = getScreenBounds(viewportElement);
|
|
279
|
+
// Get existing label group or create if it doesn't exist
|
|
280
|
+
let group = overlay.querySelector(`[data-viewport-name="${viewportName}"]`);
|
|
281
|
+
if (!group) {
|
|
290
282
|
// Create group for this viewport label
|
|
291
|
-
|
|
283
|
+
group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
292
284
|
group.classList.add("viewport-label-group");
|
|
293
285
|
group.setAttribute("data-viewport-name", viewportName);
|
|
294
|
-
group.setAttribute("transform", `translate(${bounds.left}, ${bounds.top})`);
|
|
295
286
|
// Create text element
|
|
296
287
|
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
297
288
|
text.classList.add("viewport-label-text");
|
|
@@ -302,9 +293,38 @@ const refreshViewportLabels = () => {
|
|
|
302
293
|
text.textContent = viewportName;
|
|
303
294
|
group.appendChild(text);
|
|
304
295
|
overlay.appendChild(group);
|
|
305
|
-
// Setup drag functionality
|
|
306
|
-
|
|
307
|
-
|
|
296
|
+
// Setup drag functionality only when creating new label
|
|
297
|
+
setupViewportDrag(text, viewportElement, viewportName);
|
|
298
|
+
}
|
|
299
|
+
// Update label position (this is the refresh part - updates existing label)
|
|
300
|
+
group.setAttribute("transform", `translate(${bounds.left}, ${bounds.top})`);
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const refreshViewportLabels = () => {
|
|
304
|
+
// Skip refresh if a viewport label is being dragged
|
|
305
|
+
if (isViewportDragging()) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const overlay = getViewportLabelOverlay();
|
|
309
|
+
// Update SVG dimensions to match current viewport (handles window resize and ensures coordinate system is correct)
|
|
310
|
+
const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
|
|
311
|
+
overlay.setAttribute("width", viewportWidth.toString());
|
|
312
|
+
overlay.setAttribute("height", viewportHeight.toString());
|
|
313
|
+
// Find all viewports with names and refresh each label
|
|
314
|
+
const viewports = document.querySelectorAll(".viewport[data-viewport-name]");
|
|
315
|
+
viewports.forEach((viewport) => {
|
|
316
|
+
refreshViewportLabel(viewport);
|
|
317
|
+
});
|
|
318
|
+
// Remove labels for viewports that no longer exist
|
|
319
|
+
const existingGroups = overlay.querySelectorAll(".viewport-label-group");
|
|
320
|
+
existingGroups.forEach((group) => {
|
|
321
|
+
const viewportName = group.getAttribute("data-viewport-name");
|
|
322
|
+
if (viewportName) {
|
|
323
|
+
const viewportExists = Array.from(viewports).some((viewport) => viewport.getAttribute("data-viewport-name") === viewportName);
|
|
324
|
+
if (!viewportExists) {
|
|
325
|
+
group.remove();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
308
328
|
});
|
|
309
329
|
};
|
|
310
330
|
|
|
@@ -1234,6 +1254,22 @@ const RESIZE_PRESETS = [
|
|
|
1234
1254
|
},
|
|
1235
1255
|
];
|
|
1236
1256
|
|
|
1257
|
+
/**
|
|
1258
|
+
* Removes a viewport label for a single viewport element.
|
|
1259
|
+
* @param viewportElement - The viewport element to remove the label for
|
|
1260
|
+
*/
|
|
1261
|
+
const removeViewportLabel = (viewportElement) => {
|
|
1262
|
+
const viewportName = viewportElement.getAttribute("data-viewport-name");
|
|
1263
|
+
if (!viewportName) {
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
const overlay = getViewportLabelOverlay();
|
|
1267
|
+
const labelGroup = overlay.querySelector(`[data-viewport-name="${viewportName}"]`);
|
|
1268
|
+
if (labelGroup) {
|
|
1269
|
+
labelGroup.remove();
|
|
1270
|
+
}
|
|
1271
|
+
};
|
|
1272
|
+
|
|
1237
1273
|
const createResizeHandle = (container) => {
|
|
1238
1274
|
const handle = document.createElement("div");
|
|
1239
1275
|
handle.className = "resize-handle";
|
|
@@ -1337,17 +1373,19 @@ const createViewport = (container, initialWidth) => {
|
|
|
1337
1373
|
onPreventClick: () => { },
|
|
1338
1374
|
});
|
|
1339
1375
|
document.addEventListener("mouseleave", handleMouseLeave);
|
|
1340
|
-
|
|
1376
|
+
// Create/refresh the label for this viewport
|
|
1377
|
+
refreshViewportLabel(container);
|
|
1341
1378
|
const cleanup = () => {
|
|
1342
1379
|
removeDragListeners();
|
|
1343
1380
|
document.removeEventListener("mouseleave", handleMouseLeave);
|
|
1344
1381
|
resizeHandle.remove();
|
|
1345
|
-
|
|
1382
|
+
// Remove the label for this viewport
|
|
1383
|
+
removeViewportLabel(container);
|
|
1346
1384
|
};
|
|
1347
1385
|
return {
|
|
1348
1386
|
setWidth: (width) => {
|
|
1349
1387
|
updateWidth(container, width);
|
|
1350
|
-
|
|
1388
|
+
refreshViewportLabel(container);
|
|
1351
1389
|
const nodeTools = getNodeTools();
|
|
1352
1390
|
const selectedNode = nodeTools?.getSelectedNode?.();
|
|
1353
1391
|
const nodeProvider = getNodeProvider();
|
|
@@ -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.4
|
|
5
5
|
*/
|
|
6
6
|
const getNodeTools = () => {
|
|
7
7
|
return window.nodeTools;
|
|
@@ -14,16 +14,6 @@ const getViewportDimensions = () => {
|
|
|
14
14
|
};
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
function getScreenBounds(element) {
|
|
18
|
-
const rect = element.getBoundingClientRect();
|
|
19
|
-
return {
|
|
20
|
-
top: rect.top,
|
|
21
|
-
left: rect.left,
|
|
22
|
-
width: rect.width,
|
|
23
|
-
height: rect.height,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
17
|
const getCanvasContainer = () => {
|
|
28
18
|
return document.querySelector(".canvas-container");
|
|
29
19
|
};
|
|
@@ -32,7 +22,7 @@ const getCanvasContainerOrBody = () => {
|
|
|
32
22
|
return getCanvasContainer() || document.body;
|
|
33
23
|
};
|
|
34
24
|
|
|
35
|
-
const
|
|
25
|
+
const getViewportLabelOverlay = () => {
|
|
36
26
|
const container = getCanvasContainerOrBody();
|
|
37
27
|
// Check if overlay already exists
|
|
38
28
|
let overlay = container.querySelector(".viewport-labels-overlay");
|
|
@@ -58,11 +48,21 @@ const getViewportLabelsOverlay = () => {
|
|
|
58
48
|
|
|
59
49
|
// Global flag to prevent refreshViewportLabels during drag
|
|
60
50
|
let globalIsDragging = false;
|
|
61
|
-
const
|
|
62
|
-
const
|
|
51
|
+
const isViewportDragging = () => globalIsDragging;
|
|
52
|
+
const setViewportDragging = (isDragging) => {
|
|
63
53
|
globalIsDragging = isDragging;
|
|
64
54
|
};
|
|
65
55
|
|
|
56
|
+
function getScreenBounds(element) {
|
|
57
|
+
const rect = element.getBoundingClientRect();
|
|
58
|
+
return {
|
|
59
|
+
top: rect.top,
|
|
60
|
+
left: rect.left,
|
|
61
|
+
width: rect.width,
|
|
62
|
+
height: rect.height,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
66
|
/**
|
|
67
67
|
* Creates a reusable drag handler for mouse drag operations
|
|
68
68
|
* @param element - Element that triggers the drag (mousedown listener attached here)
|
|
@@ -196,12 +196,18 @@ const selectFirstViewportNode = (viewportElement) => {
|
|
|
196
196
|
if (firstChild) {
|
|
197
197
|
const nodeTools = getNodeTools();
|
|
198
198
|
if (nodeTools?.selectNode) {
|
|
199
|
+
const wasAlreadySelected = nodeTools.getSelectedNode() === firstChild;
|
|
199
200
|
nodeTools.selectNode(firstChild);
|
|
201
|
+
// Always emit postMessage when selecting via viewport label click,
|
|
202
|
+
// even if the node was already selected (to match behavior of direct node clicks)
|
|
203
|
+
if (wasAlreadySelected) {
|
|
204
|
+
sendPostMessage("selectedNodeChanged", firstChild.getAttribute("data-node-id") ?? null);
|
|
205
|
+
}
|
|
200
206
|
}
|
|
201
207
|
}
|
|
202
208
|
};
|
|
203
209
|
|
|
204
|
-
const
|
|
210
|
+
const setupViewportDrag = (labelElement, viewportElement, viewportName) => {
|
|
205
211
|
// Get the parent group element that contains the label
|
|
206
212
|
const labelGroup = labelElement.parentElement;
|
|
207
213
|
// Track initial positions for calculations
|
|
@@ -209,7 +215,7 @@ const setupViewportLabelDrag = (labelElement, viewportElement, viewportName) =>
|
|
|
209
215
|
let initialLabelPosition = { x: 0, y: 0 };
|
|
210
216
|
return createDragHandler(labelElement, {
|
|
211
217
|
onStart: () => {
|
|
212
|
-
|
|
218
|
+
setViewportDragging(true);
|
|
213
219
|
initialTransform = getTransformValues(viewportElement);
|
|
214
220
|
initialLabelPosition = getLabelPosition(labelGroup);
|
|
215
221
|
selectFirstViewportNode(viewportElement);
|
|
@@ -230,66 +236,51 @@ const setupViewportLabelDrag = (labelElement, viewportElement, viewportName) =>
|
|
|
230
236
|
viewportElement.style.transform = `translate3d(${newX}px, ${newY}px, 0)`;
|
|
231
237
|
},
|
|
232
238
|
onStop: (_event, { hasDragged }) => {
|
|
233
|
-
|
|
239
|
+
setViewportDragging(false);
|
|
240
|
+
const finalTransform = getTransformValues(viewportElement);
|
|
234
241
|
// If it was a drag, handle drag completion
|
|
235
242
|
if (hasDragged) {
|
|
236
|
-
const finalTransform = getTransformValues(viewportElement);
|
|
237
243
|
// Trigger refresh after drag completes to update highlight frame and labels
|
|
238
244
|
const nodeTools = getNodeTools();
|
|
239
245
|
if (nodeTools?.refreshHighlightFrame) {
|
|
240
246
|
nodeTools.refreshHighlightFrame();
|
|
241
247
|
}
|
|
242
|
-
// Notify parent about the new position
|
|
243
|
-
sendPostMessage("viewport-position-changed", {
|
|
244
|
-
viewportName,
|
|
245
|
-
x: finalTransform.x,
|
|
246
|
-
y: finalTransform.y,
|
|
247
|
-
});
|
|
248
248
|
}
|
|
249
|
+
// Always notify parent about the new position on drag stop
|
|
250
|
+
sendPostMessage("viewport-position-changed", {
|
|
251
|
+
viewportName,
|
|
252
|
+
x: finalTransform.x,
|
|
253
|
+
y: finalTransform.y,
|
|
254
|
+
});
|
|
249
255
|
},
|
|
250
256
|
onCancel: () => {
|
|
251
|
-
|
|
257
|
+
setViewportDragging(false);
|
|
252
258
|
},
|
|
253
259
|
onPreventClick: () => { },
|
|
254
260
|
});
|
|
255
261
|
};
|
|
256
262
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
263
|
+
/**
|
|
264
|
+
* Refreshes (updates) a viewport label for a single viewport element.
|
|
265
|
+
* Creates the label if it doesn't exist, or updates its position if it does.
|
|
266
|
+
* Similar to refreshHighlightFrame - updates existing elements rather than recreating.
|
|
267
|
+
*
|
|
268
|
+
* @param viewportElement - The viewport element to refresh the label for
|
|
269
|
+
*/
|
|
270
|
+
const refreshViewportLabel = (viewportElement) => {
|
|
271
|
+
const viewportName = viewportElement.getAttribute("data-viewport-name");
|
|
272
|
+
if (!viewportName) {
|
|
262
273
|
return;
|
|
263
274
|
}
|
|
264
|
-
const overlay =
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
overlay.
|
|
268
|
-
|
|
269
|
-
// Find all viewports with names
|
|
270
|
-
const viewports = document.querySelectorAll(".viewport[data-viewport-name]");
|
|
271
|
-
// Clean up existing drag listeners
|
|
272
|
-
dragCleanupFunctions.forEach((cleanup) => {
|
|
273
|
-
cleanup();
|
|
274
|
-
});
|
|
275
|
-
dragCleanupFunctions.clear();
|
|
276
|
-
// Remove existing label groups
|
|
277
|
-
const existingGroups = overlay.querySelectorAll(".viewport-label-group");
|
|
278
|
-
existingGroups.forEach((group) => {
|
|
279
|
-
group.remove();
|
|
280
|
-
});
|
|
281
|
-
// Create/update labels for each viewport
|
|
282
|
-
viewports.forEach((viewport) => {
|
|
283
|
-
const viewportElement = viewport;
|
|
284
|
-
const viewportName = viewportElement.getAttribute("data-viewport-name");
|
|
285
|
-
if (!viewportName)
|
|
286
|
-
return;
|
|
287
|
-
const bounds = getScreenBounds(viewportElement);
|
|
275
|
+
const overlay = getViewportLabelOverlay();
|
|
276
|
+
const bounds = getScreenBounds(viewportElement);
|
|
277
|
+
// Get existing label group or create if it doesn't exist
|
|
278
|
+
let group = overlay.querySelector(`[data-viewport-name="${viewportName}"]`);
|
|
279
|
+
if (!group) {
|
|
288
280
|
// Create group for this viewport label
|
|
289
|
-
|
|
281
|
+
group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
290
282
|
group.classList.add("viewport-label-group");
|
|
291
283
|
group.setAttribute("data-viewport-name", viewportName);
|
|
292
|
-
group.setAttribute("transform", `translate(${bounds.left}, ${bounds.top})`);
|
|
293
284
|
// Create text element
|
|
294
285
|
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
295
286
|
text.classList.add("viewport-label-text");
|
|
@@ -300,9 +291,38 @@ const refreshViewportLabels = () => {
|
|
|
300
291
|
text.textContent = viewportName;
|
|
301
292
|
group.appendChild(text);
|
|
302
293
|
overlay.appendChild(group);
|
|
303
|
-
// Setup drag functionality
|
|
304
|
-
|
|
305
|
-
|
|
294
|
+
// Setup drag functionality only when creating new label
|
|
295
|
+
setupViewportDrag(text, viewportElement, viewportName);
|
|
296
|
+
}
|
|
297
|
+
// Update label position (this is the refresh part - updates existing label)
|
|
298
|
+
group.setAttribute("transform", `translate(${bounds.left}, ${bounds.top})`);
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const refreshViewportLabels = () => {
|
|
302
|
+
// Skip refresh if a viewport label is being dragged
|
|
303
|
+
if (isViewportDragging()) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const overlay = getViewportLabelOverlay();
|
|
307
|
+
// Update SVG dimensions to match current viewport (handles window resize and ensures coordinate system is correct)
|
|
308
|
+
const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
|
|
309
|
+
overlay.setAttribute("width", viewportWidth.toString());
|
|
310
|
+
overlay.setAttribute("height", viewportHeight.toString());
|
|
311
|
+
// Find all viewports with names and refresh each label
|
|
312
|
+
const viewports = document.querySelectorAll(".viewport[data-viewport-name]");
|
|
313
|
+
viewports.forEach((viewport) => {
|
|
314
|
+
refreshViewportLabel(viewport);
|
|
315
|
+
});
|
|
316
|
+
// Remove labels for viewports that no longer exist
|
|
317
|
+
const existingGroups = overlay.querySelectorAll(".viewport-label-group");
|
|
318
|
+
existingGroups.forEach((group) => {
|
|
319
|
+
const viewportName = group.getAttribute("data-viewport-name");
|
|
320
|
+
if (viewportName) {
|
|
321
|
+
const viewportExists = Array.from(viewports).some((viewport) => viewport.getAttribute("data-viewport-name") === viewportName);
|
|
322
|
+
if (!viewportExists) {
|
|
323
|
+
group.remove();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
306
326
|
});
|
|
307
327
|
};
|
|
308
328
|
|
|
@@ -1232,6 +1252,22 @@ const RESIZE_PRESETS = [
|
|
|
1232
1252
|
},
|
|
1233
1253
|
];
|
|
1234
1254
|
|
|
1255
|
+
/**
|
|
1256
|
+
* Removes a viewport label for a single viewport element.
|
|
1257
|
+
* @param viewportElement - The viewport element to remove the label for
|
|
1258
|
+
*/
|
|
1259
|
+
const removeViewportLabel = (viewportElement) => {
|
|
1260
|
+
const viewportName = viewportElement.getAttribute("data-viewport-name");
|
|
1261
|
+
if (!viewportName) {
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
const overlay = getViewportLabelOverlay();
|
|
1265
|
+
const labelGroup = overlay.querySelector(`[data-viewport-name="${viewportName}"]`);
|
|
1266
|
+
if (labelGroup) {
|
|
1267
|
+
labelGroup.remove();
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
|
|
1235
1271
|
const createResizeHandle = (container) => {
|
|
1236
1272
|
const handle = document.createElement("div");
|
|
1237
1273
|
handle.className = "resize-handle";
|
|
@@ -1335,17 +1371,19 @@ const createViewport = (container, initialWidth) => {
|
|
|
1335
1371
|
onPreventClick: () => { },
|
|
1336
1372
|
});
|
|
1337
1373
|
document.addEventListener("mouseleave", handleMouseLeave);
|
|
1338
|
-
|
|
1374
|
+
// Create/refresh the label for this viewport
|
|
1375
|
+
refreshViewportLabel(container);
|
|
1339
1376
|
const cleanup = () => {
|
|
1340
1377
|
removeDragListeners();
|
|
1341
1378
|
document.removeEventListener("mouseleave", handleMouseLeave);
|
|
1342
1379
|
resizeHandle.remove();
|
|
1343
|
-
|
|
1380
|
+
// Remove the label for this viewport
|
|
1381
|
+
removeViewportLabel(container);
|
|
1344
1382
|
};
|
|
1345
1383
|
return {
|
|
1346
1384
|
setWidth: (width) => {
|
|
1347
1385
|
updateWidth(container, width);
|
|
1348
|
-
|
|
1386
|
+
refreshViewportLabel(container);
|
|
1349
1387
|
const nodeTools = getNodeTools();
|
|
1350
1388
|
const selectedNode = nodeTools?.getSelectedNode?.();
|
|
1351
1389
|
const nodeProvider = getNodeProvider();
|