@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.
Files changed (46) hide show
  1. package/dist/lib/canvas/helpers/getCanvasContainerOrBody.d.ts +1 -0
  2. package/dist/lib/canvas/helpers/getCanvasWindowValue.d.ts +1 -1
  3. package/dist/lib/helpers/adjustForZoom.d.ts +1 -0
  4. package/dist/lib/helpers/createDragHandler.d.ts +69 -0
  5. package/dist/lib/helpers/getNodeProvider.d.ts +1 -0
  6. package/dist/lib/helpers/getNodeTools.d.ts +2 -0
  7. package/dist/lib/helpers/getViewportDimensions.d.ts +4 -0
  8. package/dist/lib/helpers/index.d.ts +9 -1
  9. package/dist/lib/helpers/parseTransform.d.ts +8 -0
  10. package/dist/lib/helpers/toggleClass.d.ts +1 -0
  11. package/dist/lib/viewport/label/helpers/selectFirstViewportNode.d.ts +5 -0
  12. package/dist/node-edit-utils.cjs.js +259 -235
  13. package/dist/node-edit-utils.esm.js +259 -235
  14. package/dist/node-edit-utils.umd.js +259 -235
  15. package/dist/node-edit-utils.umd.min.js +1 -1
  16. package/dist/styles.css +1 -1
  17. package/package.json +1 -1
  18. package/src/lib/canvas/createCanvasObserver.ts +2 -2
  19. package/src/lib/canvas/helpers/getCanvasContainerOrBody.ts +6 -0
  20. package/src/lib/canvas/helpers/getCanvasWindowValue.ts +2 -3
  21. package/src/lib/helpers/adjustForZoom.ts +4 -0
  22. package/src/lib/helpers/createDragHandler.ts +171 -0
  23. package/src/lib/helpers/getNodeProvider.ts +4 -0
  24. package/src/lib/helpers/getNodeTools.ts +6 -0
  25. package/src/lib/helpers/getViewportDimensions.ts +7 -0
  26. package/src/lib/helpers/index.ts +9 -1
  27. package/src/lib/helpers/parseTransform.ts +9 -0
  28. package/src/lib/helpers/toggleClass.ts +9 -0
  29. package/src/lib/node-tools/createNodeTools.ts +0 -1
  30. package/src/lib/node-tools/highlight/clearHighlightFrame.ts +2 -3
  31. package/src/lib/node-tools/highlight/createHighlightFrame.ts +5 -9
  32. package/src/lib/node-tools/highlight/createToolsContainer.ts +3 -6
  33. package/src/lib/node-tools/highlight/helpers/getElementBounds.ts +6 -5
  34. package/src/lib/node-tools/highlight/helpers/getHighlightFrameElement.ts +2 -3
  35. package/src/lib/node-tools/highlight/highlightNode.ts +7 -15
  36. package/src/lib/node-tools/highlight/refreshHighlightFrame.ts +12 -42
  37. package/src/lib/node-tools/highlight/updateHighlightFrameVisibility.ts +2 -3
  38. package/src/lib/styles/styles.css +1 -1
  39. package/src/lib/viewport/createViewport.ts +44 -47
  40. package/src/lib/viewport/label/getViewportLabelsOverlay.ts +4 -5
  41. package/src/lib/viewport/label/helpers/getLabelPosition.ts +3 -5
  42. package/src/lib/viewport/label/helpers/getTransformValues.ts +3 -5
  43. package/src/lib/viewport/label/helpers/selectFirstViewportNode.ts +18 -0
  44. package/src/lib/viewport/label/refreshViewportLabels.ts +2 -2
  45. package/src/lib/viewport/label/setupViewportLabelDrag.ts +58 -86
  46. package/src/lib/window/bindToWindow.ts +1 -2
