@node-edit-utils/core 2.0.4 → 2.0.6

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.
@@ -0,0 +1 @@
1
+ export declare const updateHighlightFrameVisibility: (node: HTMLElement, nodeProvider: HTMLElement) => void;
@@ -0,0 +1 @@
1
+ export declare const processPostMessage: (event: MessageEvent, onNodeSelected?: (node: HTMLElement | null) => void) => void;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Markup Canvas
3
3
  * High-performance markup canvas with zoom and pan capabilities
4
- * @version 2.0.4
4
+ * @version 2.0.6
5
5
  */
6
6
  'use strict';
7
7
 
@@ -97,13 +97,24 @@ const bindToWindow = (key, value) => {
97
97
  }
98
98
  };
99
99
 
100
- const handlePostMessage = (event) => {
100
+ const processPostMessage = (event, onNodeSelected) => {
101
101
  if (event.data.source === "markup-canvas" && event.data.canvasName === "canvas") {
102
102
  if (event.data.action === "zoom") {
103
103
  const zoom = event.data.data;
104
104
  console.log("zoom", zoom);
105
105
  }
106
106
  }
107
+ if (event.data.source === "application") {
108
+ if (event.data.action === "selectedNodeChanged") {
109
+ const nodeId = event.data.data;
110
+ console.log("selectedNodeChanged in node-edit-utils", nodeId);
111
+ const selectedNode = document.querySelector(`[data-node-id="${nodeId}"]`);
112
+ if (selectedNode) {
113
+ console.log("selectedNode", selectedNode);
114
+ onNodeSelected?.(selectedNode);
115
+ }
116
+ }
117
+ }
107
118
  };
108
119
 
109
120
  function getHighlightFrameElement(nodeProvider) {
@@ -145,7 +156,7 @@ const selectNode = (event, editableNode) => {
145
156
  const clickX = event.clientX;
146
157
  const clickY = event.clientY;
147
158
  const clickThrough = event.metaKey || event.ctrlKey;
148
- const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()));
159
+ const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()) && !element.classList.contains("select-none"));
149
160
  if (editableNode && candidates.includes(editableNode)) {
150
161
  return editableNode;
151
162
  }
