@node-edit-utils/core 2.3.1 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/lib/node-tools/select/helpers/isInsideViewport.d.ts +1 -0
  2. package/dist/lib/viewport/constants.d.ts +2 -2
  3. package/dist/lib/viewport/label/getViewportLabelsOverlay.d.ts +1 -0
  4. package/dist/lib/viewport/label/helpers/getLabelPosition.d.ts +4 -0
  5. package/dist/lib/viewport/label/helpers/getTransformValues.d.ts +4 -0
  6. package/dist/lib/viewport/label/helpers/getZoomValue.d.ts +1 -0
  7. package/dist/lib/viewport/label/index.d.ts +4 -0
  8. package/dist/lib/viewport/label/isViewportLabelDragging.d.ts +2 -0
  9. package/dist/lib/viewport/label/refreshViewportLabels.d.ts +1 -0
  10. package/dist/lib/viewport/label/setupViewportLabelDrag.d.ts +1 -0
  11. package/dist/node-edit-utils.cjs.js +273 -32
  12. package/dist/node-edit-utils.esm.js +273 -32
  13. package/dist/node-edit-utils.umd.js +273 -32
  14. package/dist/node-edit-utils.umd.min.js +1 -1
  15. package/dist/styles.css +1 -1
  16. package/package.json +1 -1
  17. package/src/lib/canvas/createCanvasObserver.ts +14 -0
  18. package/src/lib/node-tools/createNodeTools.ts +12 -5
  19. package/src/lib/node-tools/select/helpers/isInsideViewport.ts +19 -0
  20. package/src/lib/node-tools/select/selectNode.ts +13 -1
  21. package/src/lib/node-tools/text/events/setupMutationObserver.ts +1 -0
  22. package/src/lib/styles/styles.css +48 -1
  23. package/src/lib/viewport/constants.ts +2 -2
  24. package/src/lib/viewport/createViewport.ts +18 -0
  25. package/src/lib/viewport/label/getViewportLabelsOverlay.ts +33 -0
  26. package/src/lib/viewport/label/helpers/getLabelPosition.ts +8 -0
  27. package/src/lib/viewport/label/helpers/getTransformValues.ts +8 -0
  28. package/src/lib/viewport/label/helpers/getZoomValue.ts +4 -0
  29. package/src/lib/viewport/label/index.ts +4 -0
  30. package/src/lib/viewport/label/isViewportLabelDragging.ts +9 -0
  31. package/src/lib/viewport/label/refreshViewportLabels.ts +69 -0
  32. package/src/lib/viewport/label/setupViewportLabelDrag.ts +98 -0
@@ -1,8 +1,216 @@
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.2
5
5
  */
6
+ function getScreenBounds(element) {
7
+ const rect = element.getBoundingClientRect();
8
+ return {
9
+ top: rect.top,
10
+ left: rect.left,
11
+ width: rect.width,
12
+ height: rect.height,
13
+ };
14
+ }
15
+
16
+ const getCanvasContainer = () => {
17
+ return document.querySelector(".canvas-container");
18
+ };
19
+
20
+ const getViewportLabelsOverlay = () => {
21
+ const canvasContainer = getCanvasContainer();
22
+ const container = canvasContainer || document.body;
23
+ // Check if overlay already exists
24
+ let overlay = container.querySelector(".viewport-labels-overlay");
25
+ if (!overlay) {
26
+ // Create new SVG overlay
27
+ overlay = document.createElementNS("http://www.w3.org/2000/svg", "svg");
28
+ overlay.classList.add("viewport-labels-overlay");
29
+ // Set fixed positioning
30
+ overlay.style.position = "absolute";
31
+ overlay.style.top = "0";
32
+ overlay.style.left = "0";
33
+ overlay.style.width = "100vw";
34
+ overlay.style.height = "100vh";
35
+ overlay.style.pointerEvents = "none";
36
+ overlay.style.zIndex = "500";
37
+ const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
38
+ const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
39
+ overlay.setAttribute("width", viewportWidth.toString());
40
+ overlay.setAttribute("height", viewportHeight.toString());
41
+ container.appendChild(overlay);
42
+ }
43
+ return overlay;
44
+ };
45
+
46
+ // Global flag to prevent refreshViewportLabels during drag
47
+ let globalIsDragging = false;
48
+ const isViewportLabelDragging = () => globalIsDragging;
49
+ const setViewportLabelDragging = (isDragging) => {
50
+ globalIsDragging = isDragging;
51
+ };
52
+
53
+ function sendPostMessage(action, data) {
54
+ window.parent.postMessage({
55
+ source: "node-edit-utils",
56
+ action,
57
+ data,
58
+ timestamp: Date.now(),
59
+ }, "*");
60
+ }
61
+
62
+ const getLabelPosition = (labelGroup) => {
63
+ 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 };
69
+ };
70
+
71
+ const getTransformValues = (element) => {
72
+ 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 };
78
+ };
79
+
80
+ const getZoomValue = () => {
81
+ const zoomValue = getComputedStyle(document.body).getPropertyValue("--zoom").trim();
82
+ return zoomValue ? parseFloat(zoomValue) : 1;
83
+ };
84
+
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 };
91
+ // Get the parent group element that contains the label
92
+ 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
+ };
159
+ };
160
+
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()) {
166
+ return;
167
+ }
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);
193
+ // Create group for this viewport label
194
+ const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
195
+ group.classList.add("viewport-label-group");
196
+ group.setAttribute("data-viewport-name", viewportName);
197
+ group.setAttribute("transform", `translate(${bounds.left}, ${bounds.top})`);
198
+ // Create text element
199
+ const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
200
+ text.classList.add("viewport-label-text");
201
+ text.setAttribute("x", "0");
202
+ text.setAttribute("y", "-8");
203
+ text.setAttribute("vector-effect", "non-scaling-stroke");
204
+ text.setAttribute("pointer-events", "auto");
205
+ text.textContent = viewportName;
206
+ group.appendChild(text);
207
+ overlay.appendChild(group);
208
+ // Setup drag functionality for this label
209
+ const cleanup = setupViewportLabelDrag(text, viewportElement, viewportName);
210
+ dragCleanupFunctions.set(viewportName, cleanup);
211
+ });
212
+ };
213
+
6
214
  const getCanvasWindowValue = (path, canvasName = "canvas") => {
7
215
  // biome-ignore lint/suspicious/noExplicitAny: global window extension
8
216
  const canvas = window[canvasName];
@@ -32,6 +240,8 @@ function createCanvasObserver(canvasName = "canvas") {
32
240
  if (nodeTools?.refreshHighlightFrame) {
33
241
  nodeTools.refreshHighlightFrame();
34
242
  }
243
+ // Refresh viewport labels
244
+ refreshViewportLabels();
35
245
  });
