@node-edit-utils/core 2.3.1 → 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/node-tools/select/helpers/isInsideViewport.d.ts +1 -0
- package/dist/lib/viewport/constants.d.ts +2 -2
- 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/helpers/selectFirstViewportNode.d.ts +5 -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 +437 -172
- package/dist/node-edit-utils.esm.js +437 -172
- package/dist/node-edit-utils.umd.js +437 -172
- 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 +16 -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 +11 -5
- 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/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 +56 -41
- package/src/lib/viewport/label/getViewportLabelsOverlay.ts +32 -0
- package/src/lib/viewport/label/helpers/getLabelPosition.ts +6 -0
- package/src/lib/viewport/label/helpers/getTransformValues.ts +6 -0
- package/src/lib/viewport/label/helpers/getZoomValue.ts +4 -0
- package/src/lib/viewport/label/helpers/selectFirstViewportNode.ts +18 -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 +70 -0
- package/src/lib/window/bindToWindow.ts +1 -2
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getCanvasContainerOrBody } from "@/lib/canvas/helpers/getCanvasContainerOrBody";
|
|
2
2
|
import { getCanvasWindowValue } from "@/lib/canvas/helpers/getCanvasWindowValue";
|
|
3
|
+
import { getViewportDimensions } from "@/lib/helpers/getViewportDimensions";
|
|
4
|
+
import { toggleClass } from "@/lib/helpers/toggleClass";
|
|
3
5
|
import { isComponentInstance } from "../select/helpers/isComponentInstance";
|
|
4
6
|
import { getHighlightFrameElement } from "./helpers/getHighlightFrameElement";
|
|
5
7
|
import { getScreenBounds } from "./helpers/getScreenBounds";
|
|
@@ -22,24 +24,15 @@ export const refreshHighlightFrame = (node: HTMLElement, nodeProvider: HTMLEleme
|
|
|
22
24
|
|
|
23
25
|
// Update SVG dimensions to match current viewport (handles window resize and ensures coordinate system is correct)
|
|
24
26
|
// Use clientWidth/Height to match getBoundingClientRect() coordinate system (excludes scrollbars)
|
|
25
|
-
const viewportWidth
|
|
26
|
-
const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
|
|
27
|
+
const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
|
|
27
28
|
frame.setAttribute("width", viewportWidth.toString());
|
|
28
29
|
frame.setAttribute("height", viewportHeight.toString());
|
|
29
30
|
|
|
30
31
|
// Update instance class
|
|
31
|
-
|
|
32
|
-
frame.classList.add("is-instance");
|
|
33
|
-
} else {
|
|
34
|
-
frame.classList.remove("is-instance");
|
|
35
|
-
}
|
|
32
|
+
toggleClass(frame, "is-instance", isInstance);
|
|
36
33
|
|
|
37
34
|
// Update text edit class
|
|
38
|
-
|
|
39
|
-
frame.classList.add("is-text-edit");
|
|
40
|
-
} else {
|
|
41
|
-
frame.classList.remove("is-text-edit");
|
|
42
|
-
}
|
|
35
|
+
toggleClass(frame, "is-text-edit", isTextEdit);
|
|
43
36
|
|
|
44
37
|
const group = frame.querySelector(".highlight-frame-group") as SVGGElement | null;
|
|
45
38
|
if (!group) return;
|
|
@@ -56,12 +49,11 @@ export const refreshHighlightFrame = (node: HTMLElement, nodeProvider: HTMLEleme
|
|
|
56
49
|
rect.removeAttribute("stroke"); // Use CSS default
|
|
57
50
|
}
|
|
58
51
|
|
|
59
|
-
const
|
|
60
|
-
const container = canvasContainer || document.body;
|
|
52
|
+
const container = getCanvasContainerOrBody();
|
|
61
53
|
const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper") as HTMLElement | null;
|
|
62
54
|
const nodeTools = toolsWrapper?.querySelector(".node-tools") as HTMLElement | null;
|
|
63
55
|
|
|
64
|
-
const zoom = getCanvasWindowValue(["zoom", "current"], canvasName) ?? 1;
|
|
56
|
+
const zoom = (getCanvasWindowValue(["zoom", "current"], canvasName) as number | undefined) ?? 1;
|
|
65
57
|
const bounds = getScreenBounds(node);
|
|
66
58
|
|
|
67
59
|
// Calculate all values before any DOM writes
|
|
@@ -71,32 +63,10 @@ export const refreshHighlightFrame = (node: HTMLElement, nodeProvider: HTMLEleme
|
|
|
71
63
|
const bottomY = top + height;
|
|
72
64
|
|
|
73
65
|
// Update instance classes on tools wrapper and node tools
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
toolsWrapper.classList.remove("is-instance");
|
|
79
|
-
}
|
|
80
|
-
// Update text edit class
|
|
81
|
-
if (isTextEdit) {
|
|
82
|
-
toolsWrapper.classList.add("is-text-edit");
|
|
83
|
-
} else {
|
|
84
|
-
toolsWrapper.classList.remove("is-text-edit");
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
if (nodeTools) {
|
|
88
|
-
if (isInstance) {
|
|
89
|
-
nodeTools.classList.add("is-instance");
|
|
90
|
-
} else {
|
|
91
|
-
nodeTools.classList.remove("is-instance");
|
|
92
|
-
}
|
|
93
|
-
// Update text edit class
|
|
94
|
-
if (isTextEdit) {
|
|
95
|
-
nodeTools.classList.add("is-text-edit");
|
|
96
|
-
} else {
|
|
97
|
-
nodeTools.classList.remove("is-text-edit");
|
|
98
|
-
}
|
|
99
|
-
}
|
|
66
|
+
toggleClass(toolsWrapper, "is-instance", isInstance);
|
|
67
|
+
toggleClass(toolsWrapper, "is-text-edit", isTextEdit);
|
|
68
|
+
toggleClass(nodeTools, "is-instance", isInstance);
|
|
69
|
+
toggleClass(nodeTools, "is-text-edit", isTextEdit);
|
|
100
70
|
|
|
101
71
|
// Batch all DOM writes (single paint pass)
|
|
102
72
|
// Update group transform to move entire group (rect + handles) at once
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getCanvasContainerOrBody } from "@/lib/canvas/helpers/getCanvasContainerOrBody";
|
|
2
2
|
import { getHighlightFrameElement } from "./helpers/getHighlightFrameElement";
|
|
3
3
|
|
|
4
4
|
export const updateHighlightFrameVisibility = (node: HTMLElement): void => {
|
|
@@ -12,8 +12,7 @@ export const updateHighlightFrameVisibility = (node: HTMLElement): void => {
|
|
|
12
12
|
// Batch DOM writes
|
|
13
13
|
frame.style.display = displayValue;
|
|
14
14
|
|
|
15
|
-
const
|
|
16
|
-
const container = canvasContainer || document.body;
|
|
15
|
+
const container = getCanvasContainerOrBody();
|
|
17
16
|
const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper") as HTMLElement | null;
|
|
18
17
|
if (toolsWrapper) {
|
|
19
18
|
toolsWrapper.style.display = displayValue;
|
|
@@ -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.5 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,6 +1,10 @@
|
|
|
1
1
|
import { getCanvasContainer } from "../canvas/helpers/getCanvasContainer";
|
|
2
|
+
import { createDragHandler } from "../helpers/createDragHandler";
|
|
3
|
+
import { getNodeProvider } from "../helpers/getNodeProvider";
|
|
4
|
+
import { getNodeTools } from "../helpers/getNodeTools";
|
|
5
|
+
import { refreshHighlightFrame } from "../node-tools/highlight/refreshHighlightFrame";
|
|
2
6
|
import { DEFAULT_WIDTH } from "./constants";
|
|
3
|
-
import {
|
|
7
|
+
import { refreshViewportLabels } from "./label/refreshViewportLabels";
|
|
4
8
|
import { createResizeHandle } from "./resize/createResizeHandle";
|
|
5
9
|
import { createResizePresets } from "./resize/createResizePresets";
|
|
6
10
|
import type { Viewport } from "./types";
|
|
@@ -12,6 +16,7 @@ export const createViewport = (container: HTMLElement, initialWidth?: number): V
|
|
|
12
16
|
|
|
13
17
|
// Remove any existing resize handle to prevent duplicates
|
|
14
18
|
const existingHandle = container.querySelector(".resize-handle");
|
|
19
|
+
|
|
15
20
|
if (existingHandle) {
|
|
16
21
|
existingHandle.remove();
|
|
17
22
|
}
|
|
@@ -22,60 +27,70 @@ export const createViewport = (container: HTMLElement, initialWidth?: number): V
|
|
|
22
27
|
|
|
23
28
|
createResizePresets(resizeHandle, container, updateWidth);
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
let startX
|
|
27
|
-
let startWidth
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const handleResize = (event: MouseEvent): void => {
|
|
39
|
-
if (!isDragging) return;
|
|
40
|
-
|
|
41
|
-
if (canvas) {
|
|
42
|
-
canvas.style.cursor = "ew-resize";
|
|
30
|
+
// Track initial values for resize calculation
|
|
31
|
+
let startX = 0;
|
|
32
|
+
let startWidth = 0;
|
|
33
|
+
|
|
34
|
+
// Handle mouse leave for resize (specific to resize use case)
|
|
35
|
+
const handleMouseLeave = (event: MouseEvent): void => {
|
|
36
|
+
// Check if mouse is leaving the window/document
|
|
37
|
+
if (!event.relatedTarget && (event.target === document || event.target === document.documentElement)) {
|
|
38
|
+
if (canvas) {
|
|
39
|
+
canvas.style.cursor = "default";
|
|
40
|
+
}
|
|
43
41
|
}
|
|
44
|
-
|
|
45
|
-
const width = calcWidth(event, startX, startWidth);
|
|
46
|
-
updateWidth(container, width);
|
|
47
42
|
};
|
|
48
43
|
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
};
|
|
44
|
+
const removeDragListeners = createDragHandler(resizeHandle, {
|
|
45
|
+
onStart: (_event, { startX: dragStartX }) => {
|
|
46
|
+
startX = dragStartX;
|
|
47
|
+
startWidth = container.offsetWidth;
|
|
48
|
+
},
|
|
49
|
+
onDrag: (event) => {
|
|
50
|
+
if (canvas) {
|
|
51
|
+
canvas.style.cursor = "ew-resize";
|
|
52
|
+
}
|
|
59
53
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
const width = calcWidth(event, startX, startWidth);
|
|
55
|
+
updateWidth(container, width);
|
|
56
|
+
},
|
|
57
|
+
onStop: () => {
|
|
58
|
+
if (canvas) {
|
|
59
|
+
canvas.style.cursor = "default";
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
onCancel: () => {
|
|
63
|
+
if (canvas) {
|
|
64
|
+
canvas.style.cursor = "default";
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
onPreventClick: () => {},
|
|
68
|
+
});
|
|
64
69
|
|
|
65
|
-
|
|
66
|
-
};
|
|
70
|
+
document.addEventListener("mouseleave", handleMouseLeave);
|
|
67
71
|
|
|
68
|
-
|
|
72
|
+
refreshViewportLabels();
|
|
69
73
|
|
|
70
74
|
const cleanup = (): void => {
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
removeDragListeners();
|
|
76
|
+
document.removeEventListener("mouseleave", handleMouseLeave);
|
|
73
77
|
resizeHandle.remove();
|
|
78
|
+
|
|
79
|
+
refreshViewportLabels();
|
|
74
80
|
};
|
|
75
81
|
|
|
76
82
|
return {
|
|
77
83
|
setWidth: (width: number): void => {
|
|
78
84
|
updateWidth(container, width);
|
|
85
|
+
refreshViewportLabels();
|
|
86
|
+
|
|
87
|
+
const nodeTools = getNodeTools();
|
|
88
|
+
const selectedNode = nodeTools?.getSelectedNode?.();
|
|
89
|
+
const nodeProvider = getNodeProvider();
|
|
90
|
+
|
|
91
|
+
if (selectedNode && nodeProvider) {
|
|
92
|
+
refreshHighlightFrame(selectedNode, nodeProvider);
|
|
93
|
+
}
|
|
79
94
|
},
|
|
80
95
|
cleanup,
|
|
81
96
|
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { getCanvasContainerOrBody } from "../../canvas/helpers/getCanvasContainerOrBody";
|
|
2
|
+
import { getViewportDimensions } from "../../helpers/getViewportDimensions";
|
|
3
|
+
|
|
4
|
+
export const getViewportLabelsOverlay = (): SVGSVGElement => {
|
|
5
|
+
const container = getCanvasContainerOrBody();
|
|
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 { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
|
|
25
|
+
overlay.setAttribute("width", viewportWidth.toString());
|
|
26
|
+
overlay.setAttribute("height", viewportHeight.toString());
|
|
27
|
+
|
|
28
|
+
container.appendChild(overlay);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return overlay;
|
|
32
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getNodeTools } from "../../../helpers/getNodeTools";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Selects the first child node inside a viewport element.
|
|
5
|
+
* Skips the resize-handle element if present.
|
|
6
|
+
*/
|
|
7
|
+
export const selectFirstViewportNode = (viewportElement: HTMLElement): void => {
|
|
8
|
+
const firstChild = Array.from(viewportElement.children).find((child) => !child.classList.contains("resize-handle")) as
|
|
9
|
+
| HTMLElement
|
|
10
|
+
| undefined;
|
|
11
|
+
|
|
12
|
+
if (firstChild) {
|
|
13
|
+
const nodeTools = getNodeTools();
|
|
14
|
+
if (nodeTools?.selectNode) {
|
|
15
|
+
nodeTools.selectNode(firstChild);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
};
|
|
@@ -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 { getViewportDimensions } from "../../helpers/getViewportDimensions";
|
|
2
|
+
import { getScreenBounds } from "../../node-tools/highlight/helpers/getScreenBounds";
|
|
3
|
+
import { getViewportLabelsOverlay } from "./getViewportLabelsOverlay";
|
|
4
|
+
import { isViewportLabelDragging } from "./isViewportLabelDragging";
|
|
5
|
+
import { setupViewportLabelDrag } from "./setupViewportLabelDrag";
|
|
6
|
+
|
|
7
|
+
// Store cleanup functions for drag listeners
|
|
8
|
+
const dragCleanupFunctions = new Map<string, () => void>();
|
|
9
|
+
|
|
10
|
+
export const refreshViewportLabels = (): void => {
|
|
11
|
+
// Skip refresh if a viewport label is being dragged
|
|
12
|
+
if (isViewportLabelDragging()) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const overlay = getViewportLabelsOverlay();
|
|
17
|
+
|
|
18
|
+
// Update SVG dimensions to match current viewport
|
|
19
|
+
const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
|
|
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,70 @@
|
|
|
1
|
+
import { createDragHandler } from "../../helpers/createDragHandler";
|
|
2
|
+
import { getNodeTools } from "../../helpers/getNodeTools";
|
|
3
|
+
import { sendPostMessage } from "../../post-message/sendPostMessage";
|
|
4
|
+
import { getLabelPosition } from "./helpers/getLabelPosition";
|
|
5
|
+
import { getTransformValues } from "./helpers/getTransformValues";
|
|
6
|
+
import { getZoomValue } from "./helpers/getZoomValue";
|
|
7
|
+
import { selectFirstViewportNode } from "./helpers/selectFirstViewportNode";
|
|
8
|
+
import { setViewportLabelDragging } from "./isViewportLabelDragging";
|
|
9
|
+
|
|
10
|
+
export const setupViewportLabelDrag = (labelElement: SVGTextElement, viewportElement: HTMLElement, viewportName: string): (() => void) => {
|
|
11
|
+
// Get the parent group element that contains the label
|
|
12
|
+
const labelGroup = labelElement.parentElement as unknown as SVGGElement;
|
|
13
|
+
|
|
14
|
+
// Track initial positions for calculations
|
|
15
|
+
let initialTransform = { x: 0, y: 0 };
|
|
16
|
+
let initialLabelPosition = { x: 0, y: 0 };
|
|
17
|
+
|
|
18
|
+
return createDragHandler(labelElement, {
|
|
19
|
+
onStart: () => {
|
|
20
|
+
setViewportLabelDragging(true);
|
|
21
|
+
initialTransform = getTransformValues(viewportElement);
|
|
22
|
+
initialLabelPosition = getLabelPosition(labelGroup);
|
|
23
|
+
selectFirstViewportNode(viewportElement);
|
|
24
|
+
},
|
|
25
|
+
onDrag: (_event, { deltaX, deltaY }) => {
|
|
26
|
+
const zoom = getZoomValue();
|
|
27
|
+
|
|
28
|
+
// Adjust delta for zoom level (viewport is in canvas space)
|
|
29
|
+
const deltaXZoomed = deltaX / zoom;
|
|
30
|
+
const deltaYZoomed = deltaY / zoom;
|
|
31
|
+
|
|
32
|
+
// Calculate new positions
|
|
33
|
+
const newX = initialTransform.x + deltaXZoomed;
|
|
34
|
+
const newY = initialTransform.y + deltaYZoomed;
|
|
35
|
+
|
|
36
|
+
// Update label position with raw delta (labels are in screen space)
|
|
37
|
+
const newLabelX = initialLabelPosition.x + deltaX;
|
|
38
|
+
const newLabelY = initialLabelPosition.y + deltaY;
|
|
39
|
+
labelGroup.setAttribute("transform", `translate(${newLabelX}, ${newLabelY})`);
|
|
40
|
+
|
|
41
|
+
// Update viewport position with zoom-adjusted delta
|
|
42
|
+
viewportElement.style.transform = `translate3d(${newX}px, ${newY}px, 0)`;
|
|
43
|
+
},
|
|
44
|
+
onStop: (_event, { hasDragged }) => {
|
|
45
|
+
setViewportLabelDragging(false);
|
|
46
|
+
|
|
47
|
+
// If it was a drag, handle drag completion
|
|
48
|
+
if (hasDragged) {
|
|
49
|
+
const finalTransform = getTransformValues(viewportElement);
|
|
50
|
+
|
|
51
|
+
// Trigger refresh after drag completes to update highlight frame and labels
|
|
52
|
+
const nodeTools = getNodeTools();
|
|
53
|
+
if (nodeTools?.refreshHighlightFrame) {
|
|
54
|
+
nodeTools.refreshHighlightFrame();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Notify parent about the new position
|
|
58
|
+
sendPostMessage("viewport-position-changed", {
|
|
59
|
+
viewportName,
|
|
60
|
+
x: finalTransform.x,
|
|
61
|
+
y: finalTransform.y,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
onCancel: () => {
|
|
66
|
+
setViewportLabelDragging(false);
|
|
67
|
+
},
|
|
68
|
+
onPreventClick: () => {},
|
|
69
|
+
});
|
|
70
|
+
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export const bindToWindow = <T>(key: string, value: T): void => {
|
|
2
2
|
if (typeof window !== "undefined") {
|
|
3
|
-
|
|
4
|
-
(window as any)[key] = value;
|
|
3
|
+
(window as unknown as Record<string, unknown>)[key] = value;
|
|
5
4
|
}
|
|
6
5
|
};
|