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