@node-edit-utils/core 2.2.4 → 2.2.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.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Markup Canvas
3
3
  * High-performance markup canvas with zoom and pan capabilities
4
- * @version 2.2.4
4
+ * @version 2.2.6
5
5
  */
6
6
  'use strict';
7
7
 
@@ -633,6 +633,7 @@ const createNodeTools = (element, canvasName = "canvas") => {
633
633
  const nodeProvider = element;
634
634
  let resizeObserver = null;
635
635
  let mutationObserver = null;
636
+ let parentMutationObserver = null;
636
637
  let selectedNode = null;
637
638
  const text = nodeText(canvasName);
638
639
  // Combined throttled function for refresh + visibility update
@@ -650,6 +651,7 @@ const createNodeTools = (element, canvasName = "canvas") => {
650
651
  selectedNode = null;
651
652
  resizeObserver?.disconnect();
652
653
  mutationObserver?.disconnect();
654
+ parentMutationObserver?.disconnect();
653
655
  }
654
656
  }
655
657
  };
@@ -665,9 +667,26 @@ const createNodeTools = (element, canvasName = "canvas") => {
665
667
  }
666
668
  resizeObserver?.disconnect();
667
669
  mutationObserver?.disconnect();
670
+ parentMutationObserver?.disconnect();
668
671
  if (node && nodeProvider) {
669
672
  text.enableEditMode(node, nodeProvider);
673
+ // Check if node is still in DOM and handle cleanup if removed
674
+ const checkNodeExists = () => {
675
+ if (!document.contains(node)) {
676
+ // Node was removed from DOM, clear highlight and cleanup
677
+ clearHighlightFrame();
678
+ selectedNode = null;
679
+ resizeObserver?.disconnect();
680
+ mutationObserver?.disconnect();
681
+ parentMutationObserver?.disconnect();
682
+ sendPostMessage("selectedNodeChanged", null);
683
+ return;
684
+ }
685
+ };
670
686
  mutationObserver = new MutationObserver(() => {
687
+ checkNodeExists();
688
+ if (!document.contains(node))
689
+ return; // Exit early if node was removed
671
690
  // throttledRefreshAndVisibility(node, nodeProvider);
672
691
  console.log("mutationObserver", node);
673
692
  refreshHighlightFrame(node, nodeProvider, canvasName);
@@ -676,8 +695,35 @@ const createNodeTools = (element, canvasName = "canvas") => {
676
695
  mutationObserver.observe(node, {
677
696
  attributes: true,
678
697
  characterData: true,
698
+ childList: true,
699
+ subtree: true,
679
700
  });
701
+ // Also observe parent node to catch when this node is removed
702
+ const parentNode = node.parentElement;
703
+ if (parentNode) {
704
+ parentMutationObserver = new MutationObserver((mutations) => {
705
+ // Check if the selected node was removed
706
+ for (const mutation of mutations) {
707
+ if (mutation.type === "childList") {
708
+ for (const removedNode of Array.from(mutation.removedNodes)) {
709
+ // Check if the removed node is the selected node, or if the selected node is contained within the removed subtree
710
+ if (removedNode === node || (removedNode instanceof Node && removedNode.contains(node))) {
711
+ checkNodeExists();
712
+ return;
713
+ }
714
+ }
715
+ }
716
+ }
717
+ });
718
+ parentMutationObserver.observe(parentNode, {
719
+ childList: true,
720
+ subtree: false, // Only direct children to avoid too many callbacks
721
+ });
722
+ }
680
723
  resizeObserver = connectResizeObserver(node, () => {
724
+ checkNodeExists();
725
+ if (!document.contains(node))
726
+ return; // Exit early if node was removed
681
727
  // throttledRefreshAndVisibility(node, nodeProvider);
682
728
  console.log("resizeObserver", node);
683
729
  refreshHighlightFrame(node, nodeProvider, canvasName);
@@ -697,16 +743,19 @@ const createNodeTools = (element, canvasName = "canvas") => {
697
743
  removeListeners();
698
744
  resizeObserver?.disconnect();
699
745
  mutationObserver?.disconnect();
746
+ parentMutationObserver?.disconnect();
700
747
  text.blurEditMode();
701
748
  throttledRefreshAndVisibility.cleanup();
749
+ // Clear highlight frame and reset selected node
750
+ clearHighlightFrame();
751
+ selectedNode = null;
752
+ sendPostMessage("selectedNodeChanged", null);
702
753
  };
703
754
  const nodeTools = {
704
755
  selectNode,
705
756
  getSelectedNode: () => selectedNode,
706
757
  refreshHighlightFrame: () => {
707
758
  if (selectedNode && nodeProvider) {
708
- // Call directly (not throttled) since this is typically called from already-throttled contexts
709
- // to avoid double RAF
710
759
  refreshHighlightFrame(selectedNode, nodeProvider, canvasName);
711
760
  updateHighlightFrameVisibility(selectedNode);
712
761
  }
@@ -716,6 +765,7 @@ const createNodeTools = (element, canvasName = "canvas") => {
716
765
  selectedNode = null;
717
766
  resizeObserver?.disconnect();
718
767
  mutationObserver?.disconnect();
768
+ parentMutationObserver?.disconnect();
719
769
  },
720
770
  getEditableNode: () => text.getEditableNode(),
721
771
  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.2.4
4
+ * @version 2.2.6
5
5
  */
6
6
  const getCanvasWindowValue = (path, canvasName = "canvas") => {
7
7
  // biome-ignore lint/suspicious/noExplicitAny: global window extension
@@ -631,6 +631,7 @@ const createNodeTools = (element, canvasName = "canvas") => {
631
631
  const nodeProvider = element;
632
632
  let resizeObserver = null;
633
633
  let mutationObserver = null;
634
+ let parentMutationObserver = null;
634
635
  let selectedNode = null;
635
636
  const text = nodeText(canvasName);
636
637
  // Combined throttled function for refresh + visibility update
@@ -648,6 +649,7 @@ const createNodeTools = (element, canvasName = "canvas") => {
648
649
  selectedNode = null;
649
650
  resizeObserver?.disconnect();
650
651
  mutationObserver?.disconnect();
652
+ parentMutationObserver?.disconnect();
651
653
  }
652
654
  }
653
655
  };
@@ -663,9 +665,26 @@ const createNodeTools = (element, canvasName = "canvas") => {
663
665
  }
664
666
  resizeObserver?.disconnect();
665
667
  mutationObserver?.disconnect();
668
+ parentMutationObserver?.disconnect();
666
669
  if (node && nodeProvider) {
667
670
  text.enableEditMode(node, nodeProvider);
671
+ // Check if node is still in DOM and handle cleanup if removed
672
+ const checkNodeExists = () => {
673
+ if (!document.contains(node)) {
674
+ // Node was removed from DOM, clear highlight and cleanup
675
+ clearHighlightFrame();
676
+ selectedNode = null;
677
+ resizeObserver?.disconnect();
678
+ mutationObserver?.disconnect();
679
+ parentMutationObserver?.disconnect();
680
+ sendPostMessage("selectedNodeChanged", null);
681
+ return;
682
+ }
683
+ };
668
684
  mutationObserver = new MutationObserver(() => {
685
+ checkNodeExists();
686
+ if (!document.contains(node))
687
+ return; // Exit early if node was removed
669
688
  // throttledRefreshAndVisibility(node, nodeProvider);
670
689
  console.log("mutationObserver", node);
671
690
  refreshHighlightFrame(node, nodeProvider, canvasName);
@@ -674,8 +693,35 @@ const createNodeTools = (element, canvasName = "canvas") => {
674
693
  mutationObserver.observe(node, {
675
694
  attributes: true,
676
695
  characterData: true,
696
+ childList: true,
697
+ subtree: true,
677
698
  });
699
+ // Also observe parent node to catch when this node is removed
700
+ const parentNode = node.parentElement;
701
+ if (parentNode) {
702
+ parentMutationObserver = new MutationObserver((mutations) => {
703
+ // Check if the selected node was removed
704
+ for (const mutation of mutations) {
705
+ if (mutation.type === "childList") {
706
+ for (const removedNode of Array.from(mutation.removedNodes)) {
707
+ // Check if the removed node is the selected node, or if the selected node is contained within the removed subtree
708
+ if (removedNode === node || (removedNode instanceof Node && removedNode.contains(node))) {
709
+ checkNodeExists();
710
+ return;
711
+ }
712
+ }
713
+ }
714
+ }
715
+ });
716
+ parentMutationObserver.observe(parentNode, {
717
+ childList: true,
718
+ subtree: false, // Only direct children to avoid too many callbacks
719
+ });
720
+ }
678
721
  resizeObserver = connectResizeObserver(node, () => {
722
+ checkNodeExists();
723
+ if (!document.contains(node))
724
+ return; // Exit early if node was removed
679
725
  // throttledRefreshAndVisibility(node, nodeProvider);
680
726
  console.log("resizeObserver", node);
681
727
  refreshHighlightFrame(node, nodeProvider, canvasName);
@@ -695,16 +741,19 @@ const createNodeTools = (element, canvasName = "canvas") => {
695
741
  removeListeners();
696
742
  resizeObserver?.disconnect();
697
743
  mutationObserver?.disconnect();
744
+ parentMutationObserver?.disconnect();
698
745
  text.blurEditMode();
699
746
  throttledRefreshAndVisibility.cleanup();
747
+ // Clear highlight frame and reset selected node
748
+ clearHighlightFrame();
749
+ selectedNode = null;
750
+ sendPostMessage("selectedNodeChanged", null);
700
751
  };
701
752
  const nodeTools = {
702
753
  selectNode,
703
754
  getSelectedNode: () => selectedNode,
704
755
  refreshHighlightFrame: () => {
705
756
  if (selectedNode && nodeProvider) {
706
- // Call directly (not throttled) since this is typically called from already-throttled contexts
707
- // to avoid double RAF
708
757
  refreshHighlightFrame(selectedNode, nodeProvider, canvasName);
709
758
  updateHighlightFrameVisibility(selectedNode);
710
759
  }
@@ -714,6 +763,7 @@ const createNodeTools = (element, canvasName = "canvas") => {
714
763
  selectedNode = null;
715
764
  resizeObserver?.disconnect();
716
765
  mutationObserver?.disconnect();
766
+ parentMutationObserver?.disconnect();
717
767
  },
718
768
  getEditableNode: () => text.getEditableNode(),
719
769
  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.2.4
4
+ * @version 2.2.6
5
5
  */
6
6
  (function (global, factory) {
7
7
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
@@ -637,6 +637,7 @@
637
637
  const nodeProvider = element;
638
638
  let resizeObserver = null;
639
639
  let mutationObserver = null;
640
+ let parentMutationObserver = null;
640
641
  let selectedNode = null;
641
642
  const text = nodeText(canvasName);
642
643
  // Combined throttled function for refresh + visibility update
@@ -654,6 +655,7 @@
654
655
  selectedNode = null;
655
656
  resizeObserver?.disconnect();
656
657
  mutationObserver?.disconnect();
658
+ parentMutationObserver?.disconnect();
657
659
  }
658
660
  }
659
661
  };
@@ -669,9 +671,26 @@
669
671
  }
670
672
  resizeObserver?.disconnect();
671
673
  mutationObserver?.disconnect();
674
+ parentMutationObserver?.disconnect();
672
675
  if (node && nodeProvider) {
673
676
  text.enableEditMode(node, nodeProvider);
677
+ // Check if node is still in DOM and handle cleanup if removed
678
+ const checkNodeExists = () => {
679
+ if (!document.contains(node)) {
680
+ // Node was removed from DOM, clear highlight and cleanup
681
+ clearHighlightFrame();
682
+ selectedNode = null;
683
+ resizeObserver?.disconnect();
684
+ mutationObserver?.disconnect();
685
+ parentMutationObserver?.disconnect();
686
+ sendPostMessage("selectedNodeChanged", null);
687
+ return;
688
+ }
689
+ };
674
690
  mutationObserver = new MutationObserver(() => {
691
+ checkNodeExists();
692
+ if (!document.contains(node))
693
+ return; // Exit early if node was removed
675
694
  // throttledRefreshAndVisibility(node, nodeProvider);
676
695
  console.log("mutationObserver", node);
677
696
  refreshHighlightFrame(node, nodeProvider, canvasName);
@@ -680,8 +699,35 @@
680
699
  mutationObserver.observe(node, {
681
700
  attributes: true,
682
701
  characterData: true,
702
+ childList: true,
703
+ subtree: true,
683
704
  });
705
+ // Also observe parent node to catch when this node is removed
706
+ const parentNode = node.parentElement;
707
+ if (parentNode) {
708
+ parentMutationObserver = new MutationObserver((mutations) => {
709
+ // Check if the selected node was removed
710
+ for (const mutation of mutations) {
711
+ if (mutation.type === "childList") {
712
+ for (const removedNode of Array.from(mutation.removedNodes)) {
713
+ // Check if the removed node is the selected node, or if the selected node is contained within the removed subtree
714
+ if (removedNode === node || (removedNode instanceof Node && removedNode.contains(node))) {
715
+ checkNodeExists();
716
+ return;
717
+ }
718
+ }
719
+ }
720
+ }
721
+ });
722
+ parentMutationObserver.observe(parentNode, {
723
+ childList: true,
724
+ subtree: false, // Only direct children to avoid too many callbacks
725
+ });
726
+ }
684
727
  resizeObserver = connectResizeObserver(node, () => {
728
+ checkNodeExists();
729
+ if (!document.contains(node))
730
+ return; // Exit early if node was removed
685
731
  // throttledRefreshAndVisibility(node, nodeProvider);
686
732
  console.log("resizeObserver", node);
687
733
  refreshHighlightFrame(node, nodeProvider, canvasName);
@@ -701,16 +747,19 @@
701
747
  removeListeners();
702
748
  resizeObserver?.disconnect();
703
749
  mutationObserver?.disconnect();
750
+ parentMutationObserver?.disconnect();
704
751
  text.blurEditMode();
705
752
  throttledRefreshAndVisibility.cleanup();
753
+ // Clear highlight frame and reset selected node
754
+ clearHighlightFrame();
755
+ selectedNode = null;
756
+ sendPostMessage("selectedNodeChanged", null);
706
757
  };
707
758
  const nodeTools = {
708
759
  selectNode,
709
760
  getSelectedNode: () => selectedNode,
710
761
  refreshHighlightFrame: () => {
711
762
  if (selectedNode && nodeProvider) {
712
- // Call directly (not throttled) since this is typically called from already-throttled contexts
713
- // to avoid double RAF
714
763
  refreshHighlightFrame(selectedNode, nodeProvider, canvasName);
715
764
  updateHighlightFrameVisibility(selectedNode);
716
765
  }
@@ -720,6 +769,7 @@
720
769
  selectedNode = null;
721
770
  resizeObserver?.disconnect();
722
771
  mutationObserver?.disconnect();
772
+ parentMutationObserver?.disconnect();
723
773
  },
724
774
  getEditableNode: () => text.getEditableNode(),
725
775
  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";const t=(e,t="canvas")=>{const n=window[t];return e.reduce((e,t)=>e?.[t],n)};function n(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 o=()=>{const e=document.body.querySelector(".highlight-frame-overlay");e&&e.remove();const t=document.body.querySelector(".highlight-frame-tools-wrapper");t&&t.remove()},r=["path","rect","circle","ellipse","polygon","line","polyline","text","text-noci"];let s=[],i=0;const a=(e,t)=>{let n=null;const o=e.clientX,a=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,a).filter(e=>!r.includes(e.tagName.toLowerCase())&&!e.classList.contains("select-none")&&!(e=>{let t=e.parentElement;for(;t;){if("true"===t.getAttribute("data-instance"))return!0;if("node-provider"===t.getAttribute("data-role"))break;t=t.parentElement}return!1})(e));if(t&&d.includes(t))return t;if(l)return s=[],n=d[0],n;var c,u;u=d,(c=s).length===u.length&&c.every((e,t)=>e===u[t])?i<=d.length&&i++:i=0;return n=d[d.length-1-i],s=d,n},l=(e,t,n,r)=>{const s=e=>{((e,t)=>{if("application"===e.data.source&&"selectedNodeChanged"===e.data.action){const o=e.data.data,r=document.querySelector(`[data-node-id="${o}"]`);if(n=r,n?.classList.contains("select-none"))return void t?.(null);r&&t?.(r)}var n})(e,t)},i=n=>{((e,t,n,r)=>{if(e.preventDefault(),e.stopPropagation(),t&&!t.contains(e.target))return o(),void r(null);r(a(e,n))})(n,e,r(),t)},l=e=>{"Escape"===e.key&&(e.preventDefault(),e.stopPropagation(),n?.())};return window.addEventListener("message",s),document.addEventListener("click",i),document.addEventListener("keydown",l),()=>{window.removeEventListener("message",s),document.removeEventListener("click",i),document.removeEventListener("keydown",l)}},d=e=>"true"===e.getAttribute("data-instance"),c=(e,t,n,o,r=!1)=>{const s=document.createElementNS("http://www.w3.org/2000/svg","rect");return s.setAttribute("x",(t-3).toString()),s.setAttribute("y",(n-3).toString()),s.setAttribute("width",6..toString()),s.setAttribute("height",6..toString()),s.setAttribute("vector-effect","non-scaling-stroke"),s.classList.add("highlight-frame-handle",o),r&&s.setAttribute("stroke",getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim()||"oklch(65.6% 0.241 354.308)"),e.appendChild(s),s};function u(e){const t=e.getBoundingClientRect();return{top:t.top,left:t.left,width:t.width,height:t.height}}const m=(e,t=!1)=>{const{top:n,left:o,width:r,height:s}=u(e),i=document.createElementNS("http://www.w3.org/2000/svg","svg");i.classList.add("highlight-frame-overlay"),t&&i.classList.add("is-instance"),i.setAttribute("data-node-id",e.getAttribute("data-node-id")||""),i.style.position="fixed",i.style.top="0",i.style.left="0",i.style.width="100vw",i.style.height="100vh",i.style.pointerEvents="none",i.style.zIndex="5000";const a=document.documentElement.clientWidth||window.innerWidth,l=document.documentElement.clientHeight||window.innerHeight;i.setAttribute("width",a.toString()),i.setAttribute("height",l.toString());const d=document.createElementNS("http://www.w3.org/2000/svg","g");d.classList.add("highlight-frame-group"),d.setAttribute("transform",`translate(${o}, ${n})`);const m=document.createElementNS("http://www.w3.org/2000/svg","rect");return m.setAttribute("x","0"),m.setAttribute("y","0"),m.setAttribute("width",r.toString()),m.setAttribute("height",s.toString()),m.setAttribute("vector-effect","non-scaling-stroke"),m.classList.add("highlight-frame-rect"),t&&m.setAttribute("stroke",getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim()||"oklch(65.6% 0.241 354.308)"),d.appendChild(m),((e,t,n,o=!1)=>{c(e,0,0,"handle-top-left",o),c(e,t,0,"handle-top-right",o),c(e,t,n,"handle-bottom-right",o),c(e,0,n,"handle-bottom-left",o)})(d,r,s,t),i.appendChild(d),document.body.appendChild(i),i},h=(e,t,n=!1)=>{const o=document.createElement("div");o.className="node-tools",n&&o.classList.add("is-instance"),t.appendChild(o),((e,t)=>{const n=document.createElement("div");n.className="tag-label",n.textContent=e.tagName.toLowerCase(),t.appendChild(n)})(e,o)};function g(){return document.body.querySelector(".highlight-frame-overlay")}const p=()=>getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim()||"oklch(65.6% 0.241 354.308)",b=(e,n,o="canvas")=>{const r=g();if(!r)return;const s=d(e),i=document.documentElement.clientWidth||window.innerWidth,a=document.documentElement.clientHeight||window.innerHeight;r.setAttribute("width",i.toString()),r.setAttribute("height",a.toString()),s?r.classList.add("is-instance"):r.classList.remove("is-instance");const l=r.querySelector(".highlight-frame-group");if(!l)return;const c=l.querySelector("rect");if(!c)return;s?c.setAttribute("stroke",p()):c.removeAttribute("stroke");const m=document.body.querySelector(".highlight-frame-tools-wrapper"),h=m?.querySelector(".node-tools"),b=t(["zoom","current"],o)??1,v=u(e),{top:y,left:f,width:w,height:E}=v,S=y+E;m&&(s?m.classList.add("is-instance"):m.classList.remove("is-instance")),h&&(s?h.classList.add("is-instance"):h.classList.remove("is-instance")),l.setAttribute("transform",`translate(${f}, ${y})`),c.setAttribute("width",w.toString()),c.setAttribute("height",E.toString());const A=l.querySelector(".handle-top-left"),L=l.querySelector(".handle-top-right"),x=l.querySelector(".handle-bottom-right"),k=l.querySelector(".handle-bottom-left");[A,L,x,k].forEach(e=>{e&&(s?e.setAttribute("stroke",p()):e.removeAttribute("stroke"))}),A&&(A.setAttribute("x",(-3).toString()),A.setAttribute("y",(-3).toString())),L&&(L.setAttribute("x",(w-3).toString()),L.setAttribute("y",(-3).toString())),x&&(x.setAttribute("x",(w-3).toString()),x.setAttribute("y",(E-3).toString())),k&&(k.setAttribute("x",(-3).toString()),k.setAttribute("y",(E-3).toString())),m&&(m.style.left=`${f}px`,m.style.top=`${S}px`),b<=10?n.style.setProperty("--tool-opacity","1"):n.style.setProperty("--tool-opacity","0")},v=e=>{const t=g();if(!t)return;const n=e.classList.contains("hidden")||e.classList.contains("select-none")?"none":"";t.style.display=n;const o=document.body.querySelector(".highlight-frame-tools-wrapper");o&&(o.style.display=n)},y=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,n="canvas")=>{const o=((e,t)=>{const n=new MutationObserver(e=>{t(e)});return n.observe(e,{subtree:!0,childList:!0,characterData:!0}),n})(e,()=>{b(e,t,n)});return()=>o.disconnect()},w=(e="canvas")=>{let n=null,o=!1,r=null;const s=()=>{if(o||!n)return;o=!0;var s;(s=n).contentEditable="false",s.classList.remove("is-editable"),s.style.outline="none",((e="canvas")=>{const n=t(["keyboard","disableTextEditMode"],e);n?.()})(e),r?.(),n=null,o=!1};return{enableEditMode:(o,i)=>{if(n===o)return;n&&n!==o&&s();const a=(e=>Array.from(e.childNodes).some(e=>e.nodeType===Node.TEXT_NODE&&e.textContent?.trim()))(o);a&&(n=o,(e=>{e.contentEditable="true",e.classList.add("is-editable"),e.style.outline="none"})(o),((e="canvas")=>{const n=t(["keyboard","enableTextEditMode"],e);n?.()})(e),r=((e,t,n,o="canvas")=>{if(!t)return()=>{};e.addEventListener("blur",n);const r=y(e),s=f(e,t,o);return()=>{e.removeEventListener("blur",n),r(),s?.()}})(o,i,s,e))},blurEditMode:s,getEditableNode:()=>n,isEditing:()=>null!==n}},E=320,S=1680,A=[{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"}],L=(e,t,n)=>{const o=parseFloat(document.body.dataset.zoom||"1"),r=((e,t)=>{const n=e+Math.round(t);return Math.max(E,Math.min(S,n))})(n,(e.clientX-t)/o);return r},x=(e,t)=>{e.style.setProperty("--container-width",`${t}px`),((e,t)=>{e.querySelectorAll(".resize-preset-button").forEach((e,n)=>{n<A.length&&(A[n].rawValue===t?e.classList.add("is-active"):e.classList.remove("is-active"))})})(e,t)};e.createCanvasObserver=function(e="canvas"){const n=document.querySelector(".transform-layer");if(!n)return{disconnect:()=>{}};const o=new MutationObserver(()=>{((e="canvas")=>{const n=t(["zoom","current"],e)??1;document.body.style.setProperty("--zoom",n.toFixed(5)),document.body.style.setProperty("--stroke-width",(2/n).toFixed(3)),document.body.dataset.zoom=n.toFixed(5),document.body.dataset.strokeWidth=(2/n).toFixed(3)})(e);const n=window.nodeTools;n?.refreshHighlightFrame&&n.refreshHighlightFrame()});return o.observe(n,{attributes:!0,attributeOldValue:!0,subtree:!0,childList:!0}),{disconnect:function(){o.disconnect()}}},e.createNodeTools=(e,t="canvas")=>{const r=e;let s=null,i=null,a=null;const c=w(t),p=n((e,n)=>{b(e,n,t),v(e)}),y=e=>{if(a!==e){if(c.isEditing()){const t=c.getEditableNode();t&&t!==e&&c.blurEditMode()}var n,o;s?.disconnect(),i?.disconnect(),e&&r&&(c.enableEditMode(e,r),i=new MutationObserver(()=>{console.log("mutationObserver",e),b(e,r,t),v(e)}),i.observe(e,{attributes:!0,characterData:!0}),s=((e,t)=>{const n=new ResizeObserver(e=>{t(e)});return n.observe(e),n})(e,()=>{console.log("resizeObserver",e),b(e,r,t),v(e)})),a=e,n="selectedNodeChanged",o=e?.getAttribute("data-node-id")??null,window.parent.postMessage({source:"node-edit-utils",action:n,data:o,timestamp:Date.now()},"*"),(e=>{if(!e)return;const t=g(),n=document.body.querySelector(".highlight-frame-tools-wrapper");t&&t.remove(),n&&n.remove();const o=d(e),r=m(e,o);"true"===e.contentEditable&&r.classList.add("is-editable");const{left:s,top:i,height:a}=u(e),l=i+a,c=document.createElement("div");c.classList.add("highlight-frame-tools-wrapper"),o&&c.classList.add("is-instance"),c.style.position="fixed",c.style.left=`${s}px`,c.style.top=`${l}px`,c.style.transform="translateX(-50%)",c.style.transformOrigin="center",c.style.pointerEvents="none",c.style.zIndex="5000",h(e,c,o),document.body.appendChild(c)})(e),e&&r&&v(e)}},f=l(r,y,()=>{c.isEditing()&&c.blurEditMode(),a&&r&&(o(),a=null,s?.disconnect(),i?.disconnect())},c.getEditableNode),E={selectNode:y,getSelectedNode:()=>a,refreshHighlightFrame:()=>{a&&r&&(b(a,r,t),v(a))},clearSelectedNode:()=>{o(),a=null,s?.disconnect(),i?.disconnect()},getEditableNode:()=>c.getEditableNode(),cleanup:()=>{f(),s?.disconnect(),i?.disconnect(),c.blurEditMode(),p.cleanup()}};var S,A;return S="nodeTools",A=E,"undefined"!=typeof window&&(window[S]=A),E},e.createViewport=e=>{const t=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",A.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,i=0,a=0;const l=n(n=>{if(!s)return;t&&(t.style.cursor="ew-resize");const o=L(n,i,a);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,i=t.clientX,a=e.offsetWidth},l,e=>{e.preventDefault(),e.stopPropagation(),t&&(t.style.cursor="default"),s=!1},()=>{s=!1});return{setWidth:t=>{x(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";const t=(e,t="canvas")=>{const n=window[t];return e.reduce((e,t)=>e?.[t],n)};function n(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}function o(e,t){window.parent.postMessage({source:"node-edit-utils",action:e,data:t,timestamp:Date.now()},"*")}const r=()=>{const e=document.body.querySelector(".highlight-frame-overlay");e&&e.remove();const t=document.body.querySelector(".highlight-frame-tools-wrapper");t&&t.remove()},s=["path","rect","circle","ellipse","polygon","line","polyline","text","text-noci"];let i=[],a=0;const l=(e,t)=>{let n=null;const o=e.clientX,r=e.clientY,l=e.metaKey||e.ctrlKey,c=((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")&&!(e=>{let t=e.parentElement;for(;t;){if("true"===t.getAttribute("data-instance"))return!0;if("node-provider"===t.getAttribute("data-role"))break;t=t.parentElement}return!1})(e));if(t&&c.includes(t))return t;if(l)return i=[],n=c[0],n;var d,u;u=c,(d=i).length===u.length&&d.every((e,t)=>e===u[t])?a<=c.length&&a++:a=0;return n=c[c.length-1-a],i=c,n},c=(e,t,n,o)=>{const s=e=>{((e,t)=>{if("application"===e.data.source&&"selectedNodeChanged"===e.data.action){const o=e.data.data,r=document.querySelector(`[data-node-id="${o}"]`);if(n=r,n?.classList.contains("select-none"))return void t?.(null);r&&t?.(r)}var n})(e,t)},i=n=>{((e,t,n,o)=>{if(e.preventDefault(),e.stopPropagation(),t&&!t.contains(e.target))return r(),void o(null);o(l(e,n))})(n,e,o(),t)},a=e=>{"Escape"===e.key&&(e.preventDefault(),e.stopPropagation(),n?.())};return window.addEventListener("message",s),document.addEventListener("click",i),document.addEventListener("keydown",a),()=>{window.removeEventListener("message",s),document.removeEventListener("click",i),document.removeEventListener("keydown",a)}},d=e=>"true"===e.getAttribute("data-instance"),u=(e,t,n,o,r=!1)=>{const s=document.createElementNS("http://www.w3.org/2000/svg","rect");return s.setAttribute("x",(t-3).toString()),s.setAttribute("y",(n-3).toString()),s.setAttribute("width",6..toString()),s.setAttribute("height",6..toString()),s.setAttribute("vector-effect","non-scaling-stroke"),s.classList.add("highlight-frame-handle",o),r&&s.setAttribute("stroke",getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim()||"oklch(65.6% 0.241 354.308)"),e.appendChild(s),s};function m(e){const t=e.getBoundingClientRect();return{top:t.top,left:t.left,width:t.width,height:t.height}}const h=(e,t=!1)=>{const{top:n,left:o,width:r,height:s}=m(e),i=document.createElementNS("http://www.w3.org/2000/svg","svg");i.classList.add("highlight-frame-overlay"),t&&i.classList.add("is-instance"),i.setAttribute("data-node-id",e.getAttribute("data-node-id")||""),i.style.position="fixed",i.style.top="0",i.style.left="0",i.style.width="100vw",i.style.height="100vh",i.style.pointerEvents="none",i.style.zIndex="5000";const a=document.documentElement.clientWidth||window.innerWidth,l=document.documentElement.clientHeight||window.innerHeight;i.setAttribute("width",a.toString()),i.setAttribute("height",l.toString());const c=document.createElementNS("http://www.w3.org/2000/svg","g");c.classList.add("highlight-frame-group"),c.setAttribute("transform",`translate(${o}, ${n})`);const d=document.createElementNS("http://www.w3.org/2000/svg","rect");return d.setAttribute("x","0"),d.setAttribute("y","0"),d.setAttribute("width",r.toString()),d.setAttribute("height",s.toString()),d.setAttribute("vector-effect","non-scaling-stroke"),d.classList.add("highlight-frame-rect"),t&&d.setAttribute("stroke",getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim()||"oklch(65.6% 0.241 354.308)"),c.appendChild(d),((e,t,n,o=!1)=>{u(e,0,0,"handle-top-left",o),u(e,t,0,"handle-top-right",o),u(e,t,n,"handle-bottom-right",o),u(e,0,n,"handle-bottom-left",o)})(c,r,s,t),i.appendChild(c),document.body.appendChild(i),i},g=(e,t,n=!1)=>{const o=document.createElement("div");o.className="node-tools",n&&o.classList.add("is-instance"),t.appendChild(o),((e,t)=>{const n=document.createElement("div");n.className="tag-label",n.textContent=e.tagName.toLowerCase(),t.appendChild(n)})(e,o)};function p(){return document.body.querySelector(".highlight-frame-overlay")}const b=()=>getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim()||"oklch(65.6% 0.241 354.308)",v=(e,n,o="canvas")=>{const r=p();if(!r)return;const s=d(e),i=document.documentElement.clientWidth||window.innerWidth,a=document.documentElement.clientHeight||window.innerHeight;r.setAttribute("width",i.toString()),r.setAttribute("height",a.toString()),s?r.classList.add("is-instance"):r.classList.remove("is-instance");const l=r.querySelector(".highlight-frame-group");if(!l)return;const c=l.querySelector("rect");if(!c)return;s?c.setAttribute("stroke",b()):c.removeAttribute("stroke");const u=document.body.querySelector(".highlight-frame-tools-wrapper"),h=u?.querySelector(".node-tools"),g=t(["zoom","current"],o)??1,v=m(e),{top:f,left:y,width:w,height:E}=v,L=f+E;u&&(s?u.classList.add("is-instance"):u.classList.remove("is-instance")),h&&(s?h.classList.add("is-instance"):h.classList.remove("is-instance")),l.setAttribute("transform",`translate(${y}, ${f})`),c.setAttribute("width",w.toString()),c.setAttribute("height",E.toString());const A=l.querySelector(".handle-top-left"),S=l.querySelector(".handle-top-right"),x=l.querySelector(".handle-bottom-right"),N=l.querySelector(".handle-bottom-left");[A,S,x,N].forEach(e=>{e&&(s?e.setAttribute("stroke",b()):e.removeAttribute("stroke"))}),A&&(A.setAttribute("x",(-3).toString()),A.setAttribute("y",(-3).toString())),S&&(S.setAttribute("x",(w-3).toString()),S.setAttribute("y",(-3).toString())),x&&(x.setAttribute("x",(w-3).toString()),x.setAttribute("y",(E-3).toString())),N&&(N.setAttribute("x",(-3).toString()),N.setAttribute("y",(E-3).toString())),u&&(u.style.left=`${y}px`,u.style.top=`${L}px`),g<=10?n.style.setProperty("--tool-opacity","1"):n.style.setProperty("--tool-opacity","0")},f=e=>{const t=p();if(!t)return;const n=e.classList.contains("hidden")||e.classList.contains("select-none")?"none":"";t.style.display=n;const o=document.body.querySelector(".highlight-frame-tools-wrapper");o&&(o.style.display=n)},y=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)}},w=(e,t,n="canvas")=>{const o=((e,t)=>{const n=new MutationObserver(e=>{t(e)});return n.observe(e,{subtree:!0,childList:!0,characterData:!0}),n})(e,()=>{v(e,t,n)});return()=>o.disconnect()},E=(e="canvas")=>{let n=null,o=!1,r=null;const s=()=>{if(o||!n)return;o=!0;var s;(s=n).contentEditable="false",s.classList.remove("is-editable"),s.style.outline="none",((e="canvas")=>{const n=t(["keyboard","disableTextEditMode"],e);n?.()})(e),r?.(),n=null,o=!1};return{enableEditMode:(o,i)=>{if(n===o)return;n&&n!==o&&s();const a=(e=>Array.from(e.childNodes).some(e=>e.nodeType===Node.TEXT_NODE&&e.textContent?.trim()))(o);a&&(n=o,(e=>{e.contentEditable="true",e.classList.add("is-editable"),e.style.outline="none"})(o),((e="canvas")=>{const n=t(["keyboard","enableTextEditMode"],e);n?.()})(e),r=((e,t,n,o="canvas")=>{if(!t)return()=>{};e.addEventListener("blur",n);const r=y(e),s=w(e,t,o);return()=>{e.removeEventListener("blur",n),r(),s?.()}})(o,i,s,e))},blurEditMode:s,getEditableNode:()=>n,isEditing:()=>null!==n}},L=320,A=1680,S=[{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"}],x=(e,t,n)=>{const o=parseFloat(document.body.dataset.zoom||"1"),r=((e,t)=>{const n=e+Math.round(t);return Math.max(L,Math.min(A,n))})(n,(e.clientX-t)/o);return r},N=(e,t)=>{e.style.setProperty("--container-width",`${t}px`),((e,t)=>{e.querySelectorAll(".resize-preset-button").forEach((e,n)=>{n<S.length&&(S[n].rawValue===t?e.classList.add("is-active"):e.classList.remove("is-active"))})})(e,t)};e.createCanvasObserver=function(e="canvas"){const n=document.querySelector(".transform-layer");if(!n)return{disconnect:()=>{}};const o=new MutationObserver(()=>{((e="canvas")=>{const n=t(["zoom","current"],e)??1;document.body.style.setProperty("--zoom",n.toFixed(5)),document.body.style.setProperty("--stroke-width",(2/n).toFixed(3)),document.body.dataset.zoom=n.toFixed(5),document.body.dataset.strokeWidth=(2/n).toFixed(3)})(e);const n=window.nodeTools;n?.refreshHighlightFrame&&n.refreshHighlightFrame()});return o.observe(n,{attributes:!0,attributeOldValue:!0,subtree:!0,childList:!0}),{disconnect:function(){o.disconnect()}}},e.createNodeTools=(e,t="canvas")=>{const s=e;let i=null,a=null,l=null,u=null;const b=E(t),y=n((e,n)=>{v(e,n,t),f(e)}),w=e=>{if(u!==e){if(b.isEditing()){const t=b.getEditableNode();t&&t!==e&&b.blurEditMode()}if(i?.disconnect(),a?.disconnect(),l?.disconnect(),e&&s){b.enableEditMode(e,s);const n=()=>{if(!document.contains(e))return r(),u=null,i?.disconnect(),a?.disconnect(),l?.disconnect(),void o("selectedNodeChanged",null)};a=new MutationObserver(()=>{n(),document.contains(e)&&(console.log("mutationObserver",e),v(e,s,t),f(e))}),a.observe(e,{attributes:!0,characterData:!0,childList:!0,subtree:!0});const c=e.parentElement;c&&(l=new MutationObserver(t=>{for(const o of t)if("childList"===o.type)for(const t of Array.from(o.removedNodes))if(t===e||t instanceof Node&&t.contains(e))return void n()}),l.observe(c,{childList:!0,subtree:!1})),i=((e,t)=>{const n=new ResizeObserver(e=>{t(e)});return n.observe(e),n})(e,()=>{n(),document.contains(e)&&(console.log("resizeObserver",e),v(e,s,t),f(e))})}u=e,o("selectedNodeChanged",e?.getAttribute("data-node-id")??null),(e=>{if(!e)return;const t=p(),n=document.body.querySelector(".highlight-frame-tools-wrapper");t&&t.remove(),n&&n.remove();const o=d(e),r=h(e,o);"true"===e.contentEditable&&r.classList.add("is-editable");const{left:s,top:i,height:a}=m(e),l=i+a,c=document.createElement("div");c.classList.add("highlight-frame-tools-wrapper"),o&&c.classList.add("is-instance"),c.style.position="fixed",c.style.left=`${s}px`,c.style.top=`${l}px`,c.style.transform="translateX(-50%)",c.style.transformOrigin="center",c.style.pointerEvents="none",c.style.zIndex="5000",g(e,c,o),document.body.appendChild(c)})(e),e&&s&&f(e)}},L=c(s,w,()=>{b.isEditing()&&b.blurEditMode(),u&&s&&(r(),u=null,i?.disconnect(),a?.disconnect(),l?.disconnect())},b.getEditableNode),A={selectNode:w,getSelectedNode:()=>u,refreshHighlightFrame:()=>{u&&s&&(v(u,s,t),f(u))},clearSelectedNode:()=>{r(),u=null,i?.disconnect(),a?.disconnect(),l?.disconnect()},getEditableNode:()=>b.getEditableNode(),cleanup:()=>{L(),i?.disconnect(),a?.disconnect(),l?.disconnect(),b.blurEditMode(),y.cleanup(),r(),u=null,o("selectedNodeChanged",null)}};var S,x;return S="nodeTools",x=A,"undefined"!=typeof window&&(window[S]=x),A},e.createViewport=e=>{const t=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",S.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,N);let s=!1,i=0,a=0;const l=n(n=>{if(!s)return;t&&(t.style.cursor="ew-resize");const o=x(n,i,a);N(e,o)}),c=((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,i=t.clientX,a=e.offsetWidth},l,e=>{e.preventDefault(),e.stopPropagation(),t&&(t.style.cursor="default"),s=!1},()=>{s=!1});return{setWidth:t=>{N(e,t)},cleanup:()=>{s=!1,l?.cleanup(),c(),r.remove()}}}});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node-edit-utils/core",
3
- "version": "2.2.4",
3
+ "version": "2.2.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",
@@ -15,6 +15,7 @@ export const createNodeTools = (element: HTMLElement | null, canvasName: string
15
15
 
16
16
  let resizeObserver: ResizeObserver | null = null;
17
17
  let mutationObserver: MutationObserver | null = null;
18
+ let parentMutationObserver: MutationObserver | null = null;
18
19
  let selectedNode: HTMLElement | null = null;
19
20
 
20
21
  const text = nodeText(canvasName);
@@ -37,6 +38,7 @@ export const createNodeTools = (element: HTMLElement | null, canvasName: string
37
38
 
38
39
  resizeObserver?.disconnect();
39
40
  mutationObserver?.disconnect();
41
+ parentMutationObserver?.disconnect();
40
42
  }
41
43
  }
42
44
  };
@@ -55,11 +57,29 @@ export const createNodeTools = (element: HTMLElement | null, canvasName: string
55
57
 
56
58
  resizeObserver?.disconnect();
57
59
  mutationObserver?.disconnect();
60
+ parentMutationObserver?.disconnect();
58
61
 
59
62
  if (node && nodeProvider) {
60
63
  text.enableEditMode(node, nodeProvider);
61
64
 
65
+ // Check if node is still in DOM and handle cleanup if removed
66
+ const checkNodeExists = (): void => {
67
+ if (!document.contains(node)) {
68
+ // Node was removed from DOM, clear highlight and cleanup
69
+ clearHighlightFrame();
70
+ selectedNode = null;
71
+ resizeObserver?.disconnect();
72
+ mutationObserver?.disconnect();
73
+ parentMutationObserver?.disconnect();
74
+ sendPostMessage("selectedNodeChanged", null);
75
+ return;
76
+ }
77
+ };
78
+
62
79
  mutationObserver = new MutationObserver(() => {
80
+ checkNodeExists();
81
+ if (!document.contains(node)) return; // Exit early if node was removed
82
+
63
83
  // throttledRefreshAndVisibility(node, nodeProvider);
64
84
  console.log("mutationObserver", node);
65
85
  refreshHighlightFrame(node, nodeProvider, canvasName);
@@ -69,9 +89,38 @@ export const createNodeTools = (element: HTMLElement | null, canvasName: string
69
89
  mutationObserver.observe(node, {
70
90
  attributes: true,
71
91
  characterData: true,
92
+ childList: true,
93
+ subtree: true,
72
94
  });
73
95
 
96
+ // Also observe parent node to catch when this node is removed
97
+ const parentNode = node.parentElement;
98
+ if (parentNode) {
99
+ parentMutationObserver = new MutationObserver((mutations) => {
100
+ // Check if the selected node was removed
101
+ for (const mutation of mutations) {
102
+ if (mutation.type === "childList") {
103
+ for (const removedNode of Array.from(mutation.removedNodes)) {
104
+ // Check if the removed node is the selected node, or if the selected node is contained within the removed subtree
105
+ if (removedNode === node || (removedNode instanceof Node && removedNode.contains(node))) {
106
+ checkNodeExists();
107
+ return;
108
+ }
109
+ }
110
+ }
111
+ }
112
+ });
113
+
114
+ parentMutationObserver.observe(parentNode, {
115
+ childList: true,
116
+ subtree: false, // Only direct children to avoid too many callbacks
117
+ });
118
+ }
119
+
74
120
  resizeObserver = connectResizeObserver(node, () => {
121
+ checkNodeExists();
122
+ if (!document.contains(node)) return; // Exit early if node was removed
123
+
75
124
  // throttledRefreshAndVisibility(node, nodeProvider);
76
125
  console.log("resizeObserver", node);
77
126
  refreshHighlightFrame(node, nodeProvider, canvasName);
@@ -95,9 +144,15 @@ export const createNodeTools = (element: HTMLElement | null, canvasName: string
95
144
  removeListeners();
96
145
  resizeObserver?.disconnect();
97
146
  mutationObserver?.disconnect();
147
+ parentMutationObserver?.disconnect();
98
148
 
99
149
  text.blurEditMode();
100
150
  throttledRefreshAndVisibility.cleanup();
151
+
152
+ // Clear highlight frame and reset selected node
153
+ clearHighlightFrame();
154
+ selectedNode = null;
155
+ sendPostMessage("selectedNodeChanged", null);
101
156
  };
102
157
 
103
158
  const nodeTools: NodeTools = {
@@ -105,8 +160,6 @@ export const createNodeTools = (element: HTMLElement | null, canvasName: string
105
160
  getSelectedNode: () => selectedNode,
106
161
  refreshHighlightFrame: () => {
107
162
  if (selectedNode && nodeProvider) {
108
- // Call directly (not throttled) since this is typically called from already-throttled contexts
109
- // to avoid double RAF
110
163
  refreshHighlightFrame(selectedNode, nodeProvider, canvasName);
111
164
  updateHighlightFrameVisibility(selectedNode);
112
165
  }
@@ -116,6 +169,7 @@ export const createNodeTools = (element: HTMLElement | null, canvasName: string
116
169
  selectedNode = null;
117
170
  resizeObserver?.disconnect();
118
171
  mutationObserver?.disconnect();
172
+ parentMutationObserver?.disconnect();
119
173
  },
120
174
  getEditableNode: () => text.getEditableNode(),
121
175
  cleanup,