@node-edit-utils/core 2.3.2 → 2.3.4

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 (136) hide show
  1. package/dist/lib/canvas/helpers/getCanvasContainerOrBody.d.ts +1 -0
  2. package/dist/lib/canvas/helpers/getCanvasWindowValue.d.ts +1 -1
  3. package/dist/lib/helpers/adjustForZoom.d.ts +1 -0
  4. package/dist/lib/helpers/createDragHandler.d.ts +69 -0
  5. package/dist/lib/helpers/getNodeProvider.d.ts +1 -0
  6. package/dist/lib/helpers/getNodeTools.d.ts +2 -0
  7. package/dist/lib/helpers/getViewportDimensions.d.ts +4 -0
  8. package/dist/lib/helpers/index.d.ts +9 -1
  9. package/dist/lib/helpers/parseTransform.d.ts +8 -0
  10. package/dist/lib/helpers/toggleClass.d.ts +1 -0
  11. package/dist/lib/viewport/label/getViewportLabelOverlay.d.ts +1 -0
  12. package/dist/lib/viewport/label/helpers/selectFirstViewportNode.d.ts +5 -0
  13. package/dist/lib/viewport/label/index.d.ts +5 -3
  14. package/dist/lib/viewport/label/isViewportDragging.d.ts +2 -0
  15. package/dist/lib/viewport/label/refreshViewportLabel.d.ts +8 -0
  16. package/dist/lib/viewport/label/removeViewportLabel.d.ts +5 -0
  17. package/dist/lib/viewport/label/setupViewportDrag.d.ts +1 -0
  18. package/dist/node-edit-utils.cjs.js +342 -280
  19. package/dist/node-edit-utils.esm.js +342 -280
  20. package/dist/node-edit-utils.umd.js +342 -280
  21. package/dist/node-edit-utils.umd.min.js +1 -1
  22. package/dist/styles.css +1 -1
  23. package/package.json +7 -2
  24. package/src/lib/canvas/createCanvasObserver.test.ts +242 -0
  25. package/src/lib/canvas/createCanvasObserver.ts +2 -2
  26. package/src/lib/canvas/disableCanvasKeyboard.test.ts +53 -0
  27. package/src/lib/canvas/disableCanvasKeyboard.ts +1 -1
  28. package/src/lib/canvas/disableCanvasTextMode.test.ts +53 -0
  29. package/src/lib/canvas/disableCanvasTextMode.ts +1 -1
  30. package/src/lib/canvas/enableCanvasKeyboard.test.ts +53 -0
  31. package/src/lib/canvas/enableCanvasKeyboard.ts +1 -1
  32. package/src/lib/canvas/enableCanvasTextMode.test.ts +53 -0
  33. package/src/lib/canvas/enableCanvasTextMode.ts +1 -1
  34. package/src/lib/canvas/helpers/applyCanvasState.test.ts +119 -0
  35. package/src/lib/canvas/helpers/applyCanvasState.ts +1 -1
  36. package/src/lib/canvas/helpers/getCanvasContainer.test.ts +62 -0
  37. package/src/lib/canvas/helpers/getCanvasContainerOrBody.test.ts +51 -0
  38. package/src/lib/canvas/helpers/getCanvasContainerOrBody.ts +6 -0
  39. package/src/lib/canvas/helpers/getCanvasWindowValue.test.ts +116 -0
  40. package/src/lib/canvas/helpers/getCanvasWindowValue.ts +2 -3
  41. package/src/lib/helpers/adjustForZoom.test.ts +65 -0
  42. package/src/lib/helpers/adjustForZoom.ts +4 -0
  43. package/src/lib/helpers/createDragHandler.test.ts +325 -0
  44. package/src/lib/helpers/createDragHandler.ts +171 -0
  45. package/src/lib/helpers/getNodeProvider.test.ts +71 -0
  46. package/src/lib/helpers/getNodeProvider.ts +4 -0
  47. package/src/lib/helpers/getNodeTools.test.ts +50 -0
  48. package/src/lib/helpers/getNodeTools.ts +6 -0
  49. package/src/lib/helpers/getViewportDimensions.test.ts +93 -0
  50. package/src/lib/helpers/getViewportDimensions.ts +7 -0
  51. package/src/lib/helpers/index.ts +9 -1
  52. package/src/lib/helpers/observer/connectMutationObserver.test.ts +127 -0
  53. package/src/lib/helpers/observer/connectResizeObserver.test.ts +147 -0
  54. package/src/lib/helpers/parseTransform.test.ts +117 -0
  55. package/src/lib/helpers/parseTransform.ts +9 -0
  56. package/src/lib/helpers/toggleClass.test.ts +71 -0
  57. package/src/lib/helpers/toggleClass.ts +9 -0
  58. package/src/lib/helpers/withRAF.test.ts +439 -0
  59. package/src/lib/node-tools/createNodeTools.test.ts +373 -0
  60. package/src/lib/node-tools/createNodeTools.ts +0 -1
  61. package/src/lib/node-tools/events/click/handleNodeClick.test.ts +109 -0
  62. package/src/lib/node-tools/events/setupEventListener.test.ts +136 -0
  63. package/src/lib/node-tools/highlight/clearHighlightFrame.test.ts +88 -0
  64. package/src/lib/node-tools/highlight/clearHighlightFrame.ts +2 -3
  65. package/src/lib/node-tools/highlight/createCornerHandles.test.ts +150 -0
  66. package/src/lib/node-tools/highlight/createHighlightFrame.test.ts +237 -0
  67. package/src/lib/node-tools/highlight/createHighlightFrame.ts +5 -9
  68. package/src/lib/node-tools/highlight/createTagLabel.test.ts +135 -0
  69. package/src/lib/node-tools/highlight/createToolsContainer.test.ts +97 -0
  70. package/src/lib/node-tools/highlight/createToolsContainer.ts +3 -6
  71. package/src/lib/node-tools/highlight/helpers/getElementBounds.test.ts +158 -0
  72. package/src/lib/node-tools/highlight/helpers/getElementBounds.ts +6 -5
  73. package/src/lib/node-tools/highlight/helpers/getHighlightFrameElement.test.ts +78 -0
  74. package/src/lib/node-tools/highlight/helpers/getHighlightFrameElement.ts +2 -3
  75. package/src/lib/node-tools/highlight/helpers/getScreenBounds.test.ts +133 -0
  76. package/src/lib/node-tools/highlight/highlightNode.test.ts +213 -0
  77. package/src/lib/node-tools/highlight/highlightNode.ts +7 -15
  78. package/src/lib/node-tools/highlight/refreshHighlightFrame.test.ts +323 -0
  79. package/src/lib/node-tools/highlight/refreshHighlightFrame.ts +12 -42
  80. package/src/lib/node-tools/highlight/updateHighlightFrameVisibility.test.ts +110 -0
  81. package/src/lib/node-tools/highlight/updateHighlightFrameVisibility.ts +2 -3
  82. package/src/lib/node-tools/select/helpers/getElementsFromPoint.test.ts +109 -0
  83. package/src/lib/node-tools/select/helpers/isInsideComponent.test.ts +81 -0
  84. package/src/lib/node-tools/select/helpers/isInsideViewport.test.ts +82 -0
  85. package/src/lib/node-tools/select/helpers/targetSameCandidates.test.ts +81 -0
  86. package/src/lib/node-tools/select/selectNode.test.ts +238 -0
  87. package/src/lib/node-tools/text/events/setupKeydownHandler.test.ts +91 -0
  88. package/src/lib/node-tools/text/events/setupMutationObserver.test.ts +213 -0
  89. package/src/lib/node-tools/text/events/setupNodeListeners.test.ts +133 -0
  90. package/src/lib/node-tools/text/helpers/enterTextEditMode.test.ts +50 -0
  91. package/src/lib/node-tools/text/helpers/handleTextChange.test.ts +201 -0
  92. package/src/lib/node-tools/text/helpers/hasTextContent.test.ts +101 -0
  93. package/src/lib/node-tools/text/helpers/insertLineBreak.test.ts +96 -0
  94. package/src/lib/node-tools/text/helpers/makeNodeEditable.test.ts +56 -0
  95. package/src/lib/node-tools/text/helpers/makeNodeNonEditable.test.ts +57 -0
  96. package/src/lib/node-tools/text/helpers/shouldEnterTextEditMode.test.ts +61 -0
  97. package/src/lib/node-tools/text/nodeText.test.ts +233 -0
  98. package/src/lib/post-message/processPostMessage.test.ts +218 -0
  99. package/src/lib/post-message/sendPostMessage.test.ts +120 -0
  100. package/src/lib/styles/styles.css +3 -3
  101. package/src/lib/viewport/createViewport.test.ts +267 -0
  102. package/src/lib/viewport/createViewport.ts +51 -51
  103. package/src/lib/viewport/events/setupEventListener.test.ts +103 -0
  104. package/src/lib/viewport/label/getViewportLabelOverlay.test.ts +77 -0
  105. package/src/lib/viewport/label/{getViewportLabelsOverlay.ts → getViewportLabelOverlay.ts} +6 -6
  106. package/src/lib/viewport/label/helpers/getLabelPosition.test.ts +51 -0
  107. package/src/lib/viewport/label/helpers/getLabelPosition.ts +3 -5
  108. package/src/lib/viewport/label/helpers/getTransformValues.test.ts +59 -0
  109. package/src/lib/viewport/label/helpers/getTransformValues.ts +3 -5
  110. package/src/lib/viewport/label/helpers/getZoomValue.test.ts +53 -0
  111. package/src/lib/viewport/label/helpers/selectFirstViewportNode.test.ts +105 -0
  112. package/src/lib/viewport/label/helpers/selectFirstViewportNode.ts +26 -0
  113. package/src/lib/viewport/label/index.ts +5 -3
  114. package/src/lib/viewport/label/isViewportDragging.test.ts +35 -0
  115. package/src/lib/viewport/label/isViewportDragging.ts +9 -0
  116. package/src/lib/viewport/label/refreshViewportLabel.test.ts +105 -0
  117. package/src/lib/viewport/label/refreshViewportLabel.ts +50 -0
  118. package/src/lib/viewport/label/refreshViewportLabels.test.ts +107 -0
  119. package/src/lib/viewport/label/refreshViewportLabels.ts +19 -52
  120. package/src/lib/viewport/label/removeViewportLabel.test.ts +67 -0
  121. package/src/lib/viewport/label/removeViewportLabel.ts +20 -0
  122. package/src/lib/viewport/label/setupViewportDrag.test.ts +249 -0
  123. package/src/lib/viewport/label/setupViewportDrag.ts +70 -0
  124. package/src/lib/viewport/resize/createResizeHandle.test.ts +37 -0
  125. package/src/lib/viewport/resize/createResizePresets.test.ts +75 -0
  126. package/src/lib/viewport/resize/updateActivePreset.test.ts +92 -0
  127. package/src/lib/viewport/width/calcConstrainedWidth.test.ts +47 -0
  128. package/src/lib/viewport/width/calcWidth.test.ts +68 -0
  129. package/src/lib/viewport/width/updateWidth.test.ts +78 -0
  130. package/src/lib/window/bindToWindow.test.ts +166 -0
  131. package/src/lib/window/bindToWindow.ts +1 -2
  132. package/dist/lib/viewport/label/getViewportLabelsOverlay.d.ts +0 -1
  133. package/dist/lib/viewport/label/isViewportLabelDragging.d.ts +0 -2
  134. package/dist/lib/viewport/label/setupViewportLabelDrag.d.ts +0 -1
  135. package/src/lib/viewport/label/isViewportLabelDragging.ts +0 -9
  136. package/src/lib/viewport/label/setupViewportLabelDrag.ts +0 -98
