@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,25 +1,29 @@
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
- function getScreenBounds(element) {
7
- const rect = element.getBoundingClientRect();
6
+ const getNodeTools = () => {
7
+ return window.nodeTools;
8
+ };
9
+
10
+ const getViewportDimensions = () => {
8
11
  return {
9
- top: rect.top,
10
- left: rect.left,
11
- width: rect.width,
12
- height: rect.height,
12
+ width: document.documentElement.clientWidth || window.innerWidth,
13
+ height: document.documentElement.clientHeight || window.innerHeight,
13
14
  };
14
- }
15
+ };
15
16
 
16
17
  const getCanvasContainer = () => {
17
18
  return document.querySelector(".canvas-container");
18
19
  };
19
20
 
20
- const getViewportLabelsOverlay = () => {
21
- const canvasContainer = getCanvasContainer();
22
- const container = canvasContainer || document.body;
21
+ const getCanvasContainerOrBody = () => {
22
+ return getCanvasContainer() || document.body;
23
+ };
24
+
25
+ const getViewportLabelOverlay = () => {
26
+ const container = getCanvasContainerOrBody();
23
27
  // Check if overlay already exists
24
28
  let overlay = container.querySelector(".viewport-labels-overlay");
25
29
  if (!overlay) {
@@ -34,8 +38,7 @@ const getViewportLabelsOverlay = () => {
34
38
  overlay.style.height = "100vh";
35
39
  overlay.style.pointerEvents = "none";
36
40
  overlay.style.zIndex = "500";
37
- const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
38
- const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
41
+ const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
39
42
  overlay.setAttribute("width", viewportWidth.toString());
40
43
  overlay.setAttribute("height", viewportHeight.toString());
41
44
  container.appendChild(overlay);
@@ -45,11 +48,112 @@ const getViewportLabelsOverlay = () => {
45
48
 
46
49
  // Global flag to prevent refreshViewportLabels during drag
47
50
  let globalIsDragging = false;
48
- const isViewportLabelDragging = () => globalIsDragging;
49
- const setViewportLabelDragging = (isDragging) => {
51
+ const isViewportDragging = () => globalIsDragging;
52
+ const setViewportDragging = (isDragging) => {
50
53
  globalIsDragging = isDragging;
51
54
  };
52
55
 
56
+ function getScreenBounds(element) {
57
+ const rect = element.getBoundingClientRect();
58
+ return {
59
+ top: rect.top,
60
+ left: rect.left,
61
+ width: rect.width,
62
+ height: rect.height,
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Creates a reusable drag handler for mouse drag operations
68
+ * @param element - Element that triggers the drag (mousedown listener attached here)
69
+ * @param callbacks - Callbacks for drag lifecycle events
70
+ * @param options - Configuration options
71
+ * @returns Cleanup function to remove all event listeners
72
+ */
73
+ function createDragHandler(element, callbacks, options = {}) {
74
+ const { preventDefault = true, stopPropagation = true } = options;
75
+ const state = {
76
+ isDragging: false,
77
+ hasDragged: false,
78
+ startX: 0,
79
+ startY: 0,
80
+ };
81
+ const startDrag = (event) => {
82
+ if (preventDefault) {
83
+ event.preventDefault();
84
+ }
85
+ if (stopPropagation) {
86
+ event.stopPropagation();
87
+ }
88
+ state.isDragging = true;
89
+ state.hasDragged = false;
90
+ state.startX = event.clientX;
91
+ state.startY = event.clientY;
92
+ callbacks.onStart?.(event, state);
93
+ };
94
+ const handleDrag = (event) => {
95
+ if (!state.isDragging)
96
+ return;
97
+ const deltaX = event.clientX - state.startX;
98
+ const deltaY = event.clientY - state.startY;
99
+ state.hasDragged = true;
100
+ callbacks.onDrag(event, {
101
+ ...state,
102
+ deltaX,
103
+ deltaY,
104
+ });
105
+ };
106
+ const stopDrag = (event) => {
107
+ if (!state.isDragging)
108
+ return;
109
+ if (preventDefault) {
110
+ event.preventDefault();
111
+ }
112
+ if (stopPropagation) {
113
+ event.stopPropagation();
114
+ }
115
+ state.isDragging = false;
116
+ callbacks.onStop?.(event, state);
117
+ };
118
+ const cancelDrag = () => {
119
+ if (!state.isDragging)
120
+ return;
121
+ state.isDragging = false;
122
+ callbacks.onCancel?.(state);
123
+ };
124
+ const preventClick = (event) => {
125
+ if (preventDefault) {
126
+ event.preventDefault();
127
+ }
128
+ if (stopPropagation) {
129
+ event.stopPropagation();
130
+ }
131
+ callbacks.onPreventClick?.(event, state);
132
+ // Reset hasDragged flag after handling the click
133
+ if (state.hasDragged) {
134
+ state.hasDragged = false;
135
+ }
136
+ };
137
+ // Attach event listeners
138
+ element.addEventListener("mousedown", startDrag);
139
+ if (callbacks.onPreventClick) {
140
+ element.addEventListener("click", preventClick);
141
+ }
142
+ document.addEventListener("mousemove", handleDrag);
143
+ document.addEventListener("mouseup", stopDrag);
144
+ window.addEventListener("blur", cancelDrag);
145
+ // Return cleanup function
146
+ return () => {
147
+ element.removeEventListener("mousedown", startDrag);
148
+ if (callbacks.onPreventClick) {
149
+ element.removeEventListener("click", preventClick);
150
+ }
151
+ document.removeEventListener("mousemove", handleDrag);
152
+ document.removeEventListener("mouseup", stopDrag);
153
+ window.removeEventListener("blur", cancelDrag);
154
+ };
155
+ }
156
+
53
157
  function sendPostMessage(action, data) {
54
158
  window.parent.postMessage({
55
159
  source: "node-edit-utils",
@@ -59,22 +163,23 @@ function sendPostMessage(action, data) {
59
163
  }, "*");
60
164
  }
61
165
 
166
+ const parseTransform3d = (transform) => {
167
+ const match = transform.match(/translate3d\((-?\d+(?:\.\d+)?)px,\s*(-?\d+(?:\.\d+)?)px,\s*(-?\d+(?:\.\d+)?)px\)/);
168
+ return match ? { x: parseFloat(match[1]), y: parseFloat(match[2]) } : { x: 0, y: 0 };
169
+ };
170
+ const parseTransform2d = (transform) => {
171
+ const match = transform?.match(/translate\((-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)\)/);
172
+ return match ? { x: parseFloat(match[1]), y: parseFloat(match[2]) } : { x: 0, y: 0 };
173
+ };
174
+
62
175
  const getLabelPosition = (labelGroup) => {
63
176
  const transform = labelGroup.getAttribute("transform");
64
- const match = transform?.match(/translate\((-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)\)/);
65
- if (match) {
66
- return { x: parseFloat(match[1]), y: parseFloat(match[2]) };
67
- }
68
- return { x: 0, y: 0 };
177
+ return parseTransform2d(transform);
69
178
  };
70
179
 
71
180
  const getTransformValues = (element) => {
72
181
  const style = element.style.transform;
73
- const match = style.match(/translate3d\((-?\d+(?:\.\d+)?)px,\s*(-?\d+(?:\.\d+)?)px,\s*(-?\d+(?:\.\d+)?)px\)/);
74
- if (match) {
75
- return { x: parseFloat(match[1]), y: parseFloat(match[2]) };
76
- }
77
- return { x: 0, y: 0 };
182
+ return parseTransform3d(style);
78
183
  };
79
184
 
80
185
  const getZoomValue = () => {
@@ -82,119 +187,100 @@ const getZoomValue = () => {
82
187
  return zoomValue ? parseFloat(zoomValue) : 1;
83
188
  };
84
189
 
85
- const setupViewportLabelDrag = (labelElement, viewportElement, viewportName) => {
86
- let isDragging = false;
87
- let startX = 0;
88
- let startY = 0;
89
- let initialTransform = { x: 0, y: 0 };
90
- let initialLabelPosition = { x: 0, y: 0 };
190
+ /**
191
+ * Selects the first child node inside a viewport element.
192
+ * Skips the resize-handle element if present.
193
+ */
194
+ const selectFirstViewportNode = (viewportElement) => {
195
+ const firstChild = Array.from(viewportElement.children).find((child) => !child.classList.contains("resize-handle"));
196
+ if (firstChild) {
197
+ const nodeTools = getNodeTools();
198
+ if (nodeTools?.selectNode) {
199
+ const wasAlreadySelected = nodeTools.getSelectedNode() === firstChild;
200
+ nodeTools.selectNode(firstChild);
201
+ // Always emit postMessage when selecting via viewport label click,
202
+ // even if the node was already selected (to match behavior of direct node clicks)
203
+ if (wasAlreadySelected) {
204
+ sendPostMessage("selectedNodeChanged", firstChild.getAttribute("data-node-id") ?? null);
205
+ }
206
+ }
207
+ }
208
+ };
209
+
210
+ const setupViewportDrag = (labelElement, viewportElement, viewportName) => {
91
211
  // Get the parent group element that contains the label
92
212
  const labelGroup = labelElement.parentElement;
93
- const startDrag = (event) => {
94
- event.preventDefault();
95
- event.stopPropagation();
96
- isDragging = true;
97
- setViewportLabelDragging(true);
98
- startX = event.clientX;
99
- startY = event.clientY;
100
- initialTransform = getTransformValues(viewportElement);
101
- initialLabelPosition = getLabelPosition(labelGroup);
102
- };
103
- const handleDrag = (event) => {
104
- if (!isDragging)
105
- return;
106
- const zoom = getZoomValue();
107
- // Calculate mouse delta
108
- const rawDeltaX = event.clientX - startX;
109
- const rawDeltaY = event.clientY - startY;
110
- // Adjust delta for zoom level
111
- const deltaX = rawDeltaX / zoom;
112
- const deltaY = rawDeltaY / zoom;
113
- const newX = initialTransform.x + deltaX;
114
- const newY = initialTransform.y + deltaY;
115
- // Update label position with raw delta (labels are in screen space)
116
- const newLabelX = initialLabelPosition.x + rawDeltaX;
117
- const newLabelY = initialLabelPosition.y + rawDeltaY;
118
- labelGroup.setAttribute("transform", `translate(${newLabelX}, ${newLabelY})`);
119
- // Update viewport position with zoom-adjusted delta
120
- viewportElement.style.transform = `translate3d(${newX}px, ${newY}px, 0)`;
121
- };
122
- const stopDrag = (event) => {
123
- if (!isDragging)
124
- return;
125
- event.preventDefault();
126
- event.stopPropagation();
127
- isDragging = false;
128
- setViewportLabelDragging(false);
129
- const finalTransform = getTransformValues(viewportElement);
130
- // Trigger refresh after drag completes to update highlight frame and labels
131
- // biome-ignore lint/suspicious/noExplicitAny: global window extension
132
- const nodeTools = window.nodeTools;
133
- if (nodeTools?.refreshHighlightFrame) {
134
- nodeTools.refreshHighlightFrame();
135
- }
136
- // Notify parent about the new position
137
- sendPostMessage("viewport-position-changed", {
138
- viewportName,
139
- x: finalTransform.x,
140
- y: finalTransform.y,
141
- });
142
- };
143
- const cancelDrag = () => {
144
- isDragging = false;
145
- setViewportLabelDragging(false);
146
- };
147
- // Attach event listeners
148
- labelElement.addEventListener("mousedown", startDrag);
149
- document.addEventListener("mousemove", handleDrag);
150
- document.addEventListener("mouseup", stopDrag);
151
- window.addEventListener("blur", cancelDrag);
152
- // Return cleanup function
153
- return () => {
154
- labelElement.removeEventListener("mousedown", startDrag);
155
- document.removeEventListener("mousemove", handleDrag);
156
- document.removeEventListener("mouseup", stopDrag);
157
- window.removeEventListener("blur", cancelDrag);
158
- };
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
+ setViewportDragging(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
+ setViewportDragging(false);
240
+ const finalTransform = getTransformValues(viewportElement);
241
+ // If it was a drag, handle drag completion
242
+ if (hasDragged) {
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
+ }
249
+ // Always notify parent about the new position on drag stop
250
+ sendPostMessage("viewport-position-changed", {
251
+ viewportName,
252
+ x: finalTransform.x,
253
+ y: finalTransform.y,
254
+ });
255
+ },
256
+ onCancel: () => {
257
+ setViewportDragging(false);
258
+ },
259
+ onPreventClick: () => { },
260
+ });
159
261
  };
160
262
 
161
- // Store cleanup functions for drag listeners
162
- const dragCleanupFunctions = new Map();
163
- const refreshViewportLabels = () => {
164
- // Skip refresh if a viewport label is being dragged
165
- if (isViewportLabelDragging()) {
263
+ /**
264
+ * Refreshes (updates) a viewport label for a single viewport element.
265
+ * Creates the label if it doesn't exist, or updates its position if it does.
266
+ * Similar to refreshHighlightFrame - updates existing elements rather than recreating.
267
+ *
268
+ * @param viewportElement - The viewport element to refresh the label for
269
+ */
270
+ const refreshViewportLabel = (viewportElement) => {
271
+ const viewportName = viewportElement.getAttribute("data-viewport-name");
272
+ if (!viewportName) {
166
273
  return;
167
274
  }
168
- const overlay = getViewportLabelsOverlay();
169
- // Update SVG dimensions to match current viewport
170
- const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
171
- const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
172
- overlay.setAttribute("width", viewportWidth.toString());
173
- overlay.setAttribute("height", viewportHeight.toString());
174
- // Find all viewports with names
175
- const viewports = document.querySelectorAll(".viewport[data-viewport-name]");
176
- // Clean up existing drag listeners
177
- dragCleanupFunctions.forEach((cleanup) => {
178
- cleanup();
179
- });
180
- dragCleanupFunctions.clear();
181
- // Remove existing label groups
182
- const existingGroups = overlay.querySelectorAll(".viewport-label-group");
183
- existingGroups.forEach((group) => {
184
- group.remove();
185
- });
186
- // Create/update labels for each viewport
187
- viewports.forEach((viewport) => {
188
- const viewportElement = viewport;
189
- const viewportName = viewportElement.getAttribute("data-viewport-name");
190
- if (!viewportName)
191
- return;
192
- const bounds = getScreenBounds(viewportElement);
275
+ const overlay = getViewportLabelOverlay();
276
+ const bounds = getScreenBounds(viewportElement);
277
+ // Get existing label group or create if it doesn't exist
278
+ let group = overlay.querySelector(`[data-viewport-name="${viewportName}"]`);
279
+ if (!group) {
193
280
  // Create group for this viewport label
194
- const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
281
+ group = document.createElementNS("http://www.w3.org/2000/svg", "g");
195
282
  group.classList.add("viewport-label-group");
196
283
  group.setAttribute("data-viewport-name", viewportName);
197
- group.setAttribute("transform", `translate(${bounds.left}, ${bounds.top})`);
198
284
  // Create text element
199
285
  const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
200
286
  text.classList.add("viewport-label-text");
@@ -205,14 +291,42 @@ const refreshViewportLabels = () => {
205
291
  text.textContent = viewportName;
206
292
  group.appendChild(text);
207
293
  overlay.appendChild(group);
208
- // Setup drag functionality for this label
209
- const cleanup = setupViewportLabelDrag(text, viewportElement, viewportName);
210
- dragCleanupFunctions.set(viewportName, cleanup);
294
+ // Setup drag functionality only when creating new label
295
+ setupViewportDrag(text, viewportElement, viewportName);
296
+ }
297
+ // Update label position (this is the refresh part - updates existing label)
298
+ group.setAttribute("transform", `translate(${bounds.left}, ${bounds.top})`);
299
+ };
300
+
301
+ const refreshViewportLabels = () => {
302
+ // Skip refresh if a viewport label is being dragged
303
+ if (isViewportDragging()) {
304
+ return;
305
+ }
306
+ const overlay = getViewportLabelOverlay();
307
+ // Update SVG dimensions to match current viewport (handles window resize and ensures coordinate system is correct)
308
+ const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
309
+ overlay.setAttribute("width", viewportWidth.toString());
310
+ overlay.setAttribute("height", viewportHeight.toString());
311
+ // Find all viewports with names and refresh each label
312
+ const viewports = document.querySelectorAll(".viewport[data-viewport-name]");
313
+ viewports.forEach((viewport) => {
314
+ refreshViewportLabel(viewport);
315
+ });
316
+ // Remove labels for viewports that no longer exist
317
+ const existingGroups = overlay.querySelectorAll(".viewport-label-group");
318
+ existingGroups.forEach((group) => {
319
+ const viewportName = group.getAttribute("data-viewport-name");
320
+ if (viewportName) {
321
+ const viewportExists = Array.from(viewports).some((viewport) => viewport.getAttribute("data-viewport-name") === viewportName);
322
+ if (!viewportExists) {
323
+ group.remove();
324
+ }
325
+ }
211
326
  });
212
327
  };
213
328
 
214
329
  const getCanvasWindowValue = (path, canvasName = "canvas") => {
215
- // biome-ignore lint/suspicious/noExplicitAny: global window extension
216
330
  const canvas = window[canvasName];
217
331
  return path.reduce((obj, prop) => obj?.[prop], canvas);
218
332
  };
@@ -235,8 +349,7 @@ function createCanvasObserver(canvasName = "canvas") {
235
349
  const observer = new MutationObserver(() => {
236
350
  applyCanvasState(canvasName);
237
351
  // Refresh highlight frame (throttled via withRAFThrottle)
238
- // biome-ignore lint/suspicious/noExplicitAny: global window extension
239
- const nodeTools = window.nodeTools;
352
+ const nodeTools = getNodeTools();
240
353
  if (nodeTools?.refreshHighlightFrame) {
241
354
  nodeTools.refreshHighlightFrame();
242
355
  }
@@ -275,7 +388,6 @@ const connectResizeObserver = (element, handler) => {
275
388
 
276
389
  const bindToWindow = (key, value) => {
277
390
  if (typeof window !== "undefined") {
278
- // biome-ignore lint/suspicious/noExplicitAny: global window extension requires flexibility
279
391
  window[key] = value;
280
392
  }
281
393
  };
@@ -301,8 +413,7 @@ const processPostMessage = (event, onNodeSelected) => {
301
413
  };
302
414
 
303
415
  const clearHighlightFrame = () => {
304
- const canvasContainer = getCanvasContainer();
305
- const container = canvasContainer || document.body;
416
+ const container = getCanvasContainerOrBody();
306
417
  const frame = container.querySelector(".highlight-frame-overlay");
307
418
  if (frame) {
308
419
  frame.remove();
@@ -431,7 +542,7 @@ const handleNodeClick = (event, nodeProvider, text, onNodeSelected) => {
431
542
  onNodeSelected(selectedNode);
432
543
  };
433
544
 
434
- const setupEventListener$1 = (nodeProvider, onNodeSelected, onEscapePressed, text) => {
545
+ const setupEventListener = (nodeProvider, onNodeSelected, onEscapePressed, text) => {
435
546
  const messageHandler = (event) => {
436
547
  processPostMessage(event, onNodeSelected);
437
548
  };
@@ -455,6 +566,17 @@ const setupEventListener$1 = (nodeProvider, onNodeSelected, onEscapePressed, tex
455
566
  };
456
567
  };
457
568
 
569
+ const toggleClass = (element, className, condition) => {
570
+ if (!element)
571
+ return;
572
+ if (condition) {
573
+ element.classList.add(className);
574
+ }
575
+ else {
576
+ element.classList.remove(className);
577
+ }
578
+ };
579
+
458
580
  const isComponentInstance = (element) => {
459
581
  return element.getAttribute("data-instance") === "true";
460
582
  };
@@ -520,8 +642,7 @@ const createHighlightFrame = (node, isInstance = false, isTextEdit = false) => {
520
642
  svg.style.height = "100vh";
521
643
  svg.style.pointerEvents = "none";
522
644
  svg.style.zIndex = "500";
523
- const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
524
- const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
645
+ const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
525
646
  svg.setAttribute("width", viewportWidth.toString());
526
647
  svg.setAttribute("height", viewportHeight.toString());
527
648
  const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
@@ -544,13 +665,8 @@ const createHighlightFrame = (node, isInstance = false, isTextEdit = false) => {
544
665
  group.appendChild(rect);
545
666
  createCornerHandles(group, minWidth, height, isInstance, isTextEdit);
546
667
  svg.appendChild(group);
547
- const canvasContainer = getCanvasContainer();
548
- if (canvasContainer) {
549
- canvasContainer.appendChild(svg);
550
- }
551
- else {
552
- document.body.appendChild(svg);
553
- }
668
+ const container = getCanvasContainerOrBody();
669
+ container.appendChild(svg);
554
670
  return svg;
555
671
  };
556
672
 
@@ -587,19 +703,14 @@ const createTagLabel = (node, nodeTools) => {
587
703
  const createToolsContainer = (node, highlightFrame, isInstance = false, isTextEdit = false) => {
588
704
  const nodeTools = document.createElement("div");
589
705
  nodeTools.className = "node-tools";
590
- if (isInstance) {
591
- nodeTools.classList.add("is-instance");
592
- }
593
- if (isTextEdit) {
594
- nodeTools.classList.add("is-text-edit");
595
- }
706
+ toggleClass(nodeTools, "is-instance", isInstance);
707
+ toggleClass(nodeTools, "is-text-edit", isTextEdit);
596
708
  highlightFrame.appendChild(nodeTools);
597
709
  createTagLabel(node, nodeTools);
598
710
  };
599
711
 
600
712
  function getHighlightFrameElement() {
601
- const canvasContainer = getCanvasContainer();
602
- const container = canvasContainer || document.body;
713
+ const container = getCanvasContainerOrBody();
603
714
  return container.querySelector(".highlight-frame-overlay");
604
715
  }
605
716
 
@@ -607,8 +718,8 @@ const highlightNode = (node) => {
607
718
  if (!node)
608
719
  return;
609
720
  const existingHighlightFrame = getHighlightFrameElement();
610
- const canvasContainer = getCanvasContainer();
611
- const existingToolsWrapper = canvasContainer?.querySelector(".highlight-frame-tools-wrapper") || document.body.querySelector(".highlight-frame-tools-wrapper");
721
+ const container = getCanvasContainerOrBody();
722
+ const existingToolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
612
723
  if (existingHighlightFrame) {
613
724
  existingHighlightFrame.remove();
614
725
  }
@@ -627,24 +738,15 @@ const highlightNode = (node) => {
627
738
  // Create tools wrapper using CSS transform (GPU-accelerated)
628
739
  const toolsWrapper = document.createElement("div");
629
740
  toolsWrapper.classList.add("highlight-frame-tools-wrapper");
630
- if (isInstance) {
631
- toolsWrapper.classList.add("is-instance");
632
- }
633
- if (isTextEdit) {
634
- toolsWrapper.classList.add("is-text-edit");
635
- }
741
+ toggleClass(toolsWrapper, "is-instance", isInstance);
742
+ toggleClass(toolsWrapper, "is-text-edit", isTextEdit);
636
743
  toolsWrapper.style.position = "absolute";
637
744
  toolsWrapper.style.transform = `translate(${left}px, ${bottomY}px)`;
638
745
  toolsWrapper.style.transformOrigin = "left center";
639
746
  toolsWrapper.style.pointerEvents = "none";
640
747
  toolsWrapper.style.zIndex = "500";
641
748
  createToolsContainer(node, toolsWrapper, isInstance, isTextEdit);
642
- if (canvasContainer) {
643
- canvasContainer.appendChild(toolsWrapper);
644
- }
645
- else {
646
- document.body.appendChild(toolsWrapper);
647
- }
749
+ container.appendChild(toolsWrapper);
648
750
  };
649
751
 
650
752
  const getComponentColor = () => {
@@ -662,24 +764,13 @@ const refreshHighlightFrame = (node, nodeProvider, canvasName = "canvas") => {
662
764
  const isTextEdit = node.contentEditable === "true";
663
765
  // Update SVG dimensions to match current viewport (handles window resize and ensures coordinate system is correct)
664
766
  // Use clientWidth/Height to match getBoundingClientRect() coordinate system (excludes scrollbars)
665
- const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
666
- const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
767
+ const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
667
768
  frame.setAttribute("width", viewportWidth.toString());
668
769
  frame.setAttribute("height", viewportHeight.toString());
669
770
  // Update instance class
670
- if (isInstance) {
671
- frame.classList.add("is-instance");
672
- }
673
- else {
674
- frame.classList.remove("is-instance");
675
- }
771
+ toggleClass(frame, "is-instance", isInstance);
676
772
  // Update text edit class
677
- if (isTextEdit) {
678
- frame.classList.add("is-text-edit");
679
- }
680
- else {
681
- frame.classList.remove("is-text-edit");
682
- }
773
+ toggleClass(frame, "is-text-edit", isTextEdit);
683
774
  const group = frame.querySelector(".highlight-frame-group");
684
775
  if (!group)
685
776
  return;
@@ -696,8 +787,7 @@ const refreshHighlightFrame = (node, nodeProvider, canvasName = "canvas") => {
696
787
  else {
697
788
  rect.removeAttribute("stroke"); // Use CSS default
698
789
  }
699
- const canvasContainer = getCanvasContainer();
700
- const container = canvasContainer || document.body;
790
+ const container = getCanvasContainerOrBody();
701
791
  const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
702
792
  const nodeTools = toolsWrapper?.querySelector(".node-tools");
703
793
  const zoom = getCanvasWindowValue(["zoom", "current"], canvasName) ?? 1;
@@ -708,36 +798,10 @@ const refreshHighlightFrame = (node, nodeProvider, canvasName = "canvas") => {
708
798
  const minWidth = Math.max(width, 3);
709
799
  const bottomY = top + height;
710
800
  // Update instance classes on tools wrapper and node tools
711
- if (toolsWrapper) {
712
- if (isInstance) {
713
- toolsWrapper.classList.add("is-instance");
714
- }
715
- else {
716
- toolsWrapper.classList.remove("is-instance");
717
- }
718
- // Update text edit class
719
- if (isTextEdit) {
720
- toolsWrapper.classList.add("is-text-edit");
721
- }
722
- else {
723
- toolsWrapper.classList.remove("is-text-edit");
724
- }
725
- }
726
- if (nodeTools) {
727
- if (isInstance) {
728
- nodeTools.classList.add("is-instance");
729
- }
730
- else {
731
- nodeTools.classList.remove("is-instance");
732
- }
733
- // Update text edit class
734
- if (isTextEdit) {
735
- nodeTools.classList.add("is-text-edit");
736
- }
737
- else {
738
- nodeTools.classList.remove("is-text-edit");
739
- }
740
- }
801
+ toggleClass(toolsWrapper, "is-instance", isInstance);
802
+ toggleClass(toolsWrapper, "is-text-edit", isTextEdit);
803
+ toggleClass(nodeTools, "is-instance", isInstance);
804
+ toggleClass(nodeTools, "is-text-edit", isTextEdit);
741
805
  // Batch all DOM writes (single paint pass)
742
806
  // Update group transform to move entire group (rect + handles) at once
743
807
  group.setAttribute("transform", `translate(${left}, ${top})`);
@@ -803,8 +867,7 @@ const updateHighlightFrameVisibility = (node) => {
803
867
  const displayValue = hasHiddenClass ? "none" : "";
804
868
  // Batch DOM writes
805
869
  frame.style.display = displayValue;
806
- const canvasContainer = getCanvasContainer();
807
- const container = canvasContainer || document.body;
870
+ const container = getCanvasContainerOrBody();
808
871
  const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
809
872
  if (toolsWrapper) {
810
873
  toolsWrapper.style.display = displayValue;
@@ -1110,7 +1173,6 @@ const createNodeTools = (element, canvasName = "canvas") => {
1110
1173
  highlightNode(node);
1111
1174
  if (nodeProvider) {
1112
1175
  updateHighlightFrameVisibility(node);
1113
- updateHighlightFrameVisibility(node);
1114
1176
  }
1115
1177
  }
1116
1178
  else {
@@ -1118,7 +1180,7 @@ const createNodeTools = (element, canvasName = "canvas") => {
1118
1180
  }
1119
1181
  };
1120
1182
  // Setup event listener
1121
- const removeListeners = setupEventListener$1(nodeProvider, selectNode, handleEscape, text);
1183
+ const removeListeners = setupEventListener(nodeProvider, selectNode, handleEscape, text);
1122
1184
  const cleanup = () => {
1123
1185
  removeListeners();
1124
1186
  resizeObserver?.disconnect();
@@ -1153,6 +1215,10 @@ const createNodeTools = (element, canvasName = "canvas") => {
1153
1215
  return nodeTools;
1154
1216
  };
1155
1217
 
1218
+ const getNodeProvider = () => {
1219
+ return document.querySelector('[data-role="node-provider"]');
1220
+ };
1221
+
1156
1222
  const DEFAULT_WIDTH = 400;
1157
1223
  const RESIZE_CONFIG = {
1158
1224
  minWidth: 4,
@@ -1186,25 +1252,20 @@ const RESIZE_PRESETS = [
1186
1252
  },
1187
1253
  ];
1188
1254
 
1189
- const setupEventListener = (resizeHandle, startResize, handleResize, stopResize, blurResize) => {
1190
- const handleMouseLeave = (event) => {
1191
- // Check if mouse is leaving the window/document
1192
- if (!event.relatedTarget && (event.target === document || event.target === document.documentElement)) {
1193
- blurResize();
1194
- }
1195
- };
1196
- resizeHandle.addEventListener("mousedown", startResize);
1197
- document.addEventListener("mousemove", handleResize);
1198
- document.addEventListener("mouseup", stopResize);
1199
- document.addEventListener("mouseleave", handleMouseLeave);
1200
- window.addEventListener("blur", blurResize);
1201
- return () => {
1202
- resizeHandle.removeEventListener("mousedown", startResize);
1203
- document.removeEventListener("mousemove", handleResize);
1204
- document.removeEventListener("mouseup", stopResize);
1205
- document.removeEventListener("mouseleave", handleMouseLeave);
1206
- window.removeEventListener("blur", blurResize);
1207
- };
1255
+ /**
1256
+ * Removes a viewport label for a single viewport element.
1257
+ * @param viewportElement - The viewport element to remove the label for
1258
+ */
1259
+ const removeViewportLabel = (viewportElement) => {
1260
+ const viewportName = viewportElement.getAttribute("data-viewport-name");
1261
+ if (!viewportName) {
1262
+ return;
1263
+ }
1264
+ const overlay = getViewportLabelOverlay();
1265
+ const labelGroup = overlay.querySelector(`[data-viewport-name="${viewportName}"]`);
1266
+ if (labelGroup) {
1267
+ labelGroup.remove();
1268
+ }
1208
1269
  };
1209
1270
 
1210
1271
  const createResizeHandle = (container) => {
@@ -1273,58 +1334,59 @@ const createViewport = (container, initialWidth) => {
1273
1334
  const width = initialWidth ?? DEFAULT_WIDTH;
1274
1335
  container.style.setProperty("--container-width", `${width}px`);
1275
1336
  createResizePresets(resizeHandle, container, updateWidth);
1276
- let isDragging = false;
1337
+ // Track initial values for resize calculation
1277
1338
  let startX = 0;
1278
1339
  let startWidth = 0;
1279
- const startResize = (event) => {
1280
- event.preventDefault();
1281
- event.stopPropagation();
1282
- isDragging = true;
1283
- startX = event.clientX;
1284
- startWidth = container.offsetWidth;
1285
- };
1286
- const handleResize = (event) => {
1287
- if (!isDragging)
1288
- return;
1289
- if (canvas) {
1290
- canvas.style.cursor = "ew-resize";
1291
- }
1292
- const width = calcWidth(event, startX, startWidth);
1293
- updateWidth(container, width);
1294
- };
1295
- const stopResize = (event) => {
1296
- event.preventDefault();
1297
- event.stopPropagation();
1298
- if (canvas) {
1299
- canvas.style.cursor = "default";
1300
- }
1301
- isDragging = false;
1302
- };
1303
- const blurResize = () => {
1304
- if (canvas) {
1305
- canvas.style.cursor = "default";
1340
+ // Handle mouse leave for resize (specific to resize use case)
1341
+ const handleMouseLeave = (event) => {
1342
+ // Check if mouse is leaving the window/document
1343
+ if (!event.relatedTarget && (event.target === document || event.target === document.documentElement)) {
1344
+ if (canvas) {
1345
+ canvas.style.cursor = "default";
1346
+ }
1306
1347
  }
1307
- isDragging = false;
1308
1348
  };
1309
- const removeListeners = setupEventListener(resizeHandle, startResize, handleResize, stopResize, blurResize);
1310
- // Refresh viewport labels when viewport is created
1311
- refreshViewportLabels();
1349
+ const removeDragListeners = createDragHandler(resizeHandle, {
1350
+ onStart: (_event, { startX: dragStartX }) => {
1351
+ startX = dragStartX;
1352
+ startWidth = container.offsetWidth;
1353
+ },
1354
+ onDrag: (event) => {
1355
+ if (canvas) {
1356
+ canvas.style.cursor = "ew-resize";
1357
+ }
1358
+ const width = calcWidth(event, startX, startWidth);
1359
+ updateWidth(container, width);
1360
+ },
1361
+ onStop: () => {
1362
+ if (canvas) {
1363
+ canvas.style.cursor = "default";
1364
+ }
1365
+ },
1366
+ onCancel: () => {
1367
+ if (canvas) {
1368
+ canvas.style.cursor = "default";
1369
+ }
1370
+ },
1371
+ onPreventClick: () => { },
1372
+ });
1373
+ document.addEventListener("mouseleave", handleMouseLeave);
1374
+ // Create/refresh the label for this viewport
1375
+ refreshViewportLabel(container);
1312
1376
  const cleanup = () => {
1313
- isDragging = false;
1314
- removeListeners();
1377
+ removeDragListeners();
1378
+ document.removeEventListener("mouseleave", handleMouseLeave);
1315
1379
  resizeHandle.remove();
1316
- // Refresh labels after cleanup to remove this viewport's label if needed
1317
- refreshViewportLabels();
1380
+ // Remove the label for this viewport
1381
+ removeViewportLabel(container);
1318
1382
  };
1319
1383
  return {
1320
1384
  setWidth: (width) => {
1321
1385
  updateWidth(container, width);
1322
- refreshViewportLabels();
1323
- // Refresh highlight frame when viewport width changes to update node positions
1324
- // biome-ignore lint/suspicious/noExplicitAny: global window extension
1325
- const nodeTools = window.nodeTools;
1386
+ refreshViewportLabel(container);
1387
+ const nodeTools = getNodeTools();
1326
1388
  const selectedNode = nodeTools?.getSelectedNode?.();
1327
- const nodeProvider = document.querySelector('[data-role="node-provider"]');
1389
+ const nodeProvider = getNodeProvider();
1328
1390
  if (selectedNode && nodeProvider) {
1329
1391
  refreshHighlightFrame(selectedNode, nodeProvider);
1330
1392
  }