@node-edit-utils/core 2.1.9 → 2.2.1
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/highlight/clearHighlightFrame.d.ts +1 -1
- package/dist/lib/node-tools/highlight/createCornerHandles.d.ts +1 -0
- package/dist/lib/node-tools/highlight/createHighlightFrame.d.ts +1 -1
- package/dist/lib/node-tools/highlight/createToolsContainer.d.ts +1 -1
- package/dist/lib/node-tools/highlight/helpers/getHighlightFrameElement.d.ts +1 -1
- package/dist/lib/node-tools/highlight/helpers/getScreenBounds.d.ts +6 -0
- package/dist/lib/node-tools/highlight/highlightNode.d.ts +1 -1
- package/dist/lib/node-tools/highlight/updateHighlightFrameVisibility.d.ts +1 -1
- package/dist/lib/node-tools/select/helpers/isComponentInstance.d.ts +1 -0
- package/dist/lib/node-tools/select/helpers/isInsideComponent.d.ts +1 -0
- package/dist/node-edit-utils.cjs.js +300 -155
- package/dist/node-edit-utils.esm.js +300 -155
- package/dist/node-edit-utils.umd.js +300 -155
- 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 +8 -6
- package/src/lib/node-tools/createNodeTools.ts +27 -22
- package/src/lib/node-tools/events/click/handleNodeClick.ts +1 -1
- package/src/lib/node-tools/highlight/clearHighlightFrame.ts +7 -8
- package/src/lib/node-tools/highlight/createCornerHandles.ts +31 -0
- package/src/lib/node-tools/highlight/createHighlightFrame.ts +45 -24
- package/src/lib/node-tools/highlight/createToolsContainer.ts +4 -1
- package/src/lib/node-tools/highlight/helpers/getHighlightFrameElement.ts +2 -4
- package/src/lib/node-tools/highlight/helpers/getScreenBounds.ts +16 -0
- package/src/lib/node-tools/highlight/highlightNode.ts +28 -5
- package/src/lib/node-tools/highlight/refreshHighlightFrame.ts +113 -14
- package/src/lib/node-tools/highlight/updateHighlightFrameVisibility.ts +5 -5
- package/src/lib/node-tools/select/helpers/isComponentInstance.ts +3 -0
- package/src/lib/node-tools/select/helpers/isInsideComponent.ts +18 -0
- package/src/lib/node-tools/select/selectNode.ts +5 -1
- package/src/lib/post-message/processPostMessage.ts +0 -2
- package/src/lib/styles/styles.css +57 -21
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Markup Canvas
|
|
3
3
|
* High-performance markup canvas with zoom and pan capabilities
|
|
4
|
-
* @version 2.1
|
|
4
|
+
* @version 2.2.1
|
|
5
5
|
*/
|
|
6
6
|
(function (global, factory) {
|
|
7
7
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
@@ -9,68 +9,6 @@
|
|
|
9
9
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.MarkupCanvas = {}));
|
|
10
10
|
})(this, (function (exports) { 'use strict';
|
|
11
11
|
|
|
12
|
-
// biome-ignore lint/suspicious/noExplicitAny: generic constraint requires flexibility
|
|
13
|
-
function withRAFThrottle(func) {
|
|
14
|
-
let rafId = null;
|
|
15
|
-
let lastArgs = null;
|
|
16
|
-
const throttled = (...args) => {
|
|
17
|
-
lastArgs = args;
|
|
18
|
-
if (rafId === null) {
|
|
19
|
-
rafId = requestAnimationFrame(() => {
|
|
20
|
-
if (lastArgs) {
|
|
21
|
-
func(...lastArgs);
|
|
22
|
-
}
|
|
23
|
-
rafId = null;
|
|
24
|
-
lastArgs = null;
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
throttled.cleanup = () => {
|
|
29
|
-
if (rafId !== null) {
|
|
30
|
-
cancelAnimationFrame(rafId);
|
|
31
|
-
rafId = null;
|
|
32
|
-
lastArgs = null;
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
return throttled;
|
|
36
|
-
}
|
|
37
|
-
// biome-ignore lint/suspicious/noExplicitAny: generic constraint requires flexibility
|
|
38
|
-
function withDoubleRAF(func) {
|
|
39
|
-
let rafId1 = null;
|
|
40
|
-
let rafId2 = null;
|
|
41
|
-
let lastArgs = null;
|
|
42
|
-
const throttled = (...args) => {
|
|
43
|
-
lastArgs = args;
|
|
44
|
-
if (rafId1 === null) {
|
|
45
|
-
rafId1 = requestAnimationFrame(() => {
|
|
46
|
-
// First RAF: let browser complete layout
|
|
47
|
-
rafId2 = requestAnimationFrame(() => {
|
|
48
|
-
// Second RAF: read bounds after layout is complete
|
|
49
|
-
if (lastArgs) {
|
|
50
|
-
const currentArgs = lastArgs;
|
|
51
|
-
rafId1 = null;
|
|
52
|
-
rafId2 = null;
|
|
53
|
-
lastArgs = null;
|
|
54
|
-
func(...currentArgs);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
throttled.cleanup = () => {
|
|
61
|
-
if (rafId1 !== null) {
|
|
62
|
-
cancelAnimationFrame(rafId1);
|
|
63
|
-
rafId1 = null;
|
|
64
|
-
}
|
|
65
|
-
if (rafId2 !== null) {
|
|
66
|
-
cancelAnimationFrame(rafId2);
|
|
67
|
-
rafId2 = null;
|
|
68
|
-
}
|
|
69
|
-
lastArgs = null;
|
|
70
|
-
};
|
|
71
|
-
return throttled;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
12
|
const getCanvasWindowValue = (path) => {
|
|
75
13
|
// biome-ignore lint/suspicious/noExplicitAny: global window extension
|
|
76
14
|
const canvas = window.canvas;
|
|
@@ -92,11 +30,14 @@
|
|
|
92
30
|
disconnect: () => { },
|
|
93
31
|
};
|
|
94
32
|
}
|
|
95
|
-
const throttledUpdate = withRAFThrottle(() => {
|
|
96
|
-
applyCanvasState();
|
|
97
|
-
});
|
|
98
33
|
const observer = new MutationObserver(() => {
|
|
99
|
-
|
|
34
|
+
applyCanvasState();
|
|
35
|
+
// Refresh highlight frame (throttled via withRAFThrottle)
|
|
36
|
+
// biome-ignore lint/suspicious/noExplicitAny: global window extension
|
|
37
|
+
const nodeTools = window.nodeTools;
|
|
38
|
+
if (nodeTools?.refreshHighlightFrame) {
|
|
39
|
+
nodeTools.refreshHighlightFrame();
|
|
40
|
+
}
|
|
100
41
|
});
|
|
101
42
|
observer.observe(transformLayer, {
|
|
102
43
|
attributes: true,
|
|
@@ -105,7 +46,6 @@
|
|
|
105
46
|
childList: true,
|
|
106
47
|
});
|
|
107
48
|
function disconnect() {
|
|
108
|
-
throttledUpdate.cleanup();
|
|
109
49
|
observer.disconnect();
|
|
110
50
|
}
|
|
111
51
|
return {
|
|
@@ -121,6 +61,32 @@
|
|
|
121
61
|
return resizeObserver;
|
|
122
62
|
};
|
|
123
63
|
|
|
64
|
+
// biome-ignore lint/suspicious/noExplicitAny: generic constraint requires flexibility
|
|
65
|
+
function withRAFThrottle(func) {
|
|
66
|
+
let rafId = null;
|
|
67
|
+
let lastArgs = null;
|
|
68
|
+
const throttled = (...args) => {
|
|
69
|
+
lastArgs = args;
|
|
70
|
+
if (rafId === null) {
|
|
71
|
+
rafId = requestAnimationFrame(() => {
|
|
72
|
+
if (lastArgs) {
|
|
73
|
+
func(...lastArgs);
|
|
74
|
+
}
|
|
75
|
+
rafId = null;
|
|
76
|
+
lastArgs = null;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
throttled.cleanup = () => {
|
|
81
|
+
if (rafId !== null) {
|
|
82
|
+
cancelAnimationFrame(rafId);
|
|
83
|
+
rafId = null;
|
|
84
|
+
lastArgs = null;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
return throttled;
|
|
88
|
+
}
|
|
89
|
+
|
|
124
90
|
function sendPostMessage(action, data) {
|
|
125
91
|
window.parent.postMessage({
|
|
126
92
|
source: "node-edit-utils",
|
|
@@ -146,7 +112,6 @@
|
|
|
146
112
|
if (event.data.action === "selectedNodeChanged") {
|
|
147
113
|
const nodeId = event.data.data;
|
|
148
114
|
const selectedNode = document.querySelector(`[data-node-id="${nodeId}"]`);
|
|
149
|
-
console.log("selectedNode", selectedNode);
|
|
150
115
|
if (isLocked(selectedNode)) {
|
|
151
116
|
onNodeSelected?.(null);
|
|
152
117
|
return;
|
|
@@ -158,17 +123,14 @@
|
|
|
158
123
|
}
|
|
159
124
|
};
|
|
160
125
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const clearHighlightFrame = (nodeProvider) => {
|
|
166
|
-
if (!nodeProvider) {
|
|
167
|
-
return;
|
|
126
|
+
const clearHighlightFrame = () => {
|
|
127
|
+
const frame = document.body.querySelector(".highlight-frame-overlay");
|
|
128
|
+
if (frame) {
|
|
129
|
+
frame.remove();
|
|
168
130
|
}
|
|
169
|
-
const
|
|
170
|
-
if (
|
|
171
|
-
|
|
131
|
+
const toolsWrapper = document.body.querySelector(".highlight-frame-tools-wrapper");
|
|
132
|
+
if (toolsWrapper) {
|
|
133
|
+
toolsWrapper.remove();
|
|
172
134
|
}
|
|
173
135
|
};
|
|
174
136
|
|
|
@@ -188,6 +150,21 @@
|
|
|
188
150
|
}, { elements: [], found: false }).elements;
|
|
189
151
|
};
|
|
190
152
|
|
|
153
|
+
const isInsideComponent = (element) => {
|
|
154
|
+
let current = element.parentElement;
|
|
155
|
+
while (current) {
|
|
156
|
+
if (current.getAttribute("data-instance") === "true") {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
// Stop at node-provider to avoid checking beyond the editable area
|
|
160
|
+
if (current.getAttribute("data-role") === "node-provider") {
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
current = current.parentElement;
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
};
|
|
167
|
+
|
|
191
168
|
const targetSameCandidates = (cache, current) => cache.length === current.length && cache.every((el, i) => el === current[i]);
|
|
192
169
|
|
|
193
170
|
let candidateCache = [];
|
|
@@ -197,7 +174,9 @@
|
|
|
197
174
|
const clickX = event.clientX;
|
|
198
175
|
const clickY = event.clientY;
|
|
199
176
|
const clickThrough = event.metaKey || event.ctrlKey;
|
|
200
|
-
const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()) &&
|
|
177
|
+
const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()) &&
|
|
178
|
+
!element.classList.contains("select-none") &&
|
|
179
|
+
!isInsideComponent(element));
|
|
201
180
|
if (editableNode && candidates.includes(editableNode)) {
|
|
202
181
|
return editableNode;
|
|
203
182
|
}
|
|
@@ -222,7 +201,7 @@
|
|
|
222
201
|
event.preventDefault();
|
|
223
202
|
event.stopPropagation();
|
|
224
203
|
if (nodeProvider && !nodeProvider.contains(event.target)) {
|
|
225
|
-
clearHighlightFrame(
|
|
204
|
+
clearHighlightFrame();
|
|
226
205
|
onNodeSelected(null);
|
|
227
206
|
return;
|
|
228
207
|
}
|
|
@@ -254,48 +233,90 @@
|
|
|
254
233
|
};
|
|
255
234
|
};
|
|
256
235
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const
|
|
236
|
+
const isComponentInstance = (element) => {
|
|
237
|
+
return element.getAttribute("data-instance") === "true";
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const HANDLE_SIZE = 6;
|
|
241
|
+
const getComponentColor$2 = () => {
|
|
242
|
+
return getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim() || "oklch(65.6% 0.241 354.308)";
|
|
243
|
+
};
|
|
244
|
+
const createCornerHandle = (group, x, y, className, isInstance = false) => {
|
|
245
|
+
const handle = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
246
|
+
// Position relative to group (offset by half handle size to center on corner)
|
|
247
|
+
handle.setAttribute("x", (x - HANDLE_SIZE / 2).toString());
|
|
248
|
+
handle.setAttribute("y", (y - HANDLE_SIZE / 2).toString());
|
|
249
|
+
handle.setAttribute("width", HANDLE_SIZE.toString());
|
|
250
|
+
handle.setAttribute("height", HANDLE_SIZE.toString());
|
|
251
|
+
handle.setAttribute("vector-effect", "non-scaling-stroke");
|
|
252
|
+
handle.classList.add("highlight-frame-handle", className);
|
|
253
|
+
if (isInstance) {
|
|
254
|
+
handle.setAttribute("stroke", getComponentColor$2());
|
|
255
|
+
}
|
|
256
|
+
group.appendChild(handle);
|
|
257
|
+
return handle;
|
|
258
|
+
};
|
|
259
|
+
const createCornerHandles = (group, width, height, isInstance = false) => {
|
|
260
|
+
// Create corner handles using relative coordinates (group handles positioning)
|
|
261
|
+
createCornerHandle(group, 0, 0, "handle-top-left", isInstance);
|
|
262
|
+
createCornerHandle(group, width, 0, "handle-top-right", isInstance);
|
|
263
|
+
createCornerHandle(group, width, height, "handle-bottom-right", isInstance);
|
|
264
|
+
createCornerHandle(group, 0, height, "handle-bottom-left", isInstance);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
function getScreenBounds(element) {
|
|
268
|
+
const rect = element.getBoundingClientRect();
|
|
267
269
|
return {
|
|
268
|
-
top,
|
|
269
|
-
left,
|
|
270
|
-
width,
|
|
271
|
-
height,
|
|
270
|
+
top: rect.top,
|
|
271
|
+
left: rect.left,
|
|
272
|
+
width: rect.width,
|
|
273
|
+
height: rect.height,
|
|
272
274
|
};
|
|
273
275
|
}
|
|
274
276
|
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
frame.classList.add("highlight-frame");
|
|
282
|
-
frame.style.setProperty("--frame-top", `${top}px`);
|
|
283
|
-
frame.style.setProperty("--frame-left", `${left}px`);
|
|
284
|
-
frame.style.setProperty("--frame-width", `${width}px`);
|
|
285
|
-
frame.style.setProperty("--frame-height", `${height}px`);
|
|
286
|
-
// Create SVG overlay for outline
|
|
277
|
+
const getComponentColor$1 = () => {
|
|
278
|
+
return getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim() || "oklch(65.6% 0.241 354.308)";
|
|
279
|
+
};
|
|
280
|
+
const createHighlightFrame = (node, isInstance = false) => {
|
|
281
|
+
const { top, left, width, height } = getScreenBounds(node);
|
|
282
|
+
// Create fixed SVG overlay
|
|
287
283
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
288
|
-
svg.classList.add("highlight-frame-
|
|
284
|
+
svg.classList.add("highlight-frame-overlay");
|
|
285
|
+
if (isInstance) {
|
|
286
|
+
svg.classList.add("is-instance");
|
|
287
|
+
}
|
|
288
|
+
svg.setAttribute("data-node-id", node.getAttribute("data-node-id") || "");
|
|
289
|
+
// Set fixed positioning
|
|
290
|
+
svg.style.position = "fixed";
|
|
291
|
+
svg.style.top = "0";
|
|
292
|
+
svg.style.left = "0";
|
|
293
|
+
svg.style.width = "100vw";
|
|
294
|
+
svg.style.height = "100vh";
|
|
295
|
+
svg.style.pointerEvents = "none";
|
|
296
|
+
svg.style.zIndex = "5000";
|
|
297
|
+
const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
|
|
298
|
+
const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
|
|
299
|
+
svg.setAttribute("width", viewportWidth.toString());
|
|
300
|
+
svg.setAttribute("height", viewportHeight.toString());
|
|
301
|
+
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
302
|
+
group.classList.add("highlight-frame-group");
|
|
303
|
+
group.setAttribute("transform", `translate(${left}, ${top})`);
|
|
289
304
|
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
290
305
|
rect.setAttribute("x", "0");
|
|
291
306
|
rect.setAttribute("y", "0");
|
|
292
|
-
rect.setAttribute("width",
|
|
293
|
-
rect.setAttribute("height",
|
|
307
|
+
rect.setAttribute("width", width.toString());
|
|
308
|
+
rect.setAttribute("height", height.toString());
|
|
309
|
+
rect.setAttribute("vector-effect", "non-scaling-stroke");
|
|
294
310
|
rect.classList.add("highlight-frame-rect");
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
311
|
+
// Apply instance color if it's an instance
|
|
312
|
+
if (isInstance) {
|
|
313
|
+
rect.setAttribute("stroke", getComponentColor$1());
|
|
314
|
+
}
|
|
315
|
+
group.appendChild(rect);
|
|
316
|
+
createCornerHandles(group, width, height, isInstance);
|
|
317
|
+
svg.appendChild(group);
|
|
318
|
+
document.body.appendChild(svg);
|
|
319
|
+
return svg;
|
|
299
320
|
};
|
|
300
321
|
|
|
301
322
|
const createTagLabel = (node, nodeTools) => {
|
|
@@ -305,58 +326,177 @@
|
|
|
305
326
|
nodeTools.appendChild(tagLabel);
|
|
306
327
|
};
|
|
307
328
|
|
|
308
|
-
const createToolsContainer = (node, highlightFrame) => {
|
|
329
|
+
const createToolsContainer = (node, highlightFrame, isInstance = false) => {
|
|
309
330
|
const nodeTools = document.createElement("div");
|
|
310
331
|
nodeTools.className = "node-tools";
|
|
332
|
+
if (isInstance) {
|
|
333
|
+
nodeTools.classList.add("is-instance");
|
|
334
|
+
}
|
|
311
335
|
highlightFrame.appendChild(nodeTools);
|
|
312
336
|
createTagLabel(node, nodeTools);
|
|
313
337
|
};
|
|
314
338
|
|
|
315
|
-
|
|
339
|
+
function getHighlightFrameElement() {
|
|
340
|
+
return document.body.querySelector(".highlight-frame-overlay");
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const highlightNode = (node) => {
|
|
316
344
|
if (!node)
|
|
317
345
|
return;
|
|
318
|
-
const existingHighlightFrame = getHighlightFrameElement(
|
|
346
|
+
const existingHighlightFrame = getHighlightFrameElement();
|
|
347
|
+
const existingToolsWrapper = document.body.querySelector(".highlight-frame-tools-wrapper");
|
|
319
348
|
if (existingHighlightFrame) {
|
|
320
349
|
existingHighlightFrame.remove();
|
|
321
350
|
}
|
|
322
|
-
|
|
351
|
+
if (existingToolsWrapper) {
|
|
352
|
+
existingToolsWrapper.remove();
|
|
353
|
+
}
|
|
354
|
+
const isInstance = isComponentInstance(node);
|
|
355
|
+
const highlightFrame = createHighlightFrame(node, isInstance);
|
|
323
356
|
if (node.contentEditable === "true") {
|
|
324
357
|
highlightFrame.classList.add("is-editable");
|
|
325
358
|
}
|
|
326
|
-
|
|
327
|
-
|
|
359
|
+
// Create tools wrapper with tag label - centered using translateX(-50%)
|
|
360
|
+
const { left, top, height } = getScreenBounds(node);
|
|
361
|
+
const bottomY = top + height;
|
|
362
|
+
const toolsWrapper = document.createElement("div");
|
|
363
|
+
toolsWrapper.classList.add("highlight-frame-tools-wrapper");
|
|
364
|
+
if (isInstance) {
|
|
365
|
+
toolsWrapper.classList.add("is-instance");
|
|
366
|
+
}
|
|
367
|
+
toolsWrapper.style.position = "fixed";
|
|
368
|
+
toolsWrapper.style.left = `${left}px`;
|
|
369
|
+
toolsWrapper.style.top = `${bottomY}px`;
|
|
370
|
+
toolsWrapper.style.transform = "translateX(-50%)";
|
|
371
|
+
toolsWrapper.style.transformOrigin = "center";
|
|
372
|
+
toolsWrapper.style.pointerEvents = "none";
|
|
373
|
+
toolsWrapper.style.zIndex = "5000"; // Match --z-index-highlight (below canvas rulers)
|
|
374
|
+
createToolsContainer(node, toolsWrapper, isInstance);
|
|
375
|
+
document.body.appendChild(toolsWrapper);
|
|
328
376
|
};
|
|
329
377
|
|
|
378
|
+
const getComponentColor = () => {
|
|
379
|
+
return getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim() || "oklch(65.6% 0.241 354.308)";
|
|
380
|
+
};
|
|
330
381
|
const refreshHighlightFrame = (node, nodeProvider) => {
|
|
331
|
-
|
|
332
|
-
const
|
|
333
|
-
console.log("1. refreshHighlightFrame", node);
|
|
382
|
+
// Batch all DOM reads first (single layout pass)
|
|
383
|
+
const frame = getHighlightFrameElement();
|
|
334
384
|
if (!frame)
|
|
335
385
|
return;
|
|
336
|
-
|
|
386
|
+
const isInstance = isComponentInstance(node);
|
|
387
|
+
// Update SVG dimensions to match current viewport (handles window resize and ensures coordinate system is correct)
|
|
388
|
+
// Use clientWidth/Height to match getBoundingClientRect() coordinate system (excludes scrollbars)
|
|
389
|
+
const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
|
|
390
|
+
const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
|
|
391
|
+
frame.setAttribute("width", viewportWidth.toString());
|
|
392
|
+
frame.setAttribute("height", viewportHeight.toString());
|
|
393
|
+
// Update instance class
|
|
394
|
+
if (isInstance) {
|
|
395
|
+
frame.classList.add("is-instance");
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
frame.classList.remove("is-instance");
|
|
399
|
+
}
|
|
400
|
+
const group = frame.querySelector(".highlight-frame-group");
|
|
401
|
+
if (!group)
|
|
402
|
+
return;
|
|
403
|
+
const rect = group.querySelector("rect");
|
|
404
|
+
if (!rect)
|
|
405
|
+
return;
|
|
406
|
+
// Update instance color
|
|
407
|
+
if (isInstance) {
|
|
408
|
+
rect.setAttribute("stroke", getComponentColor());
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
rect.removeAttribute("stroke"); // Use CSS default
|
|
412
|
+
}
|
|
413
|
+
const toolsWrapper = document.body.querySelector(".highlight-frame-tools-wrapper");
|
|
414
|
+
const nodeTools = toolsWrapper?.querySelector(".node-tools");
|
|
415
|
+
const zoom = getCanvasWindowValue(["zoom", "current"]) ?? 1;
|
|
416
|
+
const bounds = getScreenBounds(node);
|
|
417
|
+
// Calculate all values before any DOM writes
|
|
418
|
+
const { top, left, width, height } = bounds;
|
|
419
|
+
const bottomY = top + height;
|
|
420
|
+
// Update instance classes on tools wrapper and node tools
|
|
421
|
+
if (toolsWrapper) {
|
|
422
|
+
if (isInstance) {
|
|
423
|
+
toolsWrapper.classList.add("is-instance");
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
toolsWrapper.classList.remove("is-instance");
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (nodeTools) {
|
|
430
|
+
if (isInstance) {
|
|
431
|
+
nodeTools.classList.add("is-instance");
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
nodeTools.classList.remove("is-instance");
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
// Batch all DOM writes (single paint pass)
|
|
438
|
+
// Update group transform to move entire group (rect + handles) at once
|
|
439
|
+
group.setAttribute("transform", `translate(${left}, ${top})`);
|
|
440
|
+
// Update rect dimensions (position is handled by group transform)
|
|
441
|
+
rect.setAttribute("width", width.toString());
|
|
442
|
+
rect.setAttribute("height", height.toString());
|
|
443
|
+
// Update corner handles positions (relative to group, so only width/height matter)
|
|
444
|
+
const topLeft = group.querySelector(".handle-top-left");
|
|
445
|
+
const topRight = group.querySelector(".handle-top-right");
|
|
446
|
+
const bottomRight = group.querySelector(".handle-bottom-right");
|
|
447
|
+
const bottomLeft = group.querySelector(".handle-bottom-left");
|
|
448
|
+
const HANDLE_SIZE = 6;
|
|
449
|
+
// Update handle colors and positions
|
|
450
|
+
const handles = [topLeft, topRight, bottomRight, bottomLeft];
|
|
451
|
+
handles.forEach((handle) => {
|
|
452
|
+
if (handle) {
|
|
453
|
+
if (isInstance) {
|
|
454
|
+
handle.setAttribute("stroke", getComponentColor());
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
handle.removeAttribute("stroke"); // Use CSS default
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
if (topLeft) {
|
|
462
|
+
topLeft.setAttribute("x", (-HANDLE_SIZE / 2).toString());
|
|
463
|
+
topLeft.setAttribute("y", (-HANDLE_SIZE / 2).toString());
|
|
464
|
+
}
|
|
465
|
+
if (topRight) {
|
|
466
|
+
topRight.setAttribute("x", (width - HANDLE_SIZE / 2).toString());
|
|
467
|
+
topRight.setAttribute("y", (-HANDLE_SIZE / 2).toString());
|
|
468
|
+
}
|
|
469
|
+
if (bottomRight) {
|
|
470
|
+
bottomRight.setAttribute("x", (width - HANDLE_SIZE / 2).toString());
|
|
471
|
+
bottomRight.setAttribute("y", (height - HANDLE_SIZE / 2).toString());
|
|
472
|
+
}
|
|
473
|
+
if (bottomLeft) {
|
|
474
|
+
bottomLeft.setAttribute("x", (-HANDLE_SIZE / 2).toString());
|
|
475
|
+
bottomLeft.setAttribute("y", (height - HANDLE_SIZE / 2).toString());
|
|
476
|
+
}
|
|
477
|
+
// Update tools wrapper position (use calculated bounds, not rect attributes)
|
|
478
|
+
if (toolsWrapper) {
|
|
479
|
+
toolsWrapper.style.left = `${left}px`;
|
|
480
|
+
toolsWrapper.style.top = `${bottomY}px`;
|
|
481
|
+
}
|
|
482
|
+
if (zoom <= 10) {
|
|
337
483
|
nodeProvider.style.setProperty("--tool-opacity", `1`);
|
|
338
484
|
}
|
|
339
485
|
else {
|
|
340
486
|
nodeProvider.style.setProperty("--tool-opacity", `0`);
|
|
341
487
|
}
|
|
342
|
-
const { top, left, width, height } = getElementBounds(node, nodeProvider);
|
|
343
|
-
frame.style.setProperty("--frame-top", `${top}px`);
|
|
344
|
-
frame.style.setProperty("--frame-left", `${left}px`);
|
|
345
|
-
frame.style.setProperty("--frame-width", `${width}px`);
|
|
346
|
-
frame.style.setProperty("--frame-height", `${height}px`);
|
|
347
|
-
console.log("2. refreshHighlightFrame", top, left, width, height);
|
|
348
488
|
};
|
|
349
489
|
|
|
350
|
-
const updateHighlightFrameVisibility = (node
|
|
351
|
-
const frame = getHighlightFrameElement(
|
|
490
|
+
const updateHighlightFrameVisibility = (node) => {
|
|
491
|
+
const frame = getHighlightFrameElement();
|
|
352
492
|
if (!frame)
|
|
353
493
|
return;
|
|
354
494
|
const hasHiddenClass = node.classList.contains("hidden") || node.classList.contains("select-none");
|
|
355
495
|
const displayValue = hasHiddenClass ? "none" : "";
|
|
356
496
|
frame.style.display = displayValue;
|
|
357
|
-
const
|
|
358
|
-
if (
|
|
359
|
-
|
|
497
|
+
const toolsWrapper = document.body.querySelector(".highlight-frame-tools-wrapper");
|
|
498
|
+
if (toolsWrapper) {
|
|
499
|
+
toolsWrapper.style.display = displayValue;
|
|
360
500
|
}
|
|
361
501
|
};
|
|
362
502
|
|
|
@@ -496,21 +636,23 @@
|
|
|
496
636
|
const createNodeTools = (element) => {
|
|
497
637
|
const nodeProvider = element;
|
|
498
638
|
let resizeObserver = null;
|
|
499
|
-
let nodeResizeObserver = null;
|
|
500
639
|
let mutationObserver = null;
|
|
501
640
|
let selectedNode = null;
|
|
502
641
|
const text = nodeText();
|
|
503
|
-
|
|
642
|
+
// Combined throttled function for refresh + visibility update
|
|
643
|
+
const throttledRefreshAndVisibility = withRAFThrottle((node, nodeProvider) => {
|
|
644
|
+
refreshHighlightFrame(node, nodeProvider);
|
|
645
|
+
updateHighlightFrameVisibility(node);
|
|
646
|
+
});
|
|
504
647
|
const handleEscape = () => {
|
|
505
648
|
if (text.isEditing()) {
|
|
506
649
|
text.blurEditMode();
|
|
507
650
|
}
|
|
508
651
|
if (selectedNode) {
|
|
509
652
|
if (nodeProvider) {
|
|
510
|
-
clearHighlightFrame(
|
|
653
|
+
clearHighlightFrame();
|
|
511
654
|
selectedNode = null;
|
|
512
655
|
resizeObserver?.disconnect();
|
|
513
|
-
nodeResizeObserver?.disconnect();
|
|
514
656
|
mutationObserver?.disconnect();
|
|
515
657
|
}
|
|
516
658
|
}
|
|
@@ -526,31 +668,31 @@
|
|
|
526
668
|
}
|
|
527
669
|
}
|
|
528
670
|
resizeObserver?.disconnect();
|
|
529
|
-
nodeResizeObserver?.disconnect();
|
|
530
671
|
mutationObserver?.disconnect();
|
|
531
672
|
if (node && nodeProvider) {
|
|
532
673
|
text.enableEditMode(node, nodeProvider);
|
|
533
|
-
resizeObserver = connectResizeObserver(nodeProvider, () => {
|
|
534
|
-
throttledFrameRefresh(node, nodeProvider);
|
|
535
|
-
});
|
|
536
674
|
mutationObserver = new MutationObserver(() => {
|
|
537
|
-
|
|
538
|
-
|
|
675
|
+
// throttledRefreshAndVisibility(node, nodeProvider);
|
|
676
|
+
console.log("mutationObserver", node);
|
|
677
|
+
refreshHighlightFrame(node, nodeProvider);
|
|
678
|
+
updateHighlightFrameVisibility(node);
|
|
539
679
|
});
|
|
540
680
|
mutationObserver.observe(node, {
|
|
541
681
|
attributes: true,
|
|
542
682
|
characterData: true,
|
|
543
683
|
});
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
684
|
+
resizeObserver = connectResizeObserver(node, () => {
|
|
685
|
+
// throttledRefreshAndVisibility(node, nodeProvider);
|
|
686
|
+
console.log("resizeObserver", node);
|
|
687
|
+
refreshHighlightFrame(node, nodeProvider);
|
|
688
|
+
updateHighlightFrameVisibility(node);
|
|
547
689
|
});
|
|
548
690
|
}
|
|
549
691
|
selectedNode = node;
|
|
550
692
|
sendPostMessage("selectedNodeChanged", node?.getAttribute("data-node-id") ?? null);
|
|
551
|
-
highlightNode(node
|
|
693
|
+
highlightNode(node) ?? null;
|
|
552
694
|
if (node && nodeProvider) {
|
|
553
|
-
updateHighlightFrameVisibility(node
|
|
695
|
+
updateHighlightFrameVisibility(node);
|
|
554
696
|
}
|
|
555
697
|
};
|
|
556
698
|
// Setup event listener
|
|
@@ -558,22 +700,25 @@
|
|
|
558
700
|
const cleanup = () => {
|
|
559
701
|
removeListeners();
|
|
560
702
|
resizeObserver?.disconnect();
|
|
561
|
-
nodeResizeObserver?.disconnect();
|
|
562
703
|
mutationObserver?.disconnect();
|
|
563
704
|
text.blurEditMode();
|
|
564
|
-
|
|
705
|
+
throttledRefreshAndVisibility.cleanup();
|
|
565
706
|
};
|
|
566
707
|
const nodeTools = {
|
|
567
708
|
selectNode,
|
|
568
709
|
getSelectedNode: () => selectedNode,
|
|
569
710
|
refreshHighlightFrame: () => {
|
|
570
|
-
|
|
711
|
+
if (selectedNode && nodeProvider) {
|
|
712
|
+
// Call directly (not throttled) since this is typically called from already-throttled contexts
|
|
713
|
+
// to avoid double RAF
|
|
714
|
+
refreshHighlightFrame(selectedNode, nodeProvider);
|
|
715
|
+
updateHighlightFrameVisibility(selectedNode);
|
|
716
|
+
}
|
|
571
717
|
},
|
|
572
718
|
clearSelectedNode: () => {
|
|
573
|
-
clearHighlightFrame(
|
|
719
|
+
clearHighlightFrame();
|
|
574
720
|
selectedNode = null;
|
|
575
721
|
resizeObserver?.disconnect();
|
|
576
|
-
nodeResizeObserver?.disconnect();
|
|
577
722
|
mutationObserver?.disconnect();
|
|
578
723
|
},
|
|
579
724
|
getEditableNode: () => text.getEditableNode(),
|