@node-edit-utils/core 2.2.8 → 2.3.0

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 +1 @@
1
- export declare const handleTextChange: (node: HTMLElement, mutations: MutationRecord[]) => void;
1
+ export declare const handleTextChange: (node: HTMLElement, mutations: MutationRecord[], final?: boolean) => 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.2.8
4
+ * @version 2.3.0
5
5
  */
6
6
  'use strict';
7
7
 
@@ -645,63 +645,73 @@ const connectMutationObserver = (element, handler) => {
645
645
  return mutationObserver;
646
646
  };
647
647
 
648
- // biome-ignore lint/suspicious/noExplicitAny: generic constraint requires flexibility
649
- function withRAFThrottle(func) {
650
- let rafId = null;
651
- let lastArgs = null;
652
- const throttled = (...args) => {
653
- lastArgs = args;
654
- if (rafId === null) {
655
- rafId = requestAnimationFrame(() => {
656
- if (lastArgs) {
657
- func(...lastArgs);
658
- }
659
- rafId = null;
660
- lastArgs = null;
661
- });
662
- }
663
- };
664
- throttled.cleanup = () => {
665
- if (rafId !== null) {
666
- cancelAnimationFrame(rafId);
667
- rafId = null;
668
- lastArgs = null;
669
- }
670
- };
671
- return throttled;
672
- }
673
-
674
- const handleTextChange = (node, mutations) => {
648
+ const handleTextChange = (node, mutations, final = false) => {
675
649
  // Check if any mutation is a text content change
676
650
  const hasTextChange = mutations.some((mutation) => {
677
651
  return (mutation.type === "characterData" ||
678
652
  (mutation.type === "childList" && (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0)));
679
653
  });
680
- if (!hasTextChange) {
654
+ if (!hasTextChange && !final) {
681
655
  return;
682
656
  }
683
657
  // Get the text content of the node
684
658
  const textContent = node.textContent ?? "";
685
659
  // Get the node ID
686
660
  const nodeId = node.getAttribute("data-node-id");
661
+ console.log("textContentChanged", textContent, final);
687
662
  // Send postMessage with the text change
688
663
  sendPostMessage("textContentChanged", {
689
664
  nodeId,
690
665
  textContent,
666
+ final,
691
667
  });
692
668
  };
693
669
 
694
670
  const setupMutationObserver = (node, nodeProvider, canvasName = "canvas") => {
695
- const throttledHandleTextChange = withRAFThrottle((mutations) => {
696
- handleTextChange(node, mutations);
697
- });
671
+ // Accumulate mutations instead of replacing them
672
+ let pendingMutations = [];
673
+ let rafId1 = null;
674
+ let rafId2 = null;
675
+ const processMutations = () => {
676
+ if (pendingMutations.length > 0) {
677
+ const mutationsToProcess = [...pendingMutations];
678
+ pendingMutations = [];
679
+ handleTextChange(node, mutationsToProcess, false);
680
+ }
681
+ };
682
+ const scheduleProcess = () => {
683
+ if (rafId1 === null) {
684
+ rafId1 = requestAnimationFrame(() => {
685
+ // First RAF: let browser complete layout
686
+ rafId2 = requestAnimationFrame(() => {
687
+ // Second RAF: read textContent after layout is complete
688
+ processMutations();
689
+ rafId1 = null;
690
+ rafId2 = null;
691
+ });
692
+ });
693
+ }
694
+ };
695
+ const cleanup = () => {
696
+ if (rafId1 !== null) {
697
+ cancelAnimationFrame(rafId1);
698
+ rafId1 = null;
699
+ }
700
+ if (rafId2 !== null) {
701
+ cancelAnimationFrame(rafId2);
702
+ rafId2 = null;
703
+ }
704
+ pendingMutations = [];
705
+ };
698
706
  const mutationObserver = connectMutationObserver(node, (mutations) => {
699
- throttledHandleTextChange(mutations);
707
+ // Accumulate mutations instead of replacing
708
+ pendingMutations.push(...mutations);
709
+ scheduleProcess();
700
710
  refreshHighlightFrame(node, nodeProvider, canvasName);
701
711
  });
702
712
  return () => {
703
713
  mutationObserver.disconnect();
704
- throttledHandleTextChange.cleanup();
714
+ cleanup();
705
715
  };
706
716
  };
707
717
 
@@ -766,6 +776,8 @@ const nodeText = (canvasName = "canvas") => {
766
776
  }
767
777
  blurInProgress = true;
768
778
  const nodeToCleanup = editableNode;
779
+ // Send final textContentChanged message before cleanup
780
+ handleTextChange(nodeToCleanup, [], true);
769
781
  makeNodeNonEditable(nodeToCleanup);
770
782
  disableCanvasTextMode(canvasName);
771
783
  cleanup?.();
@@ -915,6 +927,32 @@ const createNodeTools = (element, canvasName = "canvas") => {
915
927
  return nodeTools;
916
928
  };
917
929
 
930
+ // biome-ignore lint/suspicious/noExplicitAny: generic constraint requires flexibility
931
+ function withRAFThrottle(func) {
932
+ let rafId = null;
933
+ let lastArgs = null;
934
+ const throttled = (...args) => {
935
+ lastArgs = args;
936
+ if (rafId === null) {
937
+ rafId = requestAnimationFrame(() => {
938
+ if (lastArgs) {
939
+ func(...lastArgs);
940
+ }
941
+ rafId = null;
942
+ lastArgs = null;
943
+ });
944
+ }
945
+ };
946
+ throttled.cleanup = () => {
947
+ if (rafId !== null) {
948
+ cancelAnimationFrame(rafId);
949
+ rafId = null;
950
+ lastArgs = null;
951
+ }
952
+ };
953
+ return throttled;
954
+ }
955
+
918
956
  const DEFAULT_WIDTH = 400;
919
957
  const RESIZE_CONFIG = {
920
958
  minWidth: 320,
@@ -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.8
4
+ * @version 2.3.0
5
5
  */
6
6
  const getCanvasWindowValue = (path, canvasName = "canvas") => {
7
7
  // biome-ignore lint/suspicious/noExplicitAny: global window extension
@@ -643,63 +643,73 @@ const connectMutationObserver = (element, handler) => {
643
643
  return mutationObserver;
644
644
  };
645
645
 
646
- // biome-ignore lint/suspicious/noExplicitAny: generic constraint requires flexibility
647
- function withRAFThrottle(func) {
648
- let rafId = null;
649
- let lastArgs = null;
650
- const throttled = (...args) => {
651
- lastArgs = args;
652
- if (rafId === null) {
653
- rafId = requestAnimationFrame(() => {
654
- if (lastArgs) {
655
- func(...lastArgs);
656
- }
657
- rafId = null;
658
- lastArgs = null;
659
- });
660
- }
661
- };
662
- throttled.cleanup = () => {
663
- if (rafId !== null) {
664
- cancelAnimationFrame(rafId);
665
- rafId = null;
666
- lastArgs = null;
667
- }
668
- };
669
- return throttled;
670
- }
671
-
672
- const handleTextChange = (node, mutations) => {
646
+ const handleTextChange = (node, mutations, final = false) => {
673
647
  // Check if any mutation is a text content change
674
648
  const hasTextChange = mutations.some((mutation) => {
675
649
  return (mutation.type === "characterData" ||
676
650
  (mutation.type === "childList" && (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0)));
677
651
  });
678
- if (!hasTextChange) {
652
+ if (!hasTextChange && !final) {
679
653
  return;
680
654
  }
681
655
  // Get the text content of the node
682
656
  const textContent = node.textContent ?? "";
683
657
  // Get the node ID
684
658
  const nodeId = node.getAttribute("data-node-id");
659
+ console.log("textContentChanged", textContent, final);
685
660
  // Send postMessage with the text change
686
661
  sendPostMessage("textContentChanged", {
687
662
  nodeId,
688
663
  textContent,
664
+ final,
689
665
  });
690
666
  };
691
667
 
692
668
  const setupMutationObserver = (node, nodeProvider, canvasName = "canvas") => {
693
- const throttledHandleTextChange = withRAFThrottle((mutations) => {
694
- handleTextChange(node, mutations);
695
- });
669
+ // Accumulate mutations instead of replacing them
670
+ let pendingMutations = [];
671
+ let rafId1 = null;
672
+ let rafId2 = null;
673
+ const processMutations = () => {
674
+ if (pendingMutations.length > 0) {
675
+ const mutationsToProcess = [...pendingMutations];
676
+ pendingMutations = [];
677
+ handleTextChange(node, mutationsToProcess, false);
678
+ }
679
+ };
680
+ const scheduleProcess = () => {
681
+ if (rafId1 === null) {
682
+ rafId1 = requestAnimationFrame(() => {
683
+ // First RAF: let browser complete layout
684
+ rafId2 = requestAnimationFrame(() => {
685
+ // Second RAF: read textContent after layout is complete
686
+ processMutations();
687
+ rafId1 = null;
688
+ rafId2 = null;
689
+ });
690
+ });
691
+ }
692
+ };
693
+ const cleanup = () => {
694
+ if (rafId1 !== null) {
695
+ cancelAnimationFrame(rafId1);
696
+ rafId1 = null;
697
+ }
698
+ if (rafId2 !== null) {
699
+ cancelAnimationFrame(rafId2);
700
+ rafId2 = null;
701
+ }
702
+ pendingMutations = [];
703
+ };
696
704
  const mutationObserver = connectMutationObserver(node, (mutations) => {
697
- throttledHandleTextChange(mutations);
705
+ // Accumulate mutations instead of replacing
706
+ pendingMutations.push(...mutations);
707
+ scheduleProcess();
698
708
  refreshHighlightFrame(node, nodeProvider, canvasName);
699
709
  });
700
710
  return () => {
701
711
  mutationObserver.disconnect();
702
- throttledHandleTextChange.cleanup();
712
+ cleanup();
703
713
  };
704
714
  };
705
715
 
@@ -764,6 +774,8 @@ const nodeText = (canvasName = "canvas") => {
764
774
  }
765
775
  blurInProgress = true;
766
776
  const nodeToCleanup = editableNode;
777
+ // Send final textContentChanged message before cleanup
778
+ handleTextChange(nodeToCleanup, [], true);
767
779
  makeNodeNonEditable(nodeToCleanup);
768
780
  disableCanvasTextMode(canvasName);
769
781
  cleanup?.();
@@ -913,6 +925,32 @@ const createNodeTools = (element, canvasName = "canvas") => {
913
925
  return nodeTools;
914
926
  };
915
927
 
928
+ // biome-ignore lint/suspicious/noExplicitAny: generic constraint requires flexibility
929
+ function withRAFThrottle(func) {
930
+ let rafId = null;
931
+ let lastArgs = null;
932
+ const throttled = (...args) => {
933
+ lastArgs = args;
934
+ if (rafId === null) {
935
+ rafId = requestAnimationFrame(() => {
936
+ if (lastArgs) {
937
+ func(...lastArgs);
938
+ }
939
+ rafId = null;
940
+ lastArgs = null;
941
+ });
942
+ }
943
+ };
944
+ throttled.cleanup = () => {
945
+ if (rafId !== null) {
946
+ cancelAnimationFrame(rafId);
947
+ rafId = null;
948
+ lastArgs = null;
949
+ }
950
+ };
951
+ return throttled;
952
+ }
953
+
916
954
  const DEFAULT_WIDTH = 400;
917
955
  const RESIZE_CONFIG = {
918
956
  minWidth: 320,
@@ -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.8
4
+ * @version 2.3.0
5
5
  */
6
6
  (function (global, factory) {
7
7
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
@@ -649,63 +649,73 @@
649
649
  return mutationObserver;
650
650
  };
651
651
 
652
- // biome-ignore lint/suspicious/noExplicitAny: generic constraint requires flexibility
653
- function withRAFThrottle(func) {
654
- let rafId = null;
655
- let lastArgs = null;
656
- const throttled = (...args) => {
657
- lastArgs = args;
658
- if (rafId === null) {
659
- rafId = requestAnimationFrame(() => {
660
- if (lastArgs) {
661
- func(...lastArgs);
662
- }
663
- rafId = null;
664
- lastArgs = null;
665
- });
666
- }
667
- };
668
- throttled.cleanup = () => {
669
- if (rafId !== null) {
670
- cancelAnimationFrame(rafId);
671
- rafId = null;
672
- lastArgs = null;
673
- }
674
- };
675
- return throttled;
676
- }
677
-
678
- const handleTextChange = (node, mutations) => {
652
+ const handleTextChange = (node, mutations, final = false) => {
679
653
  // Check if any mutation is a text content change
680
654
  const hasTextChange = mutations.some((mutation) => {
681
655
  return (mutation.type === "characterData" ||
682
656
  (mutation.type === "childList" && (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0)));
683
657
  });
684
- if (!hasTextChange) {
658
+ if (!hasTextChange && !final) {
685
659
  return;
686
660
  }
687
661
  // Get the text content of the node
688
662
  const textContent = node.textContent ?? "";
689
663
  // Get the node ID
690
664
  const nodeId = node.getAttribute("data-node-id");
665
+ console.log("textContentChanged", textContent, final);
691
666
  // Send postMessage with the text change
692
667
  sendPostMessage("textContentChanged", {
693
668
  nodeId,
694
669
  textContent,
670
+ final,
695
671
  });
696
672
  };
697
673
 
698
674
  const setupMutationObserver = (node, nodeProvider, canvasName = "canvas") => {
699
- const throttledHandleTextChange = withRAFThrottle((mutations) => {
700
- handleTextChange(node, mutations);
701
- });
675
+ // Accumulate mutations instead of replacing them
676
+ let pendingMutations = [];
677
+ let rafId1 = null;
678
+ let rafId2 = null;
679
+ const processMutations = () => {
680
+ if (pendingMutations.length > 0) {
681
+ const mutationsToProcess = [...pendingMutations];
682
+ pendingMutations = [];
683
+ handleTextChange(node, mutationsToProcess, false);
684
+ }
685
+ };
686
+ const scheduleProcess = () => {
687
+ if (rafId1 === null) {
688
+ rafId1 = requestAnimationFrame(() => {
689
+ // First RAF: let browser complete layout
690
+ rafId2 = requestAnimationFrame(() => {
691
+ // Second RAF: read textContent after layout is complete
692
+ processMutations();
693
+ rafId1 = null;
694
+ rafId2 = null;
695
+ });
696
+ });
697
+ }
698
+ };
699
+ const cleanup = () => {
700
+ if (rafId1 !== null) {
701
+ cancelAnimationFrame(rafId1);
702
+ rafId1 = null;
703
+ }
704
+ if (rafId2 !== null) {
705
+ cancelAnimationFrame(rafId2);
706
+ rafId2 = null;
707
+ }
708
+ pendingMutations = [];
709
+ };
702
710
  const mutationObserver = connectMutationObserver(node, (mutations) => {
703
- throttledHandleTextChange(mutations);
711
+ // Accumulate mutations instead of replacing
712
+ pendingMutations.push(...mutations);
713
+ scheduleProcess();
704
714
  refreshHighlightFrame(node, nodeProvider, canvasName);
705
715
  });
706
716
  return () => {
707
717
  mutationObserver.disconnect();
708
- throttledHandleTextChange.cleanup();
718
+ cleanup();
709
719
  };
710
720
  };
711
721
 
@@ -770,6 +780,8 @@
770
780
  }
771
781
  blurInProgress = true;
772
782
  const nodeToCleanup = editableNode;
783
+ // Send final textContentChanged message before cleanup
784
+ handleTextChange(nodeToCleanup, [], true);
773
785
  makeNodeNonEditable(nodeToCleanup);
774
786
  disableCanvasTextMode(canvasName);
775
787
  cleanup?.();
@@ -919,6 +931,32 @@
919
931
  return nodeTools;
920
932
  };
921
933
 
934
+ // biome-ignore lint/suspicious/noExplicitAny: generic constraint requires flexibility
935
+ function withRAFThrottle(func) {
936
+ let rafId = null;
937
+ let lastArgs = null;
938
+ const throttled = (...args) => {
939
+ lastArgs = args;
940
+ if (rafId === null) {
941
+ rafId = requestAnimationFrame(() => {
942
+ if (lastArgs) {
943
+ func(...lastArgs);
944
+ }
945
+ rafId = null;
946
+ lastArgs = null;
947
+ });
948
+ }
949
+ };
950
+ throttled.cleanup = () => {
951
+ if (rafId !== null) {
952
+ cancelAnimationFrame(rafId);
953
+ rafId = null;
954
+ lastArgs = null;
955
+ }
956
+ };
957
+ return throttled;
958
+ }
959
+
922
960
  const DEFAULT_WIDTH = 400;
923
961
  const RESIZE_CONFIG = {
924
962
  minWidth: 320,
@@ -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,t){window.parent.postMessage({source:"node-edit-utils",action:e,data:t,timestamp:Date.now()},"*")}const o=()=>document.querySelector(".canvas-container"),r=()=>{const e=o()||document.body,t=e.querySelector(".highlight-frame-overlay");t&&t.remove();const n=e.querySelector(".highlight-frame-tools-wrapper");n&&n.remove()},s=(e,t,n)=>{e&&t&&n.enableEditMode(e,t)},i=["path","rect","circle","ellipse","polygon","line","polyline","text","text-noci"];let a=[],d=0,l=null;const c=(e,t,n)=>{let o=null;const r=e.clientX,c=e.clientY,u=e.metaKey||e.ctrlKey,m=((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})(r,c).filter(e=>!i.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)),h=n.getEditableNode();if(h&&m.includes(h))return o=h,l=o,o;if(u)return a=[],o=m[0],l&&l===o&&s(o,t,n),l=o,o;var g,p;p=m,(g=a).length===p.length&&g.every((e,t)=>e===p[t])?d<=m.length-2&&d++:d=0;return o=m[m.length-1-d],a=m,l&&l===o&&s(o,t,n),l=o,o},u=(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(c(e,t,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)}},m=e=>"true"===e.getAttribute("data-instance"),h=(e,t,n,o,r=!1,s=!1)=>{const i=document.createElementNS("http://www.w3.org/2000/svg","rect");return i.setAttribute("x",(t-3).toString()),i.setAttribute("y",(n-3).toString()),i.setAttribute("width",6..toString()),i.setAttribute("height",6..toString()),i.setAttribute("vector-effect","non-scaling-stroke"),i.classList.add("highlight-frame-handle",o),r?i.setAttribute("stroke",getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim()||"oklch(65.6% 0.241 354.308)"):s&&i.setAttribute("stroke",getComputedStyle(document.documentElement).getPropertyValue("--text-edit-color").trim()||"oklch(62.3% 0.214 259.815)"),e.appendChild(i),i};function g(e){const t=e.getBoundingClientRect();return{top:t.top,left:t.left,width:t.width,height:t.height}}const p=(e,t=!1,n=!1)=>{const{top:r,left:s,width:i,height:a}=g(e),d=Math.max(i,3),l=document.createElementNS("http://www.w3.org/2000/svg","svg");l.classList.add("highlight-frame-overlay"),t&&l.classList.add("is-instance"),n&&l.classList.add("is-text-edit"),l.setAttribute("data-node-id",e.getAttribute("data-node-id")||""),l.style.position="absolute",l.style.top="0",l.style.left="0",l.style.width="100vw",l.style.height="100vh",l.style.pointerEvents="none",l.style.zIndex="500";const c=document.documentElement.clientWidth||window.innerWidth,u=document.documentElement.clientHeight||window.innerHeight;l.setAttribute("width",c.toString()),l.setAttribute("height",u.toString());const m=document.createElementNS("http://www.w3.org/2000/svg","g");m.classList.add("highlight-frame-group"),m.setAttribute("transform",`translate(${s}, ${r})`);const p=document.createElementNS("http://www.w3.org/2000/svg","rect");p.setAttribute("x","0"),p.setAttribute("y","0"),p.setAttribute("width",d.toString()),p.setAttribute("height",a.toString()),p.setAttribute("vector-effect","non-scaling-stroke"),p.classList.add("highlight-frame-rect"),t?p.setAttribute("stroke",getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim()||"oklch(65.6% 0.241 354.308)"):n&&p.setAttribute("stroke",getComputedStyle(document.documentElement).getPropertyValue("--text-edit-color").trim()||"oklch(62.3% 0.214 259.815)"),m.appendChild(p),((e,t,n,o=!1,r=!1)=>{h(e,0,0,"handle-top-left",o,r),h(e,t,0,"handle-top-right",o,r),h(e,t,n,"handle-bottom-right",o,r),h(e,0,n,"handle-bottom-left",o,r)})(m,d,a,t,n),l.appendChild(m);const b=o();return b?b.appendChild(l):document.body.appendChild(l),l},b={div:"Container",h1:"Heading 1",h2:"Heading 2",h3:"Heading 3",h4:"Heading 4",h5:"Heading 5",h6:"Heading 6",p:"Text",li:"List Item",ul:"Unordered List",ol:"Ordered List",img:"Image",a:"Link"},v=(e,t,n=!1,o=!1)=>{const r=document.createElement("div");r.className="node-tools",n&&r.classList.add("is-instance"),o&&r.classList.add("is-text-edit"),t.appendChild(r),((e,t)=>{const n=document.createElement("div");n.className="tag-label";const o=e.getAttribute("data-instance-name"),r=e.tagName.toLowerCase(),s=o||b[r]||r;var i;n.textContent=(i=s)?i.charAt(0).toUpperCase()+i.slice(1):i,t.appendChild(n)})(e,r)};function y(){return(o()||document.body).querySelector(".highlight-frame-overlay")}const f=()=>getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim()||"oklch(65.6% 0.241 354.308)",w=()=>getComputedStyle(document.documentElement).getPropertyValue("--text-edit-color").trim()||"oklch(62.3% 0.214 259.815)",E=(e,n,r="canvas")=>{const s=y();if(!s)return;const i=m(e),a="true"===e.contentEditable,d=document.documentElement.clientWidth||window.innerWidth,l=document.documentElement.clientHeight||window.innerHeight;s.setAttribute("width",d.toString()),s.setAttribute("height",l.toString()),i?s.classList.add("is-instance"):s.classList.remove("is-instance"),a?s.classList.add("is-text-edit"):s.classList.remove("is-text-edit");const c=s.querySelector(".highlight-frame-group");if(!c)return;const u=c.querySelector("rect");if(!u)return;i?u.setAttribute("stroke",f()):a?u.setAttribute("stroke",w()):u.removeAttribute("stroke");const h=(o()||document.body).querySelector(".highlight-frame-tools-wrapper"),p=h?.querySelector(".node-tools"),b=t(["zoom","current"],r)??1,v=g(e),{top:E,left:L,width:A,height:S}=v,x=Math.max(A,3),C=E+S;h&&(i?h.classList.add("is-instance"):h.classList.remove("is-instance"),a?h.classList.add("is-text-edit"):h.classList.remove("is-text-edit")),p&&(i?p.classList.add("is-instance"):p.classList.remove("is-instance"),a?p.classList.add("is-text-edit"):p.classList.remove("is-text-edit")),c.setAttribute("transform",`translate(${L}, ${E})`),u.setAttribute("width",x.toString()),u.setAttribute("height",S.toString());const k=c.querySelector(".handle-top-left"),N=c.querySelector(".handle-top-right"),q=c.querySelector(".handle-bottom-right"),M=c.querySelector(".handle-bottom-left");[k,N,q,M].forEach(e=>{e&&(i?e.setAttribute("stroke",f()):a?e.setAttribute("stroke",w()):e.removeAttribute("stroke"))}),k&&(k.setAttribute("x",(-3).toString()),k.setAttribute("y",(-3).toString())),N&&(N.setAttribute("x",(x-3).toString()),N.setAttribute("y",(-3).toString())),q&&(q.setAttribute("x",(x-3).toString()),q.setAttribute("y",(S-3).toString())),M&&(M.setAttribute("x",(-3).toString()),M.setAttribute("y",(S-3).toString())),h&&(h.style.transform=`translate(${L}px, ${C}px)`),b<=10?n.style.setProperty("--tool-opacity","1"):n.style.setProperty("--tool-opacity","0")},L=e=>{const t=y();if(!t)return;const n=e.classList.contains("hidden")||e.classList.contains("select-none")?"none":"";t.style.display=n;const r=(o()||document.body).querySelector(".highlight-frame-tools-wrapper");r&&(r.style.display=n)},A=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)}};function S(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 x=(e,t,o="canvas")=>{const r=S(t=>{((e,t)=>{if(!t.some(e=>"characterData"===e.type||"childList"===e.type&&(e.addedNodes.length>0||e.removedNodes.length>0)))return;const o=e.textContent??"";n("textContentChanged",{nodeId:e.getAttribute("data-node-id"),textContent:o})})(e,t)}),s=((e,t)=>{const n=new MutationObserver(e=>{t(e)});return n.observe(e,{subtree:!0,childList:!0,characterData:!0}),n})(e,n=>{r(n),E(e,t,o)});return()=>{s.disconnect(),r.cleanup()}},C=(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=A(e),s=x(e,t,o);return()=>{e.removeEventListener("blur",n),r(),s?.()}})(o,i,s,e))},blurEditMode:s,getEditableNode:()=>n,isEditing:()=>null!==n}},k=320,N=1680,q=[{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"}],M=(e,t,n)=>{const o=parseFloat(document.body.dataset.zoom||"1"),r=((e,t)=>{const n=e+Math.round(t);return Math.max(k,Math.min(N,n))})(n,(e.clientX-t)/o);return r},P=(e,t)=>{e.style.setProperty("--container-width",`${t}px`),((e,t)=>{e.querySelectorAll(".resize-preset-button").forEach((e,n)=>{n<q.length&&(q[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,d=null,l=null;const c=C(t),h=e=>{if(l!==e){if(c.isEditing()){const t=c.getEditableNode();t&&t!==e&&c.blurEditMode()}if(i?.disconnect(),a?.disconnect(),d?.disconnect(),e&&s){const o=()=>{if(!document.contains(e))return r(),l=null,i?.disconnect(),a?.disconnect(),d?.disconnect(),void n("selectedNodeChanged",null)};a=new MutationObserver(()=>{o(),document.contains(e)&&(E(e,s,t),L(e))}),a.observe(e,{attributes:!0,characterData:!0,childList:!0,subtree:!0});const c=e.parentElement;c&&(d=new MutationObserver(t=>{for(const n of t)if("childList"===n.type)for(const t of Array.from(n.removedNodes))if(t===e||t instanceof Node&&t.contains(e))return void o()}),d.observe(c,{childList:!0,subtree:!1})),i=((e,t)=>{const n=new ResizeObserver(e=>{t(e)});return n.observe(e),n})(e,()=>{o(),document.contains(e)&&(E(e,s,t),L(e))})}l=e,n("selectedNodeChanged",e?.getAttribute("data-node-id")??null),(e=>{if(!e)return;const t=y(),n=o(),r=n?.querySelector(".highlight-frame-tools-wrapper")||document.body.querySelector(".highlight-frame-tools-wrapper");t&&t.remove(),r&&r.remove();const s=m(e),i="true"===e.contentEditable,a=p(e,s,i);"true"===e.contentEditable&&a.classList.add("is-editable");const{left:d,top:l,height:c}=g(e),u=l+c,h=document.createElement("div");h.classList.add("highlight-frame-tools-wrapper"),s&&h.classList.add("is-instance"),i&&h.classList.add("is-text-edit"),h.style.position="absolute",h.style.transform=`translate(${d}px, ${u}px)`,h.style.transformOrigin="left center",h.style.pointerEvents="none",h.style.zIndex="500",v(e,h,s,i),n?n.appendChild(h):document.body.appendChild(h)})(e),e&&s&&(L(e),L(e))}},b=u(s,h,()=>{c.isEditing()&&c.blurEditMode(),l&&s&&(r(),l=null,i?.disconnect(),a?.disconnect(),d?.disconnect())},c),f={selectNode:h,getSelectedNode:()=>l,refreshHighlightFrame:()=>{l&&s&&(E(l,s,t),L(l))},clearSelectedNode:()=>{r(),l=null,i?.disconnect(),a?.disconnect(),d?.disconnect()},getEditableNode:()=>c.getEditableNode(),cleanup:()=>{b(),i?.disconnect(),a?.disconnect(),d?.disconnect(),c.blurEditMode(),r(),l=null,n("selectedNodeChanged",null)}};var w,A;return w="nodeTools",A=f,"undefined"!=typeof window&&(window[w]=A),f},e.createViewport=e=>{const t=o(),n=e.querySelector(".resize-handle");n&&n.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",q.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,P);let s=!1,i=0,a=0;const d=S(n=>{if(!s)return;t&&(t.style.cursor="ew-resize");const o=M(n,i,a);P(e,o)}),l=((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},d,e=>{e.preventDefault(),e.stopPropagation(),t&&(t.style.cursor="default"),s=!1},()=>{s=!1});return{setWidth:t=>{P(e,t)},cleanup:()=>{s=!1,d?.cleanup(),l(),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,t){window.parent.postMessage({source:"node-edit-utils",action:e,data:t,timestamp:Date.now()},"*")}const o=()=>document.querySelector(".canvas-container"),r=()=>{const e=o()||document.body,t=e.querySelector(".highlight-frame-overlay");t&&t.remove();const n=e.querySelector(".highlight-frame-tools-wrapper");n&&n.remove()},i=(e,t,n)=>{e&&t&&n.enableEditMode(e,t)},s=["path","rect","circle","ellipse","polygon","line","polyline","text","text-noci"];let a=[],l=0,d=null;const c=(e,t,n)=>{let o=null;const r=e.clientX,c=e.clientY,u=e.metaKey||e.ctrlKey,m=((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})(r,c).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)),h=n.getEditableNode();if(h&&m.includes(h))return o=h,d=o,o;if(u)return a=[],o=m[0],d&&d===o&&i(o,t,n),d=o,o;var g,p;p=m,(g=a).length===p.length&&g.every((e,t)=>e===p[t])?l<=m.length-2&&l++:l=0;return o=m[m.length-1-l],a=m,d&&d===o&&i(o,t,n),d=o,o},u=(e,t,n,o)=>{const i=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)},s=n=>{((e,t,n,o)=>{if(e.preventDefault(),e.stopPropagation(),t&&!t.contains(e.target))return r(),void o(null);o(c(e,t,n))})(n,e,o,t)},a=e=>{"Escape"===e.key&&(e.preventDefault(),e.stopPropagation(),n?.())};return window.addEventListener("message",i),document.addEventListener("click",s),document.addEventListener("keydown",a),()=>{window.removeEventListener("message",i),document.removeEventListener("click",s),document.removeEventListener("keydown",a)}},m=e=>"true"===e.getAttribute("data-instance"),h=(e,t,n,o,r=!1,i=!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)"):i&&s.setAttribute("stroke",getComputedStyle(document.documentElement).getPropertyValue("--text-edit-color").trim()||"oklch(62.3% 0.214 259.815)"),e.appendChild(s),s};function g(e){const t=e.getBoundingClientRect();return{top:t.top,left:t.left,width:t.width,height:t.height}}const p=(e,t=!1,n=!1)=>{const{top:r,left:i,width:s,height:a}=g(e),l=Math.max(s,3),d=document.createElementNS("http://www.w3.org/2000/svg","svg");d.classList.add("highlight-frame-overlay"),t&&d.classList.add("is-instance"),n&&d.classList.add("is-text-edit"),d.setAttribute("data-node-id",e.getAttribute("data-node-id")||""),d.style.position="absolute",d.style.top="0",d.style.left="0",d.style.width="100vw",d.style.height="100vh",d.style.pointerEvents="none",d.style.zIndex="500";const c=document.documentElement.clientWidth||window.innerWidth,u=document.documentElement.clientHeight||window.innerHeight;d.setAttribute("width",c.toString()),d.setAttribute("height",u.toString());const m=document.createElementNS("http://www.w3.org/2000/svg","g");m.classList.add("highlight-frame-group"),m.setAttribute("transform",`translate(${i}, ${r})`);const p=document.createElementNS("http://www.w3.org/2000/svg","rect");p.setAttribute("x","0"),p.setAttribute("y","0"),p.setAttribute("width",l.toString()),p.setAttribute("height",a.toString()),p.setAttribute("vector-effect","non-scaling-stroke"),p.classList.add("highlight-frame-rect"),t?p.setAttribute("stroke",getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim()||"oklch(65.6% 0.241 354.308)"):n&&p.setAttribute("stroke",getComputedStyle(document.documentElement).getPropertyValue("--text-edit-color").trim()||"oklch(62.3% 0.214 259.815)"),m.appendChild(p),((e,t,n,o=!1,r=!1)=>{h(e,0,0,"handle-top-left",o,r),h(e,t,0,"handle-top-right",o,r),h(e,t,n,"handle-bottom-right",o,r),h(e,0,n,"handle-bottom-left",o,r)})(m,l,a,t,n),d.appendChild(m);const b=o();return b?b.appendChild(d):document.body.appendChild(d),d},b={div:"Container",h1:"Heading 1",h2:"Heading 2",h3:"Heading 3",h4:"Heading 4",h5:"Heading 5",h6:"Heading 6",p:"Text",li:"List Item",ul:"Unordered List",ol:"Ordered List",img:"Image",a:"Link"},v=(e,t,n=!1,o=!1)=>{const r=document.createElement("div");r.className="node-tools",n&&r.classList.add("is-instance"),o&&r.classList.add("is-text-edit"),t.appendChild(r),((e,t)=>{const n=document.createElement("div");n.className="tag-label";const o=e.getAttribute("data-instance-name"),r=e.tagName.toLowerCase(),i=o||b[r]||r;var s;n.textContent=(s=i)?s.charAt(0).toUpperCase()+s.slice(1):s,t.appendChild(n)})(e,r)};function y(){return(o()||document.body).querySelector(".highlight-frame-overlay")}const f=()=>getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim()||"oklch(65.6% 0.241 354.308)",w=()=>getComputedStyle(document.documentElement).getPropertyValue("--text-edit-color").trim()||"oklch(62.3% 0.214 259.815)",E=(e,n,r="canvas")=>{const i=y();if(!i)return;const s=m(e),a="true"===e.contentEditable,l=document.documentElement.clientWidth||window.innerWidth,d=document.documentElement.clientHeight||window.innerHeight;i.setAttribute("width",l.toString()),i.setAttribute("height",d.toString()),s?i.classList.add("is-instance"):i.classList.remove("is-instance"),a?i.classList.add("is-text-edit"):i.classList.remove("is-text-edit");const c=i.querySelector(".highlight-frame-group");if(!c)return;const u=c.querySelector("rect");if(!u)return;s?u.setAttribute("stroke",f()):a?u.setAttribute("stroke",w()):u.removeAttribute("stroke");const h=(o()||document.body).querySelector(".highlight-frame-tools-wrapper"),p=h?.querySelector(".node-tools"),b=t(["zoom","current"],r)??1,v=g(e),{top:E,left:L,width:A,height:x}=v,S=Math.max(A,3),C=E+x;h&&(s?h.classList.add("is-instance"):h.classList.remove("is-instance"),a?h.classList.add("is-text-edit"):h.classList.remove("is-text-edit")),p&&(s?p.classList.add("is-instance"):p.classList.remove("is-instance"),a?p.classList.add("is-text-edit"):p.classList.remove("is-text-edit")),c.setAttribute("transform",`translate(${L}, ${E})`),u.setAttribute("width",S.toString()),u.setAttribute("height",x.toString());const k=c.querySelector(".handle-top-left"),N=c.querySelector(".handle-top-right"),q=c.querySelector(".handle-bottom-right"),M=c.querySelector(".handle-bottom-left");[k,N,q,M].forEach(e=>{e&&(s?e.setAttribute("stroke",f()):a?e.setAttribute("stroke",w()):e.removeAttribute("stroke"))}),k&&(k.setAttribute("x",(-3).toString()),k.setAttribute("y",(-3).toString())),N&&(N.setAttribute("x",(S-3).toString()),N.setAttribute("y",(-3).toString())),q&&(q.setAttribute("x",(S-3).toString()),q.setAttribute("y",(x-3).toString())),M&&(M.setAttribute("x",(-3).toString()),M.setAttribute("y",(x-3).toString())),h&&(h.style.transform=`translate(${L}px, ${C}px)`),b<=10?n.style.setProperty("--tool-opacity","1"):n.style.setProperty("--tool-opacity","0")},L=e=>{const t=y();if(!t)return;const n=e.classList.contains("hidden")||e.classList.contains("select-none")?"none":"";t.style.display=n;const r=(o()||document.body).querySelector(".highlight-frame-tools-wrapper");r&&(r.style.display=n)},A=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)}},x=(e,t,o=!1)=>{if(!t.some(e=>"characterData"===e.type||"childList"===e.type&&(e.addedNodes.length>0||e.removedNodes.length>0))&&!o)return;const r=e.textContent??"",i=e.getAttribute("data-node-id");console.log("textContentChanged",r,o),n("textContentChanged",{nodeId:i,textContent:r,final:o})},S=(e,t,n="canvas")=>{let o=[],r=null,i=null;const s=()=>{null===r&&(r=requestAnimationFrame(()=>{i=requestAnimationFrame(()=>{(()=>{if(o.length>0){const t=[...o];o=[],x(e,t,!1)}})(),r=null,i=null})}))},a=((e,t)=>{const n=new MutationObserver(e=>{t(e)});return n.observe(e,{subtree:!0,childList:!0,characterData:!0}),n})(e,r=>{o.push(...r),s(),E(e,t,n)});return()=>{a.disconnect(),null!==r&&(cancelAnimationFrame(r),r=null),null!==i&&(cancelAnimationFrame(i),i=null),o=[]}},C=(e="canvas")=>{let n=null,o=!1,r=null;const i=()=>{if(o||!n)return;o=!0;const i=n;var s;x(i,[],!0),(s=i).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,s)=>{if(n===o)return;n&&n!==o&&i();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=A(e),i=S(e,t,o);return()=>{e.removeEventListener("blur",n),r(),i?.()}})(o,s,i,e))},blurEditMode:i,getEditableNode:()=>n,isEditing:()=>null!==n}};const k=320,N=1680,q=[{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"}],M=(e,t,n)=>{const o=parseFloat(document.body.dataset.zoom||"1"),r=((e,t)=>{const n=e+Math.round(t);return Math.max(k,Math.min(N,n))})(n,(e.clientX-t)/o);return r},P=(e,t)=>{e.style.setProperty("--container-width",`${t}px`),((e,t)=>{e.querySelectorAll(".resize-preset-button").forEach((e,n)=>{n<q.length&&(q[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 i=e;let s=null,a=null,l=null,d=null;const c=C(t),h=e=>{if(d!==e){if(c.isEditing()){const t=c.getEditableNode();t&&t!==e&&c.blurEditMode()}if(s?.disconnect(),a?.disconnect(),l?.disconnect(),e&&i){const o=()=>{if(!document.contains(e))return r(),d=null,s?.disconnect(),a?.disconnect(),l?.disconnect(),void n("selectedNodeChanged",null)};a=new MutationObserver(()=>{o(),document.contains(e)&&(E(e,i,t),L(e))}),a.observe(e,{attributes:!0,characterData:!0,childList:!0,subtree:!0});const c=e.parentElement;c&&(l=new MutationObserver(t=>{for(const n of t)if("childList"===n.type)for(const t of Array.from(n.removedNodes))if(t===e||t instanceof Node&&t.contains(e))return void o()}),l.observe(c,{childList:!0,subtree:!1})),s=((e,t)=>{const n=new ResizeObserver(e=>{t(e)});return n.observe(e),n})(e,()=>{o(),document.contains(e)&&(E(e,i,t),L(e))})}d=e,n("selectedNodeChanged",e?.getAttribute("data-node-id")??null),(e=>{if(!e)return;const t=y(),n=o(),r=n?.querySelector(".highlight-frame-tools-wrapper")||document.body.querySelector(".highlight-frame-tools-wrapper");t&&t.remove(),r&&r.remove();const i=m(e),s="true"===e.contentEditable,a=p(e,i,s);"true"===e.contentEditable&&a.classList.add("is-editable");const{left:l,top:d,height:c}=g(e),u=d+c,h=document.createElement("div");h.classList.add("highlight-frame-tools-wrapper"),i&&h.classList.add("is-instance"),s&&h.classList.add("is-text-edit"),h.style.position="absolute",h.style.transform=`translate(${l}px, ${u}px)`,h.style.transformOrigin="left center",h.style.pointerEvents="none",h.style.zIndex="500",v(e,h,i,s),n?n.appendChild(h):document.body.appendChild(h)})(e),e&&i&&(L(e),L(e))}},b=u(i,h,()=>{c.isEditing()&&c.blurEditMode(),d&&i&&(r(),d=null,s?.disconnect(),a?.disconnect(),l?.disconnect())},c),f={selectNode:h,getSelectedNode:()=>d,refreshHighlightFrame:()=>{d&&i&&(E(d,i,t),L(d))},clearSelectedNode:()=>{r(),d=null,s?.disconnect(),a?.disconnect(),l?.disconnect()},getEditableNode:()=>c.getEditableNode(),cleanup:()=>{b(),s?.disconnect(),a?.disconnect(),l?.disconnect(),c.blurEditMode(),r(),d=null,n("selectedNodeChanged",null)}};var w,A;return w="nodeTools",A=f,"undefined"!=typeof window&&(window[w]=A),f},e.createViewport=e=>{const t=o(),n=e.querySelector(".resize-handle");n&&n.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",q.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,P);let i=!1,s=0,a=0;const l=function(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}(n=>{if(!i)return;t&&(t.style.cursor="ew-resize");const o=M(n,s,a);P(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(),i=!0,s=t.clientX,a=e.offsetWidth},l,e=>{e.preventDefault(),e.stopPropagation(),t&&(t.style.cursor="default"),i=!1},()=>{i=!1});return{setWidth:t=>{P(e,t)},cleanup:()=>{i=!1,l?.cleanup(),d(),r.remove()}}}});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node-edit-utils/core",
3
- "version": "2.2.8",
3
+ "version": "2.3.0",
4
4
  "description": "Utilities for editing nodes in a dom tree.",
