@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.
Files changed (34) hide show
  1. package/dist/lib/node-tools/select/helpers/isInsideViewport.d.ts +1 -0
  2. package/dist/lib/viewport/constants.d.ts +2 -2
  3. package/dist/lib/viewport/createViewport.d.ts +1 -1
  4. package/dist/lib/viewport/label/getViewportLabelsOverlay.d.ts +1 -0
  5. package/dist/lib/viewport/label/helpers/getLabelPosition.d.ts +4 -0
  6. package/dist/lib/viewport/label/helpers/getTransformValues.d.ts +4 -0
  7. package/dist/lib/viewport/label/helpers/getZoomValue.d.ts +1 -0
  8. package/dist/lib/viewport/label/index.d.ts +4 -0
  9. package/dist/lib/viewport/label/isViewportLabelDragging.d.ts +2 -0
  10. package/dist/lib/viewport/label/refreshViewportLabels.d.ts +1 -0
  11. package/dist/lib/viewport/label/setupViewportLabelDrag.d.ts +1 -0
  12. package/dist/node-edit-utils.cjs.js +288 -63
  13. package/dist/node-edit-utils.esm.js +288 -63
  14. package/dist/node-edit-utils.umd.js +288 -63
  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 +14 -0
  19. package/src/lib/node-tools/createNodeTools.ts +12 -5
  20. package/src/lib/node-tools/select/helpers/isInsideViewport.ts +19 -0
  21. package/src/lib/node-tools/select/selectNode.ts +13 -1
  22. package/src/lib/node-tools/text/events/setupMutationObserver.ts +1 -0
  23. package/src/lib/styles/styles.css +48 -1
  24. package/src/lib/viewport/constants.ts +2 -2
  25. package/src/lib/viewport/createViewport.ts +26 -7
  26. package/src/lib/viewport/events/setupEventListener.ts +9 -0
  27. package/src/lib/viewport/label/getViewportLabelsOverlay.ts +33 -0
  28. package/src/lib/viewport/label/helpers/getLabelPosition.ts +8 -0
  29. package/src/lib/viewport/label/helpers/getTransformValues.ts +8 -0
  30. package/src/lib/viewport/label/helpers/getZoomValue.ts +4 -0
  31. package/src/lib/viewport/label/index.ts +4 -0
  32. package/src/lib/viewport/label/isViewportLabelDragging.ts +9 -0
  33. package/src/lib/viewport/label/refreshViewportLabels.ts +69 -0
  34. package/src/lib/viewport/label/setupViewportLabelDrag.ts +98 -0
@@ -0,0 +1 @@
1
+ export declare const isInsideViewport: (element: Element) => boolean;
@@ -1,7 +1,7 @@
1
1
  export declare const DEFAULT_WIDTH = 400;
2
2
  export declare const RESIZE_CONFIG: {
3
- readonly minWidth: 320;
4
- readonly maxWidth: 1680;
3
+ readonly minWidth: 4;
4
+ readonly maxWidth: 2560;
5
5
  };
