@node-edit-utils/core 2.2.7 → 2.2.9

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.
Files changed (34) hide show
  1. package/dist/lib/node-tools/events/click/handleNodeClick.d.ts +2 -1
  2. package/dist/lib/node-tools/events/setupEventListener.d.ts +2 -1
  3. package/dist/lib/node-tools/highlight/createCornerHandles.d.ts +1 -1
  4. package/dist/lib/node-tools/highlight/createHighlightFrame.d.ts +1 -1
  5. package/dist/lib/node-tools/highlight/createToolsContainer.d.ts +1 -1
  6. package/dist/lib/node-tools/select/selectNode.d.ts +2 -1
  7. package/dist/lib/node-tools/text/helpers/enterTextEditMode.d.ts +2 -0
  8. package/dist/lib/node-tools/text/helpers/handleTextChange.d.ts +1 -0
  9. package/dist/lib/node-tools/text/helpers/shouldEnterTextEditMode.d.ts +1 -0
  10. package/dist/node-edit-utils.cjs.js +251 -81
  11. package/dist/node-edit-utils.esm.js +251 -81
  12. package/dist/node-edit-utils.umd.js +251 -81
  13. package/dist/node-edit-utils.umd.min.js +1 -1
  14. package/dist/styles.css +1 -1
  15. package/package.json +1 -1
  16. package/src/index.ts +0 -2
  17. package/src/lib/node-tools/createNodeTools.ts +2 -16
  18. package/src/lib/node-tools/events/click/handleNodeClick.ts +3 -2
  19. package/src/lib/node-tools/events/setupEventListener.ts +3 -2
  20. package/src/lib/node-tools/highlight/clearHighlightFrame.ts +7 -2
  21. package/src/lib/node-tools/highlight/createCornerHandles.ts +12 -6
  22. package/src/lib/node-tools/highlight/createHighlightFrame.ts +25 -7
  23. package/src/lib/node-tools/highlight/createTagLabel.ts +25 -1
  24. package/src/lib/node-tools/highlight/createToolsContainer.ts +4 -1
  25. package/src/lib/node-tools/highlight/helpers/getHighlightFrameElement.ts +5 -1
  26. package/src/lib/node-tools/highlight/highlightNode.ts +17 -6
  27. package/src/lib/node-tools/highlight/refreshHighlightFrame.ts +37 -4
  28. package/src/lib/node-tools/highlight/updateHighlightFrameVisibility.ts +4 -1
  29. package/src/lib/node-tools/select/selectNode.ts +24 -5
  30. package/src/lib/node-tools/text/events/setupMutationObserver.ts +53 -3
  31. package/src/lib/node-tools/text/helpers/enterTextEditMode.ts +9 -0
  32. package/src/lib/node-tools/text/helpers/handleTextChange.ts +29 -0
  33. package/src/lib/node-tools/text/helpers/shouldEnterTextEditMode.ts +9 -0
  34. package/src/lib/styles/styles.css +23 -8