5
5
  "type": "module",
6
6
  "main": "dist/node-edit-utils.cjs.js",
@@ -1,5 +1,4 @@
1
1
  import { connectMutationObserver } from "../../../helpers/observer/connectMutationObserver";
2
- import { withRAFThrottle } from "../../../helpers/withRAF";
3
2
  import { refreshHighlightFrame } from "../../highlight/refreshHighlightFrame";
4
3
  import { handleTextChange } from "../helpers/handleTextChange";
5
4
 
@@ -8,17 +7,54 @@ export const setupMutationObserver = (
8
7
  nodeProvider: HTMLElement,
9
8
  canvasName: string = "canvas"
10
9
  ): (() => void) | undefined => {
11
- const throttledHandleTextChange = withRAFThrottle((mutations: MutationRecord[]) => {
12
- handleTextChange(node, mutations);
13
- });
10
+ // Accumulate mutations instead of replacing them
11
+ let pendingMutations: MutationRecord[] = [];
12
+ let rafId1: number | null = null;
13
+ let rafId2: number | null = null;
14
+
15
+ const processMutations = () => {
16
+ if (pendingMutations.length > 0) {
17
+ const mutationsToProcess = [...pendingMutations];
18
+ pendingMutations = [];
19
+ handleTextChange(node, mutationsToProcess, false);
20
+ }
21
+ };
22
+
23
+ const scheduleProcess = () => {
24
+ if (rafId1 === null) {
25
+ rafId1 = requestAnimationFrame(() => {
26
+ // First RAF: let browser complete layout
27
+ rafId2 = requestAnimationFrame(() => {
28
+ // Second RAF: read textContent after layout is complete
29
+ processMutations();
30
+ rafId1 = null;
31
+ rafId2 = null;
32
+ });
33
+ });
34
+ }
35
+ };
36
+
37
+ const cleanup = () => {
38
+ if (rafId1 !== null) {
39
+ cancelAnimationFrame(rafId1);
40
+ rafId1 = null;
41
+ }
42
+ if (rafId2 !== null) {
43
+ cancelAnimationFrame(rafId2);
44
+ rafId2 = null;
45
+ }
46
+ pendingMutations = [];
47
+ };
14
48
 
15
49
  const mutationObserver = connectMutationObserver(node, (mutations) => {
16
- throttledHandleTextChange(mutations);
50
+ // Accumulate mutations instead of replacing
51
+ pendingMutations.push(...mutations);
52
+ scheduleProcess();
17
53
  refreshHighlightFrame(node, nodeProvider, canvasName);
18
54
  });
19
55
 
20
56
  return () => {
21
57
  mutationObserver.disconnect();
22
- throttledHandleTextChange.cleanup();
58
+ cleanup();
23
59
  };
24
60
  };