6
6
  export declare const RESIZE_PRESETS: readonly [{
7
7
  readonly name: "Mobile";
@@ -1,2 +1,2 @@
1
1
  import type { Viewport } from "./types";
2
- export declare const createViewport: (container: HTMLElement) => Viewport;
2
+ export declare const createViewport: (container: HTMLElement, initialWidth?: number) => Viewport;
@@ -0,0 +1 @@
1
+ export declare const getViewportLabelsOverlay: () => SVGSVGElement;
@@ -0,0 +1,4 @@
1
+ export declare const getLabelPosition: (labelGroup: SVGGElement) => {
2
+ x: number;
3
+ y: number;
4
+ };
@@ -0,0 +1,4 @@
1
+ export declare const getTransformValues: (element: HTMLElement) => {
2
+ x: number;
3
+ y: number;
4
+ };
@@ -0,0 +1 @@
1
+ export declare const getZoomValue: () => number;
@@ -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,2 @@
1
+ export declare const isViewportLabelDragging: () => boolean;
2
+ export declare const setViewportLabelDragging: (isDragging: boolean) => void;
@@ -0,0 +1 @@
1
+ export declare const refreshViewportLabels: () => void;
@@ -0,0 +1 @@
1
+ export declare const setupViewportLabelDrag: (labelElement: SVGTextElement, viewportElement: HTMLElement, viewportName: string) => (() => void);
@@ -1,10 +1,218 @@
1
1
  /**
2
2
  * Markup Canvas
3
3
  * High-performance markup canvas with zoom and pan capabilities
4
- * @version 2.3.0
4
+ * @version 2.3.2
5
5
  */
6
6
  'use strict';
7
7
 
8
+ function getScreenBounds(element) {
9
+ const rect = element.getBoundingClientRect();
10
+ return {
11
+ top: rect.top,
12
+ left: rect.left,
13
+ width: rect.width,
14
+ height: rect.height,
15
+ };
16
+ }
17
+
18
+ const getCanvasContainer = () => {
19
+ return document.querySelector(".canvas-container");
20
+ };
21
+
22
+ const getViewportLabelsOverlay = () => {
23
+ const canvasContainer = getCanvasContainer();
24
+ const container = canvasContainer || document.body;
25
+ // Check if overlay already exists
26
+ let overlay = container.querySelector(".viewport-labels-overlay");
27
+ if (!overlay) {
28
+ // Create new SVG overlay
29
+ overlay = document.createElementNS("http://www.w3.org/2000/svg", "svg");
30
+ overlay.classList.add("viewport-labels-overlay");
31
+ // Set fixed positioning
32
+ overlay.style.position = "absolute";
33
+ overlay.style.top = "0";
34
+ overlay.style.left = "0";
35
+ overlay.style.width = "100vw";
36
+ overlay.style.height = "100vh";
37
+ overlay.style.pointerEvents = "none";
38
+ overlay.style.zIndex = "500";
39
+ const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
40
+ const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
41
+ overlay.setAttribute("width", viewportWidth.toString());
42
+ overlay.setAttribute("height", viewportHeight.toString());
43
+ container.appendChild(overlay);
44
+ }
45
+ return overlay;
46
+ };
47
+
48
+ // Global flag to prevent refreshViewportLabels during drag
49
+ let globalIsDragging = false;
50
+ const isViewportLabelDragging = () => globalIsDragging;
51
+ const setViewportLabelDragging = (isDragging) => {
52
+ globalIsDragging = isDragging;
53
+ };
54
+
55
+ function sendPostMessage(action, data) {
56
+ window.parent.postMessage({
57
+ source: "node-edit-utils",
58
+ action,
59
+ data,
60
+ timestamp: Date.now(),
61
+ }, "*");
62
+ }
63
+
64
+ const getLabelPosition = (labelGroup) => {
65
+ const transform = labelGroup.getAttribute("transform");
66
+ const match = transform?.match(/translate\((-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)\)/);
67
+ if (match) {
68
+ return { x: parseFloat(match[1]), y: parseFloat(match[2]) };
69
+ }
70
+ return { x: 0, y: 0 };
71
+ };
72
+
73
+ const getTransformValues = (element) => {
74
+ const style = element.style.transform;
75
+ const match = style.match(/translate3d\((-?\d+(?:\.\d+)?)px,\s*(-?\d+(?:\.\d+)?)px,\s*(-?\d+(?:\.\d+)?)px\)/);
76
+ if (match) {
77
+ return { x: parseFloat(match[1]), y: parseFloat(match[2]) };
78
+ }
79
+ return { x: 0, y: 0 };
80
+ };
81
+
82
+ const getZoomValue = () => {
83
+ const zoomValue = getComputedStyle(document.body).getPropertyValue("--zoom").trim();
84
+ return zoomValue ? parseFloat(zoomValue) : 1;
85
+ };
86
+
87
+ const setupViewportLabelDrag = (labelElement, viewportElement, viewportName) => {
88
+ let isDragging = false;
89
+ let startX = 0;
90
+ let startY = 0;
91
+ let initialTransform = { x: 0, y: 0 };
92
+ let initialLabelPosition = { x: 0, y: 0 };
93
+ // Get the parent group element that contains the label
94
+ const labelGroup = labelElement.parentElement;
95
+ const startDrag = (event) => {
96
+ event.preventDefault();
97
+ event.stopPropagation();
98
+ isDragging = true;
99
+ setViewportLabelDragging(true);
100
+ startX = event.clientX;
101
+ startY = event.clientY;
102
+ initialTransform = getTransformValues(viewportElement);
103
+ initialLabelPosition = getLabelPosition(labelGroup);
104
+ };
105
+ const handleDrag = (event) => {
106
+ if (!isDragging)
107
+ return;
108
+ const zoom = getZoomValue();
109
+ // Calculate mouse delta
110
+ const rawDeltaX = event.clientX - startX;
111
+ const rawDeltaY = event.clientY - startY;
112
+ // Adjust delta for zoom level
113
+ const deltaX = rawDeltaX / zoom;
114
+ const deltaY = rawDeltaY / zoom;
115
+ const newX = initialTransform.x + deltaX;
116
+ const newY = initialTransform.y + deltaY;
117
+ // Update label position with raw delta (labels are in screen space)
118
+ const newLabelX = initialLabelPosition.x + rawDeltaX;
119
+ const newLabelY = initialLabelPosition.y + rawDeltaY;
120
+ labelGroup.setAttribute("transform", `translate(${newLabelX}, ${newLabelY})`);
121
+ // Update viewport position with zoom-adjusted delta
122
+ viewportElement.style.transform = `translate3d(${newX}px, ${newY}px, 0)`;
123
+ };
124
+ const stopDrag = (event) => {
125
+ if (!isDragging)
126
+ return;
127
+ event.preventDefault();
128
+ event.stopPropagation();
129
+ isDragging = false;
130
+ setViewportLabelDragging(false);
131
+ const finalTransform = getTransformValues(viewportElement);
132
+ // Trigger refresh after drag completes to update highlight frame and labels
133
+ // biome-ignore lint/suspicious/noExplicitAny: global window extension
134
+ const nodeTools = window.nodeTools;
135
+ if (nodeTools?.refreshHighlightFrame) {
136
+ nodeTools.refreshHighlightFrame();
137
+ }
138
+ // Notify parent about the new position
139
+ sendPostMessage("viewport-position-changed", {
140
+ viewportName,
141
+ x: finalTransform.x,
142
+ y: finalTransform.y,
143
+ });
144
+ };
145
+ const cancelDrag = () => {
146
+ isDragging = false;
147
+ setViewportLabelDragging(false);
148
+ };
149
+ // Attach event listeners
150
+ labelElement.addEventListener("mousedown", startDrag);
151
+ document.addEventListener("mousemove", handleDrag);
152
+ document.addEventListener("mouseup", stopDrag);
153
+ window.addEventListener("blur", cancelDrag);
154
+ // Return cleanup function
155
+ return () => {
156
+ labelElement.removeEventListener("mousedown", startDrag);
157
+ document.removeEventListener("mousemove", handleDrag);
158
+ document.removeEventListener("mouseup", stopDrag);
159
+ window.removeEventListener("blur", cancelDrag);
160
+ };
161
+ };
162
+
163
+ // Store cleanup functions for drag listeners
164
+ const dragCleanupFunctions = new Map();
165
+ const refreshViewportLabels = () => {
166
+ // Skip refresh if a viewport label is being dragged
167
+ if (isViewportLabelDragging()) {
168
+ return;
169
+ }
170
+ const overlay = getViewportLabelsOverlay();
171
+ // Update SVG dimensions to match current viewport
172
+ const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
173
+ const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
174
+ overlay.setAttribute("width", viewportWidth.toString());
175
+ overlay.setAttribute("height", viewportHeight.toString());
176
+ // Find all viewports with names
177
+ const viewports = document.querySelectorAll(".viewport[data-viewport-name]");
178
+ // Clean up existing drag listeners
179
+ dragCleanupFunctions.forEach((cleanup) => {
180
+ cleanup();
181
+ });
182
+ dragCleanupFunctions.clear();
183
+ // Remove existing label groups
184
+ const existingGroups = overlay.querySelectorAll(".viewport-label-group");
185
+ existingGroups.forEach((group) => {
186
+ group.remove();
187
+ });
188
+ // Create/update labels for each viewport
189
+ viewports.forEach((viewport) => {
190
+ const viewportElement = viewport;
191
+ const viewportName = viewportElement.getAttribute("data-viewport-name");
192
+ if (!viewportName)
193
+ return;
194
+ const bounds = getScreenBounds(viewportElement);
195
+ // Create group for this viewport label
196
+ const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
197
+ group.classList.add("viewport-label-group");
198
+ group.setAttribute("data-viewport-name", viewportName);
199
+ group.setAttribute("transform", `translate(${bounds.left}, ${bounds.top})`);
200
+ // Create text element
201
+ const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
202
+ text.classList.add("viewport-label-text");
203
+ text.setAttribute("x", "0");
204
+ text.setAttribute("y", "-8");
205
+ text.setAttribute("vector-effect", "non-scaling-stroke");
206
+ text.setAttribute("pointer-events", "auto");
207
+ text.textContent = viewportName;
208
+ group.appendChild(text);
209
+ overlay.appendChild(group);
210
+ // Setup drag functionality for this label
211
+ const cleanup = setupViewportLabelDrag(text, viewportElement, viewportName);
212
+ dragCleanupFunctions.set(viewportName, cleanup);
213
+ });
214
+ };
215
+
8
216
  const getCanvasWindowValue = (path, canvasName = "canvas") => {
9
217
  // biome-ignore lint/suspicious/noExplicitAny: global window extension
10
218
  const canvas = window[canvasName];
@@ -34,6 +242,8 @@ function createCanvasObserver(canvasName = "canvas") {
34
242
  if (nodeTools?.refreshHighlightFrame) {
35
243
  nodeTools.refreshHighlightFrame();
36
244
  }
245
+ // Refresh viewport labels
246
+ refreshViewportLabels();
37
247
  });
38
248
  observer.observe(transformLayer, {
39
249
  attributes: true,
@@ -41,8 +251,16 @@ function createCanvasObserver(canvasName = "canvas") {
41
251
  subtree: true,
42
252
  childList: true,
43
253
  });
254
+ // Handle window resize for viewport labels
255
+ const handleResize = () => {
256
+ refreshViewportLabels();
257
+ };
258
+ window.addEventListener("resize", handleResize);
259
+ // Initial refresh of viewport labels
260
+ refreshViewportLabels();
44
261
  function disconnect() {
45
262
  observer.disconnect();
263
+ window.removeEventListener("resize", handleResize);
46
264
  }
47
265
  return {
48
266
  disconnect,
@@ -57,15 +275,6 @@ const connectResizeObserver = (element, handler) => {
57
275
  return resizeObserver;
58
276
  };
59
277
 
60
- function sendPostMessage(action, data) {
61
- window.parent.postMessage({
62
- source: "node-edit-utils",
63
- action,
64
- data,
65
- timestamp: Date.now(),
66
- }, "*");
67
- }
68
-
69
278
  const bindToWindow = (key, value) => {
70
279
  if (typeof window !== "undefined") {
71
280
  // biome-ignore lint/suspicious/noExplicitAny: global window extension requires flexibility
@@ -93,10 +302,6 @@ const processPostMessage = (event, onNodeSelected) => {
93
302
  }
94
303
  };
95
304
 
96
- const getCanvasContainer = () => {
97
- return document.querySelector(".canvas-container");
98
- };
99
-
100
305
  const clearHighlightFrame = () => {
101
306
  const canvasContainer = getCanvasContainer();
102
307
  const container = canvasContainer || document.body;
@@ -148,6 +353,21 @@ const isInsideComponent = (element) => {
148
353
  return false;
149
354
  };
150
355
 
356
+ const isInsideViewport = (element) => {
357
+ let current = element;
358
+ while (current) {
359
+ if (current.classList.contains("viewport")) {
360
+ return true;
361
+ }
362
+ // Stop at node-provider to avoid checking beyond the editable area
363
+ if (current.getAttribute("data-role") === "node-provider") {
364
+ break;
365
+ }
366
+ current = current.parentElement;
367
+ }
368
+ return false;
369
+ };
370
+
151
371
  const targetSameCandidates = (cache, current) => cache.length === current.length && cache.every((el, i) => el === current[i]);
152
372
 
153
373
  let candidateCache = [];
@@ -160,7 +380,16 @@ const selectNode = (event, nodeProvider, text) => {
160
380
  const clickThrough = event.metaKey || event.ctrlKey;
161
381
  const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()) &&
162
382
  !element.classList.contains("select-none") &&
163
- !isInsideComponent(element));
383
+ !element.classList.contains("content-layer") &&
384
+ !element.classList.contains("resize-handle") &&
385
+ !element.classList.contains("resize-presets") &&
386
+ !isInsideComponent(element) &&
387
+ isInsideViewport(element));
388
+ console.log("candidates", candidates);
389
+ if (candidates.length === 0) {
390
+ lastSelectedNode = null;
391
+ return null;
392
+ }
164
393
  const editableNode = text.getEditableNode();
165
394
  if (editableNode && candidates.includes(editableNode)) {
166
395
  selectedNode = editableNode;
@@ -265,16 +494,6 @@ const createCornerHandles = (group, width, height, isInstance = false, isTextEdi
265
494
  createCornerHandle(group, 0, height, "handle-bottom-left", isInstance, isTextEdit);
266
495
  };
267
496
 
268
- function getScreenBounds(element) {
269
- const rect = element.getBoundingClientRect();
270
- return {
271
- top: rect.top,
272
- left: rect.left,
273
- width: rect.width,
274
- height: rect.height,
275
- };
276
- }
277
-
278
497
  const getComponentColor$1 = () => {
279
498
  return getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim() || "oklch(65.6% 0.241 354.308)";
280
499
  };
@@ -707,6 +926,7 @@ const setupMutationObserver = (node, nodeProvider, canvasName = "canvas") => {
707
926
  // Accumulate mutations instead of replacing
708
927
  pendingMutations.push(...mutations);
709
928
  scheduleProcess();
929
+ console.log("refreshHighlightFrame in mutationObserver");
710
930
  refreshHighlightFrame(node, nodeProvider, canvasName);
711
931
  });
712
932
  return () => {
@@ -844,14 +1064,15 @@ const createNodeTools = (element, canvasName = "canvas") => {
844
1064
  checkNodeExists();
845
1065
  if (!document.contains(node))
846
1066
  return;
1067
+ console.log("refreshHighlightFrame in mutationObserver 2");
847
1068
  refreshHighlightFrame(node, nodeProvider, canvasName);
848
1069
  updateHighlightFrameVisibility(node);
849
1070
  });
850
1071
  mutationObserver.observe(node, {
851
1072
  attributes: true,
852
1073
  characterData: true,
853
- childList: true,
854
- subtree: true,
1074
+ //childList: true,
1075
+ //subtree: true,
855
1076
  });
856
1077
  // Also observe parent node to catch when this node is removed
857
1078
  const parentNode = node.parentElement;
@@ -880,15 +1101,22 @@ const createNodeTools = (element, canvasName = "canvas") => {
880
1101
  if (!document.contains(node))
881
1102
  return; // Exit early if node was removed
882
1103
  refreshHighlightFrame(node, nodeProvider, canvasName);
1104
+ console.log("refreshHighlightFrame in resizeObserver");
883
1105
  updateHighlightFrameVisibility(node);
884
1106
  });
885
1107
  }
886
1108
  selectedNode = node;
887
1109
  sendPostMessage("selectedNodeChanged", node?.getAttribute("data-node-id") ?? null);
888
1110
  highlightNode(node) ?? null;
889
- if (node && nodeProvider) {
890
- updateHighlightFrameVisibility(node);
891
- updateHighlightFrameVisibility(node);
1111
+ if (node) {
1112
+ highlightNode(node);
1113
+ if (nodeProvider) {
1114
+ updateHighlightFrameVisibility(node);
1115
+ updateHighlightFrameVisibility(node);
1116
+ }
1117
+ }
1118
+ else {
1119
+ clearHighlightFrame();
892
1120
  }
893
1121
  };
894
1122
  // Setup event listener
@@ -927,36 +1155,10 @@ const createNodeTools = (element, canvasName = "canvas") => {
927
1155
  return nodeTools;
928
1156
  };
929
1157
 
930
- // biome-ignore lint/suspicious/noExplicitAny: generic constraint requires flexibility
931
- function withRAFThrottle(func) {
932
- let rafId = null;
933
- let lastArgs = null;
934
- const throttled = (...args) => {
935
- lastArgs = args;
936
- if (rafId === null) {
937
- rafId = requestAnimationFrame(() => {
938
- if (lastArgs) {
939
- func(...lastArgs);
940
- }
941
- rafId = null;
942
- lastArgs = null;
943
- });
944
- }
945
- };
946
- throttled.cleanup = () => {
947
- if (rafId !== null) {
948
- cancelAnimationFrame(rafId);
949
- rafId = null;
950
- lastArgs = null;
951
- }
952
- };
953
- return throttled;
954
- }
955
-
956
1158
  const DEFAULT_WIDTH = 400;
957
1159
  const RESIZE_CONFIG = {
958
- minWidth: 320,
959
- maxWidth: 1680,
1160
+ minWidth: 4,
1161
+ maxWidth: 2560,
960
1162
  };
961
1163
  const RESIZE_PRESETS = [
962
1164
  {
@@ -987,14 +1189,22 @@ const RESIZE_PRESETS = [
987
1189
  ];
988
1190
 
989
1191
  const setupEventListener = (resizeHandle, startResize, handleResize, stopResize, blurResize) => {
1192
+ const handleMouseLeave = (event) => {
1193
+ // Check if mouse is leaving the window/document
1194
+ if (!event.relatedTarget && (event.target === document || event.target === document.documentElement)) {
1195
+ blurResize();
1196
+ }
1197
+ };
990
1198
  resizeHandle.addEventListener("mousedown", startResize);
991
1199
  document.addEventListener("mousemove", handleResize);
992
1200
  document.addEventListener("mouseup", stopResize);
1201
+ document.addEventListener("mouseleave", handleMouseLeave);
993
1202
  window.addEventListener("blur", blurResize);
994
1203
  return () => {
995
1204
  resizeHandle.removeEventListener("mousedown", startResize);
996
1205
  document.removeEventListener("mousemove", handleResize);
997
1206
  document.removeEventListener("mouseup", stopResize);
1207
+ document.removeEventListener("mouseleave", handleMouseLeave);
998
1208
  window.removeEventListener("blur", blurResize);
999
1209
  };
1000
1210
  };
@@ -1054,7 +1264,7 @@ const updateWidth = (container, width) => {
1054
1264
  updateActivePreset(container, width);
1055
1265
  };
1056
1266
 
1057
- const createViewport = (container) => {
1267
+ const createViewport = (container, initialWidth) => {
1058
1268
  const canvas = getCanvasContainer();
1059
1269
  // Remove any existing resize handle to prevent duplicates
1060
1270
  const existingHandle = container.querySelector(".resize-handle");
@@ -1062,7 +1272,8 @@ const createViewport = (container) => {
1062
1272
  existingHandle.remove();
1063
1273
  }
1064
1274
  const resizeHandle = createResizeHandle(container);
1065
- container.style.setProperty("--container-width", `${DEFAULT_WIDTH}px`);
1275
+ const width = initialWidth ?? DEFAULT_WIDTH;
1276
+ container.style.setProperty("--container-width", `${width}px`);
1066
1277
  createResizePresets(resizeHandle, container, updateWidth);
1067
1278
  let isDragging = false;
1068
1279
  let startX = 0;
@@ -1083,7 +1294,6 @@ const createViewport = (container) => {
1083
1294
  const width = calcWidth(event, startX, startWidth);
1084
1295
  updateWidth(container, width);
1085
1296
  };
1086
- const throttledHandleResize = withRAFThrottle(handleResize);
1087
1297
  const stopResize = (event) => {
1088
1298
  event.preventDefault();
1089
1299
  event.stopPropagation();
@@ -1093,18 +1303,33 @@ const createViewport = (container) => {
1093
1303
  isDragging = false;
1094
1304
  };
1095
1305
  const blurResize = () => {
1306
+ if (canvas) {
1307
+ canvas.style.cursor = "default";
1308
+ }
1096
1309
  isDragging = false;
1097
1310
  };
1098
- const removeListeners = setupEventListener(resizeHandle, startResize, throttledHandleResize, stopResize, blurResize);
1311
+ const removeListeners = setupEventListener(resizeHandle, startResize, handleResize, stopResize, blurResize);
1312
+ // Refresh viewport labels when viewport is created
1313
+ refreshViewportLabels();
1099
1314
  const cleanup = () => {
1100
1315
  isDragging = false;
1101
- throttledHandleResize?.cleanup();
1102
1316
  removeListeners();
1103
1317
  resizeHandle.remove();
1318
+ // Refresh labels after cleanup to remove this viewport's label if needed
1319
+ refreshViewportLabels();
1104
1320
  };
1105
1321
  return {
1106
1322
  setWidth: (width) => {
1107
1323
  updateWidth(container, width);
1324
+ refreshViewportLabels();
1325
+ // Refresh highlight frame when viewport width changes to update node positions
1326
+ // biome-ignore lint/suspicious/noExplicitAny: global window extension
1327
+ const nodeTools = window.nodeTools;
1328
+ const selectedNode = nodeTools?.getSelectedNode?.();
1329
+ const nodeProvider = document.querySelector('[data-role="node-provider"]');
1330
+ if (selectedNode && nodeProvider) {
1331
+ refreshHighlightFrame(selectedNode, nodeProvider);
1332
+ }
1108
1333
  },
1109
1334
  cleanup,
1110
1335
  };