36
246
  observer.observe(transformLayer, {
37
247
  attributes: true,
@@ -39,8 +249,16 @@ function createCanvasObserver(canvasName = "canvas") {
39
249
  subtree: true,
40
250
  childList: true,
41
251
  });
252
+ // Handle window resize for viewport labels
253
+ const handleResize = () => {
254
+ refreshViewportLabels();
255
+ };
256
+ window.addEventListener("resize", handleResize);
257
+ // Initial refresh of viewport labels
258
+ refreshViewportLabels();
42
259
  function disconnect() {
43
260
  observer.disconnect();
261
+ window.removeEventListener("resize", handleResize);
44
262
  }
45
263
  return {
46
264
  disconnect,
@@ -55,15 +273,6 @@ const connectResizeObserver = (element, handler) => {
55
273
  return resizeObserver;
56
274
  };
57
275
 
58
- function sendPostMessage(action, data) {
59
- window.parent.postMessage({
60
- source: "node-edit-utils",
61
- action,
62
- data,
63
- timestamp: Date.now(),
64
- }, "*");
65
- }
66
-
67
276
  const bindToWindow = (key, value) => {
68
277
  if (typeof window !== "undefined") {
69
278
  // biome-ignore lint/suspicious/noExplicitAny: global window extension requires flexibility
@@ -91,10 +300,6 @@ const processPostMessage = (event, onNodeSelected) => {
91
300
  }
92
301
  };
93
302
 
94
- const getCanvasContainer = () => {
95
- return document.querySelector(".canvas-container");
96
- };
97
-
98
303
  const clearHighlightFrame = () => {
99
304
  const canvasContainer = getCanvasContainer();
100
305
  const container = canvasContainer || document.body;
@@ -146,6 +351,21 @@ const isInsideComponent = (element) => {
146
351
  return false;
147
352
  };
148
353
 
354
+ const isInsideViewport = (element) => {
355
+ let current = element;
356
+ while (current) {
357
+ if (current.classList.contains("viewport")) {
358
+ return true;
359
+ }
360
+ // Stop at node-provider to avoid checking beyond the editable area
361
+ if (current.getAttribute("data-role") === "node-provider") {
362
+ break;
363
+ }
364
+ current = current.parentElement;
365
+ }
366
+ return false;
367
+ };
368
+
149
369
  const targetSameCandidates = (cache, current) => cache.length === current.length && cache.every((el, i) => el === current[i]);
150
370
 
151
371
  let candidateCache = [];
@@ -158,7 +378,16 @@ const selectNode = (event, nodeProvider, text) => {
158
378
  const clickThrough = event.metaKey || event.ctrlKey;
159
379
  const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()) &&
160
380
  !element.classList.contains("select-none") &&
161
- !isInsideComponent(element));
381
+ !element.classList.contains("content-layer") &&
382
+ !element.classList.contains("resize-handle") &&
383
+ !element.classList.contains("resize-presets") &&
384
+ !isInsideComponent(element) &&
385
+ isInsideViewport(element));
386
+ console.log("candidates", candidates);
387
+ if (candidates.length === 0) {
388
+ lastSelectedNode = null;
389
+ return null;
390
+ }
162
391
  const editableNode = text.getEditableNode();
163
392
  if (editableNode && candidates.includes(editableNode)) {
164
393
  selectedNode = editableNode;
@@ -263,16 +492,6 @@ const createCornerHandles = (group, width, height, isInstance = false, isTextEdi
263
492
  createCornerHandle(group, 0, height, "handle-bottom-left", isInstance, isTextEdit);
264
493
  };
265
494
 
266
- function getScreenBounds(element) {
267
- const rect = element.getBoundingClientRect();
268
- return {
269
- top: rect.top,
270
- left: rect.left,
271
- width: rect.width,
272
- height: rect.height,
273
- };
274
- }
275
-
276
495
  const getComponentColor$1 = () => {
277
496
  return getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim() || "oklch(65.6% 0.241 354.308)";
278
497
  };
@@ -705,6 +924,7 @@ const setupMutationObserver = (node, nodeProvider, canvasName = "canvas") => {
705
924
  // Accumulate mutations instead of replacing
706
925
  pendingMutations.push(...mutations);
707
926
  scheduleProcess();
927
+ console.log("refreshHighlightFrame in mutationObserver");
708
928
  refreshHighlightFrame(node, nodeProvider, canvasName);
709
929
  });
710
930
  return () => {
@@ -842,14 +1062,15 @@ const createNodeTools = (element, canvasName = "canvas") => {
842
1062
  checkNodeExists();
843
1063
  if (!document.contains(node))
844
1064
  return;
1065
+ console.log("refreshHighlightFrame in mutationObserver 2");
845
1066
  refreshHighlightFrame(node, nodeProvider, canvasName);
846
1067
  updateHighlightFrameVisibility(node);
847
1068
  });
848
1069
  mutationObserver.observe(node, {
849
1070
  attributes: true,
850
1071
  characterData: true,
851
- childList: true,
852
- subtree: true,
1072
+ //childList: true,
1073
+ //subtree: true,
853
1074
  });
854
1075
  // Also observe parent node to catch when this node is removed
855
1076
  const parentNode = node.parentElement;
@@ -878,15 +1099,22 @@ const createNodeTools = (element, canvasName = "canvas") => {
878
1099
  if (!document.contains(node))
879
1100
  return; // Exit early if node was removed
880
1101
  refreshHighlightFrame(node, nodeProvider, canvasName);
1102
+ console.log("refreshHighlightFrame in resizeObserver");
881
1103
  updateHighlightFrameVisibility(node);
882
1104
  });
883
1105
  }