@@ -1,5 +1,7 @@
1
- import { getCanvasContainer } from "@/lib/canvas/helpers/getCanvasContainer";
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 = document.documentElement.clientWidth || window.innerWidth;
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
- if (isInstance) {
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
- if (isTextEdit) {
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 canvasContainer = getCanvasContainer();
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
- if (toolsWrapper) {
75
- if (isInstance) {
76
- toolsWrapper.classList.add("is-instance");
77
- } else {
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 { getCanvasContainer } from "@/lib/canvas/helpers/getCanvasContainer";
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 canvasContainer = getCanvasContainer();
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;
@@ -98,7 +98,7 @@
98
98
 
99
99
  @media (prefers-color-scheme: dark) {
100
100
  .viewport-label-text {
101
- fill: oklch(0.3 0 0);
101
+ fill: oklch(0.5 0 0);
102
102
  }
103
103
 
104
104
  .viewport-label-text:hover {
@@ -1,7 +1,9 @@
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";
2
5
  import { refreshHighlightFrame } from "../node-tools/highlight/refreshHighlightFrame";
3
6
  import { DEFAULT_WIDTH } from "./constants";
4
- import { setupEventListener } from "./events/setupEventListener";
5
7
  import { refreshViewportLabels } from "./label/refreshViewportLabels";
6
8
  import { createResizeHandle } from "./resize/createResizeHandle";
7
9
  import { createResizePresets } from "./resize/createResizePresets";
@@ -14,6 +16,7 @@ export const createViewport = (container: HTMLElement, initialWidth?: number): V
14
16
 
15
17
  // Remove any existing resize handle to prevent duplicates
16
18
  const existingHandle = container.querySelector(".resize-handle");
19
+
17
20
  if (existingHandle) {
18
21
  existingHandle.remove();
19
22
  }
@@ -24,59 +27,55 @@ export const createViewport = (container: HTMLElement, initialWidth?: number): V
24
27
 
25
28
  createResizePresets(resizeHandle, container, updateWidth);
26
29
 
27
- let isDragging: boolean = false;
28
- let startX: number = 0;
29
- let startWidth: number = 0;
30
-
31
- const startResize = (event: MouseEvent): void => {
32
- event.preventDefault();
33
- event.stopPropagation();
34
-
35
- isDragging = true;
36
- startX = event.clientX;
37
- startWidth = container.offsetWidth;
38
- };
39
-
40
- const handleResize = (event: MouseEvent): void => {
41
- if (!isDragging) return;
42
-
43
- if (canvas) {
44
- canvas.style.cursor = "ew-resize";
45
- }
46
-
47
- const width = calcWidth(event, startX, startWidth);
48
- updateWidth(container, width);
49
- };
30
+ // Track initial values for resize calculation
31
+ let startX = 0;
32
+ let startWidth = 0;
50
33
 
51
- const stopResize = (event: MouseEvent): void => {
52
- event.preventDefault();
53
- event.stopPropagation();
54
-
55
- if (canvas) {
56
- canvas.style.cursor = "default";
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
+ }
57
41
  }
58
-
59
- isDragging = false;
60
42
  };
61
43
 
62
- const blurResize = (): void => {
63
- if (canvas) {
64
- canvas.style.cursor = "default";
65
- }
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
+ }
66
53
 
67
- isDragging = false;
68
- };
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
+ });
69
69
 
70
- const removeListeners = setupEventListener(resizeHandle, startResize, handleResize, stopResize, blurResize);
70
+ document.addEventListener("mouseleave", handleMouseLeave);
71
71
 
72
- // Refresh viewport labels when viewport is created
73
72
  refreshViewportLabels();
74
73
 
75
74
  const cleanup = (): void => {
76
- isDragging = false;
77
- removeListeners();
75
+ removeDragListeners();
76
+ document.removeEventListener("mouseleave", handleMouseLeave);
78
77
  resizeHandle.remove();
79
- // Refresh labels after cleanup to remove this viewport's label if needed
78
+
80
79
  refreshViewportLabels();
81
80
  };
82
81
 
@@ -85,11 +84,9 @@ export const createViewport = (container: HTMLElement, initialWidth?: number): V
85
84
  updateWidth(container, width);
86
85
  refreshViewportLabels();
87
86
 
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;
87
+ const nodeTools = getNodeTools();
91
88
  const selectedNode = nodeTools?.getSelectedNode?.();
92
- const nodeProvider = document.querySelector('[data-role="node-provider"]') as HTMLElement | null;
89
+ const nodeProvider = getNodeProvider();
93
90
 
94
91
  if (selectedNode && nodeProvider) {
95
92
  refreshHighlightFrame(selectedNode, nodeProvider);
@@ -1,8 +1,8 @@
1
- import { getCanvasContainer } from "../../canvas/helpers/getCanvasContainer";
1
+ import { getCanvasContainerOrBody } from "../../canvas/helpers/getCanvasContainerOrBody";
2
+ import { getViewportDimensions } from "../../helpers/getViewportDimensions";
2
3
 
3
4
  export const getViewportLabelsOverlay = (): SVGSVGElement => {
4
- const canvasContainer = getCanvasContainer();
5
- const container = canvasContainer || document.body;
5
+ const container = getCanvasContainerOrBody();
6
6
 
7
7
  // Check if overlay already exists
8
8
  let overlay = container.querySelector(".viewport-labels-overlay") as SVGSVGElement | null;
@@ -21,8 +21,7 @@ export const getViewportLabelsOverlay = (): SVGSVGElement => {
21
21
  overlay.style.pointerEvents = "none";
22
22
  overlay.style.zIndex = "500";
23
23
 
24
- const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
25
- const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
24
+ const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
26
25
  overlay.setAttribute("width", viewportWidth.toString());
27
26
  overlay.setAttribute("height", viewportHeight.toString());
28
27
 
@@ -1,8 +1,6 @@
1
+ import { parseTransform2d } from "../../../helpers/parseTransform";
2
+
1
3
  export const getLabelPosition = (labelGroup: SVGGElement): { x: number; y: number } => {
2
4
  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 };
5
+ return parseTransform2d(transform);
8
6
  };
@@ -1,8 +1,6 @@
1
+ import { parseTransform3d } from "../../../helpers/parseTransform";
2
+
1
3
  export const getTransformValues = (element: HTMLElement): { x: number; y: number } => {
2
4
  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 };
5
+ return parseTransform3d(style);
8
6
  };
@@ -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
+ };
@@ -1,3 +1,4 @@
1
+ import { getViewportDimensions } from "../../helpers/getViewportDimensions";
1
2
  import { getScreenBounds } from "../../node-tools/highlight/helpers/getScreenBounds";
2
3
  import { getViewportLabelsOverlay } from "./getViewportLabelsOverlay";
3
4
  import { isViewportLabelDragging } from "./isViewportLabelDragging";
@@ -15,8 +16,7 @@ export const refreshViewportLabels = (): void => {
15
16
  const overlay = getViewportLabelsOverlay();
16
17
 
17
18
  // Update SVG dimensions to match current viewport
18
- const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
19
- const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
19
+ const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
20
20
  overlay.setAttribute("width", viewportWidth.toString());
21
21
  overlay.setAttribute("height", viewportHeight.toString());
22
22
 
@@ -1,98 +1,70 @@
1
+ import { createDragHandler } from "../../helpers/createDragHandler";
2
+ import { getNodeTools } from "../../helpers/getNodeTools";
1
3
  import { sendPostMessage } from "../../post-message/sendPostMessage";
2
4
  import { getLabelPosition } from "./helpers/getLabelPosition";
3
5
  import { getTransformValues } from "./helpers/getTransformValues";
4
6
  import { getZoomValue } from "./helpers/getZoomValue";
7
+ import { selectFirstViewportNode } from "./helpers/selectFirstViewportNode";
5
8
  import { setViewportLabelDragging } from "./isViewportLabelDragging";
6
9
 
7
10
  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
11
  // Get the parent group element that contains the label
15
12
  const labelGroup = labelElement.parentElement as unknown as SVGGElement;
16
13
 
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);
14
+ // Track initial positions for calculations
15
+ let initialTransform = { x: 0, y: 0 };
16
+ let initialLabelPosition = { x: 0, y: 0 };
90
17
 
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
- };
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
+ });
98
70
  };
@@ -1,6 +1,5 @@
1
1
  export const bindToWindow = <T>(key: string, value: T): void => {
2
2
  if (typeof window !== "undefined") {
3
- // biome-ignore lint/suspicious/noExplicitAny: global window extension requires flexibility
4
- (window as any)[key] = value;
3
+ (window as unknown as Record<string, unknown>)[key] = value;
5
4
  }
6
5
  };