@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.
Files changed (63) 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/node-tools/select/helpers/isInsideViewport.d.ts +1 -0
  12. package/dist/lib/viewport/constants.d.ts +2 -2
  13. package/dist/lib/viewport/label/getViewportLabelsOverlay.d.ts +1 -0
  14. package/dist/lib/viewport/label/helpers/getLabelPosition.d.ts +4 -0
  15. package/dist/lib/viewport/label/helpers/getTransformValues.d.ts +4 -0
  16. package/dist/lib/viewport/label/helpers/getZoomValue.d.ts +1 -0
  17. package/dist/lib/viewport/label/helpers/selectFirstViewportNode.d.ts +5 -0
  18. package/dist/lib/viewport/label/index.d.ts +4 -0
  19. package/dist/lib/viewport/label/isViewportLabelDragging.d.ts +2 -0
  20. package/dist/lib/viewport/label/refreshViewportLabels.d.ts +1 -0
  21. package/dist/lib/viewport/label/setupViewportLabelDrag.d.ts +1 -0
  22. package/dist/node-edit-utils.cjs.js +437 -172
  23. package/dist/node-edit-utils.esm.js +437 -172
  24. package/dist/node-edit-utils.umd.js +437 -172
  25. package/dist/node-edit-utils.umd.min.js +1 -1
  26. package/dist/styles.css +1 -1
  27. package/package.json +1 -1
  28. package/src/lib/canvas/createCanvasObserver.ts +16 -2
  29. package/src/lib/canvas/helpers/getCanvasContainerOrBody.ts +6 -0
  30. package/src/lib/canvas/helpers/getCanvasWindowValue.ts +2 -3
  31. package/src/lib/helpers/adjustForZoom.ts +4 -0
  32. package/src/lib/helpers/createDragHandler.ts +171 -0
  33. package/src/lib/helpers/getNodeProvider.ts +4 -0
  34. package/src/lib/helpers/getNodeTools.ts +6 -0
  35. package/src/lib/helpers/getViewportDimensions.ts +7 -0
  36. package/src/lib/helpers/index.ts +9 -1
  37. package/src/lib/helpers/parseTransform.ts +9 -0
  38. package/src/lib/helpers/toggleClass.ts +9 -0
  39. package/src/lib/node-tools/createNodeTools.ts +11 -5
  40. package/src/lib/node-tools/highlight/clearHighlightFrame.ts +2 -3
  41. package/src/lib/node-tools/highlight/createHighlightFrame.ts +5 -9
  42. package/src/lib/node-tools/highlight/createToolsContainer.ts +3 -6
  43. package/src/lib/node-tools/highlight/helpers/getElementBounds.ts +6 -5
  44. package/src/lib/node-tools/highlight/helpers/getHighlightFrameElement.ts +2 -3
  45. package/src/lib/node-tools/highlight/highlightNode.ts +7 -15
  46. package/src/lib/node-tools/highlight/refreshHighlightFrame.ts +12 -42
  47. package/src/lib/node-tools/highlight/updateHighlightFrameVisibility.ts +2 -3
  48. package/src/lib/node-tools/select/helpers/isInsideViewport.ts +19 -0
  49. package/src/lib/node-tools/select/selectNode.ts +13 -1
  50. package/src/lib/node-tools/text/events/setupMutationObserver.ts +1 -0
  51. package/src/lib/styles/styles.css +48 -1
  52. package/src/lib/viewport/constants.ts +2 -2
  53. package/src/lib/viewport/createViewport.ts +56 -41
  54. package/src/lib/viewport/label/getViewportLabelsOverlay.ts +32 -0
  55. package/src/lib/viewport/label/helpers/getLabelPosition.ts +6 -0
  56. package/src/lib/viewport/label/helpers/getTransformValues.ts +6 -0
  57. package/src/lib/viewport/label/helpers/getZoomValue.ts +4 -0
  58. package/src/lib/viewport/label/helpers/selectFirstViewportNode.ts +18 -0
  59. package/src/lib/viewport/label/index.ts +4 -0
  60. package/src/lib/viewport/label/isViewportLabelDragging.ts +9 -0
  61. package/src/lib/viewport/label/refreshViewportLabels.ts +69 -0
  62. package/src/lib/viewport/label/setupViewportLabelDrag.ts +70 -0
  63. package/src/lib/window/bindToWindow.ts +1 -2
@@ -1,12 +1,314 @@
1
1
  /**
2
2
  * Markup Canvas
3
3
  * High-performance markup canvas with zoom and pan capabilities
4
- * @version 2.3.1
4
+ * @version 2.3.3
5
5
  */
6
6
  'use strict';
7
7
 