@@ -180,7 +191,7 @@ const handleNodeClick = (event, nodeProvider, editableNode, onNodeSelected) => {
180
191
 
181
192
  const setupEventListener$1 = (nodeProvider, onNodeSelected, onEscapePressed, getEditableNode) => {
182
193
  const messageHandler = (event) => {
183
- handlePostMessage(event);
194
+ processPostMessage(event, onNodeSelected);
184
195
  };
185
196
  const documentClickHandler = (event) => {
186
197
  handleNodeClick(event, nodeProvider, getEditableNode(), onNodeSelected);
@@ -293,6 +304,14 @@ const refreshHighlightFrame = (node, nodeProvider) => {
293
304
  frame.style.setProperty("--frame-height", `${height}px`);
294
305
  };
295
306
 
307
+ const updateHighlightFrameVisibility = (node, nodeProvider) => {
308
+ const frame = getHighlightFrameElement(nodeProvider);
309
+ if (!frame)
310
+ return;
311
+ const hasHiddenClass = node.classList.contains("hidden") || node.classList.contains("select-none");
312
+ frame.style.display = hasHiddenClass ? "none" : "";
313
+ };
314
+
296
315
  const disableCanvasKeyboard = () => {
297
316
  const disable = getCanvasWindowValue(["keyboard", "disable"]);
298
317
  disable?.();
@@ -429,6 +448,7 @@ const nodeText = () => {
429
448
  const createNodeTools = (element) => {
430
449
  const nodeProvider = element;
431
450
  let resizeObserver = null;
451
+ let mutationObserver = null;
432
452
  let selectedNode = null;
433
453
  const text = nodeText();
434
454
  const throttledFrameRefresh = withRAFThrottle(refreshHighlightFrame);
@@ -441,10 +461,14 @@ const createNodeTools = (element) => {
441
461
  clearHighlightFrame(nodeProvider);
442
462
  selectedNode = null;
443
463
  resizeObserver?.disconnect();
464
+ mutationObserver?.disconnect();
444
465
  }
445
466
  }
446
467
  };
447
468
  const selectNode = (node) => {
469
+ if (selectedNode === node) {
470
+ return;
471
+ }
448
472
  if (text.isEditing()) {
449
473
  const currentEditable = text.getEditableNode();
450
474
  if (currentEditable && currentEditable !== node) {
@@ -452,21 +476,34 @@ const createNodeTools = (element) => {
452
476
  }
453
477
  }
454
478
  resizeObserver?.disconnect();
479
+ mutationObserver?.disconnect();
455
480
  if (node && nodeProvider) {
456
481
  text.enableEditMode(node, nodeProvider);
457
482
  resizeObserver = connectResizeObserver(nodeProvider, () => {
458
483
  throttledFrameRefresh(node, nodeProvider);
459
484
  });
485
+ mutationObserver = new MutationObserver(() => {
486
+ throttledFrameRefresh(node, nodeProvider);
487
+ updateHighlightFrameVisibility(node, nodeProvider);
488
+ });
489
+ mutationObserver.observe(node, {
490
+ attributes: true,
491
+ attributeFilter: ["class"],
492
+ });
460
493
  }
461
494
  selectedNode = node;
462
495
  sendPostMessage("selectedNodeChanged", node?.getAttribute("data-node-id") ?? null);
463
496
  highlightNode(node, nodeProvider) ?? null;
497
+ if (node && nodeProvider) {
498
+ updateHighlightFrameVisibility(node, nodeProvider);
499
+ }
464
500
  };
465
501
  // Setup event listener
466
502
  const removeListeners = setupEventListener$1(nodeProvider, selectNode, handleEscape, text.getEditableNode);
467
503
  const cleanup = () => {
468
504
  removeListeners();
469
505
  resizeObserver?.disconnect();
506
+ mutationObserver?.disconnect();
470
507
  text.blurEditMode();
471
508
  throttledFrameRefresh.cleanup();
472
509
  };
@@ -480,6 +517,7 @@ const createNodeTools = (element) => {
480
517
  clearHighlightFrame(nodeProvider);
481
518
  selectedNode = null;
482
519
  resizeObserver?.disconnect();
520
+ mutationObserver?.disconnect();
483
521
  },
484
522
  getEditableNode: () => text.getEditableNode(),
485
523
  cleanup,
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Markup Canvas
3
3
  * High-performance markup canvas with zoom and pan capabilities
4
- * @version 2.0.4
4
+ * @version 2.0.6
5
5
  */
6
6
  // biome-ignore lint/suspicious/noExplicitAny: generic constraint requires flexibility
7
7
  function withRAFThrottle(func) {
@@ -95,13 +95,24 @@ const bindToWindow = (key, value) => {
95
95
  }
96
96
  };
97
97
 
98
- const handlePostMessage = (event) => {
98
+ const processPostMessage = (event, onNodeSelected) => {
99
99
  if (event.data.source === "markup-canvas" && event.data.canvasName === "canvas") {
100
100
  if (event.data.action === "zoom") {
101
101
  const zoom = event.data.data;
102
102
  console.log("zoom", zoom);
103
103
  }
104
104
  }
105
+ if (event.data.source === "application") {
106
+ if (event.data.action === "selectedNodeChanged") {
107
+ const nodeId = event.data.data;
108
+ console.log("selectedNodeChanged in node-edit-utils", nodeId);
109
+ const selectedNode = document.querySelector(`[data-node-id="${nodeId}"]`);
110
+ if (selectedNode) {
111
+ console.log("selectedNode", selectedNode);
112
+ onNodeSelected?.(selectedNode);
113
+ }
114
+ }
115
+ }
105
116
  };
106
117
 
107
118
  function getHighlightFrameElement(nodeProvider) {
@@ -143,7 +154,7 @@ const selectNode = (event, editableNode) => {
143
154
  const clickX = event.clientX;
144
155
  const clickY = event.clientY;
145
156
  const clickThrough = event.metaKey || event.ctrlKey;
146
- const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()));
157
+ const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()) && !element.classList.contains("select-none"));
147
158
  if (editableNode && candidates.includes(editableNode)) {
148
159
  return editableNode;
149
160
  }
@@ -178,7 +189,7 @@ const handleNodeClick = (event, nodeProvider, editableNode, onNodeSelected) => {
178
189
 
179
190
  const setupEventListener$1 = (nodeProvider, onNodeSelected, onEscapePressed, getEditableNode) => {
180
191
  const messageHandler = (event) => {
181
- handlePostMessage(event);
192
+ processPostMessage(event, onNodeSelected);
182
193
  };
183
194
  const documentClickHandler = (event) => {
184
195
  handleNodeClick(event, nodeProvider, getEditableNode(), onNodeSelected);
@@ -291,6 +302,14 @@ const refreshHighlightFrame = (node, nodeProvider) => {
291
302
  frame.style.setProperty("--frame-height", `${height}px`);
292
303
  };
293
304
 
305
+ const updateHighlightFrameVisibility = (node, nodeProvider) => {
306
+ const frame = getHighlightFrameElement(nodeProvider);
307
+ if (!frame)
308
+ return;
309
+ const hasHiddenClass = node.classList.contains("hidden") || node.classList.contains("select-none");
310
+ frame.style.display = hasHiddenClass ? "none" : "";
311
+ };
312
+
294
313
  const disableCanvasKeyboard = () => {
295
314
  const disable = getCanvasWindowValue(["keyboard", "disable"]);
296
315
  disable?.();
@@ -427,6 +446,7 @@ const nodeText = () => {
427
446
  const createNodeTools = (element) => {
428
447
  const nodeProvider = element;
429
448
  let resizeObserver = null;
449
+ let mutationObserver = null;
430
450
  let selectedNode = null;
431
451
  const text = nodeText();
432
452
  const throttledFrameRefresh = withRAFThrottle(refreshHighlightFrame);
@@ -439,10 +459,14 @@ const createNodeTools = (element) => {
439
459
  clearHighlightFrame(nodeProvider);
440
460
  selectedNode = null;
441
461
  resizeObserver?.disconnect();
462
+ mutationObserver?.disconnect();
442
463
  }
443
464
  }
444
465
  };
445
466
  const selectNode = (node) => {
467
+ if (selectedNode === node) {
468
+ return;
469
+ }
446
470
  if (text.isEditing()) {
447
471
  const currentEditable = text.getEditableNode();
448
472
  if (currentEditable && currentEditable !== node) {
@@ -450,21 +474,34 @@ const createNodeTools = (element) => {
450
474
  }
451
475
  }
452
476
  resizeObserver?.disconnect();
477
+ mutationObserver?.disconnect();
453
478
  if (node && nodeProvider) {
454
479
  text.enableEditMode(node, nodeProvider);
455
480
  resizeObserver = connectResizeObserver(nodeProvider, () => {
456
481
  throttledFrameRefresh(node, nodeProvider);
457
482
  });
483
+ mutationObserver = new MutationObserver(() => {
484
+ throttledFrameRefresh(node, nodeProvider);
485
+ updateHighlightFrameVisibility(node, nodeProvider);
486
+ });
487
+ mutationObserver.observe(node, {
488
+ attributes: true,
489
+ attributeFilter: ["class"],
490
+ });
458
491
  }
459
492
  selectedNode = node;
460
493
  sendPostMessage("selectedNodeChanged", node?.getAttribute("data-node-id") ?? null);
461
494
  highlightNode(node, nodeProvider) ?? null;
495
+ if (node && nodeProvider) {
496
+ updateHighlightFrameVisibility(node, nodeProvider);
497
+ }
462
498
  };
463
499
  // Setup event listener
464
500
  const removeListeners = setupEventListener$1(nodeProvider, selectNode, handleEscape, text.getEditableNode);
465
501
  const cleanup = () => {
466
502
  removeListeners();
467
503
  resizeObserver?.disconnect();
504
+ mutationObserver?.disconnect();
468
505
  text.blurEditMode();
469
506
  throttledFrameRefresh.cleanup();
470
507
  };
@@ -478,6 +515,7 @@ const createNodeTools = (element) => {
478
515
  clearHighlightFrame(nodeProvider);
479
516
  selectedNode = null;
480
517
  resizeObserver?.disconnect();
518
+ mutationObserver?.disconnect();
481
519
  },
482
520
  getEditableNode: () => text.getEditableNode(),
483
521
  cleanup,
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Markup Canvas
3
3
  * High-performance markup canvas with zoom and pan capabilities
4
- * @version 2.0.4
4
+ * @version 2.0.6
5
5
  */
6
6
  (function (global, factory) {
7
7
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
@@ -101,13 +101,24 @@
101
101
  }
102
102
  };
103
103
 
104
- const handlePostMessage = (event) => {
104
+ const processPostMessage = (event, onNodeSelected) => {
105
105
  if (event.data.source === "markup-canvas" && event.data.canvasName === "canvas") {
106
106
  if (event.data.action === "zoom") {
107
107
  const zoom = event.data.data;
108
108
  console.log("zoom", zoom);
109
109
  }
110
110
  }
111
+ if (event.data.source === "application") {
112
+ if (event.data.action === "selectedNodeChanged") {
113
+ const nodeId = event.data.data;
114
+ console.log("selectedNodeChanged in node-edit-utils", nodeId);
115
+ const selectedNode = document.querySelector(`[data-node-id="${nodeId}"]`);
116
+ if (selectedNode) {
117
+ console.log("selectedNode", selectedNode);
118
+ onNodeSelected?.(selectedNode);
119
+ }
120
+ }
121
+ }
111
122
  };
112
123
 
113
124
  function getHighlightFrameElement(nodeProvider) {
@@ -149,7 +160,7 @@
149
160
  const clickX = event.clientX;
150
161
  const clickY = event.clientY;
151
162
  const clickThrough = event.metaKey || event.ctrlKey;
152
- const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()));
163
+ const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()) && !element.classList.contains("select-none"));
153
164
  if (editableNode && candidates.includes(editableNode)) {
154
165
  return editableNode;
155
166
  }
@@ -184,7 +195,7 @@
184
195
 
185
196
  const setupEventListener$1 = (nodeProvider, onNodeSelected, onEscapePressed, getEditableNode) => {
186
197
  const messageHandler = (event) => {
187
- handlePostMessage(event);
198
+ processPostMessage(event, onNodeSelected);
188
199
  };
189
200
  const documentClickHandler = (event) => {
190
201
  handleNodeClick(event, nodeProvider, getEditableNode(), onNodeSelected);
@@ -297,6 +308,14 @@
297
308
  frame.style.setProperty("--frame-height", `${height}px`);
298
309
  };
299
310
 
311
+ const updateHighlightFrameVisibility = (node, nodeProvider) => {
312
+ const frame = getHighlightFrameElement(nodeProvider);
313
+ if (!frame)
314
+ return;
315
+ const hasHiddenClass = node.classList.contains("hidden") || node.classList.contains("select-none");
316
+ frame.style.display = hasHiddenClass ? "none" : "";
317
+ };
318
+
300
319
  const disableCanvasKeyboard = () => {
301
320
  const disable = getCanvasWindowValue(["keyboard", "disable"]);
302
321
  disable?.();
@@ -433,6 +452,7 @@
433
452
  const createNodeTools = (element) => {
434
453
  const nodeProvider = element;
435
454
  let resizeObserver = null;
455
+ let mutationObserver = null;
436
456
  let selectedNode = null;
437
457
  const text = nodeText();
438
458
  const throttledFrameRefresh = withRAFThrottle(refreshHighlightFrame);
@@ -445,10 +465,14 @@
445
465
  clearHighlightFrame(nodeProvider);
446
466
  selectedNode = null;
447
467
  resizeObserver?.disconnect();
468
+ mutationObserver?.disconnect();
448
469
  }
449
470
  }
450
471
  };
451
472
  const selectNode = (node) => {
473
+ if (selectedNode === node) {
474
+ return;
475
+ }
452
476
  if (text.isEditing()) {
453
477
  const currentEditable = text.getEditableNode();
454
478
  if (currentEditable && currentEditable !== node) {
@@ -456,21 +480,34 @@
456
480
  }
457
481
  }
458
482
  resizeObserver?.disconnect();
483
+ mutationObserver?.disconnect();
459
484
  if (node && nodeProvider) {
460
485
  text.enableEditMode(node, nodeProvider);
461
486
  resizeObserver = connectResizeObserver(nodeProvider, () => {
462
487
  throttledFrameRefresh(node, nodeProvider);
463
488
  });
489
+ mutationObserver = new MutationObserver(() => {
490
+ throttledFrameRefresh(node, nodeProvider);
491
+ updateHighlightFrameVisibility(node, nodeProvider);
492
+ });
493
+ mutationObserver.observe(node, {
494
+ attributes: true,
495
+ attributeFilter: ["class"],
496
+ });
464
497
  }
465
498
  selectedNode = node;
466
499
  sendPostMessage("selectedNodeChanged", node?.getAttribute("data-node-id") ?? null);
467
500
  highlightNode(node, nodeProvider) ?? null;
501
+ if (node && nodeProvider) {
502
+ updateHighlightFrameVisibility(node, nodeProvider);
503
+ }
468
504
  };
469
505
  // Setup event listener
470
506
  const removeListeners = setupEventListener$1(nodeProvider, selectNode, handleEscape, text.getEditableNode);
471
507
  const cleanup = () => {
472
508
  removeListeners();
473
509
  resizeObserver?.disconnect();
510
+ mutationObserver?.disconnect();
474
511
  text.blurEditMode();
475
512
  throttledFrameRefresh.cleanup();
476
513
  };
@@ -484,6 +521,7 @@
484
521
  clearHighlightFrame(nodeProvider);
485
522
  selectedNode = null;
486
523
  resizeObserver?.disconnect();
524
+ mutationObserver?.disconnect();
487
525
  },
488
526
  getEditableNode: () => text.getEditableNode(),
489
527
  cleanup,
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).MarkupCanvas={})}(this,function(e){"use strict";function t(e){let t=null,n=null;const o=(...o)=>{n=o,null===t&&(t=requestAnimationFrame(()=>{n&&e(...n),t=null,n=null}))};return o.cleanup=()=>{null!==t&&(cancelAnimationFrame(t),t=null,n=null)},o}const n=e=>{const t=window.canvas;return e.reduce((e,t)=>e?.[t],t)};function o(e){return e.querySelector(".highlight-frame")}const r=e=>{if(!e)return;const t=o(e);t&&t.remove()},s=["path","rect","circle","ellipse","polygon","line","polyline","text","text-noci"];let a=[],i=0;const l=(e,t)=>{let n=null;const o=e.clientX,r=e.clientY,l=e.metaKey||e.ctrlKey,d=((e,t)=>{const n=document.elementsFromPoint(e,t);return Array.from(n).reduce((e,t)=>e.found?e:"node-provider"===t.getAttribute("data-role")?(e.found=!0,e):(e.elements.push(t),e),{elements:[],found:!1}).elements})(o,r).filter(e=>!s.includes(e.tagName.toLowerCase()));if(t&&d.includes(t))return t;if(l)return a=[],n=d[0],n;var c,u;u=d,(c=a).length===u.length&&c.every((e,t)=>e===u[t])?i<=d.length&&i++:i=0;return n=d[d.length-1-i],a=d,n},d=(e,t,n,o)=>{const s=e=>{(e=>{if("markup-canvas"===e.data.source&&"canvas"===e.data.canvasName&&"zoom"===e.data.action){const t=e.data.data;console.log("zoom",t)}})(e)},a=n=>{((e,t,n,o)=>{if(e.preventDefault(),e.stopPropagation(),t&&!t.contains(e.target))return r(t),void o(null);o(l(e,n))})(n,e,o(),t)},i=e=>{"Escape"===e.key&&(e.preventDefault(),e.stopPropagation(),n?.())};return window.addEventListener("message",s),document.addEventListener("click",a),document.addEventListener("keydown",i),()=>{window.removeEventListener("message",s),document.removeEventListener("click",a),document.removeEventListener("keydown",i)}};function c(e,t){const o=e.getBoundingClientRect(),r=t.getBoundingClientRect(),s=o.top-r.top,a=o.left-r.left,i=n(["zoom","current"])??1;return{top:parseFloat((s/i).toFixed(5)),left:parseFloat((a/i).toFixed(5)),width:Math.max(4,parseFloat((o.width/i).toFixed(5))),height:parseFloat((o.height/i).toFixed(5))}}const u=(e,t)=>{const n=document.createElement("div");n.className="node-tools",t.appendChild(n),((e,t)=>{const n=document.createElement("div");n.className="tag-label",n.textContent=e.tagName.toLowerCase(),t.appendChild(n)})(e,n)},m=(e,t)=>{if(!e)return;const r=o(t);r&&r.remove();const s=((e,t)=>{const{top:o,left:r,width:s,height:a}=c(e,t),i=n(["zoom","current"])??1;document.body.style.setProperty("--zoom",i.toString()),document.body.style.setProperty("--stroke-width",(2/i).toFixed(3));const l=document.createElement("div");l.classList.add("highlight-frame"),l.style.setProperty("--frame-top",`${o}px`),l.style.setProperty("--frame-left",`${r}px`),l.style.setProperty("--frame-width",`${s}px`),l.style.setProperty("--frame-height",`${a}px`);const d=document.createElementNS("http://www.w3.org/2000/svg","svg");d.classList.add("highlight-frame-svg");const u=document.createElementNS("http://www.w3.org/2000/svg","rect");return u.setAttribute("x","0"),u.setAttribute("y","0"),u.setAttribute("width","100%"),u.setAttribute("height","100%"),u.classList.add("highlight-frame-rect"),d.appendChild(u),l.appendChild(d),l})(e,t);"true"===e.contentEditable&&s.classList.add("is-editable"),u(e,s),t.appendChild(s)},p=(e,t)=>{const r=o(t),s=n(["zoom","current"])??1;if(!r)return;s>=.3?t.style.setProperty("--tool-opacity","1"):t.style.setProperty("--tool-opacity","0");const{top:a,left:i,width:l,height:d}=c(e,t);r.style.setProperty("--frame-top",`${a}px`),r.style.setProperty("--frame-left",`${i}px`),r.style.setProperty("--frame-width",`${l}px`),r.style.setProperty("--frame-height",`${d}px`)},v=e=>{const t=e=>{"Enter"===e.key&&(e.preventDefault(),e.stopPropagation(),(()=>{const e=window.getSelection();if(e&&e.rangeCount>0){const t=e.getRangeAt(0);t.deleteContents();const n=document.createElement("br");t.insertNode(n),t.setStartAfter(n),t.setEndAfter(n),e.removeAllRanges(),e.addRange(t)}})())};return e.addEventListener("keydown",t),()=>{e.removeEventListener("keydown",t)}},h=(e,t)=>{const n=((e,t)=>{const n=new MutationObserver(e=>{t(e)});return n.observe(e,{subtree:!0,childList:!0,characterData:!0}),n})(e,()=>{p(e,t)});return()=>n.disconnect()},f=()=>{let e=null,t=!1,o=null;const r=()=>{if(t||!e)return;t=!0;var r;(r=e).contentEditable="false",r.classList.remove("is-editable"),r.style.outline="none",(()=>{const e=n(["keyboard","enable"]);e?.()})(),o?.(),e=null,t=!1};return{enableEditMode:(t,s)=>{if(e===t)return;e&&e!==t&&r();const a=(e=>Array.from(e.childNodes).some(e=>e.nodeType===Node.TEXT_NODE&&e.textContent?.trim()))(t);a&&(e=t,(e=>{e.contentEditable="true",e.classList.add("is-editable"),e.style.outline="none"})(t),(()=>{const e=n(["keyboard","disable"]);e?.()})(),o=((e,t,n)=>{if(!t)return()=>{};e.addEventListener("blur",n);const o=v(e),r=h(e,t);return()=>{e.removeEventListener("blur",n),o(),r?.()}})(t,s,r))},blurEditMode:r,getEditableNode:()=>e,isEditing:()=>null!==e}},y=320,g=1680,b=[{name:"Mobile",rawValue:390,value:"320px"},{name:"Tablet Portrait",rawValue:768,value:"768px"},{name:"Tablet Landscape",rawValue:1024,value:"1024px"},{name:"Notebook",rawValue:1280,value:"1280px"},{name:"Desktop",rawValue:1680,value:"1680px"}],w=(e,t,n)=>{const o=parseFloat(document.body.dataset.zoom||"1"),r=((e,t)=>{const n=e+Math.round(t);return Math.max(y,Math.min(g,n))})(n,(e.clientX-t)/o);return r},E=(e,t)=>{e.style.setProperty("--container-width",`${t}px`),((e,t)=>{console.log("updateActivePreset",t);const n=e.querySelectorAll(".resize-preset-button");console.log("presetButtons",n),n.forEach((e,n)=>{n<b.length&&(b[n].rawValue===t?e.classList.add("is-active"):e.classList.remove("is-active"))})})(e,t)};e.createCanvasObserver=function(){const e=document.querySelector(".transform-layer");if(!e)return{disconnect:()=>{}};const o=t(()=>{(()=>{const e=n(["zoom","current"]);document.body.style.setProperty("--zoom",e.toFixed(5)),document.body.style.setProperty("--stroke-width",(2/e).toFixed(3)),document.body.dataset.zoom=e.toFixed(5),document.body.dataset.strokeWidth=(2/e).toFixed(3)})()}),r=new MutationObserver(()=>{o()});return r.observe(e,{attributes:!0,attributeOldValue:!0,subtree:!0,childList:!0}),{disconnect:function(){o.cleanup(),r.disconnect()}}},e.createNodeTools=e=>{const n=e;let o=null,s=null;const a=f(),i=t(p),l=e=>{if(a.isEditing()){const t=a.getEditableNode();t&&t!==e&&a.blurEditMode()}var t,r;o?.disconnect(),e&&n&&(a.enableEditMode(e,n),o=((e,t)=>{const n=new ResizeObserver(e=>{t(e)});return n.observe(e),n})(n,()=>{i(e,n)})),s=e,t="selectedNodeChanged",r=e?.getAttribute("data-node-id")??null,window.parent.postMessage({source:"node-edit-utils",action:t,data:r,timestamp:Date.now()},"*"),m(e,n)},c=d(n,l,()=>{a.isEditing()&&a.blurEditMode(),s&&n&&(r(n),s=null,o?.disconnect())},a.getEditableNode),u={selectNode:l,getSelectedNode:()=>s,refreshHighlightFrame:()=>{i(s,n)},clearSelectedNode:()=>{r(n),s=null,o?.disconnect()},getEditableNode:()=>a.getEditableNode(),cleanup:()=>{c(),o?.disconnect(),a.blurEditMode(),i.cleanup()}};var v,h;return v="nodeTools",h=u,"undefined"!=typeof window&&(window[v]=h),u},e.createViewport=e=>{const n=document.querySelector(".canvas-container"),o=e.querySelector(".resize-handle");o&&o.remove();const r=(e=>{const t=document.createElement("div");return t.className="resize-handle",e.appendChild(t),t})(e);e.style.setProperty("--container-width","400px"),((e,t,n)=>{const o=document.createElement("div");o.className="resize-presets",b.forEach(e=>{const r=document.createElement("button");r.textContent=e.name,r.className="resize-preset-button",r.addEventListener("click",()=>{n(t,e.rawValue)}),o.appendChild(r)}),e.appendChild(o)})(r,e,E);let s=!1,a=0,i=0;const l=t(t=>{if(!s)return;n&&(n.style.cursor="ew-resize");const o=w(t,a,i);E(e,o)}),d=((e,t,n,o,r)=>(e.addEventListener("mousedown",t),document.addEventListener("mousemove",n),document.addEventListener("mouseup",o),window.addEventListener("blur",r),()=>{e.removeEventListener("mousedown",t),document.removeEventListener("mousemove",n),document.removeEventListener("mouseup",o),window.removeEventListener("blur",r)}))(r,t=>{t.preventDefault(),t.stopPropagation(),s=!0,a=t.clientX,i=e.offsetWidth},l,e=>{e.preventDefault(),e.stopPropagation(),n&&(n.style.cursor="default"),s=!1},()=>{s=!1});return{setWidth:t=>{E(e,t)},cleanup:()=>{s=!1,l?.cleanup(),d(),r.remove()}}}});
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).MarkupCanvas={})}(this,function(e){"use strict";function t(e){let t=null,n=null;const o=(...o)=>{n=o,null===t&&(t=requestAnimationFrame(()=>{n&&e(...n),t=null,n=null}))};return o.cleanup=()=>{null!==t&&(cancelAnimationFrame(t),t=null,n=null)},o}const n=e=>{const t=window.canvas;return e.reduce((e,t)=>e?.[t],t)};function o(e){return e.querySelector(".highlight-frame")}const r=e=>{if(!e)return;const t=o(e);t&&t.remove()},s=["path","rect","circle","ellipse","polygon","line","polyline","text","text-noci"];let a=[],i=0;const l=(e,t)=>{let n=null;const o=e.clientX,r=e.clientY,l=e.metaKey||e.ctrlKey,d=((e,t)=>{const n=document.elementsFromPoint(e,t);return Array.from(n).reduce((e,t)=>e.found?e:"node-provider"===t.getAttribute("data-role")?(e.found=!0,e):(e.elements.push(t),e),{elements:[],found:!1}).elements})(o,r).filter(e=>!s.includes(e.tagName.toLowerCase())&&!e.classList.contains("select-none"));if(t&&d.includes(t))return t;if(l)return a=[],n=d[0],n;var c,u;u=d,(c=a).length===u.length&&c.every((e,t)=>e===u[t])?i<=d.length&&i++:i=0;return n=d[d.length-1-i],a=d,n},d=(e,t,n,o)=>{const s=e=>{((e,t)=>{if("markup-canvas"===e.data.source&&"canvas"===e.data.canvasName&&"zoom"===e.data.action){const t=e.data.data;console.log("zoom",t)}if("application"===e.data.source&&"selectedNodeChanged"===e.data.action){const n=e.data.data;console.log("selectedNodeChanged in node-edit-utils",n);const o=document.querySelector(`[data-node-id="${n}"]`);o&&(console.log("selectedNode",o),t?.(o))}})(e,t)},a=n=>{((e,t,n,o)=>{if(e.preventDefault(),e.stopPropagation(),t&&!t.contains(e.target))return r(t),void o(null);o(l(e,n))})(n,e,o(),t)},i=e=>{"Escape"===e.key&&(e.preventDefault(),e.stopPropagation(),n?.())};return window.addEventListener("message",s),document.addEventListener("click",a),document.addEventListener("keydown",i),()=>{window.removeEventListener("message",s),document.removeEventListener("click",a),document.removeEventListener("keydown",i)}};function c(e,t){const o=e.getBoundingClientRect(),r=t.getBoundingClientRect(),s=o.top-r.top,a=o.left-r.left,i=n(["zoom","current"])??1;return{top:parseFloat((s/i).toFixed(5)),left:parseFloat((a/i).toFixed(5)),width:Math.max(4,parseFloat((o.width/i).toFixed(5))),height:parseFloat((o.height/i).toFixed(5))}}const u=(e,t)=>{const n=document.createElement("div");n.className="node-tools",t.appendChild(n),((e,t)=>{const n=document.createElement("div");n.className="tag-label",n.textContent=e.tagName.toLowerCase(),t.appendChild(n)})(e,n)},m=(e,t)=>{if(!e)return;const r=o(t);r&&r.remove();const s=((e,t)=>{const{top:o,left:r,width:s,height:a}=c(e,t),i=n(["zoom","current"])??1;document.body.style.setProperty("--zoom",i.toString()),document.body.style.setProperty("--stroke-width",(2/i).toFixed(3));const l=document.createElement("div");l.classList.add("highlight-frame"),l.style.setProperty("--frame-top",`${o}px`),l.style.setProperty("--frame-left",`${r}px`),l.style.setProperty("--frame-width",`${s}px`),l.style.setProperty("--frame-height",`${a}px`);const d=document.createElementNS("http://www.w3.org/2000/svg","svg");d.classList.add("highlight-frame-svg");const u=document.createElementNS("http://www.w3.org/2000/svg","rect");return u.setAttribute("x","0"),u.setAttribute("y","0"),u.setAttribute("width","100%"),u.setAttribute("height","100%"),u.classList.add("highlight-frame-rect"),d.appendChild(u),l.appendChild(d),l})(e,t);"true"===e.contentEditable&&s.classList.add("is-editable"),u(e,s),t.appendChild(s)},p=(e,t)=>{const r=o(t),s=n(["zoom","current"])??1;if(!r)return;s>=.3?t.style.setProperty("--tool-opacity","1"):t.style.setProperty("--tool-opacity","0");const{top:a,left:i,width:l,height:d}=c(e,t);r.style.setProperty("--frame-top",`${a}px`),r.style.setProperty("--frame-left",`${i}px`),r.style.setProperty("--frame-width",`${l}px`),r.style.setProperty("--frame-height",`${d}px`)},v=(e,t)=>{const n=o(t);if(!n)return;const r=e.classList.contains("hidden")||e.classList.contains("select-none");n.style.display=r?"none":""},h=e=>{const t=e=>{"Enter"===e.key&&(e.preventDefault(),e.stopPropagation(),(()=>{const e=window.getSelection();if(e&&e.rangeCount>0){const t=e.getRangeAt(0);t.deleteContents();const n=document.createElement("br");t.insertNode(n),t.setStartAfter(n),t.setEndAfter(n),e.removeAllRanges(),e.addRange(t)}})())};return e.addEventListener("keydown",t),()=>{e.removeEventListener("keydown",t)}},f=(e,t)=>{const n=((e,t)=>{const n=new MutationObserver(e=>{t(e)});return n.observe(e,{subtree:!0,childList:!0,characterData:!0}),n})(e,()=>{p(e,t)});return()=>n.disconnect()},y=()=>{let e=null,t=!1,o=null;const r=()=>{if(t||!e)return;t=!0;var r;(r=e).contentEditable="false",r.classList.remove("is-editable"),r.style.outline="none",(()=>{const e=n(["keyboard","enable"]);e?.()})(),o?.(),e=null,t=!1};return{enableEditMode:(t,s)=>{if(e===t)return;e&&e!==t&&r();const a=(e=>Array.from(e.childNodes).some(e=>e.nodeType===Node.TEXT_NODE&&e.textContent?.trim()))(t);a&&(e=t,(e=>{e.contentEditable="true",e.classList.add("is-editable"),e.style.outline="none"})(t),(()=>{const e=n(["keyboard","disable"]);e?.()})(),o=((e,t,n)=>{if(!t)return()=>{};e.addEventListener("blur",n);const o=h(e),r=f(e,t);return()=>{e.removeEventListener("blur",n),o(),r?.()}})(t,s,r))},blurEditMode:r,getEditableNode:()=>e,isEditing:()=>null!==e}},g=320,b=1680,w=[{name:"Mobile",rawValue:390,value:"320px"},{name:"Tablet Portrait",rawValue:768,value:"768px"},{name:"Tablet Landscape",rawValue:1024,value:"1024px"},{name:"Notebook",rawValue:1280,value:"1280px"},{name:"Desktop",rawValue:1680,value:"1680px"}],E=(e,t,n)=>{const o=parseFloat(document.body.dataset.zoom||"1"),r=((e,t)=>{const n=e+Math.round(t);return Math.max(g,Math.min(b,n))})(n,(e.clientX-t)/o);return r},x=(e,t)=>{e.style.setProperty("--container-width",`${t}px`),((e,t)=>{console.log("updateActivePreset",t);const n=e.querySelectorAll(".resize-preset-button");console.log("presetButtons",n),n.forEach((e,n)=>{n<w.length&&(w[n].rawValue===t?e.classList.add("is-active"):e.classList.remove("is-active"))})})(e,t)};e.createCanvasObserver=function(){const e=document.querySelector(".transform-layer");if(!e)return{disconnect:()=>{}};const o=t(()=>{(()=>{const e=n(["zoom","current"]);document.body.style.setProperty("--zoom",e.toFixed(5)),document.body.style.setProperty("--stroke-width",(2/e).toFixed(3)),document.body.dataset.zoom=e.toFixed(5),document.body.dataset.strokeWidth=(2/e).toFixed(3)})()}),r=new MutationObserver(()=>{o()});return r.observe(e,{attributes:!0,attributeOldValue:!0,subtree:!0,childList:!0}),{disconnect:function(){o.cleanup(),r.disconnect()}}},e.createNodeTools=e=>{const n=e;let o=null,s=null,a=null;const i=y(),l=t(p),c=e=>{if(a!==e){if(i.isEditing()){const t=i.getEditableNode();t&&t!==e&&i.blurEditMode()}var t,r;o?.disconnect(),s?.disconnect(),e&&n&&(i.enableEditMode(e,n),o=((e,t)=>{const n=new ResizeObserver(e=>{t(e)});return n.observe(e),n})(n,()=>{l(e,n)}),s=new MutationObserver(()=>{l(e,n),v(e,n)}),s.observe(e,{attributes:!0,attributeFilter:["class"]})),a=e,t="selectedNodeChanged",r=e?.getAttribute("data-node-id")??null,window.parent.postMessage({source:"node-edit-utils",action:t,data:r,timestamp:Date.now()},"*"),m(e,n),e&&n&&v(e,n)}},u=d(n,c,()=>{i.isEditing()&&i.blurEditMode(),a&&n&&(r(n),a=null,o?.disconnect(),s?.disconnect())},i.getEditableNode),h={selectNode:c,getSelectedNode:()=>a,refreshHighlightFrame:()=>{l(a,n)},clearSelectedNode:()=>{r(n),a=null,o?.disconnect(),s?.disconnect()},getEditableNode:()=>i.getEditableNode(),cleanup:()=>{u(),o?.disconnect(),s?.disconnect(),i.blurEditMode(),l.cleanup()}};var f,g;return f="nodeTools",g=h,"undefined"!=typeof window&&(window[f]=g),h},e.createViewport=e=>{const n=document.querySelector(".canvas-container"),o=e.querySelector(".resize-handle");o&&o.remove();const r=(e=>{const t=document.createElement("div");return t.className="resize-handle",e.appendChild(t),t})(e);e.style.setProperty("--container-width","400px"),((e,t,n)=>{const o=document.createElement("div");o.className="resize-presets",w.forEach(e=>{const r=document.createElement("button");r.textContent=e.name,r.className="resize-preset-button",r.addEventListener("click",()=>{n(t,e.rawValue)}),o.appendChild(r)}),e.appendChild(o)})(r,e,x);let s=!1,a=0,i=0;const l=t(t=>{if(!s)return;n&&(n.style.cursor="ew-resize");const o=E(t,a,i);x(e,o)}),d=((e,t,n,o,r)=>(e.addEventListener("mousedown",t),document.addEventListener("mousemove",n),document.addEventListener("mouseup",o),window.addEventListener("blur",r),()=>{e.removeEventListener("mousedown",t),document.removeEventListener("mousemove",n),document.removeEventListener("mouseup",o),window.removeEventListener("blur",r)}))(r,t=>{t.preventDefault(),t.stopPropagation(),s=!0,a=t.clientX,i=e.offsetWidth},l,e=>{e.preventDefault(),e.stopPropagation(),n&&(n.style.cursor="default"),s=!1},()=>{s=!1});return{setWidth:t=>{x(e,t)},cleanup:()=>{s=!1,l?.cleanup(),d(),r.remove()}}}});
package/dist/styles.css CHANGED
@@ -1 +1 @@
1
- .node-provider{::selection{background:transparent}::-moz-selection{background:transparent}::-webkit-selection{background:transparent}}.node-tools{bottom:0;display:flex;gap:.25rem;justify-content:center;left:0;opacity:var(--tool-opacity);padding-top:.25rem;position:absolute;transform:scale(calc(1/var(--zoom))) translate3d(0,100%,0);transform-origin:bottom left;transition:opacity .1s ease;z-index:10000}.highlight-frame{height:var(--frame-height);left:var(--frame-left);pointer-events:none;position:absolute;top:var(--frame-top);width:var(--frame-width);z-index:1000}.highlight-frame-svg{height:100%;left:0;overflow:visible;position:absolute;top:0;width:100%}.highlight-frame-rect{fill:none;stroke:oklch(62.7% .265 303.9);stroke-width:var(--stroke-width)}.tag-label{align-items:center;background-color:oklch(62.7% .265 303.9);border-radius:.375rem;color:#fff;display:flex;font-size:.575rem;font-weight:500;height:1.25rem;justify-content:center;line-height:1;padding:0 .375rem;text-transform:uppercase}.viewport{position:relative;width:var(--container-width)}.resize-handle{align-items:center;cursor:ew-resize;display:flex;height:40px;justify-content:center;opacity:0;pointer-events:auto;position:absolute;right:-12px;top:1.25rem;transform:scale(calc(1/var(--zoom)));transform-origin:top left;transition:opacity .1s ease-in-out,visibility .1s ease-in-out;visibility:hidden;width:12px;z-index:10000}.viewport:hover .resize-handle{opacity:1;visibility:visible}.resize-handle:before{background:oklch(55.2% .016 285.938);border-radius:4px;content:"";height:32px;position:relative;right:0;top:0;transition:transform .2s ease-in-out;width:3px}.resize-handle:hover:before{transform:scaleY(1.3)}.resize-presets{display:flex;flex-direction:column;gap:.25rem;left:0;opacity:0;padding-left:1.25rem;position:absolute;top:0;transition:visibility .1s ease-in-out,opacity .1s ease-in-out;visibility:hidden}.resize-handle:hover .resize-presets{opacity:1;visibility:visible}.resize-preset-button{text-wrap:nowrap;backdrop-filter:blur(8px);background:oklch(55.2% .016 285.938/.7);border:none;border-radius:.5rem;color:#fff;cursor:pointer;font-size:.6875rem;padding:.25rem .5rem;text-align:left;transition:background .1s ease-in-out;width:fit-content;&:hover{background:oklch(55.2% .016 285.938/1)}&.is-active{background:oklch(45.7% .24 277.023)}}.is-editable{outline:none;user-select:text;&::selection{background:oklch(62.7% .265 303.9/.3)}&::-moz-selection{background:oklch(62.7% .265 303.9/.3)}&::-webkit-selection{background:oklch(62.7% .265 303.9/.3)}}
1
+ .node-provider{::selection{background:transparent}::-moz-selection{background:transparent}::-webkit-selection{background:transparent}}.node-tools{bottom:0;display:flex;gap:.25rem;justify-content:center;left:0;opacity:var(--tool-opacity);padding-top:.25rem;position:absolute;top:0;transform:scale(calc(1/var(--zoom))) translate3d(0,100%,0);transform-origin:bottom left;transition:opacity .1s ease;z-index:10000}.highlight-frame{font-family:Manrope,sans-serif;height:var(--frame-height);left:var(--frame-left);letter-spacing:.02em;pointer-events:none;position:absolute;top:var(--frame-top);width:var(--frame-width);z-index:1000}.highlight-frame-svg{height:100%;left:0;overflow:visible;position:absolute;top:0;width:100%}.highlight-frame-rect{fill:none;stroke:oklch(45.7% .24 277.023);stroke-width:var(--stroke-width)}.tag-label{align-items:center;background-color:oklch(45.7% .24 277.023);border-radius:.375rem;color:#fff;display:flex;font-size:.575rem;font-weight:500;height:1.25rem;justify-content:center;line-height:1;padding:0 .375rem;text-transform:uppercase}.viewport{position:relative;width:var(--container-width)}.resize-handle{align-items:center;cursor:ew-resize;display:flex;height:40px;justify-content:center;opacity:0;pointer-events:auto;position:absolute;right:-12px;top:1.25rem;transform:scale(calc(1/var(--zoom)));transform-origin:top left;transition:opacity .1s ease-in-out,visibility .1s ease-in-out;visibility:hidden;width:12px;z-index:10000}.viewport:hover .resize-handle{opacity:1;visibility:visible}.resize-handle:before{background:oklch(55.2% .016 285.938);border-radius:4px;content:"";height:32px;position:relative;right:0;top:0;transition:transform .2s ease-in-out;width:3px}.resize-handle:hover:before{transform:scaleY(1.3)}.resize-presets{display:flex;flex-direction:column;font-family:Manrope,sans-serif;gap:.25rem;left:0;letter-spacing:.02em;opacity:0;padding-left:1.25rem;position:absolute;top:0;transition:visibility .1s ease-in-out,opacity .1s ease-in-out;visibility:hidden}.resize-handle:hover .resize-presets{opacity:1;visibility:visible}.resize-preset-button{text-wrap:nowrap;backdrop-filter:blur(8px);background:oklch(55.2% .016 285.938/.7);border:none;border-radius:.5rem;color:#fff;cursor:pointer;font-size:.6875rem;padding:.25rem .5rem;text-align:left;transition:background .1s ease-in-out;width:fit-content;&:hover{background:oklch(55.2% .016 285.938/1)}&.is-active{background:oklch(45.7% .24 277.023)}}.is-editable{outline:none;user-select:text;&::selection{background:oklch(62.7% .265 303.9/.3)}&::-moz-selection{background:oklch(62.7% .265 303.9/.3)}&::-webkit-selection{background:oklch(62.7% .265 303.9/.3)}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node-edit-utils/core",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
4
4
  "description": "Utilities for editing nodes in a dom tree.",
5
5
  "type": "module",
6
6
  "main": "dist/node-edit-utils.cjs.js",
@@ -6,6 +6,7 @@ import { setupEventListener } from "./events/setupEventListener";
6
6
  import { clearHighlightFrame } from "./highlight/clearHighlightFrame";
7
7
  import { highlightNode } from "./highlight/highlightNode";
8
8
  import { refreshHighlightFrame } from "./highlight/refreshHighlightFrame";
9
+ import { updateHighlightFrameVisibility } from "./highlight/updateHighlightFrameVisibility";
9
10
  import { nodeText } from "./text/nodeText";
10
11
  import type { NodeTools } from "./types";
11
12
 
@@ -13,6 +14,7 @@ export const createNodeTools = (element: HTMLElement | null): NodeTools => {
13
14
  const nodeProvider = element;
14
15
 
15
16
  let resizeObserver: ResizeObserver | null = null;
17
+ let mutationObserver: MutationObserver | null = null;
16
18
  let selectedNode: HTMLElement | null = null;
17
19
 
18
20
  const text = nodeText();
@@ -29,11 +31,16 @@ export const createNodeTools = (element: HTMLElement | null): NodeTools => {
29
31
  selectedNode = null;
30
32
 
31
33
  resizeObserver?.disconnect();
34
+ mutationObserver?.disconnect();
32
35
  }
33
36
  }
34
37
  };
35
38
 
36
39
  const selectNode = (node: HTMLElement | null): void => {
40
+ if (selectedNode === node) {
41
+ return;
42
+ }
43
+
37
44
  if (text.isEditing()) {
38
45
  const currentEditable = text.getEditableNode();
39
46
  if (currentEditable && currentEditable !== node) {
@@ -42,6 +49,7 @@ export const createNodeTools = (element: HTMLElement | null): NodeTools => {
42
49
  }
43
50
 
44
51
  resizeObserver?.disconnect();
52
+ mutationObserver?.disconnect();
45
53
 
46
54
  if (node && nodeProvider) {
47
55
  text.enableEditMode(node, nodeProvider);
@@ -49,11 +57,25 @@ export const createNodeTools = (element: HTMLElement | null): NodeTools => {
49
57
  resizeObserver = connectResizeObserver(nodeProvider, () => {
50
58
  throttledFrameRefresh(node, nodeProvider);
51
59
  });
60
+
61
+ mutationObserver = new MutationObserver(() => {
62
+ throttledFrameRefresh(node, nodeProvider);
63
+ updateHighlightFrameVisibility(node, nodeProvider);
64
+ });
65
+
66
+ mutationObserver.observe(node, {
67
+ attributes: true,
68
+ attributeFilter: ["class"],
69
+ });
52
70
  }
53
71
 
54
72
  selectedNode = node;
55
73
  sendPostMessage("selectedNodeChanged", node?.getAttribute("data-node-id") ?? null);
56
74
  highlightNode(node, nodeProvider as HTMLElement) ?? null;
75
+
76
+ if (node && nodeProvider) {
77
+ updateHighlightFrameVisibility(node, nodeProvider);
78
+ }
57
79
  };
58
80
 
59
81
  // Setup event listener
@@ -62,6 +84,7 @@ export const createNodeTools = (element: HTMLElement | null): NodeTools => {
62
84
  const cleanup = (): void => {
63
85
  removeListeners();
64
86
  resizeObserver?.disconnect();
87
+ mutationObserver?.disconnect();
65
88
 
66
89
  text.blurEditMode();
67
90
  throttledFrameRefresh.cleanup();
@@ -77,6 +100,7 @@ export const createNodeTools = (element: HTMLElement | null): NodeTools => {
77
100
  clearHighlightFrame(nodeProvider);
78
101
  selectedNode = null;
79
102
  resizeObserver?.disconnect();
103
+ mutationObserver?.disconnect();
80
104
  },
81
105
  getEditableNode: () => text.getEditableNode(),
82
106
  cleanup,
@@ -1,4 +1,4 @@
1
- import { handlePostMessage } from "../../post-message/handlePostMessage";
1
+ import { processPostMessage } from "@/lib/post-message/processPostMessage";
2
2
  import { handleNodeClick } from "./click/handleNodeClick";
3
3
 
4
4
  export const setupEventListener = (
@@ -8,7 +8,7 @@ export const setupEventListener = (
8
8
  getEditableNode: () => HTMLElement | null
9
9
  ): (() => void) => {
10
10
  const messageHandler = (event: MessageEvent) => {
11
- handlePostMessage(event);
11
+ processPostMessage(event, onNodeSelected);
12
12
  };
13
13
 
14
14
  const documentClickHandler = (event: MouseEvent) => {
@@ -0,0 +1,10 @@
1
+ import { getHighlightFrameElement } from "./helpers/getHighlightFrameElement";
2
+
3
+ export const updateHighlightFrameVisibility = (node: HTMLElement, nodeProvider: HTMLElement): void => {
4
+ const frame = getHighlightFrameElement(nodeProvider);
5
+ if (!frame) return;
6
+
7
+ const hasHiddenClass = node.classList.contains("hidden") || node.classList.contains("select-none");
8
+ frame.style.display = hasHiddenClass ? "none" : "";
9
+ };
10
+
@@ -14,7 +14,7 @@ export const selectNode = (event: MouseEvent, editableNode: HTMLElement | null):
14
14
  const clickThrough = event.metaKey || event.ctrlKey;
15
15
 
16
16
  const candidates = getElementsFromPoint(clickX, clickY).filter(
17
- (element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase())
17
+ (element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()) && !element.classList.contains("select-none")
18
18
  );
19
19
 
20
20
  if (editableNode && candidates.includes(editableNode)) {
@@ -0,0 +1,21 @@
1
+ export const processPostMessage = (event: MessageEvent, onNodeSelected?: (node: HTMLElement | null) => void) => {
2
+ if (event.data.source === "markup-canvas" && event.data.canvasName === "canvas") {
3
+ if (event.data.action === "zoom") {
4
+ const zoom = event.data.data;
5
+ console.log("zoom", zoom);
6
+ }
7
+ }
8
+
9
+ if (event.data.source === "application") {
10
+ if (event.data.action === "selectedNodeChanged") {
11
+ const nodeId = event.data.data;
12
+ console.log("selectedNodeChanged in node-edit-utils", nodeId);
13
+ const selectedNode = document.querySelector(`[data-node-id="${nodeId}"]`);
14
+
15
+ if (selectedNode) {
16
+ console.log("selectedNode", selectedNode);
17
+ onNodeSelected?.(selectedNode as HTMLElement);
18
+ }
19
+ }
20
+ }
21
+ };
@@ -16,6 +16,7 @@
16
16
  position: absolute;
17
17
  left: 0;
18
18
  bottom: 0;
19
+ top: 0;
19
20
  gap: 0.25rem;
20
21
  z-index: 10000;
21
22
  transform: scale(calc(1 / var(--zoom))) translate3d(0, 100%, 0);
@@ -35,6 +36,8 @@
35
36
  height: var(--frame-height);
36
37
  z-index: 1000;
37
38
  pointer-events: none;
39
+ font-family: "Manrope", sans-serif;
40
+ letter-spacing: 0.02em;
38
41
  /* outline: calc(0.125em / var(--zoom)) solid oklch(62.7% 0.265 303.9); */
39
42
  }
40
43
 
@@ -49,12 +52,12 @@
49
52
 
50
53
  .highlight-frame-rect {
51
54
  fill: none;
52
- stroke: oklch(62.7% 0.265 303.9);
55
+ stroke: oklch(45.7% 0.24 277.023);
53
56
  stroke-width: var(--stroke-width);
54
57
  }
55
58
 
56
59
  .tag-label {
57
- background-color: oklch(62.7% 0.265 303.9);
60
+ background-color: oklch(45.7% 0.24 277.023);
58
61
  color: white;
59
62
  font-size: 0.575rem;
60
63
  font-weight: 500;
@@ -125,6 +128,8 @@
125
128
  gap: 0.25rem;
126
129
  visibility: hidden;
127
130
  opacity: 0;
131
+ font-family: "Manrope", sans-serif;
132
+ letter-spacing: 0.02em;
128
133
  transition:
129
134
  visibility 0.1s ease-in-out,
130
135
  opacity 0.1s ease-in-out;
@@ -1 +0,0 @@
1
- export declare const handlePostMessage: (event: MessageEvent) => void;
@@ -1,8 +0,0 @@
1
- export const handlePostMessage = (event: MessageEvent) => {
2
- if (event.data.source === "markup-canvas" && event.data.canvasName === "canvas") {
3
- if (event.data.action === "zoom") {
4
- const zoom = event.data.data;
5
- console.log("zoom", zoom);
6
- }
7
- }
8
- };