@@ -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}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.transform=`translate(${y}px, ${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.transform=`translate(${s}px, ${l}px)`,c.style.transformOrigin="left center",c.style.pointerEvents="none",c.style.zIndex="5000",g(e,c,o),document.body.appendChild(c)})(e),e&&s&&(f(e),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()}}}});
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="canvas")=>{let r=[],i=null,s=null;const a=()=>{if(r.length>0){const t=[...r];r=[],((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??"",r=e.getAttribute("data-node-id");console.log("textContentChanged",r,o),n("textContentChanged",{nodeId:r,textContent:o})})(e,t)}},l=((e,t)=>{const n=new MutationObserver(e=>{t(e)});return n.observe(e,{subtree:!0,childList:!0,characterData:!0}),n})(e,n=>{r.push(...n),null===i&&(i=requestAnimationFrame(()=>{s=requestAnimationFrame(()=>{a(),i=null,s=null})})),E(e,t,o)});return()=>{l.disconnect(),null!==i&&(cancelAnimationFrame(i),i=null),null!==s&&(cancelAnimationFrame(s),s=null),r=[]}},S=(e="canvas")=>{let n=null,o=!1,r=null;const i=()=>{if(o||!n)return;o=!0;var i;(i=n).contentEditable="false",i.classList.remove("is-editable"),i.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=x(e,t,o);return()=>{e.removeEventListener("blur",n),r(),i?.()}})(o,s,i,e))},blurEditMode:i,getEditableNode:()=>n,isEditing:()=>null!==n}};const C=320,k=1680,N=[{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"}],q=(e,t,n)=>{const o=parseFloat(document.body.dataset.zoom||"1"),r=((e,t)=>{const n=e+Math.round(t);return Math.max(C,Math.min(k,n))})(n,(e.clientX-t)/o);return r},M=(e,t)=>{e.style.setProperty("--container-width",`${t}px`),((e,t)=>{e.querySelectorAll(".resize-preset-button").forEach((e,n)=>{n<N.length&&(N[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=S(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",N.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,M);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=q(n,s,a);M(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=>{M(e,t)},cleanup:()=>{i=!1,l?.cleanup(),d(),r.remove()}}}});
package/dist/styles.css CHANGED
@@ -1 +1 @@
1
- :root{--primary-color:oklch(0.6235 0.22 294);--uncode-color:oklch(45.7% 0.24 277.023);--component-color:oklch(65.6% 0.241 354.308);--handle-color:oklch(55.2% 0.016 285.938);--handle-color-transparent:oklch(55.2% 0.016 285.938/0.7);--primary-color-selection:oklch(0.59 0.18 294/0.3);--text-color-white:#fff;--spacing-2xs:0.1875rem;--spacing-xs:0.25rem;--spacing-sm:0.375rem;--spacing-md:0.5rem;--spacing-lg:1.25rem;--transition-fast:0.1s ease-in-out;--transition-medium:0.2s ease-in-out;--z-index-high:10000;--z-index-highlight:5000;--z-index-medium:1000;--letter-spacing:0.02em;--font-family-primary:"Manrope",sans-serif}.node-provider{::selection{background:transparent}::-moz-selection{background:transparent}::-webkit-selection{background:transparent}}.node-tools{bottom:0;contain:layout style;display:flex;gap:var(--spacing-xs);justify-content:center;left:0;opacity:var(--tool-opacity);padding-top:var(--spacing-2xs);position:absolute;top:0;transform:translate3d(0,100%,0);transform-origin:bottom center;transition:opacity var(--transition-fast);z-index:var(--z-index-high)}.highlight-frame-overlay{contain:layout style paint;height:100vh;inset:0;overflow:visible;pointer-events:none;position:fixed;width:100vw;will-change:transform;z-index:var(--z-index-highlight)}.highlight-frame-rect{fill:none;stroke:var(--primary-color);stroke-width:2px}.highlight-frame-overlay.is-instance .highlight-frame-rect{stroke:var(--component-color)}.highlight-frame-handle{fill:#fff;stroke:var(--primary-color);stroke-width:1px;cursor:nwse-resize;pointer-events:auto}.highlight-frame-overlay.is-instance .highlight-frame-handle{stroke:var(--component-color)}.highlight-frame-handle.handle-top-left{cursor:nwse-resize}.highlight-frame-handle.handle-top-right{cursor:nesw-resize}.highlight-frame-handle.handle-bottom-right{cursor:nwse-resize}.highlight-frame-handle.handle-bottom-left{cursor:nesw-resize}.highlight-frame-tools-wrapper{contain:layout style;left:0;pointer-events:none;position:fixed;top:0;z-index:var(--z-index-highlight)}.tag-label{align-items:center;background-color:var(--primary-color);border-radius:var(--spacing-sm);color:var(--text-color-white);display:flex;font-size:.575rem;font-weight:500;height:1rem;justify-content:center;line-height:1;padding:.5625rem var(--spacing-sm);text-transform:uppercase}.highlight-frame-tools-wrapper.is-instance .tag-label,.node-tools.is-instance .tag-label{background-color:var(--component-color)}.viewport{position:relative;width:var(--container-width)}.resize-handle{align-items:center;cursor:ew-resize;display:flex;height:40px;justify-content:center;opacity:0;pointer-events:auto;position:absolute;right:-12px;top:var(--spacing-lg);transform:scale(calc(1/var(--zoom)));transform-origin:top left;transition:opacity var(--transition-fast),visibility var(--transition-fast);visibility:hidden;width:12px;z-index:var(--z-index-high)}.viewport:hover .resize-handle{opacity:1;visibility:visible}.resize-handle:before{background:var(--handle-color);border-radius:4px;content:"";height:32px;position:relative;right:0;top:0;transition:transform var(--transition-medium);width:3px}.resize-handle:hover:before{transform:scaleY(1.3)}.resize-presets{display:flex;flex-direction:column;font-family:var(--font-family-primary);gap:var(--spacing-xs);left:0;letter-spacing:var(--letter-spacing);opacity:0;padding-left:var(--spacing-lg);position:absolute;top:0;transition:visibility var(--transition-fast),opacity var(--transition-fast);visibility:hidden}.resize-handle:hover .resize-presets{opacity:1;visibility:visible}.resize-preset-button{text-wrap:nowrap;backdrop-filter:blur(8px);background:var(--handle-color-transparent);border:none;border-radius:var(--spacing-md);color:var(--text-color-white);cursor:pointer;font-size:.625rem;padding:var(--spacing-xs) var(--spacing-md);text-align:left;transition:background var(--transition-fast);width:fit-content;&:hover{background:var(--handle-color)}&.is-active{background:var(--uncode-color)}}.is-editable{outline:none;user-select:text;&::selection{background:var(--primary-color-selection)}&::-moz-selection{background:var(--primary-color-selection)}&::-webkit-selection{background:var(--primary-color-selection)}}
1
+ :root{--primary-color:oklch(0.6235 0.22 294);--uncode-color:oklch(45.7% 0.24 277.023);--component-color:oklch(65.6% 0.241 354.308);--text-edit-color:oklch(62.3% 0.214 259.815);--text-edit-selection-color:oklch(62.3% 0.214 259.815/0.2);--handle-color:oklch(55.2% 0.016 285.938);--handle-color-transparent:oklch(55.2% 0.016 285.938/0.7);--primary-color-selection:oklch(0.59 0.18 294/0.3);--text-color-white:#fff;--spacing-2xs:0.1875rem;--spacing-xs:0.25rem;--spacing-sm:0.375rem;--spacing-md:0.5rem;--spacing-lg:1.25rem;--transition-fast:0.1s ease-in-out;--transition-medium:0.2s ease-in-out;--z-index-high:10000;--z-index-highlight:500;--z-index-medium:1000;--letter-spacing:0.02em;--font-family-primary:"Manrope",sans-serif}.node-provider{::selection{background:transparent}::-moz-selection{background:transparent}::-webkit-selection{background:transparent}}.node-tools{bottom:0;contain:layout style;display:flex;gap:var(--spacing-xs);justify-content:center;left:0;opacity:var(--tool-opacity);padding-top:var(--spacing-2xs);position:absolute;top:0;transform:translate3d(0,100%,0);transform-origin:bottom center;transition:opacity var(--transition-fast);z-index:var(--z-index-high)}.highlight-frame-overlay{contain:layout style paint;height:100vh;inset:0;overflow:visible;pointer-events:none;position:absolute;width:100vw;will-change:transform;z-index:var(--z-index-highlight)}.highlight-frame-rect{fill:none;stroke:var(--primary-color);stroke-width:2px}.highlight-frame-overlay.is-instance .highlight-frame-rect{stroke:var(--component-color)}.highlight-frame-overlay.is-text-edit .highlight-frame-rect{stroke:var(--text-edit-color)}.highlight-frame-handle{fill:#fff;stroke:var(--primary-color);stroke-width:1px;cursor:nwse-resize;pointer-events:auto}.highlight-frame-overlay.is-instance .highlight-frame-handle{stroke:var(--component-color)}.highlight-frame-overlay.is-text-edit .highlight-frame-handle{stroke:var(--text-edit-color)}.highlight-frame-handle.handle-top-left{cursor:nwse-resize}.highlight-frame-handle.handle-top-right{cursor:nesw-resize}.highlight-frame-handle.handle-bottom-right{cursor:nwse-resize}.highlight-frame-handle.handle-bottom-left{cursor:nesw-resize}.highlight-frame-tools-wrapper{contain:layout style;left:0;pointer-events:none;position:absolute;top:0;z-index:var(--z-index-highlight)}.tag-label{align-items:center;background-color:var(--primary-color);border-radius:var(--spacing-sm);color:var(--text-color-white);display:flex;font-size:.575rem;font-weight:500;height:1rem;justify-content:center;line-height:1.4;padding:.5625rem var(--spacing-sm);white-space:nowrap}.highlight-frame-tools-wrapper.is-instance .tag-label,.node-tools.is-instance .tag-label{background-color:var(--component-color)}.highlight-frame-tools-wrapper.is-text-edit .tag-label,.node-tools.is-text-edit .tag-label{background-color:var(--text-edit-color)}.viewport{position:relative;width:var(--container-width)}.resize-handle{align-items:center;cursor:ew-resize;display:flex;height:40px;justify-content:center;opacity:0;pointer-events:auto;position:absolute;right:-12px;top:var(--spacing-lg);transform:scale(calc(1/var(--zoom)));transform-origin:top left;transition:opacity var(--transition-fast),visibility var(--transition-fast);visibility:hidden;width:12px;z-index:var(--z-index-high)}.viewport:hover .resize-handle{opacity:1;visibility:visible}.resize-handle:before{background:var(--handle-color);border-radius:4px;content:"";height:32px;position:relative;right:0;top:0;transition:transform var(--transition-medium);width:3px}.resize-handle:hover:before{transform:scaleY(1.3)}.resize-presets{display:flex;flex-direction:column;font-family:var(--font-family-primary);gap:var(--spacing-xs);left:0;letter-spacing:var(--letter-spacing);opacity:0;padding-left:var(--spacing-lg);position:absolute;top:0;transition:visibility var(--transition-fast),opacity var(--transition-fast);visibility:hidden}.resize-handle:hover .resize-presets{opacity:1;visibility:visible}.resize-preset-button{text-wrap:nowrap;backdrop-filter:blur(8px);background:var(--handle-color-transparent);border:none;border-radius:var(--spacing-md);color:var(--text-color-white);cursor:pointer;font-size:.625rem;padding:var(--spacing-xs) var(--spacing-md);text-align:left;transition:background var(--transition-fast);width:fit-content;&:hover{background:var(--handle-color)}&.is-active{background:var(--uncode-color)}}.is-editable{outline:none;user-select:text;&::selection{background:var(--text-edit-selection-color)}&::-moz-selection{background:var(--text-edit-selection-color)}&::-webkit-selection{background:var(--text-edit-selection-color)}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node-edit-utils/core",
3
- "version": "2.2.7",
3
+ "version": "2.2.9",
4
4
  "description": "Utilities for editing nodes in a dom tree.",
5
5
  "type": "module",
6
6
  "main": "dist/node-edit-utils.cjs.js",
package/src/index.ts CHANGED
@@ -1,9 +1,7 @@
1
1
  import "@/lib/styles/styles.css";
2
2
 
3
3
  export { createCanvasObserver } from "@/lib/canvas/createCanvasObserver";
4
-
5
4
  export { createNodeTools } from "@/lib/node-tools/createNodeTools";
6
5
  export type { NodeTools, NodeToolsRef } from "@/lib/node-tools/types";
7
-
8
6
  export { createViewport } from "@/lib/viewport/createViewport";
9
7
  export type { Viewport, ViewportRef } from "@/lib/viewport/types";
@@ -1,5 +1,4 @@
1
1
  import { connectResizeObserver } from "../helpers/observer/connectResizeObserver";
2
- import { withRAFThrottle } from "../helpers/withRAF";
3
2
  import { sendPostMessage } from "../post-message/sendPostMessage";
4
3
  import { bindToWindow } from "../window/bindToWindow";
5
4
  import { setupEventListener } from "./events/setupEventListener";
@@ -20,12 +19,6 @@ export const createNodeTools = (element: HTMLElement | null, canvasName: string
20
19
 
21
20
  const text = nodeText(canvasName);
22
21
 
23
- // Combined throttled function for refresh + visibility update
24
- const throttledRefreshAndVisibility = withRAFThrottle((node: HTMLElement, nodeProvider: HTMLElement) => {
25
- refreshHighlightFrame(node, nodeProvider, canvasName);
26
- updateHighlightFrameVisibility(node);
27
- });
28
-
29
22
  const handleEscape = (): void => {
30
23
  if (text.isEditing()) {
31
24
  text.blurEditMode();
@@ -60,8 +53,6 @@ export const createNodeTools = (element: HTMLElement | null, canvasName: string
60
53
  parentMutationObserver?.disconnect();
61
54
 
62
55
  if (node && nodeProvider) {
63
- text.enableEditMode(node, nodeProvider);
64
-
65
56
  // Check if node is still in DOM and handle cleanup if removed
66
57
  const checkNodeExists = (): void => {
67
58
  if (!document.contains(node)) {
@@ -78,10 +69,8 @@ export const createNodeTools = (element: HTMLElement | null, canvasName: string
78
69
 
79
70
  mutationObserver = new MutationObserver(() => {
80
71
  checkNodeExists();
81
- if (!document.contains(node)) return; // Exit early if node was removed
72
+ if (!document.contains(node)) return;
82
73
 
83
- // throttledRefreshAndVisibility(node, nodeProvider);
84
- console.log("mutationObserver", node);
85
74
  refreshHighlightFrame(node, nodeProvider, canvasName);
86
75
  updateHighlightFrameVisibility(node);
87
76
  });
@@ -121,8 +110,6 @@ export const createNodeTools = (element: HTMLElement | null, canvasName: string
121
110
  checkNodeExists();
122
111
  if (!document.contains(node)) return; // Exit early if node was removed
123
112
 
124
- // throttledRefreshAndVisibility(node, nodeProvider);
125
- console.log("resizeObserver", node);
126
113
  refreshHighlightFrame(node, nodeProvider, canvasName);
127
114
  updateHighlightFrameVisibility(node);
128
115
  });
@@ -139,7 +126,7 @@ export const createNodeTools = (element: HTMLElement | null, canvasName: string
139
126
  };
140
127
 
141
128
  // Setup event listener
142
- const removeListeners = setupEventListener(nodeProvider, selectNode, handleEscape, text.getEditableNode);
129
+ const removeListeners = setupEventListener(nodeProvider, selectNode, handleEscape, text);
143
130
 
144
131
  const cleanup = (): void => {
145
132
  removeListeners();
@@ -148,7 +135,6 @@ export const createNodeTools = (element: HTMLElement | null, canvasName: string
148
135
  parentMutationObserver?.disconnect();
149
136
 
150
137
  text.blurEditMode();
151
- throttledRefreshAndVisibility.cleanup();
152
138
 
153
139
  // Clear highlight frame and reset selected node
154
140
  clearHighlightFrame();
@@ -1,10 +1,11 @@
1
1
  import { clearHighlightFrame } from "../../highlight/clearHighlightFrame";
2
2
  import { selectNode } from "../../select/selectNode";
3
+ import type { NodeText } from "../../text/types";
3
4
 
4
5
  export const handleNodeClick = (
5
6
  event: MouseEvent,
6
7
  nodeProvider: HTMLElement | null,
7
- editableNode: HTMLElement | null,
8
+ text: NodeText,
8
9
  onNodeSelected: (node: HTMLElement | null) => void
9
10
  ): void => {
10
11
  event.preventDefault();
@@ -16,6 +17,6 @@ export const handleNodeClick = (
16
17
  return;
17
18
  }
18
19
 
19
- const selectedNode = selectNode(event, editableNode);
20
+ const selectedNode = selectNode(event, nodeProvider, text);
20
21
  onNodeSelected(selectedNode);
21
22
  };
@@ -1,18 +1,19 @@
1
1
  import { processPostMessage } from "@/lib/post-message/processPostMessage";
2
+ import type { NodeText } from "../text/types";
2
3
  import { handleNodeClick } from "./click/handleNodeClick";
3
4
 
4
5
  export const setupEventListener = (
5
6
  nodeProvider: HTMLElement | null,
6
7
  onNodeSelected: (node: HTMLElement | null) => void,
7
8
  onEscapePressed: () => void,
8
- getEditableNode: () => HTMLElement | null
9
+ text: NodeText
9
10
  ): (() => void) => {
10
11
  const messageHandler = (event: MessageEvent) => {
11
12
  processPostMessage(event, onNodeSelected);
12
13
  };
13
14
 
14
15
  const documentClickHandler = (event: MouseEvent) => {
15
- handleNodeClick(event, nodeProvider, getEditableNode(), onNodeSelected);
16
+ handleNodeClick(event, nodeProvider, text, onNodeSelected);
16
17
  };
17
18
 
18
19
  const documentKeydownHandler = (event: KeyboardEvent) => {
@@ -1,10 +1,15 @@
1
+ import { getCanvasContainer } from "@/lib/canvas/helpers/getCanvasContainer";
2
+
1
3
  export const clearHighlightFrame = (): void => {
2
- const frame = document.body.querySelector(".highlight-frame-overlay");
4
+ const canvasContainer = getCanvasContainer();
5
+ const container = canvasContainer || document.body;
6
+
7
+ const frame = container.querySelector(".highlight-frame-overlay");
3
8
  if (frame) {
4
9
  frame.remove();
5
10
  }
6
11
 
7
- const toolsWrapper = document.body.querySelector(".highlight-frame-tools-wrapper");
12
+ const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
8
13
  if (toolsWrapper) {
9
14
  toolsWrapper.remove();
10
15
  }
@@ -4,7 +4,11 @@ const getComponentColor = (): string => {
4
4
  return getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim() || "oklch(65.6% 0.241 354.308)";
5
5
  };
6
6
 
7
- const createCornerHandle = (group: SVGGElement, x: number, y: number, className: string, isInstance: boolean = false): SVGRectElement => {
7
+ const getTextEditColor = (): string => {
8
+ return getComputedStyle(document.documentElement).getPropertyValue("--text-edit-color").trim() || "oklch(62.3% 0.214 259.815)";
9
+ };
10
+
11
+ const createCornerHandle = (group: SVGGElement, x: number, y: number, className: string, isInstance: boolean = false, isTextEdit: boolean = false): SVGRectElement => {
8
12
  const handle = document.createElementNS("http://www.w3.org/2000/svg", "rect");
9
13
  // Position relative to group (offset by half handle size to center on corner)
10
14
  handle.setAttribute("x", (x - HANDLE_SIZE / 2).toString());
@@ -16,16 +20,18 @@ const createCornerHandle = (group: SVGGElement, x: number, y: number, className:
16
20
 
17
21
  if (isInstance) {
18
22
  handle.setAttribute("stroke", getComponentColor());
23
+ } else if (isTextEdit) {
24
+ handle.setAttribute("stroke", getTextEditColor());
19
25
  }
20
26
 
21
27
  group.appendChild(handle);
22
28
  return handle;
23
29
  };
24
30
 
25
- export const createCornerHandles = (group: SVGGElement, width: number, height: number, isInstance: boolean = false): void => {
31
+ export const createCornerHandles = (group: SVGGElement, width: number, height: number, isInstance: boolean = false, isTextEdit: boolean = false): void => {
26
32
  // Create corner handles using relative coordinates (group handles positioning)
27
- createCornerHandle(group, 0, 0, "handle-top-left", isInstance);
28
- createCornerHandle(group, width, 0, "handle-top-right", isInstance);
29
- createCornerHandle(group, width, height, "handle-bottom-right", isInstance);
30
- createCornerHandle(group, 0, height, "handle-bottom-left", isInstance);
33
+ createCornerHandle(group, 0, 0, "handle-top-left", isInstance, isTextEdit);
34
+ createCornerHandle(group, width, 0, "handle-top-right", isInstance, isTextEdit);
35
+ createCornerHandle(group, width, height, "handle-bottom-right", isInstance, isTextEdit);
36
+ createCornerHandle(group, 0, height, "handle-bottom-left", isInstance, isTextEdit);
31
37
  };
@@ -1,3 +1,4 @@
1
+ import { getCanvasContainer } from "@/lib/canvas/helpers/getCanvasContainer";
1
2
  import { createCornerHandles } from "./createCornerHandles";
2
3
  import { getScreenBounds } from "./helpers/getScreenBounds";
3
4
 
@@ -5,25 +6,35 @@ const getComponentColor = (): string => {
5
6
  return getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim() || "oklch(65.6% 0.241 354.308)";
6
7
  };
7
8
 
8
- export const createHighlightFrame = (node: HTMLElement, isInstance: boolean = false): SVGSVGElement => {
9
+ const getTextEditColor = (): string => {
10
+ return getComputedStyle(document.documentElement).getPropertyValue("--text-edit-color").trim() || "oklch(62.3% 0.214 259.815)";
11
+ };
12
+
13
+ export const createHighlightFrame = (node: HTMLElement, isInstance: boolean = false, isTextEdit: boolean = false): SVGSVGElement => {
9
14
  const { top, left, width, height } = getScreenBounds(node);
10
15
 
16
+ // Ensure minimum width of 2px
17
+ const minWidth = Math.max(width, 3);
18
+
11
19
  // Create fixed SVG overlay
12
20
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
13
21
  svg.classList.add("highlight-frame-overlay");
14
22
  if (isInstance) {
15
23
  svg.classList.add("is-instance");
16
24
  }
25
+ if (isTextEdit) {
26
+ svg.classList.add("is-text-edit");
27
+ }
17
28
  svg.setAttribute("data-node-id", node.getAttribute("data-node-id") || "");
18
29
 
19
30
  // Set fixed positioning
20
- svg.style.position = "fixed";
31
+ svg.style.position = "absolute";
21
32
  svg.style.top = "0";
22
33
  svg.style.left = "0";
23
34
  svg.style.width = "100vw";
24
35
  svg.style.height = "100vh";
25
36
  svg.style.pointerEvents = "none";
26
- svg.style.zIndex = "5000";
37
+ svg.style.zIndex = "500";
27
38
 
28
39
  const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
29
40
  const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
@@ -37,22 +48,29 @@ export const createHighlightFrame = (node: HTMLElement, isInstance: boolean = fa
37
48
  const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
38
49
  rect.setAttribute("x", "0");
39
50
  rect.setAttribute("y", "0");
40
- rect.setAttribute("width", width.toString());
51
+ rect.setAttribute("width", minWidth.toString());
41
52
  rect.setAttribute("height", height.toString());
42
53
  rect.setAttribute("vector-effect", "non-scaling-stroke");
43
54
  rect.classList.add("highlight-frame-rect");
44
55
 
45
- // Apply instance color if it's an instance
56
+ // Apply instance color if it's an instance, otherwise text edit color if in text edit mode
46
57
  if (isInstance) {
47
58
  rect.setAttribute("stroke", getComponentColor());
59
+ } else if (isTextEdit) {
60
+ rect.setAttribute("stroke", getTextEditColor());
48
61
  }
49
62
 
50
63
  group.appendChild(rect);
51
64
 
52
- createCornerHandles(group, width, height, isInstance);
65
+ createCornerHandles(group, minWidth, height, isInstance, isTextEdit);
53
66
 
54
67
  svg.appendChild(group);
55
- document.body.appendChild(svg);
68
+ const canvasContainer = getCanvasContainer();
69
+ if (canvasContainer) {
70
+ canvasContainer.appendChild(svg);
71
+ } else {
72
+ document.body.appendChild(svg);
73
+ }
56
74
 
57
75
  return svg as SVGSVGElement;
58
76
  };
@@ -1,7 +1,31 @@
1
+ const TAG_NAME_MAP: Record<string, string> = {
2
+ div: "Container",
3
+ h1: "Heading 1",
4
+ h2: "Heading 2",
5
+ h3: "Heading 3",
6
+ h4: "Heading 4",
7
+ h5: "Heading 5",
8
+ h6: "Heading 6",
9
+ p: "Text",
10
+ li: "List Item",
11
+ ul: "Unordered List",
12
+ ol: "Ordered List",
13
+ img: "Image",
14
+ a: "Link",
15
+ };
16
+
17
+ const capitalize = (str: string): string => {
18
+ if (!str) return str;
19
+ return str.charAt(0).toUpperCase() + str.slice(1);
20
+ };
21
+
1
22
  export const createTagLabel = (node: HTMLElement, nodeTools: HTMLElement): void => {
2
23
  const tagLabel = document.createElement("div");
3
24
  tagLabel.className = "tag-label";
4
- tagLabel.textContent = node.tagName.toLowerCase();
25
+ const instanceName = node.getAttribute("data-instance-name");
26
+ const tagName = node.tagName.toLowerCase();
27
+ const labelText = instanceName || TAG_NAME_MAP[tagName] || tagName;
28
+ tagLabel.textContent = capitalize(labelText);
5
29
 
6
30
  nodeTools.appendChild(tagLabel);
7
31
  };
@@ -1,12 +1,15 @@
1
1
  import { createTagLabel } from "./createTagLabel";
2
2
 
3
- export const createToolsContainer = (node: HTMLElement, highlightFrame: HTMLElement, isInstance: boolean = false): void => {
3
+ export const createToolsContainer = (node: HTMLElement, highlightFrame: HTMLElement, isInstance: boolean = false, isTextEdit: boolean = false): void => {
4
4
  const nodeTools = document.createElement("div");
5
5
 
6
6
  nodeTools.className = "node-tools";
7
7
  if (isInstance) {
8
8
  nodeTools.classList.add("is-instance");
9
9
  }
10
+ if (isTextEdit) {
11
+ nodeTools.classList.add("is-text-edit");
12
+ }
10
13
  highlightFrame.appendChild(nodeTools);
11
14
 
12
15
  createTagLabel(node, nodeTools);
@@ -1,3 +1,7 @@
1
+ import { getCanvasContainer } from "@/lib/canvas/helpers/getCanvasContainer";
2
+
1
3
  export function getHighlightFrameElement(): SVGSVGElement | null {
2
- return document.body.querySelector(".highlight-frame-overlay") as SVGSVGElement | null;
4
+ const canvasContainer = getCanvasContainer();
5
+ const container = canvasContainer || document.body;
6
+ return container.querySelector(".highlight-frame-overlay") as SVGSVGElement | null;
3
7
  }
@@ -1,3 +1,4 @@
1
+ import { getCanvasContainer } from "@/lib/canvas/helpers/getCanvasContainer";
1
2
  import { isComponentInstance } from "../select/helpers/isComponentInstance";
2
3
  import { createHighlightFrame } from "./createHighlightFrame";
3
4
  import { createToolsContainer } from "./createToolsContainer";
@@ -8,7 +9,9 @@ export const highlightNode = (node: HTMLElement | null): void => {
8
9
  if (!node) return;
9
10
 
10
11
  const existingHighlightFrame = getHighlightFrameElement();
11
- const existingToolsWrapper = document.body.querySelector(".highlight-frame-tools-wrapper");
12
+ const canvasContainer = getCanvasContainer();
13
+ const existingToolsWrapper =
14
+ canvasContainer?.querySelector(".highlight-frame-tools-wrapper") || document.body.querySelector(".highlight-frame-tools-wrapper");
12
15
 
13
16
  if (existingHighlightFrame) {
14
17
  existingHighlightFrame.remove();
@@ -18,7 +21,8 @@ export const highlightNode = (node: HTMLElement | null): void => {
18
21
  }
19
22
 
20
23
  const isInstance = isComponentInstance(node);
21
- const highlightFrame = createHighlightFrame(node, isInstance);
24
+ const isTextEdit = node.contentEditable === "true";
25
+ const highlightFrame = createHighlightFrame(node, isInstance, isTextEdit);
22
26
 
23
27
  if (node.contentEditable === "true") {
24
28
  highlightFrame.classList.add("is-editable");
@@ -34,12 +38,19 @@ export const highlightNode = (node: HTMLElement | null): void => {
34
38
  if (isInstance) {
35
39
  toolsWrapper.classList.add("is-instance");
36
40
  }
37
- toolsWrapper.style.position = "fixed";
41
+ if (isTextEdit) {
42
+ toolsWrapper.classList.add("is-text-edit");
43
+ }
44
+ toolsWrapper.style.position = "absolute";
38
45
  toolsWrapper.style.transform = `translate(${left}px, ${bottomY}px)`;
39
46
  toolsWrapper.style.transformOrigin = "left center";
40
47
  toolsWrapper.style.pointerEvents = "none";
41
- toolsWrapper.style.zIndex = "5000"; // Match --z-index-highlight (below canvas rulers)
48
+ toolsWrapper.style.zIndex = "500";
42
49
 
43
- createToolsContainer(node, toolsWrapper, isInstance);
44
- document.body.appendChild(toolsWrapper);
50
+ createToolsContainer(node, toolsWrapper, isInstance, isTextEdit);
51
+ if (canvasContainer) {
52
+ canvasContainer.appendChild(toolsWrapper);
53
+ } else {
54
+ document.body.appendChild(toolsWrapper);
55
+ }
45
56
  };
@@ -1,3 +1,4 @@
1
+ import { getCanvasContainer } from "@/lib/canvas/helpers/getCanvasContainer";
1
2
  import { getCanvasWindowValue } from "@/lib/canvas/helpers/getCanvasWindowValue";
2
3
  import { isComponentInstance } from "../select/helpers/isComponentInstance";
3
4
  import { getHighlightFrameElement } from "./helpers/getHighlightFrameElement";
@@ -7,12 +8,17 @@ const getComponentColor = (): string => {
7
8
  return getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim() || "oklch(65.6% 0.241 354.308)";
8
9
  };
9
10
 
11
+ const getTextEditColor = (): string => {
12
+ return getComputedStyle(document.documentElement).getPropertyValue("--text-edit-color").trim() || "oklch(62.3% 0.214 259.815)";
13
+ };
14
+
10
15
  export const refreshHighlightFrame = (node: HTMLElement, nodeProvider: HTMLElement, canvasName: string = "canvas") => {
11
16
  // Batch all DOM reads first (single layout pass)
12
17
  const frame = getHighlightFrameElement();
13
18
  if (!frame) return;
14
19
 
15
20
  const isInstance = isComponentInstance(node);
21
+ const isTextEdit = node.contentEditable === "true";
16
22
 
17
23
  // Update SVG dimensions to match current viewport (handles window resize and ensures coordinate system is correct)
18
24
  // Use clientWidth/Height to match getBoundingClientRect() coordinate system (excludes scrollbars)
@@ -28,6 +34,13 @@ export const refreshHighlightFrame = (node: HTMLElement, nodeProvider: HTMLEleme
28
34
  frame.classList.remove("is-instance");
29
35
  }
30
36
 
37
+ // Update text edit class
38
+ if (isTextEdit) {
39
+ frame.classList.add("is-text-edit");
40
+ } else {
41
+ frame.classList.remove("is-text-edit");
42
+ }
43
+
31
44
  const group = frame.querySelector(".highlight-frame-group") as SVGGElement | null;
32
45
  if (!group) return;
33
46
 
@@ -37,11 +50,15 @@ export const refreshHighlightFrame = (node: HTMLElement, nodeProvider: HTMLEleme
37
50
  // Update instance color
38
51
  if (isInstance) {
39
52
  rect.setAttribute("stroke", getComponentColor());
53
+ } else if (isTextEdit) {
54
+ rect.setAttribute("stroke", getTextEditColor());
40
55
  } else {
41
56
  rect.removeAttribute("stroke"); // Use CSS default
42
57
  }
43
58
 
44
- const toolsWrapper = document.body.querySelector(".highlight-frame-tools-wrapper") as HTMLElement | null;
59
+ const canvasContainer = getCanvasContainer();
60
+ const container = canvasContainer || document.body;
61
+ const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper") as HTMLElement | null;
45
62
  const nodeTools = toolsWrapper?.querySelector(".node-tools") as HTMLElement | null;
46
63
 
47
64
  const zoom = getCanvasWindowValue(["zoom", "current"], canvasName) ?? 1;
@@ -49,6 +66,8 @@ export const refreshHighlightFrame = (node: HTMLElement, nodeProvider: HTMLEleme
49
66
 
50
67
  // Calculate all values before any DOM writes
51
68
  const { top, left, width, height } = bounds;
69
+ // Ensure minimum width of 2px
70
+ const minWidth = Math.max(width, 3);
52
71
  const bottomY = top + height;
53
72
 
54
73
  // Update instance classes on tools wrapper and node tools
@@ -58,6 +77,12 @@ export const refreshHighlightFrame = (node: HTMLElement, nodeProvider: HTMLEleme
58
77
  } else {
59
78
  toolsWrapper.classList.remove("is-instance");
60
79
  }
80
+ // Update text edit class
81
+ if (isTextEdit) {
82
+ toolsWrapper.classList.add("is-text-edit");
83
+ } else {
84
+ toolsWrapper.classList.remove("is-text-edit");
85
+ }
61
86
  }
62
87
  if (nodeTools) {
63
88
  if (isInstance) {
@@ -65,6 +90,12 @@ export const refreshHighlightFrame = (node: HTMLElement, nodeProvider: HTMLEleme
65
90
  } else {
66
91
  nodeTools.classList.remove("is-instance");
67
92
  }
93
+ // Update text edit class
94
+ if (isTextEdit) {
95
+ nodeTools.classList.add("is-text-edit");
96
+ } else {
97
+ nodeTools.classList.remove("is-text-edit");
98
+ }
68
99
  }
69
100
 
70
101
  // Batch all DOM writes (single paint pass)
@@ -72,7 +103,7 @@ export const refreshHighlightFrame = (node: HTMLElement, nodeProvider: HTMLEleme
72
103
  group.setAttribute("transform", `translate(${left}, ${top})`);
73
104
 
74
105
  // Update rect dimensions (position is handled by group transform)
75
- rect.setAttribute("width", width.toString());
106
+ rect.setAttribute("width", minWidth.toString());
76
107
  rect.setAttribute("height", height.toString());
77
108
 
78
109
  // Update corner handles positions (relative to group, so only width/height matter)
@@ -89,6 +120,8 @@ export const refreshHighlightFrame = (node: HTMLElement, nodeProvider: HTMLEleme
89
120
  if (handle) {
90
121
  if (isInstance) {
91
122
  handle.setAttribute("stroke", getComponentColor());
123
+ } else if (isTextEdit) {
124
+ handle.setAttribute("stroke", getTextEditColor());
92
125
  } else {
93
126
  handle.removeAttribute("stroke"); // Use CSS default
94
127
  }
@@ -100,11 +133,11 @@ export const refreshHighlightFrame = (node: HTMLElement, nodeProvider: HTMLEleme
100
133
  topLeft.setAttribute("y", (-HANDLE_SIZE / 2).toString());
101
134
  }
102
135
  if (topRight) {
103
- topRight.setAttribute("x", (width - HANDLE_SIZE / 2).toString());
136
+ topRight.setAttribute("x", (minWidth - HANDLE_SIZE / 2).toString());
104
137
  topRight.setAttribute("y", (-HANDLE_SIZE / 2).toString());
105
138
  }
106
139
  if (bottomRight) {
107
- bottomRight.setAttribute("x", (width - HANDLE_SIZE / 2).toString());
140
+ bottomRight.setAttribute("x", (minWidth - HANDLE_SIZE / 2).toString());
108
141
  bottomRight.setAttribute("y", (height - HANDLE_SIZE / 2).toString());
109
142
  }
110
143
  if (bottomLeft) {
@@ -1,3 +1,4 @@
1
+ import { getCanvasContainer } from "@/lib/canvas/helpers/getCanvasContainer";
1
2
  import { getHighlightFrameElement } from "./helpers/getHighlightFrameElement";
2
3
 
3
4
  export const updateHighlightFrameVisibility = (node: HTMLElement): void => {
@@ -11,7 +12,9 @@ export const updateHighlightFrameVisibility = (node: HTMLElement): void => {
11
12
  // Batch DOM writes
12
13
  frame.style.display = displayValue;
13
14
 
14
- const toolsWrapper = document.body.querySelector(".highlight-frame-tools-wrapper") as HTMLElement | null;
15
+ const canvasContainer = getCanvasContainer();
16
+ const container = canvasContainer || document.body;
17
+ const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper") as HTMLElement | null;
15
18
  if (toolsWrapper) {
16
19
  toolsWrapper.style.display = displayValue;
17
20
  }