8
+ const getNodeTools = () => {
9
+ return window.nodeTools;
10
+ };
11
+
12
+ const getViewportDimensions = () => {
13
+ return {
14
+ width: document.documentElement.clientWidth || window.innerWidth,
15
+ height: document.documentElement.clientHeight || window.innerHeight,
16
+ };
17
+ };
18
+
19
+ function getScreenBounds(element) {
20
+ const rect = element.getBoundingClientRect();
21
+ return {
22
+ top: rect.top,
23
+ left: rect.left,
24
+ width: rect.width,
25
+ height: rect.height,
26
+ };
27
+ }
28
+
29
+ const getCanvasContainer = () => {
30
+ return document.querySelector(".canvas-container");
31
+ };
32
+
33
+ const getCanvasContainerOrBody = () => {
34
+ return getCanvasContainer() || document.body;
35
+ };
36
+
37
+ const getViewportLabelsOverlay = () => {
38
+ const container = getCanvasContainerOrBody();
39
+ // Check if overlay already exists
40
+ let overlay = container.querySelector(".viewport-labels-overlay");
41
+ if (!overlay) {
42
+ // Create new SVG overlay
43
+ overlay = document.createElementNS("http://www.w3.org/2000/svg", "svg");
44
+ overlay.classList.add("viewport-labels-overlay");
45
+ // Set fixed positioning
46
+ overlay.style.position = "absolute";
47
+ overlay.style.top = "0";
48
+ overlay.style.left = "0";
49
+ overlay.style.width = "100vw";
50
+ overlay.style.height = "100vh";
51
+ overlay.style.pointerEvents = "none";
52
+ overlay.style.zIndex = "500";
53
+ const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
54
+ overlay.setAttribute("width", viewportWidth.toString());
55
+ overlay.setAttribute("height", viewportHeight.toString());
56
+ container.appendChild(overlay);
57
+ }
58
+ return overlay;
59
+ };
60
+
61
+ // Global flag to prevent refreshViewportLabels during drag
62
+ let globalIsDragging = false;
63
+ const isViewportLabelDragging = () => globalIsDragging;
64
+ const setViewportLabelDragging = (isDragging) => {
65
+ globalIsDragging = isDragging;
66
+ };
67
+
68
+ /**
69
+ * Creates a reusable drag handler for mouse drag operations
70
+ * @param element - Element that triggers the drag (mousedown listener attached here)
71
+ * @param callbacks - Callbacks for drag lifecycle events
72
+ * @param options - Configuration options
73
+ * @returns Cleanup function to remove all event listeners
74
+ */
75
+ function createDragHandler(element, callbacks, options = {}) {
76
+ const { preventDefault = true, stopPropagation = true } = options;
77
+ const state = {
78
+ isDragging: false,
79
+ hasDragged: false,
80
+ startX: 0,
81
+ startY: 0,
82
+ };
83
+ const startDrag = (event) => {
84
+ if (preventDefault) {
85
+ event.preventDefault();
86
+ }
87
+ if (stopPropagation) {
88
+ event.stopPropagation();
89
+ }
90
+ state.isDragging = true;
91
+ state.hasDragged = false;
92
+ state.startX = event.clientX;
93
+ state.startY = event.clientY;
94
+ callbacks.onStart?.(event, state);
95
+ };
96
+ const handleDrag = (event) => {
97
+ if (!state.isDragging)
98
+ return;
99
+ const deltaX = event.clientX - state.startX;
100
+ const deltaY = event.clientY - state.startY;
101
+ state.hasDragged = true;
102
+ callbacks.onDrag(event, {
103
+ ...state,
104
+ deltaX,
105
+ deltaY,
106
+ });
107
+ };
108
+ const stopDrag = (event) => {
109
+ if (!state.isDragging)
110
+ return;
111
+ if (preventDefault) {
112
+ event.preventDefault();
113
+ }
114
+ if (stopPropagation) {
115
+ event.stopPropagation();
116
+ }
117
+ state.isDragging = false;
118
+ callbacks.onStop?.(event, state);
119
+ };
120
+ const cancelDrag = () => {
121
+ if (!state.isDragging)
122
+ return;
123
+ state.isDragging = false;
124
+ callbacks.onCancel?.(state);
125
+ };
126
+ const preventClick = (event) => {
127
+ if (preventDefault) {
128
+ event.preventDefault();
129
+ }
130
+ if (stopPropagation) {
131
+ event.stopPropagation();
132
+ }
133
+ callbacks.onPreventClick?.(event, state);
134
+ // Reset hasDragged flag after handling the click
135
+ if (state.hasDragged) {
136
+ state.hasDragged = false;
137
+ }
138
+ };
139
+ // Attach event listeners
140
+ element.addEventListener("mousedown", startDrag);
141
+ if (callbacks.onPreventClick) {
142
+ element.addEventListener("click", preventClick);
143
+ }
144
+ document.addEventListener("mousemove", handleDrag);
145
+ document.addEventListener("mouseup", stopDrag);
146
+ window.addEventListener("blur", cancelDrag);
147
+ // Return cleanup function
148
+ return () => {
149
+ element.removeEventListener("mousedown", startDrag);
150
+ if (callbacks.onPreventClick) {
151
+ element.removeEventListener("click", preventClick);
152
+ }
153
+ document.removeEventListener("mousemove", handleDrag);
154
+ document.removeEventListener("mouseup", stopDrag);
155
+ window.removeEventListener("blur", cancelDrag);
156
+ };
157
+ }
158
+
159
+ function sendPostMessage(action, data) {
160
+ window.parent.postMessage({
161
+ source: "node-edit-utils",
162
+ action,
163
+ data,
164
+ timestamp: Date.now(),
165
+ }, "*");
166
+ }
167
+
168
+ const parseTransform3d = (transform) => {
169
+ const match = transform.match(/translate3d\((-?\d+(?:\.\d+)?)px,\s*(-?\d+(?:\.\d+)?)px,\s*(-?\d+(?:\.\d+)?)px\)/);
170
+ return match ? { x: parseFloat(match[1]), y: parseFloat(match[2]) } : { x: 0, y: 0 };
171
+ };
172
+ const parseTransform2d = (transform) => {
173
+ const match = transform?.match(/translate\((-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)\)/);
174
+ return match ? { x: parseFloat(match[1]), y: parseFloat(match[2]) } : { x: 0, y: 0 };
175
+ };
176
+
177
+ const getLabelPosition = (labelGroup) => {
178
+ const transform = labelGroup.getAttribute("transform");
179
+ return parseTransform2d(transform);
180
+ };
181
+
182
+ const getTransformValues = (element) => {
183
+ const style = element.style.transform;
184
+ return parseTransform3d(style);
185
+ };
186
+
187
+ const getZoomValue = () => {
188
+ const zoomValue = getComputedStyle(document.body).getPropertyValue("--zoom").trim();
189
+ return zoomValue ? parseFloat(zoomValue) : 1;
190
+ };
191
+
192
+ /**
193
+ * Selects the first child node inside a viewport element.
194
+ * Skips the resize-handle element if present.
195
+ */
196
+ const selectFirstViewportNode = (viewportElement) => {
197
+ const firstChild = Array.from(viewportElement.children).find((child) => !child.classList.contains("resize-handle"));
198
+ if (firstChild) {
199
+ const nodeTools = getNodeTools();
200
+ if (nodeTools?.selectNode) {
201
+ nodeTools.selectNode(firstChild);
202
+ }
203
+ }
204
+ };
205
+
206
+ const setupViewportLabelDrag = (labelElement, viewportElement, viewportName) => {
207
+ // Get the parent group element that contains the label
208
+ const labelGroup = labelElement.parentElement;
209
+ // Track initial positions for calculations
210
+ let initialTransform = { x: 0, y: 0 };
211
+ let initialLabelPosition = { x: 0, y: 0 };
212
+ return createDragHandler(labelElement, {
213
+ onStart: () => {
214
+ setViewportLabelDragging(true);
215
+ initialTransform = getTransformValues(viewportElement);
216
+ initialLabelPosition = getLabelPosition(labelGroup);
217
+ selectFirstViewportNode(viewportElement);
218
+ },
219
+ onDrag: (_event, { deltaX, deltaY }) => {
220
+ const zoom = getZoomValue();
221
+ // Adjust delta for zoom level (viewport is in canvas space)
222
+ const deltaXZoomed = deltaX / zoom;
223
+ const deltaYZoomed = deltaY / zoom;
224
+ // Calculate new positions
225
+ const newX = initialTransform.x + deltaXZoomed;
226
+ const newY = initialTransform.y + deltaYZoomed;
227
+ // Update label position with raw delta (labels are in screen space)
228
+ const newLabelX = initialLabelPosition.x + deltaX;
229
+ const newLabelY = initialLabelPosition.y + deltaY;
230
+ labelGroup.setAttribute("transform", `translate(${newLabelX}, ${newLabelY})`);
231
+ // Update viewport position with zoom-adjusted delta
232
+ viewportElement.style.transform = `translate3d(${newX}px, ${newY}px, 0)`;
233
+ },
234
+ onStop: (_event, { hasDragged }) => {
235
+ setViewportLabelDragging(false);
236
+ // If it was a drag, handle drag completion
237
+ if (hasDragged) {
238
+ const finalTransform = getTransformValues(viewportElement);
239
+ // Trigger refresh after drag completes to update highlight frame and labels
240
+ const nodeTools = getNodeTools();
241
+ if (nodeTools?.refreshHighlightFrame) {
242
+ nodeTools.refreshHighlightFrame();
243
+ }
244
+ // Notify parent about the new position
245
+ sendPostMessage("viewport-position-changed", {
246
+ viewportName,
247
+ x: finalTransform.x,
248
+ y: finalTransform.y,
249
+ });
250
+ }
251
+ },
252
+ onCancel: () => {
253
+ setViewportLabelDragging(false);
254
+ },
255
+ onPreventClick: () => { },
256
+ });
257
+ };
258
+
259
+ // Store cleanup functions for drag listeners
260
+ const dragCleanupFunctions = new Map();
261
+ const refreshViewportLabels = () => {
262
+ // Skip refresh if a viewport label is being dragged
263
+ if (isViewportLabelDragging()) {
264
+ return;
265
+ }
266
+ const overlay = getViewportLabelsOverlay();
267
+ // Update SVG dimensions to match current viewport
268
+ const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
269
+ overlay.setAttribute("width", viewportWidth.toString());
270
+ overlay.setAttribute("height", viewportHeight.toString());
271
+ // Find all viewports with names
272
+ const viewports = document.querySelectorAll(".viewport[data-viewport-name]");
273
+ // Clean up existing drag listeners
274
+ dragCleanupFunctions.forEach((cleanup) => {
275
+ cleanup();
276
+ });
277
+ dragCleanupFunctions.clear();
278
+ // Remove existing label groups
279
+ const existingGroups = overlay.querySelectorAll(".viewport-label-group");
280
+ existingGroups.forEach((group) => {
281
+ group.remove();
282
+ });
283
+ // Create/update labels for each viewport
284
+ viewports.forEach((viewport) => {
285
+ const viewportElement = viewport;
286
+ const viewportName = viewportElement.getAttribute("data-viewport-name");
287
+ if (!viewportName)
288
+ return;
289
+ const bounds = getScreenBounds(viewportElement);
290
+ // Create group for this viewport label
291
+ const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
292
+ group.classList.add("viewport-label-group");
293
+ group.setAttribute("data-viewport-name", viewportName);
294
+ group.setAttribute("transform", `translate(${bounds.left}, ${bounds.top})`);
295
+ // Create text element
296
+ const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
297
+ text.classList.add("viewport-label-text");
298
+ text.setAttribute("x", "0");
299
+ text.setAttribute("y", "-8");
300
+ text.setAttribute("vector-effect", "non-scaling-stroke");
301
+ text.setAttribute("pointer-events", "auto");
302
+ text.textContent = viewportName;
303
+ group.appendChild(text);
304
+ overlay.appendChild(group);
305
+ // Setup drag functionality for this label
306
+ const cleanup = setupViewportLabelDrag(text, viewportElement, viewportName);
307
+ dragCleanupFunctions.set(viewportName, cleanup);
308
+ });
309
+ };
310
+
8
311
  const getCanvasWindowValue = (path, canvasName = "canvas") => {
9
- // biome-ignore lint/suspicious/noExplicitAny: global window extension
10
312
  const canvas = window[canvasName];
11
313
  return path.reduce((obj, prop) => obj?.[prop], canvas);
12
314
  };