@@ -1,27 +1,31 @@
1
1
  /**
2
2
  * Markup Canvas
3
3
  * High-performance markup canvas with zoom and pan capabilities
4
- * @version 2.3.2
4
+ * @version 2.3.4
5
5
  */
6
6
  'use strict';
7
7
 
8
- function getScreenBounds(element) {
9
- const rect = element.getBoundingClientRect();
8
+ const getNodeTools = () => {
9
+ return window.nodeTools;
10
+ };
11
+
12
+ const getViewportDimensions = () => {
10
13
  return {
11
- top: rect.top,
12
- left: rect.left,
13
- width: rect.width,
14
- height: rect.height,
14
+ width: document.documentElement.clientWidth || window.innerWidth,
15
+ height: document.documentElement.clientHeight || window.innerHeight,
15
16
  };
16
- }
17
+ };
17
18
 
18
19
  const getCanvasContainer = () => {
19
20
  return document.querySelector(".canvas-container");
20
21
  };
21
22
 
22
- const getViewportLabelsOverlay = () => {
23
- const canvasContainer = getCanvasContainer();
24
- const container = canvasContainer || document.body;
23
+ const getCanvasContainerOrBody = () => {
24
+ return getCanvasContainer() || document.body;
25
+ };
26
+
27
+ const getViewportLabelOverlay = () => {
28
+ const container = getCanvasContainerOrBody();
25
29
  // Check if overlay already exists
26
30
  let overlay = container.querySelector(".viewport-labels-overlay");
27
31
  if (!overlay) {
@@ -36,8 +40,7 @@ const getViewportLabelsOverlay = () => {
36
40
  overlay.style.height = "100vh";
37
41
  overlay.style.pointerEvents = "none";
38
42
  overlay.style.zIndex = "500";
39
- const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
40
- const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
43
+ const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
41
44
  overlay.setAttribute("width", viewportWidth.toString());
42
45
  overlay.setAttribute("height", viewportHeight.toString());
43
46
  container.appendChild(overlay);
@@ -47,11 +50,112 @@ const getViewportLabelsOverlay = () => {
47
50
 
48
51
  // Global flag to prevent refreshViewportLabels during drag
49
52
  let globalIsDragging = false;
50
- const isViewportLabelDragging = () => globalIsDragging;
51
- const setViewportLabelDragging = (isDragging) => {
53
+ const isViewportDragging = () => globalIsDragging;
54
+ const setViewportDragging = (isDragging) => {
52
55
  globalIsDragging = isDragging;
53
56
  };
54
57
 
58
+ function getScreenBounds(element) {
59
+ const rect = element.getBoundingClientRect();
60
+ return {
61
+ top: rect.top,
62
+ left: rect.left,
63
+ width: rect.width,
64
+ height: rect.height,
65
+ };
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
+
55
159
  function sendPostMessage(action, data) {
56
160
  window.parent.postMessage({
57
161
  source: "node-edit-utils",
@@ -61,22 +165,23 @@ function sendPostMessage(action, data) {
61
165
  }, "*");
62
166
  }
63
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
+
64
177
  const getLabelPosition = (labelGroup) => {
65
178
  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 };
179
+ return parseTransform2d(transform);
71
180
  };
72
181
 
73
182
  const getTransformValues = (element) => {
74
183
  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 };
184
+ return parseTransform3d(style);
80
185
  };
81
186
 
82
187
  const getZoomValue = () => {
@@ -84,119 +189,100 @@ const getZoomValue = () => {
84
189
  return zoomValue ? parseFloat(zoomValue) : 1;
85
190
  };
86
191
 
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 };
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
+ const wasAlreadySelected = nodeTools.getSelectedNode() === firstChild;
202
+ nodeTools.selectNode(firstChild);
203
+ // Always emit postMessage when selecting via viewport label click,
204
+ // even if the node was already selected (to match behavior of direct node clicks)
205
+ if (wasAlreadySelected) {
206
+ sendPostMessage("selectedNodeChanged", firstChild.getAttribute("data-node-id") ?? null);
207
+ }
208
+ }
209
+ }
210
+ };
211
+
212
+ const setupViewportDrag = (labelElement, viewportElement, viewportName) => {
93
213
  // Get the parent group element that contains the label
94
214
  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
- };
215
+ // Track initial positions for calculations
216
+ let initialTransform = { x: 0, y: 0 };
217
+ let initialLabelPosition = { x: 0, y: 0 };
218
+ return createDragHandler(labelElement, {
219
+ onStart: () => {
220
+ setViewportDragging(true);
221
+ initialTransform = getTransformValues(viewportElement);
222
+ initialLabelPosition = getLabelPosition(labelGroup);
223
+ selectFirstViewportNode(viewportElement);
224
+ },
225
+ onDrag: (_event, { deltaX, deltaY }) => {
226
+ const zoom = getZoomValue();
227
+ // Adjust delta for zoom level (viewport is in canvas space)
228
+ const deltaXZoomed = deltaX / zoom;
229
+ const deltaYZoomed = deltaY / zoom;
230
+ // Calculate new positions
231
+ const newX = initialTransform.x + deltaXZoomed;
232
+ const newY = initialTransform.y + deltaYZoomed;
233
+ // Update label position with raw delta (labels are in screen space)
234
+ const newLabelX = initialLabelPosition.x + deltaX;
235
+ const newLabelY = initialLabelPosition.y + deltaY;
236
+ labelGroup.setAttribute("transform", `translate(${newLabelX}, ${newLabelY})`);
237
+ // Update viewport position with zoom-adjusted delta
238
+ viewportElement.style.transform = `translate3d(${newX}px, ${newY}px, 0)`;
239
+ },
240
+ onStop: (_event, { hasDragged }) => {
241
+ setViewportDragging(false);
242
+ const finalTransform = getTransformValues(viewportElement);
243
+ // If it was a drag, handle drag completion
244
+ if (hasDragged) {
245
+ // Trigger refresh after drag completes to update highlight frame and labels
246
+ const nodeTools = getNodeTools();
247
+ if (nodeTools?.refreshHighlightFrame) {
248
+ nodeTools.refreshHighlightFrame();
249
+ }
250
+ }
251
+ // Always notify parent about the new position on drag stop
252
+ sendPostMessage("viewport-position-changed", {
253
+ viewportName,
254
+ x: finalTransform.x,
255
+ y: finalTransform.y,
256
+ });
257
+ },
258
+ onCancel: () => {
259
+ setViewportDragging(false);
260
+ },
261
+ onPreventClick: () => { },
262
+ });
161
263
  };
162
264
 
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()) {
265
+ /**
266
+ * Refreshes (updates) a viewport label for a single viewport element.
267
+ * Creates the label if it doesn't exist, or updates its position if it does.
268
+ * Similar to refreshHighlightFrame - updates existing elements rather than recreating.
269
+ *
270
+ * @param viewportElement - The viewport element to refresh the label for
271
+ */
272
+ const refreshViewportLabel = (viewportElement) => {
273
+ const viewportName = viewportElement.getAttribute("data-viewport-name");
274
+ if (!viewportName) {
168
275
  return;
169
276
  }
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);
277
+ const overlay = getViewportLabelOverlay();
278
+ const bounds = getScreenBounds(viewportElement);
279
+ // Get existing label group or create if it doesn't exist
280
+ let group = overlay.querySelector(`[data-viewport-name="${viewportName}"]`);
281
+ if (!group) {
195
282
  // Create group for this viewport label
196
- const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
283
+ group = document.createElementNS("http://www.w3.org/2000/svg", "g");
197
284
  group.classList.add("viewport-label-group");
198
285
  group.setAttribute("data-viewport-name", viewportName);
199
- group.setAttribute("transform", `translate(${bounds.left}, ${bounds.top})`);
200
286
  // Create text element
201
287
  const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
202
288
  text.classList.add("viewport-label-text");
@@ -207,14 +293,42 @@ const refreshViewportLabels = () => {
207
293
  text.textContent = viewportName;
208
294
  group.appendChild(text);
209
295
  overlay.appendChild(group);
210
- // Setup drag functionality for this label
211
- const cleanup = setupViewportLabelDrag(text, viewportElement, viewportName);
212
- dragCleanupFunctions.set(viewportName, cleanup);
296
+ // Setup drag functionality only when creating new label
297
+ setupViewportDrag(text, viewportElement, viewportName);
298
+ }
299
+ // Update label position (this is the refresh part - updates existing label)
300
+ group.setAttribute("transform", `translate(${bounds.left}, ${bounds.top})`);
301
+ };
302
+
303
+ const refreshViewportLabels = () => {
304
+ // Skip refresh if a viewport label is being dragged
305
+ if (isViewportDragging()) {
306
+ return;
307
+ }
308
+ const overlay = getViewportLabelOverlay();
309
+ // Update SVG dimensions to match current viewport (handles window resize and ensures coordinate system is correct)
310
+ const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
311
+ overlay.setAttribute("width", viewportWidth.toString());
312
+ overlay.setAttribute("height", viewportHeight.toString());
313
+ // Find all viewports with names and refresh each label
314
+ const viewports = document.querySelectorAll(".viewport[data-viewport-name]");
315
+ viewports.forEach((viewport) => {
316
+ refreshViewportLabel(viewport);
317
+ });
318
+ // Remove labels for viewports that no longer exist
319
+ const existingGroups = overlay.querySelectorAll(".viewport-label-group");
320
+ existingGroups.forEach((group) => {
321
+ const viewportName = group.getAttribute("data-viewport-name");
322
+ if (viewportName) {
323
+ const viewportExists = Array.from(viewports).some((viewport) => viewport.getAttribute("data-viewport-name") === viewportName);
324
+ if (!viewportExists) {
325
+ group.remove();
326
+ }
327
+ }
213
328
  });
214
329
  };
215
330
 
216
331
  const getCanvasWindowValue = (path, canvasName = "canvas") => {
217
- // biome-ignore lint/suspicious/noExplicitAny: global window extension
218
332
  const canvas = window[canvasName];
219
333
  return path.reduce((obj, prop) => obj?.[prop], canvas);
220
334
  };
@@ -237,8 +351,7 @@ function createCanvasObserver(canvasName = "canvas") {
237
351
  const observer = new MutationObserver(() => {
238
352
  applyCanvasState(canvasName);
239
353
  // Refresh highlight frame (throttled via withRAFThrottle)
240
- // biome-ignore lint/suspicious/noExplicitAny: global window extension
241
- const nodeTools = window.nodeTools;
354
+ const nodeTools = getNodeTools();
242
355
  if (nodeTools?.refreshHighlightFrame) {
243
356
  nodeTools.refreshHighlightFrame();
244
357
  }
@@ -277,7 +390,6 @@ const connectResizeObserver = (element, handler) => {
277
390
 
278
391
  const bindToWindow = (key, value) => {
279
392
  if (typeof window !== "undefined") {
280
- // biome-ignore lint/suspicious/noExplicitAny: global window extension requires flexibility
281
393
  window[key] = value;
282
394
  }
283
395
  };
@@ -303,8 +415,7 @@ const processPostMessage = (event, onNodeSelected) => {
303
415
  };
304
416
 
305
417
  const clearHighlightFrame = () => {
306
- const canvasContainer = getCanvasContainer();
307
- const container = canvasContainer || document.body;
418
+ const container = getCanvasContainerOrBody();
308
419
  const frame = container.querySelector(".highlight-frame-overlay");
309
420
  if (frame) {
310
421
  frame.remove();
@@ -433,7 +544,7 @@ const handleNodeClick = (event, nodeProvider, text, onNodeSelected) => {
433
544
  onNodeSelected(selectedNode);
434
545
  };
435
546
 
436
- const setupEventListener$1 = (nodeProvider, onNodeSelected, onEscapePressed, text) => {
547
+ const setupEventListener = (nodeProvider, onNodeSelected, onEscapePressed, text) => {
437
548
  const messageHandler = (event) => {
438
549
  processPostMessage(event, onNodeSelected);
439
550
  };
@@ -457,6 +568,17 @@ const setupEventListener$1 = (nodeProvider, onNodeSelected, onEscapePressed, tex
457
568
  };
458
569
  };
459
570
 
571
+ const toggleClass = (element, className, condition) => {
572
+ if (!element)
573
+ return;
574
+ if (condition) {
575
+ element.classList.add(className);
576
+ }
577
+ else {
578
+ element.classList.remove(className);
579
+ }
580
+ };
581
+
460
582
  const isComponentInstance = (element) => {
461
583
  return element.getAttribute("data-instance") === "true";
462
584
  };
@@ -522,8 +644,7 @@ const createHighlightFrame = (node, isInstance = false, isTextEdit = false) => {
522
644
  svg.style.height = "100vh";
523
645
  svg.style.pointerEvents = "none";
524
646
  svg.style.zIndex = "500";
525
- const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
526
- const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
647
+ const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
527
648
  svg.setAttribute("width", viewportWidth.toString());
528
649
  svg.setAttribute("height", viewportHeight.toString());
529
650
  const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
@@ -546,13 +667,8 @@ const createHighlightFrame = (node, isInstance = false, isTextEdit = false) => {
546
667
  group.appendChild(rect);
547
668
  createCornerHandles(group, minWidth, height, isInstance, isTextEdit);
548
669
  svg.appendChild(group);
549
- const canvasContainer = getCanvasContainer();
550
- if (canvasContainer) {
551
- canvasContainer.appendChild(svg);
552
- }
553
- else {
554
- document.body.appendChild(svg);
555
- }
670
+ const container = getCanvasContainerOrBody();
671
+ container.appendChild(svg);
556
672
  return svg;
557
673
  };
558
674
 
@@ -589,19 +705,14 @@ const createTagLabel = (node, nodeTools) => {
589
705
  const createToolsContainer = (node, highlightFrame, isInstance = false, isTextEdit = false) => {
590
706
  const nodeTools = document.createElement("div");
591
707
  nodeTools.className = "node-tools";
592
- if (isInstance) {
593
- nodeTools.classList.add("is-instance");
594
- }
595
- if (isTextEdit) {
596
- nodeTools.classList.add("is-text-edit");
597
- }
708
+ toggleClass(nodeTools, "is-instance", isInstance);
709
+ toggleClass(nodeTools, "is-text-edit", isTextEdit);
598
710
  highlightFrame.appendChild(nodeTools);
599
711
  createTagLabel(node, nodeTools);
600
712
  };
601
713
 
602
714
  function getHighlightFrameElement() {
603
- const canvasContainer = getCanvasContainer();
604
- const container = canvasContainer || document.body;
715
+ const container = getCanvasContainerOrBody();
605
716
  return container.querySelector(".highlight-frame-overlay");
606
717
  }
607
718
 
@@ -609,8 +720,8 @@ const highlightNode = (node) => {
609
720
  if (!node)
610
721
  return;
611
722
  const existingHighlightFrame = getHighlightFrameElement();
612
- const canvasContainer = getCanvasContainer();
613
- const existingToolsWrapper = canvasContainer?.querySelector(".highlight-frame-tools-wrapper") || document.body.querySelector(".highlight-frame-tools-wrapper");
723
+ const container = getCanvasContainerOrBody();
724
+ const existingToolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
614
725
  if (existingHighlightFrame) {
615
726
  existingHighlightFrame.remove();
616
727
  }
@@ -629,24 +740,15 @@ const highlightNode = (node) => {
629
740
  // Create tools wrapper using CSS transform (GPU-accelerated)
630
741
  const toolsWrapper = document.createElement("div");
631
742
  toolsWrapper.classList.add("highlight-frame-tools-wrapper");
632
- if (isInstance) {
633
- toolsWrapper.classList.add("is-instance");
634
- }
635
- if (isTextEdit) {
636
- toolsWrapper.classList.add("is-text-edit");
637
- }
743
+ toggleClass(toolsWrapper, "is-instance", isInstance);
744
+ toggleClass(toolsWrapper, "is-text-edit", isTextEdit);
638
745
  toolsWrapper.style.position = "absolute";
639
746
  toolsWrapper.style.transform = `translate(${left}px, ${bottomY}px)`;
640
747
  toolsWrapper.style.transformOrigin = "left center";
641
748
  toolsWrapper.style.pointerEvents = "none";
642
749
  toolsWrapper.style.zIndex = "500";
643
750
  createToolsContainer(node, toolsWrapper, isInstance, isTextEdit);
644
- if (canvasContainer) {
645
- canvasContainer.appendChild(toolsWrapper);
646
- }
647
- else {
648
- document.body.appendChild(toolsWrapper);
649
- }
751
+ container.appendChild(toolsWrapper);
650
752
  };
651
753
 
652
754
  const getComponentColor = () => {
@@ -664,24 +766,13 @@ const refreshHighlightFrame = (node, nodeProvider, canvasName = "canvas") => {
664
766
  const isTextEdit = node.contentEditable === "true";
665
767
  // Update SVG dimensions to match current viewport (handles window resize and ensures coordinate system is correct)
666
768
  // Use clientWidth/Height to match getBoundingClientRect() coordinate system (excludes scrollbars)
667
- const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
668
- const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
769
+ const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
669
770
  frame.setAttribute("width", viewportWidth.toString());
670
771
  frame.setAttribute("height", viewportHeight.toString());
671
772
  // Update instance class
672
- if (isInstance) {
673
- frame.classList.add("is-instance");
674
- }
675
- else {
676
- frame.classList.remove("is-instance");
677
- }
773
+ toggleClass(frame, "is-instance", isInstance);
678
774
  // Update text edit class
679
- if (isTextEdit) {
680
- frame.classList.add("is-text-edit");
681
- }
682
- else {
683
- frame.classList.remove("is-text-edit");
684
- }
775
+ toggleClass(frame, "is-text-edit", isTextEdit);
685
776
  const group = frame.querySelector(".highlight-frame-group");
686
777
  if (!group)
687
778
  return;
@@ -698,8 +789,7 @@ const refreshHighlightFrame = (node, nodeProvider, canvasName = "canvas") => {
698
789
  else {
699
790
  rect.removeAttribute("stroke"); // Use CSS default
700
791
  }
701
- const canvasContainer = getCanvasContainer();
702
- const container = canvasContainer || document.body;
792
+ const container = getCanvasContainerOrBody();
703
793
  const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
704
794
  const nodeTools = toolsWrapper?.querySelector(".node-tools");
705
795
  const zoom = getCanvasWindowValue(["zoom", "current"], canvasName) ?? 1;
@@ -710,36 +800,10 @@ const refreshHighlightFrame = (node, nodeProvider, canvasName = "canvas") => {
710
800
  const minWidth = Math.max(width, 3);
711
801
  const bottomY = top + height;
712
802
  // Update instance classes on tools wrapper and node tools
713
- if (toolsWrapper) {
714
- if (isInstance) {
715
- toolsWrapper.classList.add("is-instance");
716
- }
717
- else {
718
- toolsWrapper.classList.remove("is-instance");
719
- }
720
- // Update text edit class
721
- if (isTextEdit) {
722
- toolsWrapper.classList.add("is-text-edit");
723
- }
724
- else {
725
- toolsWrapper.classList.remove("is-text-edit");
726
- }
727
- }
728
- if (nodeTools) {
729
- if (isInstance) {
730
- nodeTools.classList.add("is-instance");
731
- }
732
- else {
733
- nodeTools.classList.remove("is-instance");
734
- }
735
- // Update text edit class
736
- if (isTextEdit) {
737
- nodeTools.classList.add("is-text-edit");
738
- }
739
- else {
740
- nodeTools.classList.remove("is-text-edit");
741
- }
742
- }
803
+ toggleClass(toolsWrapper, "is-instance", isInstance);
804
+ toggleClass(toolsWrapper, "is-text-edit", isTextEdit);
805
+ toggleClass(nodeTools, "is-instance", isInstance);
806
+ toggleClass(nodeTools, "is-text-edit", isTextEdit);
743
807
  // Batch all DOM writes (single paint pass)
744
808
  // Update group transform to move entire group (rect + handles) at once
745
809
  group.setAttribute("transform", `translate(${left}, ${top})`);
@@ -805,8 +869,7 @@ const updateHighlightFrameVisibility = (node) => {
805
869
  const displayValue = hasHiddenClass ? "none" : "";
806
870
  // Batch DOM writes
807
871
  frame.style.display = displayValue;
808
- const canvasContainer = getCanvasContainer();
809
- const container = canvasContainer || document.body;
872
+ const container = getCanvasContainerOrBody();
810
873
  const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
811
874
  if (toolsWrapper) {
812
875
  toolsWrapper.style.display = displayValue;
@@ -1112,7 +1175,6 @@ const createNodeTools = (element, canvasName = "canvas") => {
1112
1175
  highlightNode(node);
1113
1176
  if (nodeProvider) {
1114
1177
  updateHighlightFrameVisibility(node);
1115
- updateHighlightFrameVisibility(node);
1116
1178
  }
1117
1179
  }
1118
1180
  else {
@@ -1120,7 +1182,7 @@ const createNodeTools = (element, canvasName = "canvas") => {
1120
1182
  }
1121
1183
  };
1122
1184
  // Setup event listener
1123
- const removeListeners = setupEventListener$1(nodeProvider, selectNode, handleEscape, text);
1185
+ const removeListeners = setupEventListener(nodeProvider, selectNode, handleEscape, text);
1124
1186
  const cleanup = () => {
1125
1187
  removeListeners();
1126
1188
  resizeObserver?.disconnect();
@@ -1155,6 +1217,10 @@ const createNodeTools = (element, canvasName = "canvas") => {
1155
1217
  return nodeTools;
1156
1218
  };
1157
1219
 
1220
+ const getNodeProvider = () => {
1221
+ return document.querySelector('[data-role="node-provider"]');
1222
+ };
1223
+
1158
1224
  const DEFAULT_WIDTH = 400;
1159
1225
  const RESIZE_CONFIG = {
1160
1226
  minWidth: 4,
@@ -1188,25 +1254,20 @@ const RESIZE_PRESETS = [
1188
1254
  },
1189
1255
  ];
1190
1256
 
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
- };
1198
- resizeHandle.addEventListener("mousedown", startResize);
1199
- document.addEventListener("mousemove", handleResize);
1200
- document.addEventListener("mouseup", stopResize);
1201
- document.addEventListener("mouseleave", handleMouseLeave);
1202
- window.addEventListener("blur", blurResize);
1203
- return () => {
1204
- resizeHandle.removeEventListener("mousedown", startResize);
1205
- document.removeEventListener("mousemove", handleResize);
1206
- document.removeEventListener("mouseup", stopResize);
1207
- document.removeEventListener("mouseleave", handleMouseLeave);
1208
- window.removeEventListener("blur", blurResize);
1209
- };
1257
+ /**
1258
+ * Removes a viewport label for a single viewport element.
1259
+ * @param viewportElement - The viewport element to remove the label for
1260
+ */
1261
+ const removeViewportLabel = (viewportElement) => {
1262
+ const viewportName = viewportElement.getAttribute("data-viewport-name");
1263
+ if (!viewportName) {
1264
+ return;
1265
+ }
1266
+ const overlay = getViewportLabelOverlay();
1267
+ const labelGroup = overlay.querySelector(`[data-viewport-name="${viewportName}"]`);
1268
+ if (labelGroup) {
1269
+ labelGroup.remove();
1270
+ }
1210
1271
  };
1211
1272
 
1212
1273
  const createResizeHandle = (container) => {
@@ -1275,58 +1336,59 @@ const createViewport = (container, initialWidth) => {
1275
1336
  const width = initialWidth ?? DEFAULT_WIDTH;
1276
1337
  container.style.setProperty("--container-width", `${width}px`);
1277
1338
  createResizePresets(resizeHandle, container, updateWidth);
1278
- let isDragging = false;
1339
+ // Track initial values for resize calculation
1279
1340
  let startX = 0;
1280
1341
  let startWidth = 0;
1281
- const startResize = (event) => {
1282
- event.preventDefault();
1283
- event.stopPropagation();
1284
- isDragging = true;
1285
- startX = event.clientX;
1286
- startWidth = container.offsetWidth;
1287
- };
1288
- const handleResize = (event) => {
1289
- if (!isDragging)
1290
- return;
1291
- if (canvas) {
1292
- canvas.style.cursor = "ew-resize";
1293
- }
1294
- const width = calcWidth(event, startX, startWidth);
1295
- updateWidth(container, width);
1296
- };
1297
- const stopResize = (event) => {
1298
- event.preventDefault();
1299
- event.stopPropagation();
1300
- if (canvas) {
1301
- canvas.style.cursor = "default";
1302
- }
1303
- isDragging = false;
1304
- };
1305
- const blurResize = () => {
1306
- if (canvas) {
1307
- canvas.style.cursor = "default";
1342
+ // Handle mouse leave for resize (specific to resize use case)
1343
+ const handleMouseLeave = (event) => {
1344
+ // Check if mouse is leaving the window/document
1345
+ if (!event.relatedTarget && (event.target === document || event.target === document.documentElement)) {
1346
+ if (canvas) {
1347
+ canvas.style.cursor = "default";
1348
+ }
1308
1349
  }
1309
- isDragging = false;
1310
1350
  };
1311
- const removeListeners = setupEventListener(resizeHandle, startResize, handleResize, stopResize, blurResize);
1312
- // Refresh viewport labels when viewport is created
1313
- refreshViewportLabels();
1351
+ const removeDragListeners = createDragHandler(resizeHandle, {
1352
+ onStart: (_event, { startX: dragStartX }) => {
1353
+ startX = dragStartX;
1354
+ startWidth = container.offsetWidth;
1355
+ },
1356
+ onDrag: (event) => {
1357
+ if (canvas) {
1358
+ canvas.style.cursor = "ew-resize";
1359
+ }
1360
+ const width = calcWidth(event, startX, startWidth);
1361
+ updateWidth(container, width);
1362
+ },
1363
+ onStop: () => {
1364
+ if (canvas) {
1365
+ canvas.style.cursor = "default";
1366
+ }
1367
+ },
1368
+ onCancel: () => {
1369
+ if (canvas) {
1370
+ canvas.style.cursor = "default";
1371
+ }
1372
+ },
1373
+ onPreventClick: () => { },
1374
+ });
1375
+ document.addEventListener("mouseleave", handleMouseLeave);
1376
+ // Create/refresh the label for this viewport
1377
+ refreshViewportLabel(container);
1314
1378
  const cleanup = () => {
1315
- isDragging = false;
1316
- removeListeners();
1379
+ removeDragListeners();
1380
+ document.removeEventListener("mouseleave", handleMouseLeave);
1317
1381
  resizeHandle.remove();
1318
- // Refresh labels after cleanup to remove this viewport's label if needed
1319
- refreshViewportLabels();
1382
+ // Remove the label for this viewport
1383
+ removeViewportLabel(container);
1320
1384
  };
1321
1385
  return {
1322
1386
  setWidth: (width) => {
1323
1387
  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;
1388
+ refreshViewportLabel(container);
1389
+ const nodeTools = getNodeTools();
1328
1390
  const selectedNode = nodeTools?.getSelectedNode?.();
1329
- const nodeProvider = document.querySelector('[data-role="node-provider"]');
1391
+ const nodeProvider = getNodeProvider();
1330
1392
  if (selectedNode && nodeProvider) {
1331
1393
  refreshHighlightFrame(selectedNode, nodeProvider);
1332
1394
  }