@node-edit-utils/core 2.2.6 → 2.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/node-tools/events/click/handleNodeClick.d.ts +2 -1
- package/dist/lib/node-tools/events/setupEventListener.d.ts +2 -1
- package/dist/lib/node-tools/highlight/createCornerHandles.d.ts +1 -1
- package/dist/lib/node-tools/highlight/createHighlightFrame.d.ts +1 -1
- package/dist/lib/node-tools/highlight/createToolsContainer.d.ts +1 -1
- package/dist/lib/node-tools/select/selectNode.d.ts +2 -1
- package/dist/lib/node-tools/text/helpers/enterTextEditMode.d.ts +2 -0
- package/dist/lib/node-tools/text/helpers/handleTextChange.d.ts +1 -0
- package/dist/lib/node-tools/text/helpers/shouldEnterTextEditMode.d.ts +1 -0
- package/dist/node-edit-utils.cjs.js +227 -90
- package/dist/node-edit-utils.esm.js +227 -90
- package/dist/node-edit-utils.umd.js +227 -90
- package/dist/node-edit-utils.umd.min.js +1 -1
- package/dist/styles.css +1 -1
- package/package.json +4 -2
- package/src/index.ts +0 -2
- package/src/lib/node-tools/createNodeTools.ts +3 -16
- package/src/lib/node-tools/events/click/handleNodeClick.ts +3 -2
- package/src/lib/node-tools/events/setupEventListener.ts +3 -2
- package/src/lib/node-tools/highlight/clearHighlightFrame.ts +7 -2
- package/src/lib/node-tools/highlight/createCornerHandles.ts +12 -6
- package/src/lib/node-tools/highlight/createHighlightFrame.ts +25 -7
- package/src/lib/node-tools/highlight/createTagLabel.ts +25 -1
- package/src/lib/node-tools/highlight/createToolsContainer.ts +4 -1
- package/src/lib/node-tools/highlight/helpers/getHighlightFrameElement.ts +5 -1
- package/src/lib/node-tools/highlight/highlightNode.ts +21 -11
- package/src/lib/node-tools/highlight/refreshHighlightFrame.ts +40 -7
- package/src/lib/node-tools/highlight/updateHighlightFrameVisibility.ts +6 -1
- package/src/lib/node-tools/select/selectNode.ts +24 -5
- package/src/lib/node-tools/text/events/setupMutationObserver.ts +17 -3
- package/src/lib/node-tools/text/helpers/enterTextEditMode.ts +9 -0
- package/src/lib/node-tools/text/helpers/handleTextChange.ts +27 -0
- package/src/lib/node-tools/text/helpers/shouldEnterTextEditMode.ts +9 -0
- package/src/lib/styles/styles.css +28 -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.left=`${y}px`,u.style.top=`${L}px`),g<=10?n.style.setProperty("--tool-opacity","1"):n.style.setProperty("--tool-opacity","0")},f=e=>{const t=p();if(!t)return;const n=e.classList.contains("hidden")||e.classList.contains("select-none")?"none":"";t.style.display=n;const o=document.body.querySelector(".highlight-frame-tools-wrapper");o&&(o.style.display=n)},y=e=>{const t=e=>{"Enter"===e.key&&(e.preventDefault(),e.stopPropagation(),(()=>{const e=window.getSelection();if(e&&e.rangeCount>0){const t=e.getRangeAt(0);t.deleteContents();const n=document.createElement("br");t.insertNode(n),t.setStartAfter(n),t.setEndAfter(n),e.removeAllRanges(),e.addRange(t)}})())};return e.addEventListener("keydown",t),()=>{e.removeEventListener("keydown",t)}},w=(e,t,n="canvas")=>{const o=((e,t)=>{const n=new MutationObserver(e=>{t(e)});return n.observe(e,{subtree:!0,childList:!0,characterData:!0}),n})(e,()=>{v(e,t,n)});return()=>o.disconnect()},E=(e="canvas")=>{let n=null,o=!1,r=null;const s=()=>{if(o||!n)return;o=!0;var s;(s=n).contentEditable="false",s.classList.remove("is-editable"),s.style.outline="none",((e="canvas")=>{const n=t(["keyboard","disableTextEditMode"],e);n?.()})(e),r?.(),n=null,o=!1};return{enableEditMode:(o,i)=>{if(n===o)return;n&&n!==o&&s();const a=(e=>Array.from(e.childNodes).some(e=>e.nodeType===Node.TEXT_NODE&&e.textContent?.trim()))(o);a&&(n=o,(e=>{e.contentEditable="true",e.classList.add("is-editable"),e.style.outline="none"})(o),((e="canvas")=>{const n=t(["keyboard","enableTextEditMode"],e);n?.()})(e),r=((e,t,n,o="canvas")=>{if(!t)return()=>{};e.addEventListener("blur",n);const r=y(e),s=w(e,t,o);return()=>{e.removeEventListener("blur",n),r(),s?.()}})(o,i,s,e))},blurEditMode:s,getEditableNode:()=>n,isEditing:()=>null!==n}},L=320,A=1680,S=[{name:"Mobile",rawValue:390,value:"320px"},{name:"Tablet Portrait",rawValue:768,value:"768px"},{name:"Tablet Landscape",rawValue:1024,value:"1024px"},{name:"Notebook",rawValue:1280,value:"1280px"},{name:"Desktop",rawValue:1680,value:"1680px"}],x=(e,t,n)=>{const o=parseFloat(document.body.dataset.zoom||"1"),r=((e,t)=>{const n=e+Math.round(t);return Math.max(L,Math.min(A,n))})(n,(e.clientX-t)/o);return r},N=(e,t)=>{e.style.setProperty("--container-width",`${t}px`),((e,t)=>{e.querySelectorAll(".resize-preset-button").forEach((e,n)=>{n<S.length&&(S[n].rawValue===t?e.classList.add("is-active"):e.classList.remove("is-active"))})})(e,t)};e.createCanvasObserver=function(e="canvas"){const n=document.querySelector(".transform-layer");if(!n)return{disconnect:()=>{}};const o=new MutationObserver(()=>{((e="canvas")=>{const n=t(["zoom","current"],e)??1;document.body.style.setProperty("--zoom",n.toFixed(5)),document.body.style.setProperty("--stroke-width",(2/n).toFixed(3)),document.body.dataset.zoom=n.toFixed(5),document.body.dataset.strokeWidth=(2/n).toFixed(3)})(e);const n=window.nodeTools;n?.refreshHighlightFrame&&n.refreshHighlightFrame()});return o.observe(n,{attributes:!0,attributeOldValue:!0,subtree:!0,childList:!0}),{disconnect:function(){o.disconnect()}}},e.createNodeTools=(e,t="canvas")=>{const s=e;let i=null,a=null,l=null,u=null;const b=E(t),y=n((e,n)=>{v(e,n,t),f(e)}),w=e=>{if(u!==e){if(b.isEditing()){const t=b.getEditableNode();t&&t!==e&&b.blurEditMode()}if(i?.disconnect(),a?.disconnect(),l?.disconnect(),e&&s){b.enableEditMode(e,s);const n=()=>{if(!document.contains(e))return r(),u=null,i?.disconnect(),a?.disconnect(),l?.disconnect(),void o("selectedNodeChanged",null)};a=new MutationObserver(()=>{n(),document.contains(e)&&(console.log("mutationObserver",e),v(e,s,t),f(e))}),a.observe(e,{attributes:!0,characterData:!0,childList:!0,subtree:!0});const c=e.parentElement;c&&(l=new MutationObserver(t=>{for(const o of t)if("childList"===o.type)for(const t of Array.from(o.removedNodes))if(t===e||t instanceof Node&&t.contains(e))return void n()}),l.observe(c,{childList:!0,subtree:!1})),i=((e,t)=>{const n=new ResizeObserver(e=>{t(e)});return n.observe(e),n})(e,()=>{n(),document.contains(e)&&(console.log("resizeObserver",e),v(e,s,t),f(e))})}u=e,o("selectedNodeChanged",e?.getAttribute("data-node-id")??null),(e=>{if(!e)return;const t=p(),n=document.body.querySelector(".highlight-frame-tools-wrapper");t&&t.remove(),n&&n.remove();const o=d(e),r=h(e,o);"true"===e.contentEditable&&r.classList.add("is-editable");const{left:s,top:i,height:a}=m(e),l=i+a,c=document.createElement("div");c.classList.add("highlight-frame-tools-wrapper"),o&&c.classList.add("is-instance"),c.style.position="fixed",c.style.left=`${s}px`,c.style.top=`${l}px`,c.style.transform="translateX(-50%)",c.style.transformOrigin="center",c.style.pointerEvents="none",c.style.zIndex="5000",g(e,c,o),document.body.appendChild(c)})(e),e&&s&&f(e)}},L=c(s,w,()=>{b.isEditing()&&b.blurEditMode(),u&&s&&(r(),u=null,i?.disconnect(),a?.disconnect(),l?.disconnect())},b.getEditableNode),A={selectNode:w,getSelectedNode:()=>u,refreshHighlightFrame:()=>{u&&s&&(v(u,s,t),f(u))},clearSelectedNode:()=>{r(),u=null,i?.disconnect(),a?.disconnect(),l?.disconnect()},getEditableNode:()=>b.getEditableNode(),cleanup:()=>{L(),i?.disconnect(),a?.disconnect(),l?.disconnect(),b.blurEditMode(),y.cleanup(),r(),u=null,o("selectedNodeChanged",null)}};var S,x;return S="nodeTools",x=A,"undefined"!=typeof window&&(window[S]=x),A},e.createViewport=e=>{const t=document.querySelector(".canvas-container"),o=e.querySelector(".resize-handle");o&&o.remove();const r=(e=>{const t=document.createElement("div");return t.className="resize-handle",e.appendChild(t),t})(e);e.style.setProperty("--container-width","400px"),((e,t,n)=>{const o=document.createElement("div");o.className="resize-presets",S.forEach(e=>{const r=document.createElement("button");r.textContent=e.name,r.className="resize-preset-button",r.addEventListener("click",()=>{n(t,e.rawValue)}),o.appendChild(r)}),e.appendChild(o)})(r,e,N);let s=!1,i=0,a=0;const l=n(n=>{if(!s)return;t&&(t.style.cursor="ew-resize");const o=x(n,i,a);N(e,o)}),c=((e,t,n,o,r)=>(e.addEventListener("mousedown",t),document.addEventListener("mousemove",n),document.addEventListener("mouseup",o),window.addEventListener("blur",r),()=>{e.removeEventListener("mousedown",t),document.removeEventListener("mousemove",n),document.removeEventListener("mouseup",o),window.removeEventListener("blur",r)}))(r,t=>{t.preventDefault(),t.stopPropagation(),s=!0,i=t.clientX,a=e.offsetWidth},l,e=>{e.preventDefault(),e.stopPropagation(),t&&(t.style.cursor="default"),s=!1},()=>{s=!1});return{setWidth:t=>{N(e,t)},cleanup:()=>{s=!1,l?.cleanup(),c(),r.remove()}}}});
|
|
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()}}}});
|
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:
|
|
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.
|
|
3
|
+
"version": "2.2.8",
|
|
4
4
|
"description": "Utilities for editing nodes in a dom tree.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/node-edit-utils.cjs.js",
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
"import": "./dist/node-edit-utils.esm.js",
|
|
19
19
|
"require": "./dist/node-edit-utils.cjs.js"
|
|
20
20
|
},
|
|
21
|
-
"./styles.css":
|
|
21
|
+
"./styles.css": {
|
|
22
|
+
"default": "./dist/styles.css"
|
|
23
|
+
}
|
|
22
24
|
},
|
|
23
25
|
"scripts": {
|
|
24
26
|
"build": "rollup -c",
|
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;
|
|
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
|
});
|
|
@@ -134,11 +121,12 @@ export const createNodeTools = (element: HTMLElement | null, canvasName: string
|
|
|
134
121
|
|
|
135
122
|
if (node && nodeProvider) {
|
|
136
123
|
updateHighlightFrameVisibility(node);
|
|
124
|
+
updateHighlightFrameVisibility(node);
|
|
137
125
|
}
|
|
138
126
|
};
|
|
139
127
|
|
|
140
128
|
// Setup event listener
|
|
141
|
-
const removeListeners = setupEventListener(nodeProvider, selectNode, handleEscape, text
|
|
129
|
+
const removeListeners = setupEventListener(nodeProvider, selectNode, handleEscape, text);
|
|
142
130
|
|
|
143
131
|
const cleanup = (): void => {
|
|
144
132
|
removeListeners();
|
|
@@ -147,7 +135,6 @@ export const createNodeTools = (element: HTMLElement | null, canvasName: string
|
|
|
147
135
|
parentMutationObserver?.disconnect();
|
|
148
136
|
|
|
149
137
|
text.blurEditMode();
|
|
150
|
-
throttledRefreshAndVisibility.cleanup();
|
|
151
138
|
|
|
152
139
|
// Clear highlight frame and reset selected node
|
|
153
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 = "
|
|
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 = "
|
|
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",
|
|
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,
|
|
65
|
+
createCornerHandles(group, minWidth, height, isInstance, isTextEdit);
|
|
53
66
|
|
|
54
67
|
svg.appendChild(group);
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,29 +21,36 @@ export const highlightNode = (node: HTMLElement | null): void => {
|
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
const isInstance = isComponentInstance(node);
|
|
21
|
-
const
|
|
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");
|
|
25
29
|
}
|
|
26
30
|
|
|
27
|
-
//
|
|
31
|
+
// Batch DOM reads
|
|
28
32
|
const { left, top, height } = getScreenBounds(node);
|
|
29
33
|
const bottomY = top + height;
|
|
30
34
|
|
|
35
|
+
// Create tools wrapper using CSS transform (GPU-accelerated)
|
|
31
36
|
const toolsWrapper = document.createElement("div");
|
|
32
37
|
toolsWrapper.classList.add("highlight-frame-tools-wrapper");
|
|
33
38
|
if (isInstance) {
|
|
34
39
|
toolsWrapper.classList.add("is-instance");
|
|
35
40
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
toolsWrapper.style.
|
|
40
|
-
toolsWrapper.style.
|
|
41
|
+
if (isTextEdit) {
|
|
42
|
+
toolsWrapper.classList.add("is-text-edit");
|
|
43
|
+
}
|
|
44
|
+
toolsWrapper.style.position = "absolute";
|
|
45
|
+
toolsWrapper.style.transform = `translate(${left}px, ${bottomY}px)`;
|
|
46
|
+
toolsWrapper.style.transformOrigin = "left center";
|
|
41
47
|
toolsWrapper.style.pointerEvents = "none";
|
|
42
|
-
toolsWrapper.style.zIndex = "
|
|
48
|
+
toolsWrapper.style.zIndex = "500";
|
|
43
49
|
|
|
44
|
-
createToolsContainer(node, toolsWrapper, isInstance);
|
|
45
|
-
|
|
50
|
+
createToolsContainer(node, toolsWrapper, isInstance, isTextEdit);
|
|
51
|
+
if (canvasContainer) {
|
|
52
|
+
canvasContainer.appendChild(toolsWrapper);
|
|
53
|
+
} else {
|
|
54
|
+
document.body.appendChild(toolsWrapper);
|
|
55
|
+
}
|
|
46
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
|
|
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",
|
|
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", (
|
|
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", (
|
|
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) {
|
|
@@ -112,12 +145,12 @@ export const refreshHighlightFrame = (node: HTMLElement, nodeProvider: HTMLEleme
|
|
|
112
145
|
bottomLeft.setAttribute("y", (height - HANDLE_SIZE / 2).toString());
|
|
113
146
|
}
|
|
114
147
|
|
|
115
|
-
// Update tools wrapper position
|
|
148
|
+
// Update tools wrapper position using CSS transform (GPU-accelerated)
|
|
116
149
|
if (toolsWrapper) {
|
|
117
|
-
toolsWrapper.style.
|
|
118
|
-
toolsWrapper.style.top = `${bottomY}px`;
|
|
150
|
+
toolsWrapper.style.transform = `translate(${left}px, ${bottomY}px)`;
|
|
119
151
|
}
|
|
120
152
|
|
|
153
|
+
// Update tool opacity
|
|
121
154
|
if (zoom <= 10) {
|
|
122
155
|
nodeProvider.style.setProperty("--tool-opacity", `1`);
|
|
123
156
|
} else {
|