884
1106
  selectedNode = node;
885
1107
  sendPostMessage("selectedNodeChanged", node?.getAttribute("data-node-id") ?? null);
886
1108
  highlightNode(node) ?? null;
887
- if (node && nodeProvider) {
888
- updateHighlightFrameVisibility(node);
889
- updateHighlightFrameVisibility(node);
1109
+ if (node) {
1110
+ highlightNode(node);
1111
+ if (nodeProvider) {
1112
+ updateHighlightFrameVisibility(node);
1113
+ updateHighlightFrameVisibility(node);
1114
+ }
1115
+ }
1116
+ else {
1117
+ clearHighlightFrame();
890
1118
  }
891
1119
  };
892
1120
  // Setup event listener
@@ -927,8 +1155,8 @@ const createNodeTools = (element, canvasName = "canvas") => {
927
1155
 
928
1156
  const DEFAULT_WIDTH = 400;
929
1157
  const RESIZE_CONFIG = {
930
- minWidth: 320,
931
- maxWidth: 1680,
1158
+ minWidth: 4,
1159
+ maxWidth: 2560,
932
1160
  };
933
1161
  const RESIZE_PRESETS = [
934
1162
  {
@@ -1079,14 +1307,27 @@ const createViewport = (container, initialWidth) => {
1079
1307
  isDragging = false;
1080
1308
  };
1081
1309
  const removeListeners = setupEventListener(resizeHandle, startResize, handleResize, stopResize, blurResize);
1310
+ // Refresh viewport labels when viewport is created
1311
+ refreshViewportLabels();
1082
1312
  const cleanup = () => {
1083
1313
  isDragging = false;
1084
1314
  removeListeners();
1085
1315
  resizeHandle.remove();
1316
+ // Refresh labels after cleanup to remove this viewport's label if needed
1317
+ refreshViewportLabels();
1086
1318
  };
1087
1319
  return {
1088
1320
  setWidth: (width) => {
1089
1321
  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;
1326
+ const selectedNode = nodeTools?.getSelectedNode?.();
1327
+ const nodeProvider = document.querySelector('[data-role="node-provider"]');
1328
+ if (selectedNode && nodeProvider) {
1329
+ refreshHighlightFrame(selectedNode, nodeProvider);
1330
+ }
1090
1331
  },
1091
1332
  cleanup,
1092
1333
  };