@@ -29,11 +331,12 @@ function createCanvasObserver(canvasName = "canvas") {
29
331
  const observer = new MutationObserver(() => {
30
332
  applyCanvasState(canvasName);
31
333
  // Refresh highlight frame (throttled via withRAFThrottle)
32
- // biome-ignore lint/suspicious/noExplicitAny: global window extension
33
- const nodeTools = window.nodeTools;
334
+ const nodeTools = getNodeTools();
34
335
  if (nodeTools?.refreshHighlightFrame) {
35
336
  nodeTools.refreshHighlightFrame();
36
337
  }
338
+ // Refresh viewport labels
339
+ refreshViewportLabels();
37
340
  });
38
341
  observer.observe(transformLayer, {
39
342
  attributes: true,
@@ -41,8 +344,16 @@ function createCanvasObserver(canvasName = "canvas") {
41
344
  subtree: true,
42
345
  childList: true,
43
346
  });
347
+ // Handle window resize for viewport labels
348
+ const handleResize = () => {
349
+ refreshViewportLabels();
350
+ };
351
+ window.addEventListener("resize", handleResize);
352
+ // Initial refresh of viewport labels
353
+ refreshViewportLabels();
44
354
  function disconnect() {
45
355
  observer.disconnect();
356
+ window.removeEventListener("resize", handleResize);
46
357
  }
47
358
  return {
48
359
  disconnect,
@@ -57,18 +368,8 @@ const connectResizeObserver = (element, handler) => {
57
368
  return resizeObserver;
58
369
  };
59
370
 
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
371
  const bindToWindow = (key, value) => {
70
372
  if (typeof window !== "undefined") {
71
- // biome-ignore lint/suspicious/noExplicitAny: global window extension requires flexibility
72
373
  window[key] = value;
73
374
  }
74
375
  };
@@ -93,13 +394,8 @@ const processPostMessage = (event, onNodeSelected) => {
93
394
  }
94
395
  };
95
396
 
96
- const getCanvasContainer = () => {
97
- return document.querySelector(".canvas-container");
98
- };
99
-
100
397
  const clearHighlightFrame = () => {
101
- const canvasContainer = getCanvasContainer();
102
- const container = canvasContainer || document.body;
398
+ const container = getCanvasContainerOrBody();
103
399
  const frame = container.querySelector(".highlight-frame-overlay");
104
400
  if (frame) {
105
401
  frame.remove();
@@ -148,6 +444,21 @@ const isInsideComponent = (element) => {
148
444
  return false;
149
445
  };
150
446
 
447
+ const isInsideViewport = (element) => {
448
+ let current = element;
449
+ while (current) {
450
+ if (current.classList.contains("viewport")) {
451
+ return true;
452
+ }
453
+ // Stop at node-provider to avoid checking beyond the editable area
454
+ if (current.getAttribute("data-role") === "node-provider") {
455
+ break;
456
+ }
457
+ current = current.parentElement;
458
+ }
459
+ return false;
460
+ };
461
+
151
462
  const targetSameCandidates = (cache, current) => cache.length === current.length && cache.every((el, i) => el === current[i]);
152
463
 
153
464
  let candidateCache = [];
@@ -160,7 +471,16 @@ const selectNode = (event, nodeProvider, text) => {
160
471
  const clickThrough = event.metaKey || event.ctrlKey;
161
472
  const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()) &&
162
473
  !element.classList.contains("select-none") &&
163
- !isInsideComponent(element));
474
+ !element.classList.contains("content-layer") &&
475
+ !element.classList.contains("resize-handle") &&
476
+ !element.classList.contains("resize-presets") &&
477
+ !isInsideComponent(element) &&
478
+ isInsideViewport(element));
479
+ console.log("candidates", candidates);
480
+ if (candidates.length === 0) {
481
+ lastSelectedNode = null;
482
+ return null;
483
+ }
164
484
  const editableNode = text.getEditableNode();
165
485
  if (editableNode && candidates.includes(editableNode)) {
166
486
  selectedNode = editableNode;
@@ -204,7 +524,7 @@ const handleNodeClick = (event, nodeProvider, text, onNodeSelected) => {
204
524
  onNodeSelected(selectedNode);
205
525
  };
206
526
 
207
- const setupEventListener$1 = (nodeProvider, onNodeSelected, onEscapePressed, text) => {
527
+ const setupEventListener = (nodeProvider, onNodeSelected, onEscapePressed, text) => {
208
528
  const messageHandler = (event) => {
209
529
  processPostMessage(event, onNodeSelected);
210
530
  };
@@ -228,6 +548,17 @@ const setupEventListener$1 = (nodeProvider, onNodeSelected, onEscapePressed, tex
228
548
  };
229
549
  };
230
550
 
551
+ const toggleClass = (element, className, condition) => {
552
+ if (!element)
553
+ return;
554
+ if (condition) {
555
+ element.classList.add(className);
556
+ }
557
+ else {
558
+ element.classList.remove(className);
559
+ }
560
+ };
561
+
231
562
  const isComponentInstance = (element) => {
232
563
  return element.getAttribute("data-instance") === "true";
233
564
  };
@@ -265,16 +596,6 @@ const createCornerHandles = (group, width, height, isInstance = false, isTextEdi
265
596
  createCornerHandle(group, 0, height, "handle-bottom-left", isInstance, isTextEdit);
266
597
  };
267
598
 
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
599
  const getComponentColor$1 = () => {
279
600
  return getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim() || "oklch(65.6% 0.241 354.308)";
280
601
  };
@@ -303,8 +624,7 @@ const createHighlightFrame = (node, isInstance = false, isTextEdit = false) => {
303
624
  svg.style.height = "100vh";
304
625
  svg.style.pointerEvents = "none";
305
626
  svg.style.zIndex = "500";
306
- const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
307
- const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
627
+ const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
308
628
  svg.setAttribute("width", viewportWidth.toString());
309
629
  svg.setAttribute("height", viewportHeight.toString());
310
630
  const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
@@ -327,13 +647,8 @@ const createHighlightFrame = (node, isInstance = false, isTextEdit = false) => {
327
647
  group.appendChild(rect);
328
648
  createCornerHandles(group, minWidth, height, isInstance, isTextEdit);
329
649
  svg.appendChild(group);
330
- const canvasContainer = getCanvasContainer();
331
- if (canvasContainer) {
332
- canvasContainer.appendChild(svg);
333
- }
334
- else {
335
- document.body.appendChild(svg);
336
- }
650
+ const container = getCanvasContainerOrBody();
651
+ container.appendChild(svg);
337
652
  return svg;
338
653
  };
339
654
 
@@ -370,19 +685,14 @@ const createTagLabel = (node, nodeTools) => {
370
685
  const createToolsContainer = (node, highlightFrame, isInstance = false, isTextEdit = false) => {
371
686
  const nodeTools = document.createElement("div");
372
687
  nodeTools.className = "node-tools";
373
- if (isInstance) {
374
- nodeTools.classList.add("is-instance");
375
- }
376
- if (isTextEdit) {
377
- nodeTools.classList.add("is-text-edit");
378
- }
688
+ toggleClass(nodeTools, "is-instance", isInstance);
689
+ toggleClass(nodeTools, "is-text-edit", isTextEdit);
379
690
  highlightFrame.appendChild(nodeTools);
380
691
  createTagLabel(node, nodeTools);
381
692
  };
382
693
 
383
694
  function getHighlightFrameElement() {
384
- const canvasContainer = getCanvasContainer();
385
- const container = canvasContainer || document.body;
695
+ const container = getCanvasContainerOrBody();
386
696
  return container.querySelector(".highlight-frame-overlay");
387
697
  }
388
698
 
@@ -390,8 +700,8 @@ const highlightNode = (node) => {
390
700
  if (!node)
391
701
  return;
392
702
  const existingHighlightFrame = getHighlightFrameElement();
393
- const canvasContainer = getCanvasContainer();
394
- const existingToolsWrapper = canvasContainer?.querySelector(".highlight-frame-tools-wrapper") || document.body.querySelector(".highlight-frame-tools-wrapper");
703
+ const container = getCanvasContainerOrBody();
704
+ const existingToolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
395
705
  if (existingHighlightFrame) {
396
706
  existingHighlightFrame.remove();
397
707
  }
@@ -410,24 +720,15 @@ const highlightNode = (node) => {
410
720
  // Create tools wrapper using CSS transform (GPU-accelerated)
411
721
  const toolsWrapper = document.createElement("div");
412
722
  toolsWrapper.classList.add("highlight-frame-tools-wrapper");
413
- if (isInstance) {
414
- toolsWrapper.classList.add("is-instance");
415
- }
416
- if (isTextEdit) {
417
- toolsWrapper.classList.add("is-text-edit");
418
- }
723
+ toggleClass(toolsWrapper, "is-instance", isInstance);
724
+ toggleClass(toolsWrapper, "is-text-edit", isTextEdit);
419
725
  toolsWrapper.style.position = "absolute";
420
726
  toolsWrapper.style.transform = `translate(${left}px, ${bottomY}px)`;
421
727
  toolsWrapper.style.transformOrigin = "left center";
422
728
  toolsWrapper.style.pointerEvents = "none";
423
729
  toolsWrapper.style.zIndex = "500";
424
730
  createToolsContainer(node, toolsWrapper, isInstance, isTextEdit);
425
- if (canvasContainer) {
426
- canvasContainer.appendChild(toolsWrapper);
427
- }
428
- else {
429
- document.body.appendChild(toolsWrapper);
430
- }
731
+ container.appendChild(toolsWrapper);
431
732
  };
432
733
 
433
734
  const getComponentColor = () => {
@@ -445,24 +746,13 @@ const refreshHighlightFrame = (node, nodeProvider, canvasName = "canvas") => {
445
746
  const isTextEdit = node.contentEditable === "true";
446
747
  // Update SVG dimensions to match current viewport (handles window resize and ensures coordinate system is correct)
447
748
  // Use clientWidth/Height to match getBoundingClientRect() coordinate system (excludes scrollbars)
448
- const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
449
- const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
749
+ const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
450
750
  frame.setAttribute("width", viewportWidth.toString());
451
751
  frame.setAttribute("height", viewportHeight.toString());
452
752
  // Update instance class
453
- if (isInstance) {
454
- frame.classList.add("is-instance");
455
- }
456
- else {
457
- frame.classList.remove("is-instance");
458
- }
753
+ toggleClass(frame, "is-instance", isInstance);
459
754
  // Update text edit class
460
- if (isTextEdit) {
461
- frame.classList.add("is-text-edit");
462
- }
463
- else {
464
- frame.classList.remove("is-text-edit");
465
- }
755
+ toggleClass(frame, "is-text-edit", isTextEdit);
466
756
  const group = frame.querySelector(".highlight-frame-group");
467
757
  if (!group)
468
758
  return;
@@ -479,8 +769,7 @@ const refreshHighlightFrame = (node, nodeProvider, canvasName = "canvas") => {
479
769
  else {
480
770
  rect.removeAttribute("stroke"); // Use CSS default
481
771
  }
482
- const canvasContainer = getCanvasContainer();
483
- const container = canvasContainer || document.body;
772
+ const container = getCanvasContainerOrBody();
484
773
  const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
485
774
  const nodeTools = toolsWrapper?.querySelector(".node-tools");
486
775
  const zoom = getCanvasWindowValue(["zoom", "current"], canvasName) ?? 1;
@@ -491,36 +780,10 @@ const refreshHighlightFrame = (node, nodeProvider, canvasName = "canvas") => {
491
780
  const minWidth = Math.max(width, 3);
492
781
  const bottomY = top + height;
493
782
  // Update instance classes on tools wrapper and node tools
494
- if (toolsWrapper) {
495
- if (isInstance) {
496
- toolsWrapper.classList.add("is-instance");
497
- }
498
- else {
499
- toolsWrapper.classList.remove("is-instance");
500
- }
501
- // Update text edit class
502
- if (isTextEdit) {
503
- toolsWrapper.classList.add("is-text-edit");
504
- }
505
- else {
506
- toolsWrapper.classList.remove("is-text-edit");
507
- }
508
- }
509
- if (nodeTools) {
510
- if (isInstance) {
511
- nodeTools.classList.add("is-instance");
512
- }
513
- else {
514
- nodeTools.classList.remove("is-instance");
515
- }
516
- // Update text edit class
517
- if (isTextEdit) {
518
- nodeTools.classList.add("is-text-edit");
519
- }
520
- else {
521
- nodeTools.classList.remove("is-text-edit");
522
- }
523
- }
783
+ toggleClass(toolsWrapper, "is-instance", isInstance);
784
+ toggleClass(toolsWrapper, "is-text-edit", isTextEdit);
785
+ toggleClass(nodeTools, "is-instance", isInstance);
786
+ toggleClass(nodeTools, "is-text-edit", isTextEdit);
524
787
  // Batch all DOM writes (single paint pass)
525
788
  // Update group transform to move entire group (rect + handles) at once
526
789
  group.setAttribute("transform", `translate(${left}, ${top})`);
@@ -586,8 +849,7 @@ const updateHighlightFrameVisibility = (node) => {
586
849
  const displayValue = hasHiddenClass ? "none" : "";
587
850
  // Batch DOM writes
588
851
  frame.style.display = displayValue;
589
- const canvasContainer = getCanvasContainer();
590
- const container = canvasContainer || document.body;
852
+ const container = getCanvasContainerOrBody();
591
853
  const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
592
854
  if (toolsWrapper) {
593
855
  toolsWrapper.style.display = displayValue;
@@ -707,6 +969,7 @@ const setupMutationObserver = (node, nodeProvider, canvasName = "canvas") => {
707
969
  // Accumulate mutations instead of replacing
708
970
  pendingMutations.push(...mutations);
709
971
  scheduleProcess();
972
+ console.log("refreshHighlightFrame in mutationObserver");
710
973
  refreshHighlightFrame(node, nodeProvider, canvasName);
711
974
  });
712
975
  return () => {
@@ -844,14 +1107,15 @@ const createNodeTools = (element, canvasName = "canvas") => {
844
1107
  checkNodeExists();
845
1108
  if (!document.contains(node))
846
1109
  return;
1110
+ console.log("refreshHighlightFrame in mutationObserver 2");
847
1111
  refreshHighlightFrame(node, nodeProvider, canvasName);
848
1112
  updateHighlightFrameVisibility(node);
849
1113
  });
850
1114
  mutationObserver.observe(node, {
851
1115
  attributes: true,
852
1116
  characterData: true,
853
- childList: true,
854
- subtree: true,
1117
+ //childList: true,
1118
+ //subtree: true,
855
1119
  });
856
1120
  // Also observe parent node to catch when this node is removed
857
1121
  const parentNode = node.parentElement;
@@ -880,19 +1144,25 @@ const createNodeTools = (element, canvasName = "canvas") => {
880
1144
  if (!document.contains(node))
881
1145
  return; // Exit early if node was removed
882
1146
  refreshHighlightFrame(node, nodeProvider, canvasName);
1147
+ console.log("refreshHighlightFrame in resizeObserver");
883
1148
  updateHighlightFrameVisibility(node);
884
1149
  });
885
1150
  }
886
1151
  selectedNode = node;
887
1152
  sendPostMessage("selectedNodeChanged", node?.getAttribute("data-node-id") ?? null);
888
1153
  highlightNode(node) ?? null;
889
- if (node && nodeProvider) {
890
- updateHighlightFrameVisibility(node);
891
- updateHighlightFrameVisibility(node);
1154
+ if (node) {
1155
+ highlightNode(node);
1156
+ if (nodeProvider) {
1157
+ updateHighlightFrameVisibility(node);
1158
+ }
1159
+ }
1160
+ else {
1161
+ clearHighlightFrame();
892
1162
  }
893
1163
  };
894
1164
  // Setup event listener
895
- const removeListeners = setupEventListener$1(nodeProvider, selectNode, handleEscape, text);
1165
+ const removeListeners = setupEventListener(nodeProvider, selectNode, handleEscape, text);
896
1166
  const cleanup = () => {
897
1167
  removeListeners();
898
1168
  resizeObserver?.disconnect();
@@ -927,10 +1197,14 @@ const createNodeTools = (element, canvasName = "canvas") => {
927
1197
  return nodeTools;
928
1198
  };
929
1199
 
1200
+ const getNodeProvider = () => {
1201
+ return document.querySelector('[data-role="node-provider"]');
1202
+ };
1203
+
930
1204
  const DEFAULT_WIDTH = 400;
931
1205
  const RESIZE_CONFIG = {
932
- minWidth: 320,
933
- maxWidth: 1680,
1206
+ minWidth: 4,
1207
+ maxWidth: 2560,
934
1208
  };
935
1209
  const RESIZE_PRESETS = [
936
1210
  {
@@ -960,27 +1234,6 @@ const RESIZE_PRESETS = [
960
1234
  },
961
1235
  ];
962
1236
 
963
- const setupEventListener = (resizeHandle, startResize, handleResize, stopResize, blurResize) => {
964
- const handleMouseLeave = (event) => {
965
- // Check if mouse is leaving the window/document
966
- if (!event.relatedTarget && (event.target === document || event.target === document.documentElement)) {
967
- blurResize();
968
- }
969
- };
970
- resizeHandle.addEventListener("mousedown", startResize);
971
- document.addEventListener("mousemove", handleResize);
972
- document.addEventListener("mouseup", stopResize);
973
- document.addEventListener("mouseleave", handleMouseLeave);
974
- window.addEventListener("blur", blurResize);
975
- return () => {
976
- resizeHandle.removeEventListener("mousedown", startResize);
977
- document.removeEventListener("mousemove", handleResize);
978
- document.removeEventListener("mouseup", stopResize);
979
- document.removeEventListener("mouseleave", handleMouseLeave);
980
- window.removeEventListener("blur", blurResize);
981
- };
982
- };
983
-
984
1237
  const createResizeHandle = (container) => {
985
1238
  const handle = document.createElement("div");
986
1239
  handle.className = "resize-handle";
@@ -1047,48 +1300,60 @@ const createViewport = (container, initialWidth) => {
1047
1300
  const width = initialWidth ?? DEFAULT_WIDTH;
1048
1301
  container.style.setProperty("--container-width", `${width}px`);
1049
1302
  createResizePresets(resizeHandle, container, updateWidth);
1050
- let isDragging = false;
1303
+ // Track initial values for resize calculation
1051
1304
  let startX = 0;
1052
1305
  let startWidth = 0;
1053
- const startResize = (event) => {
1054
- event.preventDefault();
1055
- event.stopPropagation();
1056
- isDragging = true;
1057
- startX = event.clientX;
1058
- startWidth = container.offsetWidth;
1059
- };
1060
- const handleResize = (event) => {
1061
- if (!isDragging)
1062
- return;
1063
- if (canvas) {
1064
- canvas.style.cursor = "ew-resize";
1065
- }
1066
- const width = calcWidth(event, startX, startWidth);
1067
- updateWidth(container, width);
1068
- };
1069
- const stopResize = (event) => {
1070
- event.preventDefault();
1071
- event.stopPropagation();
1072
- if (canvas) {
1073
- canvas.style.cursor = "default";
1074
- }
1075
- isDragging = false;
1076
- };
1077
- const blurResize = () => {
1078
- if (canvas) {
1079
- canvas.style.cursor = "default";
1306
+ // Handle mouse leave for resize (specific to resize use case)
1307
+ const handleMouseLeave = (event) => {
1308
+ // Check if mouse is leaving the window/document
1309
+ if (!event.relatedTarget && (event.target === document || event.target === document.documentElement)) {
1310
+ if (canvas) {
1311
+ canvas.style.cursor = "default";
1312
+ }
1080
1313
  }
1081
- isDragging = false;
1082
1314
  };
1083
- const removeListeners = setupEventListener(resizeHandle, startResize, handleResize, stopResize, blurResize);
1315
+ const removeDragListeners = createDragHandler(resizeHandle, {
1316
+ onStart: (_event, { startX: dragStartX }) => {
1317
+ startX = dragStartX;
1318
+ startWidth = container.offsetWidth;
1319
+ },
1320
+ onDrag: (event) => {
1321
+ if (canvas) {
1322
+ canvas.style.cursor = "ew-resize";
1323
+ }
1324
+ const width = calcWidth(event, startX, startWidth);
1325
+ updateWidth(container, width);
1326
+ },
1327
+ onStop: () => {
1328
+ if (canvas) {
1329
+ canvas.style.cursor = "default";
1330
+ }
1331
+ },
1332
+ onCancel: () => {
1333
+ if (canvas) {
1334
+ canvas.style.cursor = "default";
1335
+ }
1336
+ },
1337
+ onPreventClick: () => { },
1338
+ });
1339
+ document.addEventListener("mouseleave", handleMouseLeave);
1340
+ refreshViewportLabels();
1084
1341
  const cleanup = () => {
1085
- isDragging = false;
1086
- removeListeners();
1342
+ removeDragListeners();
1343
+ document.removeEventListener("mouseleave", handleMouseLeave);
1087
1344
  resizeHandle.remove();
1345
+ refreshViewportLabels();
1088
1346
  };
1089
1347
  return {
1090
1348
  setWidth: (width) => {
1091
1349
  updateWidth(container, width);
1350
+ refreshViewportLabels();
1351
+ const nodeTools = getNodeTools();
1352
+ const selectedNode = nodeTools?.getSelectedNode?.();
1353
+ const nodeProvider = getNodeProvider();
1354
+ if (selectedNode && nodeProvider) {
1355
+ refreshHighlightFrame(selectedNode, nodeProvider);
1356
+ }
1092
1357
  },
1093
1358
  cleanup,
1094
1359
  };