@node-edit-utils/core 2.3.0 → 2.3.2
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/node-tools/select/helpers/isInsideViewport.d.ts +1 -0
- package/dist/lib/viewport/constants.d.ts +2 -2
- package/dist/lib/viewport/createViewport.d.ts +1 -1
- package/dist/lib/viewport/label/getViewportLabelsOverlay.d.ts +1 -0
- package/dist/lib/viewport/label/helpers/getLabelPosition.d.ts +4 -0
- package/dist/lib/viewport/label/helpers/getTransformValues.d.ts +4 -0
- package/dist/lib/viewport/label/helpers/getZoomValue.d.ts +1 -0
- package/dist/lib/viewport/label/index.d.ts +4 -0
- package/dist/lib/viewport/label/isViewportLabelDragging.d.ts +2 -0
- package/dist/lib/viewport/label/refreshViewportLabels.d.ts +1 -0
- package/dist/lib/viewport/label/setupViewportLabelDrag.d.ts +1 -0
- package/dist/node-edit-utils.cjs.js +288 -63
- package/dist/node-edit-utils.esm.js +288 -63
- package/dist/node-edit-utils.umd.js +288 -63
- 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 +14 -0
- package/src/lib/node-tools/createNodeTools.ts +12 -5
- package/src/lib/node-tools/select/helpers/isInsideViewport.ts +19 -0
- package/src/lib/node-tools/select/selectNode.ts +13 -1
- package/src/lib/node-tools/text/events/setupMutationObserver.ts +1 -0
- package/src/lib/styles/styles.css +48 -1
- package/src/lib/viewport/constants.ts +2 -2
- package/src/lib/viewport/createViewport.ts +26 -7
- package/src/lib/viewport/events/setupEventListener.ts +9 -0
- package/src/lib/viewport/label/getViewportLabelsOverlay.ts +33 -0
- package/src/lib/viewport/label/helpers/getLabelPosition.ts +8 -0
- package/src/lib/viewport/label/helpers/getTransformValues.ts +8 -0
- package/src/lib/viewport/label/helpers/getZoomValue.ts +4 -0
- package/src/lib/viewport/label/index.ts +4 -0
- package/src/lib/viewport/label/isViewportLabelDragging.ts +9 -0
- package/src/lib/viewport/label/refreshViewportLabels.ts +69 -0
- package/src/lib/viewport/label/setupViewportLabelDrag.ts +98 -0
|
@@ -71,6 +71,7 @@ export const createNodeTools = (element: HTMLElement | null, canvasName: string
|
|
|
71
71
|
checkNodeExists();
|
|
72
72
|
if (!document.contains(node)) return;
|
|
73
73
|
|
|
74
|
+
console.log("refreshHighlightFrame in mutationObserver 2");
|
|
74
75
|
refreshHighlightFrame(node, nodeProvider, canvasName);
|
|
75
76
|
updateHighlightFrameVisibility(node);
|
|
76
77
|
});
|
|
@@ -78,8 +79,8 @@ export const createNodeTools = (element: HTMLElement | null, canvasName: string
|
|
|
78
79
|
mutationObserver.observe(node, {
|
|
79
80
|
attributes: true,
|
|
80
81
|
characterData: true,
|
|
81
|
-
childList: true,
|
|
82
|
-
subtree: true,
|
|
82
|
+
//childList: true,
|
|
83
|
+
//subtree: true,
|
|
83
84
|
});
|
|
84
85
|
|
|
85
86
|
// Also observe parent node to catch when this node is removed
|
|
@@ -111,6 +112,7 @@ export const createNodeTools = (element: HTMLElement | null, canvasName: string
|
|
|
111
112
|
if (!document.contains(node)) return; // Exit early if node was removed
|
|
112
113
|
|
|
113
114
|
refreshHighlightFrame(node, nodeProvider, canvasName);
|
|
115
|
+
console.log("refreshHighlightFrame in resizeObserver");
|
|
114
116
|
updateHighlightFrameVisibility(node);
|
|
115
117
|
});
|
|
116
118
|
}
|
|
@@ -119,9 +121,14 @@ export const createNodeTools = (element: HTMLElement | null, canvasName: string
|
|
|
119
121
|
sendPostMessage("selectedNodeChanged", node?.getAttribute("data-node-id") ?? null);
|
|
120
122
|
highlightNode(node) ?? null;
|
|
121
123
|
|
|
122
|
-
if (node
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
if (node) {
|
|
125
|
+
highlightNode(node);
|
|
126
|
+
if (nodeProvider) {
|
|
127
|
+
updateHighlightFrameVisibility(node);
|
|
128
|
+
updateHighlightFrameVisibility(node);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
clearHighlightFrame();
|
|
125
132
|
}
|
|
126
133
|
};
|
|
127
134
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const isInsideViewport = (element: Element): boolean => {
|
|
2
|
+
let current: Element | null = element;
|
|
3
|
+
|
|
4
|
+
while (current) {
|
|
5
|
+
if (current.classList.contains("viewport")) {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Stop at node-provider to avoid checking beyond the editable area
|
|
10
|
+
if (current.getAttribute("data-role") === "node-provider") {
|
|
11
|
+
break;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
current = current.parentElement;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return false;
|
|
18
|
+
};
|
|
19
|
+
|
|
@@ -3,6 +3,7 @@ import type { NodeText } from "../text/types";
|
|
|
3
3
|
import { IGNORED_DOM_ELEMENTS } from "./constants";
|
|
4
4
|
import { getElementsFromPoint } from "./helpers/getElementsFromPoint";
|
|
5
5
|
import { isInsideComponent } from "./helpers/isInsideComponent";
|
|
6
|
+
import { isInsideViewport } from "./helpers/isInsideViewport";
|
|
6
7
|
import { targetSameCandidates } from "./helpers/targetSameCandidates";
|
|
7
8
|
|
|
8
9
|
let candidateCache: Element[] = [];
|
|
@@ -22,9 +23,20 @@ export const selectNode = (event: MouseEvent, nodeProvider: HTMLElement | null,
|
|
|
22
23
|
(element) =>
|
|
23
24
|
!IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()) &&
|
|
24
25
|
!element.classList.contains("select-none") &&
|
|
25
|
-
!
|
|
26
|
+
!element.classList.contains("content-layer") &&
|
|
27
|
+
!element.classList.contains("resize-handle") &&
|
|
28
|
+
!element.classList.contains("resize-presets") &&
|
|
29
|
+
!isInsideComponent(element) &&
|
|
30
|
+
isInsideViewport(element)
|
|
26
31
|
);
|
|
27
32
|
|
|
33
|
+
console.log("candidates", candidates);
|
|
34
|
+
|
|
35
|
+
if (candidates.length === 0) {
|
|
36
|
+
lastSelectedNode = null;
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
28
40
|
const editableNode = text.getEditableNode();
|
|
29
41
|
if (editableNode && candidates.includes(editableNode)) {
|
|
30
42
|
selectedNode = editableNode;
|
|
@@ -50,6 +50,7 @@ export const setupMutationObserver = (
|
|
|
50
50
|
// Accumulate mutations instead of replacing
|
|
51
51
|
pendingMutations.push(...mutations);
|
|
52
52
|
scheduleProcess();
|
|
53
|
+
console.log("refreshHighlightFrame in mutationObserver");
|
|
53
54
|
refreshHighlightFrame(node, nodeProvider, canvasName);
|
|
54
55
|
});
|
|
55
56
|
|
|
@@ -71,6 +71,51 @@
|
|
|
71
71
|
will-change: transform;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
.viewport-labels-overlay {
|
|
75
|
+
position: absolute;
|
|
76
|
+
inset: 0;
|
|
77
|
+
width: 100vw;
|
|
78
|
+
height: 100vh;
|
|
79
|
+
pointer-events: none;
|
|
80
|
+
z-index: var(--z-index-highlight);
|
|
81
|
+
overflow: visible;
|
|
82
|
+
contain: layout style paint;
|
|
83
|
+
will-change: transform;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.viewport-label-text {
|
|
87
|
+
font-size: 0.6875rem;
|
|
88
|
+
font-weight: 500;
|
|
89
|
+
font-family: var(--font-family-primary);
|
|
90
|
+
letter-spacing: var(--letter-spacing);
|
|
91
|
+
fill: oklch(0.5 0 0);
|
|
92
|
+
pointer-events: auto;
|
|
93
|
+
user-select: none;
|
|
94
|
+
-webkit-user-select: none;
|
|
95
|
+
-moz-user-select: none;
|
|
96
|
+
-ms-user-select: none;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@media (prefers-color-scheme: dark) {
|
|
100
|
+
.viewport-label-text {
|
|
101
|
+
fill: oklch(0.3 0 0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.viewport-label-text:hover {
|
|
105
|
+
fill: oklch(0 0 0);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@media (prefers-color-scheme: light) {
|
|
110
|
+
.viewport-label-text {
|
|
111
|
+
fill: oklch(0.7 0 0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.viewport-label-text:hover {
|
|
115
|
+
fill: oklch(0.3 0 0);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
74
119
|
.highlight-frame-rect {
|
|
75
120
|
fill: none;
|
|
76
121
|
stroke: var(--primary-color);
|
|
@@ -152,7 +197,9 @@
|
|
|
152
197
|
}
|
|
153
198
|
|
|
154
199
|
.viewport {
|
|
155
|
-
position:
|
|
200
|
+
position: absolute;
|
|
201
|
+
top: 0;
|
|
202
|
+
left: 0;
|
|
156
203
|
width: var(--container-width);
|
|
157
204
|
}
|
|
158
205
|
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { getCanvasContainer } from "../canvas/helpers/getCanvasContainer";
|
|
2
|
-
import {
|
|
2
|
+
import { refreshHighlightFrame } from "../node-tools/highlight/refreshHighlightFrame";
|
|
3
3
|
import { DEFAULT_WIDTH } from "./constants";
|
|
4
4
|
import { setupEventListener } from "./events/setupEventListener";
|
|
5
|
+
import { refreshViewportLabels } from "./label/refreshViewportLabels";
|
|
5
6
|
import { createResizeHandle } from "./resize/createResizeHandle";
|
|
6
7
|
import { createResizePresets } from "./resize/createResizePresets";
|
|
7
8
|
import type { Viewport } from "./types";
|
|
8
9
|
import { calcWidth } from "./width/calcWidth";
|
|
9
10
|
import { updateWidth } from "./width/updateWidth";
|
|
10
11
|
|
|
11
|
-
export const createViewport = (container: HTMLElement): Viewport => {
|
|
12
|
+
export const createViewport = (container: HTMLElement, initialWidth?: number): Viewport => {
|
|
12
13
|
const canvas: HTMLElement | null = getCanvasContainer();
|
|
13
14
|
|
|
14
15
|
// Remove any existing resize handle to prevent duplicates
|
|
@@ -18,7 +19,8 @@ export const createViewport = (container: HTMLElement): Viewport => {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
const resizeHandle = createResizeHandle(container);
|
|
21
|
-
|
|
22
|
+
const width = initialWidth ?? DEFAULT_WIDTH;
|
|
23
|
+
container.style.setProperty("--container-width", `${width}px`);
|
|
22
24
|
|
|
23
25
|
createResizePresets(resizeHandle, container, updateWidth);
|
|
24
26
|
|
|
@@ -46,8 +48,6 @@ export const createViewport = (container: HTMLElement): Viewport => {
|
|
|
46
48
|
updateWidth(container, width);
|
|
47
49
|
};
|
|
48
50
|
|
|
49
|
-
const throttledHandleResize = withRAFThrottle(handleResize);
|
|
50
|
-
|
|
51
51
|
const stopResize = (event: MouseEvent): void => {
|
|
52
52
|
event.preventDefault();
|
|
53
53
|
event.stopPropagation();
|
|
@@ -60,21 +60,40 @@ export const createViewport = (container: HTMLElement): Viewport => {
|
|
|
60
60
|
};
|
|
61
61
|
|
|
62
62
|
const blurResize = (): void => {
|
|
63
|
+
if (canvas) {
|
|
64
|
+
canvas.style.cursor = "default";
|
|
65
|
+
}
|
|
66
|
+
|
|
63
67
|
isDragging = false;
|
|
64
68
|
};
|
|
65
69
|
|
|
66
|
-
const removeListeners = setupEventListener(resizeHandle, startResize,
|
|
70
|
+
const removeListeners = setupEventListener(resizeHandle, startResize, handleResize, stopResize, blurResize);
|
|
71
|
+
|
|
72
|
+
// Refresh viewport labels when viewport is created
|
|
73
|
+
refreshViewportLabels();
|
|
67
74
|
|
|
68
75
|
const cleanup = (): void => {
|
|
69
76
|
isDragging = false;
|
|
70
|
-
throttledHandleResize?.cleanup();
|
|
71
77
|
removeListeners();
|
|
72
78
|
resizeHandle.remove();
|
|
79
|
+
// Refresh labels after cleanup to remove this viewport's label if needed
|
|
80
|
+
refreshViewportLabels();
|
|
73
81
|
};
|
|
74
82
|
|
|
75
83
|
return {
|
|
76
84
|
setWidth: (width: number): void => {
|
|
77
85
|
updateWidth(container, width);
|
|
86
|
+
refreshViewportLabels();
|
|
87
|
+
|
|
88
|
+
// Refresh highlight frame when viewport width changes to update node positions
|
|
89
|
+
// biome-ignore lint/suspicious/noExplicitAny: global window extension
|
|
90
|
+
const nodeTools = (window as any).nodeTools;
|
|
91
|
+
const selectedNode = nodeTools?.getSelectedNode?.();
|
|
92
|
+
const nodeProvider = document.querySelector('[data-role="node-provider"]') as HTMLElement | null;
|
|
93
|
+
|
|
94
|
+
if (selectedNode && nodeProvider) {
|
|
95
|
+
refreshHighlightFrame(selectedNode, nodeProvider);
|
|
96
|
+
}
|
|
78
97
|
},
|
|
79
98
|
cleanup,
|
|
80
99
|
};
|
|
@@ -5,9 +5,17 @@ export const setupEventListener = (
|
|
|
5
5
|
stopResize: (event: MouseEvent) => void,
|
|
6
6
|
blurResize: () => void
|
|
7
7
|
): (() => void) => {
|
|
8
|
+
const handleMouseLeave = (event: MouseEvent): void => {
|
|
9
|
+
// Check if mouse is leaving the window/document
|
|
10
|
+
if (!event.relatedTarget && (event.target === document || event.target === document.documentElement)) {
|
|
11
|
+
blurResize();
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
8
15
|
resizeHandle.addEventListener("mousedown", startResize);
|
|
9
16
|
document.addEventListener("mousemove", handleResize);
|
|
10
17
|
document.addEventListener("mouseup", stopResize);
|
|
18
|
+
document.addEventListener("mouseleave", handleMouseLeave);
|
|
11
19
|
|
|
12
20
|
window.addEventListener("blur", blurResize);
|
|
13
21
|
|
|
@@ -15,6 +23,7 @@ export const setupEventListener = (
|
|
|
15
23
|
resizeHandle.removeEventListener("mousedown", startResize);
|
|
16
24
|
document.removeEventListener("mousemove", handleResize);
|
|
17
25
|
document.removeEventListener("mouseup", stopResize);
|
|
26
|
+
document.removeEventListener("mouseleave", handleMouseLeave);
|
|
18
27
|
window.removeEventListener("blur", blurResize);
|
|
19
28
|
};
|
|
20
29
|
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getCanvasContainer } from "../../canvas/helpers/getCanvasContainer";
|
|
2
|
+
|
|
3
|
+
export const getViewportLabelsOverlay = (): SVGSVGElement => {
|
|
4
|
+
const canvasContainer = getCanvasContainer();
|
|
5
|
+
const container = canvasContainer || document.body;
|
|
6
|
+
|
|
7
|
+
// Check if overlay already exists
|
|
8
|
+
let overlay = container.querySelector(".viewport-labels-overlay") as SVGSVGElement | null;
|
|
9
|
+
|
|
10
|
+
if (!overlay) {
|
|
11
|
+
// Create new SVG overlay
|
|
12
|
+
overlay = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
13
|
+
overlay.classList.add("viewport-labels-overlay");
|
|
14
|
+
|
|
15
|
+
// Set fixed positioning
|
|
16
|
+
overlay.style.position = "absolute";
|
|
17
|
+
overlay.style.top = "0";
|
|
18
|
+
overlay.style.left = "0";
|
|
19
|
+
overlay.style.width = "100vw";
|
|
20
|
+
overlay.style.height = "100vh";
|
|
21
|
+
overlay.style.pointerEvents = "none";
|
|
22
|
+
overlay.style.zIndex = "500";
|
|
23
|
+
|
|
24
|
+
const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
|
|
25
|
+
const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
|
|
26
|
+
overlay.setAttribute("width", viewportWidth.toString());
|
|
27
|
+
overlay.setAttribute("height", viewportHeight.toString());
|
|
28
|
+
|
|
29
|
+
container.appendChild(overlay);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return overlay;
|
|
33
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const getLabelPosition = (labelGroup: SVGGElement): { x: number; y: number } => {
|
|
2
|
+
const transform = labelGroup.getAttribute("transform");
|
|
3
|
+
const match = transform?.match(/translate\((-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)\)/);
|
|
4
|
+
if (match) {
|
|
5
|
+
return { x: parseFloat(match[1]), y: parseFloat(match[2]) };
|
|
6
|
+
}
|
|
7
|
+
return { x: 0, y: 0 };
|
|
8
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const getTransformValues = (element: HTMLElement): { x: number; y: number } => {
|
|
2
|
+
const style = element.style.transform;
|
|
3
|
+
const match = style.match(/translate3d\((-?\d+(?:\.\d+)?)px,\s*(-?\d+(?:\.\d+)?)px,\s*(-?\d+(?:\.\d+)?)px\)/);
|
|
4
|
+
if (match) {
|
|
5
|
+
return { x: parseFloat(match[1]), y: parseFloat(match[2]) };
|
|
6
|
+
}
|
|
7
|
+
return { x: 0, y: 0 };
|
|
8
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { getViewportLabelsOverlay } from "./getViewportLabelsOverlay";
|
|
2
|
+
export { isViewportLabelDragging } from "./isViewportLabelDragging";
|
|
3
|
+
export { refreshViewportLabels } from "./refreshViewportLabels";
|
|
4
|
+
export { setupViewportLabelDrag } from "./setupViewportLabelDrag";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Global flag to prevent refreshViewportLabels during drag
|
|
2
|
+
let globalIsDragging = false;
|
|
3
|
+
|
|
4
|
+
export const isViewportLabelDragging = (): boolean => globalIsDragging;
|
|
5
|
+
|
|
6
|
+
export const setViewportLabelDragging = (isDragging: boolean): void => {
|
|
7
|
+
globalIsDragging = isDragging;
|
|
8
|
+
};
|
|
9
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { getScreenBounds } from "../../node-tools/highlight/helpers/getScreenBounds";
|
|
2
|
+
import { getViewportLabelsOverlay } from "./getViewportLabelsOverlay";
|
|
3
|
+
import { isViewportLabelDragging } from "./isViewportLabelDragging";
|
|
4
|
+
import { setupViewportLabelDrag } from "./setupViewportLabelDrag";
|
|
5
|
+
|
|
6
|
+
// Store cleanup functions for drag listeners
|
|
7
|
+
const dragCleanupFunctions = new Map<string, () => void>();
|
|
8
|
+
|
|
9
|
+
export const refreshViewportLabels = (): void => {
|
|
10
|
+
// Skip refresh if a viewport label is being dragged
|
|
11
|
+
if (isViewportLabelDragging()) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const overlay = getViewportLabelsOverlay();
|
|
16
|
+
|
|
17
|
+
// Update SVG dimensions to match current viewport
|
|
18
|
+
const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
|
|
19
|
+
const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
|
|
20
|
+
overlay.setAttribute("width", viewportWidth.toString());
|
|
21
|
+
overlay.setAttribute("height", viewportHeight.toString());
|
|
22
|
+
|
|
23
|
+
// Find all viewports with names
|
|
24
|
+
const viewports = document.querySelectorAll(".viewport[data-viewport-name]");
|
|
25
|
+
|
|
26
|
+
// Clean up existing drag listeners
|
|
27
|
+
dragCleanupFunctions.forEach((cleanup) => {
|
|
28
|
+
cleanup();
|
|
29
|
+
});
|
|
30
|
+
dragCleanupFunctions.clear();
|
|
31
|
+
|
|
32
|
+
// Remove existing label groups
|
|
33
|
+
const existingGroups = overlay.querySelectorAll(".viewport-label-group");
|
|
34
|
+
existingGroups.forEach((group) => {
|
|
35
|
+
group.remove();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Create/update labels for each viewport
|
|
39
|
+
viewports.forEach((viewport) => {
|
|
40
|
+
const viewportElement = viewport as HTMLElement;
|
|
41
|
+
const viewportName = viewportElement.getAttribute("data-viewport-name");
|
|
42
|
+
|
|
43
|
+
if (!viewportName) return;
|
|
44
|
+
|
|
45
|
+
const bounds = getScreenBounds(viewportElement);
|
|
46
|
+
|
|
47
|
+
// Create group for this viewport label
|
|
48
|
+
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
49
|
+
group.classList.add("viewport-label-group");
|
|
50
|
+
group.setAttribute("data-viewport-name", viewportName);
|
|
51
|
+
group.setAttribute("transform", `translate(${bounds.left}, ${bounds.top})`);
|
|
52
|
+
|
|
53
|
+
// Create text element
|
|
54
|
+
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
55
|
+
text.classList.add("viewport-label-text");
|
|
56
|
+
text.setAttribute("x", "0");
|
|
57
|
+
text.setAttribute("y", "-8");
|
|
58
|
+
text.setAttribute("vector-effect", "non-scaling-stroke");
|
|
59
|
+
text.setAttribute("pointer-events", "auto");
|
|
60
|
+
text.textContent = viewportName;
|
|
61
|
+
|
|
62
|
+
group.appendChild(text);
|
|
63
|
+
overlay.appendChild(group);
|
|
64
|
+
|
|
65
|
+
// Setup drag functionality for this label
|
|
66
|
+
const cleanup = setupViewportLabelDrag(text, viewportElement, viewportName);
|
|
67
|
+
dragCleanupFunctions.set(viewportName, cleanup);
|
|
68
|
+
});
|
|
69
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { sendPostMessage } from "../../post-message/sendPostMessage";
|
|
2
|
+
import { getLabelPosition } from "./helpers/getLabelPosition";
|
|
3
|
+
import { getTransformValues } from "./helpers/getTransformValues";
|
|
4
|
+
import { getZoomValue } from "./helpers/getZoomValue";
|
|
5
|
+
import { setViewportLabelDragging } from "./isViewportLabelDragging";
|
|
6
|
+
|
|
7
|
+
export const setupViewportLabelDrag = (labelElement: SVGTextElement, viewportElement: HTMLElement, viewportName: string): (() => void) => {
|
|
8
|
+
let isDragging = false;
|
|
9
|
+
let startX = 0;
|
|
10
|
+
let startY = 0;
|
|
11
|
+
let initialTransform = { x: 0, y: 0 };
|
|
12
|
+
let initialLabelPosition = { x: 0, y: 0 };
|
|
13
|
+
|
|
14
|
+
// Get the parent group element that contains the label
|
|
15
|
+
const labelGroup = labelElement.parentElement as unknown as SVGGElement;
|
|
16
|
+
|
|
17
|
+
const startDrag = (event: MouseEvent): void => {
|
|
18
|
+
event.preventDefault();
|
|
19
|
+
event.stopPropagation();
|
|
20
|
+
|
|
21
|
+
isDragging = true;
|
|
22
|
+
setViewportLabelDragging(true);
|
|
23
|
+
startX = event.clientX;
|
|
24
|
+
startY = event.clientY;
|
|
25
|
+
initialTransform = getTransformValues(viewportElement);
|
|
26
|
+
initialLabelPosition = getLabelPosition(labelGroup);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const handleDrag = (event: MouseEvent): void => {
|
|
30
|
+
if (!isDragging) return;
|
|
31
|
+
|
|
32
|
+
const zoom = getZoomValue();
|
|
33
|
+
|
|
34
|
+
// Calculate mouse delta
|
|
35
|
+
const rawDeltaX = event.clientX - startX;
|
|
36
|
+
const rawDeltaY = event.clientY - startY;
|
|
37
|
+
|
|
38
|
+
// Adjust delta for zoom level
|
|
39
|
+
const deltaX = rawDeltaX / zoom;
|
|
40
|
+
const deltaY = rawDeltaY / zoom;
|
|
41
|
+
|
|
42
|
+
const newX = initialTransform.x + deltaX;
|
|
43
|
+
const newY = initialTransform.y + deltaY;
|
|
44
|
+
|
|
45
|
+
// Update label position with raw delta (labels are in screen space)
|
|
46
|
+
const newLabelX = initialLabelPosition.x + rawDeltaX;
|
|
47
|
+
const newLabelY = initialLabelPosition.y + rawDeltaY;
|
|
48
|
+
labelGroup.setAttribute("transform", `translate(${newLabelX}, ${newLabelY})`);
|
|
49
|
+
|
|
50
|
+
// Update viewport position with zoom-adjusted delta
|
|
51
|
+
viewportElement.style.transform = `translate3d(${newX}px, ${newY}px, 0)`;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const stopDrag = (event: MouseEvent): void => {
|
|
55
|
+
if (!isDragging) return;
|
|
56
|
+
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
event.stopPropagation();
|
|
59
|
+
|
|
60
|
+
isDragging = false;
|
|
61
|
+
setViewportLabelDragging(false);
|
|
62
|
+
|
|
63
|
+
const finalTransform = getTransformValues(viewportElement);
|
|
64
|
+
|
|
65
|
+
// Trigger refresh after drag completes to update highlight frame and labels
|
|
66
|
+
// biome-ignore lint/suspicious/noExplicitAny: global window extension
|
|
67
|
+
const nodeTools = (window as any).nodeTools;
|
|
68
|
+
if (nodeTools?.refreshHighlightFrame) {
|
|
69
|
+
nodeTools.refreshHighlightFrame();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Notify parent about the new position
|
|
73
|
+
sendPostMessage("viewport-position-changed", {
|
|
74
|
+
viewportName,
|
|
75
|
+
x: finalTransform.x,
|
|
76
|
+
y: finalTransform.y,
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const cancelDrag = (): void => {
|
|
81
|
+
isDragging = false;
|
|
82
|
+
setViewportLabelDragging(false);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Attach event listeners
|
|
86
|
+
labelElement.addEventListener("mousedown", startDrag);
|
|
87
|
+
document.addEventListener("mousemove", handleDrag);
|
|
88
|
+
document.addEventListener("mouseup", stopDrag);
|
|
89
|
+
window.addEventListener("blur", cancelDrag);
|
|
90
|
+
|
|
91
|
+
// Return cleanup function
|
|
92
|
+
return () => {
|
|
93
|
+
labelElement.removeEventListener("mousedown", startDrag);
|
|
94
|
+
document.removeEventListener("mousemove", handleDrag);
|
|
95
|
+
document.removeEventListener("mouseup", stopDrag);
|
|
96
|
+
window.removeEventListener("blur", cancelDrag);
|
|
97
|
+
};
|
|
98
|
+
};
|