@@ -1,6 +1,6 @@
1
1
  import { sendPostMessage } from "@/lib/post-message/sendPostMessage";
2
2
 
3
- export const handleTextChange = (node: HTMLElement, mutations: MutationRecord[]): void => {
3
+ export const handleTextChange = (node: HTMLElement, mutations: MutationRecord[], final: boolean = false): void => {
4
4
  // Check if any mutation is a text content change
5
5
  const hasTextChange = mutations.some((mutation) => {
6
6
  return (
@@ -9,7 +9,7 @@ export const handleTextChange = (node: HTMLElement, mutations: MutationRecord[])
9
9
  );
10
10
  });
11
11
 
12
- if (!hasTextChange) {
12
+ if (!hasTextChange && !final) {
13
13
  return;
14
14
  }
15
15
 
@@ -19,9 +19,12 @@ export const handleTextChange = (node: HTMLElement, mutations: MutationRecord[])
19
19
  // Get the node ID
20
20
  const nodeId = node.getAttribute("data-node-id");
21
21
 
22
+ console.log("textContentChanged", textContent, final);
23
+
22
24
  // Send postMessage with the text change
23
25
  sendPostMessage("textContentChanged", {
24
26
  nodeId,
25
27
  textContent,
28
+ final,
26
29
  });
27
30
  };
@@ -1,6 +1,7 @@
1
1
  import { disableCanvasTextMode } from "@/lib/canvas/disableCanvasTextMode";
2
2
  import { enableCanvasTextMode } from "@/lib/canvas/enableCanvasTextMode";
3
3
  import { setupNodeListeners } from "./events/setupNodeListeners";
4
+ import { handleTextChange } from "./helpers/handleTextChange";
4
5
  import { hasTextContent } from "./helpers/hasTextContent";
5
6
  import { makeNodeEditable } from "./helpers/makeNodeEditable";
6
7
  import { makeNodeNonEditable } from "./helpers/makeNodeNonEditable";
@@ -49,6 +50,9 @@ export const nodeText = (canvasName: string = "canvas"): NodeText => {
49
50
 
50
51
  const nodeToCleanup = editableNode;
51
52
 
53
+ // Send final textContentChanged message before cleanup
54
+ handleTextChange(nodeToCleanup, [], true);
55
+
52
56
  makeNodeNonEditable(nodeToCleanup);
53
57
  disableCanvasTextMode(canvasName);
54
58
  cleanup?.();