@node-edit-utils/core 2.3.0 → 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.
- package/dist/lib/node-tools/select/helpers/isInsideViewport.d.ts +1 -0
- package/dist/lib/viewport/constants.d.ts +2 -2
- package/dist/lib/viewport/createViewport.d.ts +1 -1
- package/dist/lib/viewport/label/getViewportLabelsOverlay.d.ts +1 -0
- package/dist/lib/viewport/label/helpers/getLabelPosition.d.ts +4 -0
- package/dist/lib/viewport/label/helpers/getTransformValues.d.ts +4 -0
- package/dist/lib/viewport/label/helpers/getZoomValue.d.ts +1 -0
- package/dist/lib/viewport/label/index.d.ts +4 -0
- package/dist/lib/viewport/label/isViewportLabelDragging.d.ts +2 -0
- package/dist/lib/viewport/label/refreshViewportLabels.d.ts +1 -0
- package/dist/lib/viewport/label/setupViewportLabelDrag.d.ts +1 -0
- package/dist/node-edit-utils.cjs.js +288 -63
- package/dist/node-edit-utils.esm.js +288 -63
- package/dist/node-edit-utils.umd.js +288 -63
- package/dist/node-edit-utils.umd.min.js +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/lib/canvas/createCanvasObserver.ts +14 -0
- package/src/lib/node-tools/createNodeTools.ts +12 -5
- package/src/lib/node-tools/select/helpers/isInsideViewport.ts +19 -0
- package/src/lib/node-tools/select/selectNode.ts +13 -1
- package/src/lib/node-tools/text/events/setupMutationObserver.ts +1 -0
- package/src/lib/styles/styles.css +48 -1
- package/src/lib/viewport/constants.ts +2 -2
- package/src/lib/viewport/createViewport.ts +26 -7
- package/src/lib/viewport/events/setupEventListener.ts +9 -0
- package/src/lib/viewport/label/getViewportLabelsOverlay.ts +33 -0
- package/src/lib/viewport/label/helpers/getLabelPosition.ts +8 -0
- package/src/lib/viewport/label/helpers/getTransformValues.ts +8 -0
- package/src/lib/viewport/label/helpers/getZoomValue.ts +4 -0
- package/src/lib/viewport/label/index.ts +4 -0
- package/src/lib/viewport/label/isViewportLabelDragging.ts +9 -0
- package/src/lib/viewport/label/refreshViewportLabels.ts +69 -0
- 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.
|
|
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
|
-
!
|
|
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
|
|
888
|
-
|
|
889
|
-
|
|
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
|
|
@@ -925,36 +1153,10 @@ const createNodeTools = (element, canvasName = "canvas") => {
|
|
|
925
1153
|
return nodeTools;
|
|
926
1154
|
};
|
|
927
1155
|
|
|
928
|
-
// biome-ignore lint/suspicious/noExplicitAny: generic constraint requires flexibility
|
|
929
|
-
function withRAFThrottle(func) {
|
|
930
|
-
let rafId = null;
|
|
931
|
-
let lastArgs = null;
|
|
932
|
-
const throttled = (...args) => {
|
|
933
|
-
lastArgs = args;
|
|
934
|
-
if (rafId === null) {
|
|
935
|
-
rafId = requestAnimationFrame(() => {
|
|
936
|
-
if (lastArgs) {
|
|
937
|
-
func(...lastArgs);
|
|
938
|
-
}
|
|
939
|
-
rafId = null;
|
|
940
|
-
lastArgs = null;
|
|
941
|
-
});
|
|
942
|
-
}
|
|
943
|
-
};
|
|
944
|
-
throttled.cleanup = () => {
|
|
945
|
-
if (rafId !== null) {
|
|
946
|
-
cancelAnimationFrame(rafId);
|
|
947
|
-
rafId = null;
|
|
948
|
-
lastArgs = null;
|
|
949
|
-
}
|
|
950
|
-
};
|
|
951
|
-
return throttled;
|
|
952
|
-
}
|
|
953
|
-
|
|
954
1156
|
const DEFAULT_WIDTH = 400;
|
|
955
1157
|
const RESIZE_CONFIG = {
|
|
956
|
-
minWidth:
|
|
957
|
-
maxWidth:
|
|
1158
|
+
minWidth: 4,
|
|
1159
|
+
maxWidth: 2560,
|
|
958
1160
|
};
|
|
959
1161
|
const RESIZE_PRESETS = [
|
|
960
1162
|
{
|
|
@@ -985,14 +1187,22 @@ const RESIZE_PRESETS = [
|
|
|
985
1187
|
];
|
|
986
1188
|
|
|
987
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
|
+
};
|
|
988
1196
|
resizeHandle.addEventListener("mousedown", startResize);
|
|
989
1197
|
document.addEventListener("mousemove", handleResize);
|
|
990
1198
|
document.addEventListener("mouseup", stopResize);
|
|
1199
|
+
document.addEventListener("mouseleave", handleMouseLeave);
|
|
991
1200
|
window.addEventListener("blur", blurResize);
|
|
992
1201
|
return () => {
|
|
993
1202
|
resizeHandle.removeEventListener("mousedown", startResize);
|
|
994
1203
|
document.removeEventListener("mousemove", handleResize);
|
|
995
1204
|
document.removeEventListener("mouseup", stopResize);
|
|
1205
|
+
document.removeEventListener("mouseleave", handleMouseLeave);
|
|
996
1206
|
window.removeEventListener("blur", blurResize);
|
|
997
1207
|
};
|
|
998
1208
|
};
|
|
@@ -1052,7 +1262,7 @@ const updateWidth = (container, width) => {
|
|
|
1052
1262
|
updateActivePreset(container, width);
|
|
1053
1263
|
};
|
|
1054
1264
|
|
|
1055
|
-
const createViewport = (container) => {
|
|
1265
|
+
const createViewport = (container, initialWidth) => {
|
|
1056
1266
|
const canvas = getCanvasContainer();
|
|
1057
1267
|
// Remove any existing resize handle to prevent duplicates
|
|
1058
1268
|
const existingHandle = container.querySelector(".resize-handle");
|
|
@@ -1060,7 +1270,8 @@ const createViewport = (container) => {
|
|
|
1060
1270
|
existingHandle.remove();
|
|
1061
1271
|
}
|
|
1062
1272
|
const resizeHandle = createResizeHandle(container);
|
|
1063
|
-
|
|
1273
|
+
const width = initialWidth ?? DEFAULT_WIDTH;
|
|
1274
|
+
container.style.setProperty("--container-width", `${width}px`);
|
|
1064
1275
|
createResizePresets(resizeHandle, container, updateWidth);
|
|
1065
1276
|
let isDragging = false;
|
|
1066
1277
|
let startX = 0;
|
|
@@ -1081,7 +1292,6 @@ const createViewport = (container) => {
|
|
|
1081
1292
|
const width = calcWidth(event, startX, startWidth);
|
|
1082
1293
|
updateWidth(container, width);
|
|
1083
1294
|
};
|
|
1084
|
-
const throttledHandleResize = withRAFThrottle(handleResize);
|
|
1085
1295
|
const stopResize = (event) => {
|
|
1086
1296
|
event.preventDefault();
|
|
1087
1297
|
event.stopPropagation();
|
|
@@ -1091,18 +1301,33 @@ const createViewport = (container) => {
|
|
|
1091
1301
|
isDragging = false;
|
|
1092
1302
|
};
|
|
1093
1303
|
const blurResize = () => {
|
|
1304
|
+
if (canvas) {
|
|
1305
|
+
canvas.style.cursor = "default";
|
|
1306
|
+
}
|
|
1094
1307
|
isDragging = false;
|
|
1095
1308
|
};
|
|
1096
|
-
const removeListeners = setupEventListener(resizeHandle, startResize,
|
|
1309
|
+
const removeListeners = setupEventListener(resizeHandle, startResize, handleResize, stopResize, blurResize);
|
|
1310
|
+
// Refresh viewport labels when viewport is created
|
|
1311
|
+
refreshViewportLabels();
|
|
1097
1312
|
const cleanup = () => {
|
|
1098
1313
|
isDragging = false;
|
|
1099
|
-
throttledHandleResize?.cleanup();
|
|
1100
1314
|
removeListeners();
|
|
1101
1315
|
resizeHandle.remove();
|
|
1316
|
+
// Refresh labels after cleanup to remove this viewport's label if needed
|
|
1317
|
+
refreshViewportLabels();
|
|
1102
1318
|
};
|
|
1103
1319
|
return {
|
|
1104
1320
|
setWidth: (width) => {
|
|
1105
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
|
+
}
|
|
1106
1331
|
},
|
|
1107
1332
|
cleanup,
|
|
1108
1333
|
};
|