@node-edit-utils/core 2.1.9 → 2.2.1
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/highlight/clearHighlightFrame.d.ts +1 -1
- package/dist/lib/node-tools/highlight/createCornerHandles.d.ts +1 -0
- 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/highlight/helpers/getHighlightFrameElement.d.ts +1 -1
- package/dist/lib/node-tools/highlight/helpers/getScreenBounds.d.ts +6 -0
- package/dist/lib/node-tools/highlight/highlightNode.d.ts +1 -1
- package/dist/lib/node-tools/highlight/updateHighlightFrameVisibility.d.ts +1 -1
- package/dist/lib/node-tools/select/helpers/isComponentInstance.d.ts +1 -0
- package/dist/lib/node-tools/select/helpers/isInsideComponent.d.ts +1 -0
- package/dist/node-edit-utils.cjs.js +300 -155
- package/dist/node-edit-utils.esm.js +300 -155
- package/dist/node-edit-utils.umd.js +300 -155
- package/dist/node-edit-utils.umd.min.js +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/lib/canvas/createCanvasObserver.ts +8 -6
- package/src/lib/node-tools/createNodeTools.ts +27 -22
- package/src/lib/node-tools/events/click/handleNodeClick.ts +1 -1
- package/src/lib/node-tools/highlight/clearHighlightFrame.ts +7 -8
- package/src/lib/node-tools/highlight/createCornerHandles.ts +31 -0
- package/src/lib/node-tools/highlight/createHighlightFrame.ts +45 -24
- package/src/lib/node-tools/highlight/createToolsContainer.ts +4 -1
- package/src/lib/node-tools/highlight/helpers/getHighlightFrameElement.ts +2 -4
- package/src/lib/node-tools/highlight/helpers/getScreenBounds.ts +16 -0
- package/src/lib/node-tools/highlight/highlightNode.ts +28 -5
- package/src/lib/node-tools/highlight/refreshHighlightFrame.ts +113 -14
- package/src/lib/node-tools/highlight/updateHighlightFrameVisibility.ts +5 -5
- package/src/lib/node-tools/select/helpers/isComponentInstance.ts +3 -0
- package/src/lib/node-tools/select/helpers/isInsideComponent.ts +18 -0
- package/src/lib/node-tools/select/selectNode.ts +5 -1
- package/src/lib/post-message/processPostMessage.ts +0 -2
- package/src/lib/styles/styles.css +57 -21
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).MarkupCanvas={})}(this,function(e){"use strict";function t(e){let t=null,n=null;const o=(...o)=>{n=o,null===t&&(t=requestAnimationFrame(()=>{n&&e(...n),t=null,n=null}))};return o.cleanup=()=>{null!==t&&(cancelAnimationFrame(t),t=null,n=null)},o}const n=e=>{const t=window.canvas;return e.reduce((e,t)=>e?.[t],t)};const o=(e,t)=>{const n=new ResizeObserver(e=>{t(e)});return n.observe(e),n};function r(e){return e.querySelector(".highlight-frame")}const s=e=>{if(!e)return;const t=r(e);t&&t.remove()},l=["path","rect","circle","ellipse","polygon","line","polyline","text","text-noci"];let a=[],i=0;const d=(e,t)=>{let n=null;const o=e.clientX,r=e.clientY,s=e.metaKey||e.ctrlKey,d=((e,t)=>{const n=document.elementsFromPoint(e,t);return Array.from(n).reduce((e,t)=>e.found?e:"node-provider"===t.getAttribute("data-role")?(e.found=!0,e):(e.elements.push(t),e),{elements:[],found:!1}).elements})(o,r).filter(e=>!l.includes(e.tagName.toLowerCase())&&!e.classList.contains("select-none"));if(t&&d.includes(t))return t;if(s)return a=[],n=d[0],n;var c,u;u=d,(c=a).length===u.length&&c.every((e,t)=>e===u[t])?i<=d.length&&i++:i=0;return n=d[d.length-1-i],a=d,n},c=(e,t,n,o)=>{const r=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(console.log("selectedNode",r),n=r,n?.classList.contains("select-none"))return void t?.(null);r&&t?.(r)}var n})(e,t)},l=n=>{((e,t,n,o)=>{if(e.preventDefault(),e.stopPropagation(),t&&!t.contains(e.target))return s(t),void o(null);o(d(e,n))})(n,e,o(),t)},a=e=>{"Escape"===e.key&&(e.preventDefault(),e.stopPropagation(),n?.())};return window.addEventListener("message",r),document.addEventListener("click",l),document.addEventListener("keydown",a),()=>{window.removeEventListener("message",r),document.removeEventListener("click",l),document.removeEventListener("keydown",a)}};function u(e,t){const o=e.getBoundingClientRect(),r=t.getBoundingClientRect(),s=o.top-r.top,l=o.left-r.left,a=n(["zoom","current"])??1;return{top:parseFloat((s/a).toFixed(5)),left:parseFloat((l/a).toFixed(5)),width:Math.max(4,parseFloat((o.width/a).toFixed(5))),height:parseFloat((o.height/a).toFixed(5))}}const m=(e,t)=>{const n=document.createElement("div");n.className="node-tools",t.appendChild(n),((e,t)=>{const n=document.createElement("div");n.className="tag-label",n.textContent=e.tagName.toLowerCase(),t.appendChild(n)})(e,n)},p=(e,t)=>{if(!e)return;const o=r(t);o&&o.remove();const s=((e,t)=>{const{top:o,left:r,width:s,height:l}=u(e,t),a=n(["zoom","current"])??1;document.body.style.setProperty("--zoom",a.toString()),document.body.style.setProperty("--stroke-width",(2/a).toFixed(3));const i=document.createElement("div");i.classList.add("highlight-frame"),i.style.setProperty("--frame-top",`${o}px`),i.style.setProperty("--frame-left",`${r}px`),i.style.setProperty("--frame-width",`${s}px`),i.style.setProperty("--frame-height",`${l}px`);const d=document.createElementNS("http://www.w3.org/2000/svg","svg");d.classList.add("highlight-frame-svg");const c=document.createElementNS("http://www.w3.org/2000/svg","rect");return c.setAttribute("x","0"),c.setAttribute("y","0"),c.setAttribute("width","100%"),c.setAttribute("height","100%"),c.classList.add("highlight-frame-rect"),d.appendChild(c),i.appendChild(d),i})(e,t);"true"===e.contentEditable&&s.classList.add("is-editable"),m(e,s),t.appendChild(s)},h=(e,t)=>{const o=r(t),s=n(["zoom","current"])??1;if(console.log("1. refreshHighlightFrame",e),!o)return;s>=.3?t.style.setProperty("--tool-opacity","1"):t.style.setProperty("--tool-opacity","0");const{top:l,left:a,width:i,height:d}=u(e,t);o.style.setProperty("--frame-top",`${l}px`),o.style.setProperty("--frame-left",`${a}px`),o.style.setProperty("--frame-width",`${i}px`),o.style.setProperty("--frame-height",`${d}px`),console.log("2. refreshHighlightFrame",l,a,i,d)},f=(e,t)=>{const n=r(t);if(!n)return;const o=e.classList.contains("hidden")||e.classList.contains("select-none")?"none":"";n.style.display=o;const s=n.querySelector(".tag-label");s&&(s.style.display=o)},v=e=>{const t=e=>{"Enter"===e.key&&(e.preventDefault(),e.stopPropagation(),(()=>{const e=window.getSelection();if(e&&e.rangeCount>0){const t=e.getRangeAt(0);t.deleteContents();const n=document.createElement("br");t.insertNode(n),t.setStartAfter(n),t.setEndAfter(n),e.removeAllRanges(),e.addRange(t)}})())};return e.addEventListener("keydown",t),()=>{e.removeEventListener("keydown",t)}},y=(e,t)=>{const n=((e,t)=>{const n=new MutationObserver(e=>{t(e)});return n.observe(e,{subtree:!0,childList:!0,characterData:!0}),n})(e,()=>{h(e,t)});return()=>n.disconnect()},g=()=>{let e=null,t=!1,o=null;const r=()=>{if(t||!e)return;t=!0;var r;(r=e).contentEditable="false",r.classList.remove("is-editable"),r.style.outline="none",(()=>{const e=n(["keyboard","disableTextEditMode"]);e?.()})(),o?.(),e=null,t=!1};return{enableEditMode:(t,s)=>{if(e===t)return;e&&e!==t&&r();const l=(e=>Array.from(e.childNodes).some(e=>e.nodeType===Node.TEXT_NODE&&e.textContent?.trim()))(t);l&&(e=t,(e=>{e.contentEditable="true",e.classList.add("is-editable"),e.style.outline="none"})(t),(()=>{const e=n(["keyboard","enableTextEditMode"]);e?.()})(),o=((e,t,n)=>{if(!t)return()=>{};e.addEventListener("blur",n);const o=v(e),r=y(e,t);return()=>{e.removeEventListener("blur",n),o(),r?.()}})(t,s,r))},blurEditMode:r,getEditableNode:()=>e,isEditing:()=>null!==e}},b=320,w=1680,E=[{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(b,Math.min(w,n))})(n,(e.clientX-t)/o);return r},L=(e,t)=>{e.style.setProperty("--container-width",`${t}px`),((e,t)=>{e.querySelectorAll(".resize-preset-button").forEach((e,n)=>{n<E.length&&(E[n].rawValue===t?e.classList.add("is-active"):e.classList.remove("is-active"))})})(e,t)};e.createCanvasObserver=function(){const e=document.querySelector(".transform-layer");if(!e)return{disconnect:()=>{}};const o=t(()=>{(()=>{const e=n(["zoom","current"]);document.body.style.setProperty("--zoom",e.toFixed(5)),document.body.style.setProperty("--stroke-width",(2/e).toFixed(3)),document.body.dataset.zoom=e.toFixed(5),document.body.dataset.strokeWidth=(2/e).toFixed(3)})()}),r=new MutationObserver(()=>{o()});return r.observe(e,{attributes:!0,attributeOldValue:!0,subtree:!0,childList:!0}),{disconnect:function(){o.cleanup(),r.disconnect()}}},e.createNodeTools=e=>{const t=e;let n=null,r=null,l=null,a=null;const i=g(),d=function(e){let t=null,n=null,o=null;const r=(...r)=>{o=r,null===t&&(t=requestAnimationFrame(()=>{n=requestAnimationFrame(()=>{if(o){const r=o;t=null,n=null,o=null,e(...r)}})}))};return r.cleanup=()=>{null!==t&&(cancelAnimationFrame(t),t=null),null!==n&&(cancelAnimationFrame(n),n=null),o=null},r}(h),u=e=>{if(a!==e){if(i.isEditing()){const t=i.getEditableNode();t&&t!==e&&i.blurEditMode()}var s,c;n?.disconnect(),r?.disconnect(),l?.disconnect(),e&&t&&(i.enableEditMode(e,t),n=o(t,()=>{d(e,t)}),l=new MutationObserver(()=>{d(e,t),f(e,t)}),l.observe(e,{attributes:!0,characterData:!0}),r=o(e,()=>{d(e,t),f(e,t)})),a=e,s="selectedNodeChanged",c=e?.getAttribute("data-node-id")??null,window.parent.postMessage({source:"node-edit-utils",action:s,data:c,timestamp:Date.now()},"*"),p(e,t),e&&t&&f(e,t)}},m=c(t,u,()=>{i.isEditing()&&i.blurEditMode(),a&&t&&(s(t),a=null,n?.disconnect(),r?.disconnect(),l?.disconnect())},i.getEditableNode),v={selectNode:u,getSelectedNode:()=>a,refreshHighlightFrame:()=>{d(a,t)},clearSelectedNode:()=>{s(t),a=null,n?.disconnect(),r?.disconnect(),l?.disconnect()},getEditableNode:()=>i.getEditableNode(),cleanup:()=>{m(),n?.disconnect(),r?.disconnect(),l?.disconnect(),i.blurEditMode(),d.cleanup()}};var y,b;return y="nodeTools",b=v,"undefined"!=typeof window&&(window[y]=b),v},e.createViewport=e=>{const n=document.querySelector(".canvas-container"),o=e.querySelector(".resize-handle");o&&o.remove();const r=(e=>{const t=document.createElement("div");return t.className="resize-handle",e.appendChild(t),t})(e);e.style.setProperty("--container-width","400px"),((e,t,n)=>{const o=document.createElement("div");o.className="resize-presets",E.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,L);let s=!1,l=0,a=0;const i=t(t=>{if(!s)return;n&&(n.style.cursor="ew-resize");const o=x(t,l,a);L(e,o)}),d=((e,t,n,o,r)=>(e.addEventListener("mousedown",t),document.addEventListener("mousemove",n),document.addEventListener("mouseup",o),window.addEventListener("blur",r),()=>{e.removeEventListener("mousedown",t),document.removeEventListener("mousemove",n),document.removeEventListener("mouseup",o),window.removeEventListener("blur",r)}))(r,t=>{t.preventDefault(),t.stopPropagation(),s=!0,l=t.clientX,a=e.offsetWidth},i,e=>{e.preventDefault(),e.stopPropagation(),n&&(n.style.cursor="default"),s=!1},()=>{s=!1});return{setWidth:t=>{L(e,t)},cleanup:()=>{s=!1,i?.cleanup(),d(),r.remove()}}}});
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).MarkupCanvas={})}(this,function(e){"use strict";const t=e=>{const t=window.canvas;return e.reduce((e,t)=>e?.[t],t)};function n(e){let t=null,n=null;const o=(...o)=>{n=o,null===t&&(t=requestAnimationFrame(()=>{n&&e(...n),t=null,n=null}))};return o.cleanup=()=>{null!==t&&(cancelAnimationFrame(t),t=null,n=null)},o}const o=()=>{const e=document.body.querySelector(".highlight-frame-overlay");e&&e.remove();const t=document.body.querySelector(".highlight-frame-tools-wrapper");t&&t.remove()},r=["path","rect","circle","ellipse","polygon","line","polyline","text","text-noci"];let i=[],s=0;const l=(e,t)=>{let n=null;const o=e.clientX,l=e.clientY,a=e.metaKey||e.ctrlKey,d=((e,t)=>{const n=document.elementsFromPoint(e,t);return Array.from(n).reduce((e,t)=>e.found?e:"node-provider"===t.getAttribute("data-role")?(e.found=!0,e):(e.elements.push(t),e),{elements:[],found:!1}).elements})(o,l).filter(e=>!r.includes(e.tagName.toLowerCase())&&!e.classList.contains("select-none")&&!(e=>{let t=e.parentElement;for(;t;){if("true"===t.getAttribute("data-instance"))return!0;if("node-provider"===t.getAttribute("data-role"))break;t=t.parentElement}return!1})(e));if(t&&d.includes(t))return t;if(a)return i=[],n=d[0],n;var c,u;u=d,(c=i).length===u.length&&c.every((e,t)=>e===u[t])?s<=d.length&&s++:s=0;return n=d[d.length-1-s],i=d,n},a=(e,t,n,r)=>{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,r)=>{if(e.preventDefault(),e.stopPropagation(),t&&!t.contains(e.target))return o(),void r(null);r(l(e,n))})(n,e,r(),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)}},d=e=>"true"===e.getAttribute("data-instance"),c=(e,t,n,o,r=!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)"),e.appendChild(i),i};function u(e){const t=e.getBoundingClientRect();return{top:t.top,left:t.left,width:t.width,height:t.height}}const m=(e,t=!1)=>{const{top:n,left:o,width:r,height:i}=u(e),s=document.createElementNS("http://www.w3.org/2000/svg","svg");s.classList.add("highlight-frame-overlay"),t&&s.classList.add("is-instance"),s.setAttribute("data-node-id",e.getAttribute("data-node-id")||""),s.style.position="fixed",s.style.top="0",s.style.left="0",s.style.width="100vw",s.style.height="100vh",s.style.pointerEvents="none",s.style.zIndex="5000";const l=document.documentElement.clientWidth||window.innerWidth,a=document.documentElement.clientHeight||window.innerHeight;s.setAttribute("width",l.toString()),s.setAttribute("height",a.toString());const d=document.createElementNS("http://www.w3.org/2000/svg","g");d.classList.add("highlight-frame-group"),d.setAttribute("transform",`translate(${o}, ${n})`);const m=document.createElementNS("http://www.w3.org/2000/svg","rect");return m.setAttribute("x","0"),m.setAttribute("y","0"),m.setAttribute("width",r.toString()),m.setAttribute("height",i.toString()),m.setAttribute("vector-effect","non-scaling-stroke"),m.classList.add("highlight-frame-rect"),t&&m.setAttribute("stroke",getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim()||"oklch(65.6% 0.241 354.308)"),d.appendChild(m),((e,t,n,o=!1)=>{c(e,0,0,"handle-top-left",o),c(e,t,0,"handle-top-right",o),c(e,t,n,"handle-bottom-right",o),c(e,0,n,"handle-bottom-left",o)})(d,r,i,t),s.appendChild(d),document.body.appendChild(s),s},h=(e,t,n=!1)=>{const o=document.createElement("div");o.className="node-tools",n&&o.classList.add("is-instance"),t.appendChild(o),((e,t)=>{const n=document.createElement("div");n.className="tag-label",n.textContent=e.tagName.toLowerCase(),t.appendChild(n)})(e,o)};function g(){return document.body.querySelector(".highlight-frame-overlay")}const p=()=>getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim()||"oklch(65.6% 0.241 354.308)",b=(e,n)=>{const o=g();if(!o)return;const r=d(e),i=document.documentElement.clientWidth||window.innerWidth,s=document.documentElement.clientHeight||window.innerHeight;o.setAttribute("width",i.toString()),o.setAttribute("height",s.toString()),r?o.classList.add("is-instance"):o.classList.remove("is-instance");const l=o.querySelector(".highlight-frame-group");if(!l)return;const a=l.querySelector("rect");if(!a)return;r?a.setAttribute("stroke",p()):a.removeAttribute("stroke");const c=document.body.querySelector(".highlight-frame-tools-wrapper"),m=c?.querySelector(".node-tools"),h=t(["zoom","current"])??1,b=u(e),{top:y,left:v,width:f,height:w}=b,E=y+w;c&&(r?c.classList.add("is-instance"):c.classList.remove("is-instance")),m&&(r?m.classList.add("is-instance"):m.classList.remove("is-instance")),l.setAttribute("transform",`translate(${v}, ${y})`),a.setAttribute("width",f.toString()),a.setAttribute("height",w.toString());const S=l.querySelector(".handle-top-left"),A=l.querySelector(".handle-top-right"),L=l.querySelector(".handle-bottom-right"),x=l.querySelector(".handle-bottom-left");[S,A,L,x].forEach(e=>{e&&(r?e.setAttribute("stroke",p()):e.removeAttribute("stroke"))}),S&&(S.setAttribute("x",(-3).toString()),S.setAttribute("y",(-3).toString())),A&&(A.setAttribute("x",(f-3).toString()),A.setAttribute("y",(-3).toString())),L&&(L.setAttribute("x",(f-3).toString()),L.setAttribute("y",(w-3).toString())),x&&(x.setAttribute("x",(-3).toString()),x.setAttribute("y",(w-3).toString())),c&&(c.style.left=`${v}px`,c.style.top=`${E}px`),h<=10?n.style.setProperty("--tool-opacity","1"):n.style.setProperty("--tool-opacity","0")},y=e=>{const t=g();if(!t)return;const n=e.classList.contains("hidden")||e.classList.contains("select-none")?"none":"";t.style.display=n;const o=document.body.querySelector(".highlight-frame-tools-wrapper");o&&(o.style.display=n)},v=e=>{const t=e=>{"Enter"===e.key&&(e.preventDefault(),e.stopPropagation(),(()=>{const e=window.getSelection();if(e&&e.rangeCount>0){const t=e.getRangeAt(0);t.deleteContents();const n=document.createElement("br");t.insertNode(n),t.setStartAfter(n),t.setEndAfter(n),e.removeAllRanges(),e.addRange(t)}})())};return e.addEventListener("keydown",t),()=>{e.removeEventListener("keydown",t)}},f=(e,t)=>{const n=((e,t)=>{const n=new MutationObserver(e=>{t(e)});return n.observe(e,{subtree:!0,childList:!0,characterData:!0}),n})(e,()=>{b(e,t)});return()=>n.disconnect()},w=()=>{let e=null,n=!1,o=null;const r=()=>{if(n||!e)return;n=!0;var r;(r=e).contentEditable="false",r.classList.remove("is-editable"),r.style.outline="none",(()=>{const e=t(["keyboard","disableTextEditMode"]);e?.()})(),o?.(),e=null,n=!1};return{enableEditMode:(n,i)=>{if(e===n)return;e&&e!==n&&r();const s=(e=>Array.from(e.childNodes).some(e=>e.nodeType===Node.TEXT_NODE&&e.textContent?.trim()))(n);s&&(e=n,(e=>{e.contentEditable="true",e.classList.add("is-editable"),e.style.outline="none"})(n),(()=>{const e=t(["keyboard","enableTextEditMode"]);e?.()})(),o=((e,t,n)=>{if(!t)return()=>{};e.addEventListener("blur",n);const o=v(e),r=f(e,t);return()=>{e.removeEventListener("blur",n),o(),r?.()}})(n,i,r))},blurEditMode:r,getEditableNode:()=>e,isEditing:()=>null!==e}},E=320,S=1680,A=[{name:"Mobile",rawValue:390,value:"320px"},{name:"Tablet Portrait",rawValue:768,value:"768px"},{name:"Tablet Landscape",rawValue:1024,value:"1024px"},{name:"Notebook",rawValue:1280,value:"1280px"},{name:"Desktop",rawValue:1680,value:"1680px"}],L=(e,t,n)=>{const o=parseFloat(document.body.dataset.zoom||"1"),r=((e,t)=>{const n=e+Math.round(t);return Math.max(E,Math.min(S,n))})(n,(e.clientX-t)/o);return r},x=(e,t)=>{e.style.setProperty("--container-width",`${t}px`),((e,t)=>{e.querySelectorAll(".resize-preset-button").forEach((e,n)=>{n<A.length&&(A[n].rawValue===t?e.classList.add("is-active"):e.classList.remove("is-active"))})})(e,t)};e.createCanvasObserver=function(){const e=document.querySelector(".transform-layer");if(!e)return{disconnect:()=>{}};const n=new MutationObserver(()=>{(()=>{const e=t(["zoom","current"]);document.body.style.setProperty("--zoom",e.toFixed(5)),document.body.style.setProperty("--stroke-width",(2/e).toFixed(3)),document.body.dataset.zoom=e.toFixed(5),document.body.dataset.strokeWidth=(2/e).toFixed(3)})();const e=window.nodeTools;e?.refreshHighlightFrame&&e.refreshHighlightFrame()});return n.observe(e,{attributes:!0,attributeOldValue:!0,subtree:!0,childList:!0}),{disconnect:function(){n.disconnect()}}},e.createNodeTools=e=>{const t=e;let r=null,i=null,s=null;const l=w(),c=n((e,t)=>{b(e,t),y(e)}),p=e=>{if(s!==e){if(l.isEditing()){const t=l.getEditableNode();t&&t!==e&&l.blurEditMode()}var n,o;r?.disconnect(),i?.disconnect(),e&&t&&(l.enableEditMode(e,t),i=new MutationObserver(()=>{console.log("mutationObserver",e),b(e,t),y(e)}),i.observe(e,{attributes:!0,characterData:!0}),r=((e,t)=>{const n=new ResizeObserver(e=>{t(e)});return n.observe(e),n})(e,()=>{console.log("resizeObserver",e),b(e,t),y(e)})),s=e,n="selectedNodeChanged",o=e?.getAttribute("data-node-id")??null,window.parent.postMessage({source:"node-edit-utils",action:n,data:o,timestamp:Date.now()},"*"),(e=>{if(!e)return;const t=g(),n=document.body.querySelector(".highlight-frame-tools-wrapper");t&&t.remove(),n&&n.remove();const o=d(e),r=m(e,o);"true"===e.contentEditable&&r.classList.add("is-editable");const{left:i,top:s,height:l}=u(e),a=s+l,c=document.createElement("div");c.classList.add("highlight-frame-tools-wrapper"),o&&c.classList.add("is-instance"),c.style.position="fixed",c.style.left=`${i}px`,c.style.top=`${a}px`,c.style.transform="translateX(-50%)",c.style.transformOrigin="center",c.style.pointerEvents="none",c.style.zIndex="5000",h(e,c,o),document.body.appendChild(c)})(e),e&&t&&y(e)}},v=a(t,p,()=>{l.isEditing()&&l.blurEditMode(),s&&t&&(o(),s=null,r?.disconnect(),i?.disconnect())},l.getEditableNode),f={selectNode:p,getSelectedNode:()=>s,refreshHighlightFrame:()=>{s&&t&&(b(s,t),y(s))},clearSelectedNode:()=>{o(),s=null,r?.disconnect(),i?.disconnect()},getEditableNode:()=>l.getEditableNode(),cleanup:()=>{v(),r?.disconnect(),i?.disconnect(),l.blurEditMode(),c.cleanup()}};var E,S;return E="nodeTools",S=f,"undefined"!=typeof window&&(window[E]=S),f},e.createViewport=e=>{const t=document.querySelector(".canvas-container"),o=e.querySelector(".resize-handle");o&&o.remove();const r=(e=>{const t=document.createElement("div");return t.className="resize-handle",e.appendChild(t),t})(e);e.style.setProperty("--container-width","400px"),((e,t,n)=>{const o=document.createElement("div");o.className="resize-presets",A.forEach(e=>{const r=document.createElement("button");r.textContent=e.name,r.className="resize-preset-button",r.addEventListener("click",()=>{n(t,e.rawValue)}),o.appendChild(r)}),e.appendChild(o)})(r,e,x);let i=!1,s=0,l=0;const a=n(n=>{if(!i)return;t&&(t.style.cursor="ew-resize");const o=L(n,s,l);x(e,o)}),d=((e,t,n,o,r)=>(e.addEventListener("mousedown",t),document.addEventListener("mousemove",n),document.addEventListener("mouseup",o),window.addEventListener("blur",r),()=>{e.removeEventListener("mousedown",t),document.removeEventListener("mousemove",n),document.removeEventListener("mouseup",o),window.removeEventListener("blur",r)}))(r,t=>{t.preventDefault(),t.stopPropagation(),i=!0,s=t.clientX,l=e.offsetWidth},a,e=>{e.preventDefault(),e.stopPropagation(),t&&(t.style.cursor="default"),i=!1},()=>{i=!1});return{setWidth:t=>{x(e,t)},cleanup:()=>{i=!1,a?.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);--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-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-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;display:flex;gap:var(--spacing-xs);justify-content:center;left:0;opacity:var(--tool-opacity);padding-top:var(--spacing-
|
|
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;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{height:100vh;inset:0;overflow:visible;pointer-events:none;position:fixed;width:100vw;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;pointer-events:none;position:fixed;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)}}
|
package/package.json
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { withRAFThrottle } from "../helpers/withRAF";
|
|
2
1
|
import { applyCanvasState } from "./helpers/applyCanvasState";
|
|
3
2
|
import type { CanvasObserver } from "./types";
|
|
4
3
|
|
|
@@ -11,12 +10,16 @@ export function createCanvasObserver(): CanvasObserver {
|
|
|
11
10
|
};
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
const
|
|
13
|
+
const observer = new MutationObserver(() => {
|
|
15
14
|
applyCanvasState();
|
|
16
|
-
});
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
// Refresh highlight frame (throttled via withRAFThrottle)
|
|
17
|
+
// biome-ignore lint/suspicious/noExplicitAny: global window extension
|
|
18
|
+
const nodeTools = (window as any).nodeTools;
|
|
19
|
+
|
|
20
|
+
if (nodeTools?.refreshHighlightFrame) {
|
|
21
|
+
nodeTools.refreshHighlightFrame();
|
|
22
|
+
}
|
|
20
23
|
});
|
|
21
24
|
|
|
22
25
|
observer.observe(transformLayer, {
|
|
@@ -27,7 +30,6 @@ export function createCanvasObserver(): CanvasObserver {
|
|
|
27
30
|
});
|
|
28
31
|
|
|
29
32
|
function disconnect(): void {
|
|
30
|
-
throttledUpdate.cleanup();
|
|
31
33
|
observer.disconnect();
|
|
32
34
|
}
|
|
33
35
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { withDoubleRAF } from "../helpers";
|
|
2
1
|
import { connectResizeObserver } from "../helpers/observer/connectResizeObserver";
|
|
2
|
+
import { withRAFThrottle } from "../helpers/withRAF";
|
|
3
3
|
import { sendPostMessage } from "../post-message/sendPostMessage";
|
|
4
4
|
import { bindToWindow } from "../window/bindToWindow";
|
|
5
5
|
import { setupEventListener } from "./events/setupEventListener";
|
|
@@ -14,12 +14,16 @@ export const createNodeTools = (element: HTMLElement | null): NodeTools => {
|
|
|
14
14
|
const nodeProvider = element;
|
|
15
15
|
|
|
16
16
|
let resizeObserver: ResizeObserver | null = null;
|
|
17
|
-
let nodeResizeObserver: ResizeObserver | null = null;
|
|
18
17
|
let mutationObserver: MutationObserver | null = null;
|
|
19
18
|
let selectedNode: HTMLElement | null = null;
|
|
20
19
|
|
|
21
20
|
const text = nodeText();
|
|
22
|
-
|
|
21
|
+
|
|
22
|
+
// Combined throttled function for refresh + visibility update
|
|
23
|
+
const throttledRefreshAndVisibility = withRAFThrottle((node: HTMLElement, nodeProvider: HTMLElement) => {
|
|
24
|
+
refreshHighlightFrame(node, nodeProvider);
|
|
25
|
+
updateHighlightFrameVisibility(node);
|
|
26
|
+
});
|
|
23
27
|
|
|
24
28
|
const handleEscape = (): void => {
|
|
25
29
|
if (text.isEditing()) {
|
|
@@ -28,11 +32,10 @@ export const createNodeTools = (element: HTMLElement | null): NodeTools => {
|
|
|
28
32
|
|
|
29
33
|
if (selectedNode) {
|
|
30
34
|
if (nodeProvider) {
|
|
31
|
-
clearHighlightFrame(
|
|
35
|
+
clearHighlightFrame();
|
|
32
36
|
selectedNode = null;
|
|
33
37
|
|
|
34
38
|
resizeObserver?.disconnect();
|
|
35
|
-
nodeResizeObserver?.disconnect();
|
|
36
39
|
mutationObserver?.disconnect();
|
|
37
40
|
}
|
|
38
41
|
}
|
|
@@ -51,19 +54,16 @@ export const createNodeTools = (element: HTMLElement | null): NodeTools => {
|
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
resizeObserver?.disconnect();
|
|
54
|
-
nodeResizeObserver?.disconnect();
|
|
55
57
|
mutationObserver?.disconnect();
|
|
56
58
|
|
|
57
59
|
if (node && nodeProvider) {
|
|
58
60
|
text.enableEditMode(node, nodeProvider);
|
|
59
61
|
|
|
60
|
-
resizeObserver = connectResizeObserver(nodeProvider, () => {
|
|
61
|
-
throttledFrameRefresh(node, nodeProvider);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
62
|
mutationObserver = new MutationObserver(() => {
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
// throttledRefreshAndVisibility(node, nodeProvider);
|
|
64
|
+
console.log("mutationObserver", node);
|
|
65
|
+
refreshHighlightFrame(node, nodeProvider);
|
|
66
|
+
updateHighlightFrameVisibility(node);
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
mutationObserver.observe(node, {
|
|
@@ -71,18 +71,20 @@ export const createNodeTools = (element: HTMLElement | null): NodeTools => {
|
|
|
71
71
|
characterData: true,
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
resizeObserver = connectResizeObserver(node, () => {
|
|
75
|
+
// throttledRefreshAndVisibility(node, nodeProvider);
|
|
76
|
+
console.log("resizeObserver", node);
|
|
77
|
+
refreshHighlightFrame(node, nodeProvider);
|
|
78
|
+
updateHighlightFrameVisibility(node);
|
|
77
79
|
});
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
selectedNode = node;
|
|
81
83
|
sendPostMessage("selectedNodeChanged", node?.getAttribute("data-node-id") ?? null);
|
|
82
|
-
highlightNode(node
|
|
84
|
+
highlightNode(node) ?? null;
|
|
83
85
|
|
|
84
86
|
if (node && nodeProvider) {
|
|
85
|
-
updateHighlightFrameVisibility(node
|
|
87
|
+
updateHighlightFrameVisibility(node);
|
|
86
88
|
}
|
|
87
89
|
};
|
|
88
90
|
|
|
@@ -92,24 +94,27 @@ export const createNodeTools = (element: HTMLElement | null): NodeTools => {
|
|
|
92
94
|
const cleanup = (): void => {
|
|
93
95
|
removeListeners();
|
|
94
96
|
resizeObserver?.disconnect();
|
|
95
|
-
nodeResizeObserver?.disconnect();
|
|
96
97
|
mutationObserver?.disconnect();
|
|
97
98
|
|
|
98
99
|
text.blurEditMode();
|
|
99
|
-
|
|
100
|
+
throttledRefreshAndVisibility.cleanup();
|
|
100
101
|
};
|
|
101
102
|
|
|
102
103
|
const nodeTools: NodeTools = {
|
|
103
104
|
selectNode,
|
|
104
105
|
getSelectedNode: () => selectedNode,
|
|
105
106
|
refreshHighlightFrame: () => {
|
|
106
|
-
|
|
107
|
+
if (selectedNode && nodeProvider) {
|
|
108
|
+
// Call directly (not throttled) since this is typically called from already-throttled contexts
|
|
109
|
+
// to avoid double RAF
|
|
110
|
+
refreshHighlightFrame(selectedNode, nodeProvider);
|
|
111
|
+
updateHighlightFrameVisibility(selectedNode);
|
|
112
|
+
}
|
|
107
113
|
},
|
|
108
114
|
clearSelectedNode: () => {
|
|
109
|
-
clearHighlightFrame(
|
|
115
|
+
clearHighlightFrame();
|
|
110
116
|
selectedNode = null;
|
|
111
117
|
resizeObserver?.disconnect();
|
|
112
|
-
nodeResizeObserver?.disconnect();
|
|
113
118
|
mutationObserver?.disconnect();
|
|
114
119
|
},
|
|
115
120
|
getEditableNode: () => text.getEditableNode(),
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
return;
|
|
1
|
+
export const clearHighlightFrame = (): void => {
|
|
2
|
+
const frame = document.body.querySelector(".highlight-frame-overlay");
|
|
3
|
+
if (frame) {
|
|
4
|
+
frame.remove();
|
|
6
5
|
}
|
|
7
6
|
|
|
8
|
-
const
|
|
9
|
-
if (
|
|
10
|
-
|
|
7
|
+
const toolsWrapper = document.body.querySelector(".highlight-frame-tools-wrapper");
|
|
8
|
+
if (toolsWrapper) {
|
|
9
|
+
toolsWrapper.remove();
|
|
11
10
|
}
|
|
12
11
|
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const HANDLE_SIZE = 6;
|
|
2
|
+
|
|
3
|
+
const getComponentColor = (): string => {
|
|
4
|
+
return getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim() || "oklch(65.6% 0.241 354.308)";
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const createCornerHandle = (group: SVGGElement, x: number, y: number, className: string, isInstance: boolean = false): SVGRectElement => {
|
|
8
|
+
const handle = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
9
|
+
// Position relative to group (offset by half handle size to center on corner)
|
|
10
|
+
handle.setAttribute("x", (x - HANDLE_SIZE / 2).toString());
|
|
11
|
+
handle.setAttribute("y", (y - HANDLE_SIZE / 2).toString());
|
|
12
|
+
handle.setAttribute("width", HANDLE_SIZE.toString());
|
|
13
|
+
handle.setAttribute("height", HANDLE_SIZE.toString());
|
|
14
|
+
handle.setAttribute("vector-effect", "non-scaling-stroke");
|
|
15
|
+
handle.classList.add("highlight-frame-handle", className);
|
|
16
|
+
|
|
17
|
+
if (isInstance) {
|
|
18
|
+
handle.setAttribute("stroke", getComponentColor());
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
group.appendChild(handle);
|
|
22
|
+
return handle;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const createCornerHandles = (group: SVGGElement, width: number, height: number, isInstance: boolean = false): void => {
|
|
26
|
+
// 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);
|
|
31
|
+
};
|
|
@@ -1,37 +1,58 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { createCornerHandles } from "./createCornerHandles";
|
|
2
|
+
import { getScreenBounds } from "./helpers/getScreenBounds";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const zoom = getCanvasWindowValue(["zoom", "current"]) ?? 1;
|
|
8
|
-
|
|
9
|
-
document.body.style.setProperty("--zoom", zoom.toString());
|
|
10
|
-
document.body.style.setProperty("--stroke-width", (2 / zoom).toFixed(3));
|
|
11
|
-
|
|
12
|
-
const frame = document.createElement("div");
|
|
13
|
-
frame.classList.add("highlight-frame");
|
|
4
|
+
const getComponentColor = (): string => {
|
|
5
|
+
return getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim() || "oklch(65.6% 0.241 354.308)";
|
|
6
|
+
};
|
|
14
7
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
frame.style.setProperty("--frame-width", `${width}px`);
|
|
18
|
-
frame.style.setProperty("--frame-height", `${height}px`);
|
|
8
|
+
export const createHighlightFrame = (node: HTMLElement, isInstance: boolean = false): SVGSVGElement => {
|
|
9
|
+
const { top, left, width, height } = getScreenBounds(node);
|
|
19
10
|
|
|
20
|
-
// Create SVG overlay
|
|
11
|
+
// Create fixed SVG overlay
|
|
21
12
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
22
|
-
svg.classList.add("highlight-frame-
|
|
13
|
+
svg.classList.add("highlight-frame-overlay");
|
|
14
|
+
if (isInstance) {
|
|
15
|
+
svg.classList.add("is-instance");
|
|
16
|
+
}
|
|
17
|
+
svg.setAttribute("data-node-id", node.getAttribute("data-node-id") || "");
|
|
18
|
+
|
|
19
|
+
// Set fixed positioning
|
|
20
|
+
svg.style.position = "fixed";
|
|
21
|
+
svg.style.top = "0";
|
|
22
|
+
svg.style.left = "0";
|
|
23
|
+
svg.style.width = "100vw";
|
|
24
|
+
svg.style.height = "100vh";
|
|
25
|
+
svg.style.pointerEvents = "none";
|
|
26
|
+
svg.style.zIndex = "5000";
|
|
27
|
+
|
|
28
|
+
const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
|
|
29
|
+
const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
|
|
30
|
+
svg.setAttribute("width", viewportWidth.toString());
|
|
31
|
+
svg.setAttribute("height", viewportHeight.toString());
|
|
32
|
+
|
|
33
|
+
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
34
|
+
group.classList.add("highlight-frame-group");
|
|
35
|
+
group.setAttribute("transform", `translate(${left}, ${top})`);
|
|
23
36
|
|
|
24
37
|
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
25
38
|
rect.setAttribute("x", "0");
|
|
26
39
|
rect.setAttribute("y", "0");
|
|
27
|
-
rect.setAttribute("width",
|
|
28
|
-
rect.setAttribute("height",
|
|
40
|
+
rect.setAttribute("width", width.toString());
|
|
41
|
+
rect.setAttribute("height", height.toString());
|
|
42
|
+
rect.setAttribute("vector-effect", "non-scaling-stroke");
|
|
29
43
|
rect.classList.add("highlight-frame-rect");
|
|
30
44
|
|
|
31
|
-
|
|
32
|
-
|
|
45
|
+
// Apply instance color if it's an instance
|
|
46
|
+
if (isInstance) {
|
|
47
|
+
rect.setAttribute("stroke", getComponentColor());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
group.appendChild(rect);
|
|
51
|
+
|
|
52
|
+
createCornerHandles(group, width, height, isInstance);
|
|
33
53
|
|
|
34
|
-
|
|
54
|
+
svg.appendChild(group);
|
|
55
|
+
document.body.appendChild(svg);
|
|
35
56
|
|
|
36
|
-
return
|
|
57
|
+
return svg as SVGSVGElement;
|
|
37
58
|
};
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { createTagLabel } from "./createTagLabel";
|
|
2
2
|
|
|
3
|
-
export const createToolsContainer = (node: HTMLElement, highlightFrame: HTMLElement): void => {
|
|
3
|
+
export const createToolsContainer = (node: HTMLElement, highlightFrame: HTMLElement, isInstance: boolean = false): void => {
|
|
4
4
|
const nodeTools = document.createElement("div");
|
|
5
5
|
|
|
6
6
|
nodeTools.className = "node-tools";
|
|
7
|
+
if (isInstance) {
|
|
8
|
+
nodeTools.classList.add("is-instance");
|
|
9
|
+
}
|
|
7
10
|
highlightFrame.appendChild(nodeTools);
|
|
8
11
|
|
|
9
12
|
createTagLabel(node, nodeTools);
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
export function getHighlightFrameElement(
|
|
2
|
-
|
|
3
|
-
): HTMLElement | null {
|
|
4
|
-
return nodeProvider.querySelector(".highlight-frame");
|
|
1
|
+
export function getHighlightFrameElement(): SVGSVGElement | null {
|
|
2
|
+
return document.body.querySelector(".highlight-frame-overlay") as SVGSVGElement | null;
|
|
5
3
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function getScreenBounds(element: HTMLElement): {
|
|
2
|
+
top: number;
|
|
3
|
+
left: number;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
} {
|
|
7
|
+
const rect = element.getBoundingClientRect();
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
top: rect.top,
|
|
11
|
+
left: rect.left,
|
|
12
|
+
width: rect.width,
|
|
13
|
+
height: rect.height,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
@@ -1,23 +1,46 @@
|
|
|
1
|
+
import { isComponentInstance } from "../select/helpers/isComponentInstance";
|
|
1
2
|
import { createHighlightFrame } from "./createHighlightFrame";
|
|
2
3
|
import { createToolsContainer } from "./createToolsContainer";
|
|
3
4
|
import { getHighlightFrameElement } from "./helpers/getHighlightFrameElement";
|
|
5
|
+
import { getScreenBounds } from "./helpers/getScreenBounds";
|
|
4
6
|
|
|
5
|
-
export const highlightNode = (node: HTMLElement | null
|
|
7
|
+
export const highlightNode = (node: HTMLElement | null): void => {
|
|
6
8
|
if (!node) return;
|
|
7
9
|
|
|
8
|
-
const existingHighlightFrame = getHighlightFrameElement(
|
|
10
|
+
const existingHighlightFrame = getHighlightFrameElement();
|
|
11
|
+
const existingToolsWrapper = document.body.querySelector(".highlight-frame-tools-wrapper");
|
|
9
12
|
|
|
10
13
|
if (existingHighlightFrame) {
|
|
11
14
|
existingHighlightFrame.remove();
|
|
12
15
|
}
|
|
16
|
+
if (existingToolsWrapper) {
|
|
17
|
+
existingToolsWrapper.remove();
|
|
18
|
+
}
|
|
13
19
|
|
|
14
|
-
const
|
|
20
|
+
const isInstance = isComponentInstance(node);
|
|
21
|
+
const highlightFrame = createHighlightFrame(node, isInstance);
|
|
15
22
|
|
|
16
23
|
if (node.contentEditable === "true") {
|
|
17
24
|
highlightFrame.classList.add("is-editable");
|
|
18
25
|
}
|
|
19
26
|
|
|
20
|
-
|
|
27
|
+
// Create tools wrapper with tag label - centered using translateX(-50%)
|
|
28
|
+
const { left, top, height } = getScreenBounds(node);
|
|
29
|
+
const bottomY = top + height;
|
|
30
|
+
|
|
31
|
+
const toolsWrapper = document.createElement("div");
|
|
32
|
+
toolsWrapper.classList.add("highlight-frame-tools-wrapper");
|
|
33
|
+
if (isInstance) {
|
|
34
|
+
toolsWrapper.classList.add("is-instance");
|
|
35
|
+
}
|
|
36
|
+
toolsWrapper.style.position = "fixed";
|
|
37
|
+
toolsWrapper.style.left = `${left}px`;
|
|
38
|
+
toolsWrapper.style.top = `${bottomY}px`;
|
|
39
|
+
toolsWrapper.style.transform = "translateX(-50%)";
|
|
40
|
+
toolsWrapper.style.transformOrigin = "center";
|
|
41
|
+
toolsWrapper.style.pointerEvents = "none";
|
|
42
|
+
toolsWrapper.style.zIndex = "5000"; // Match --z-index-highlight (below canvas rulers)
|
|
21
43
|
|
|
22
|
-
|
|
44
|
+
createToolsContainer(node, toolsWrapper, isInstance);
|
|
45
|
+
document.body.appendChild(toolsWrapper);
|
|
23
46
|
};
|
|
@@ -1,27 +1,126 @@
|
|
|
1
1
|
import { getCanvasWindowValue } from "@/lib/canvas/helpers/getCanvasWindowValue";
|
|
2
|
-
import {
|
|
2
|
+
import { isComponentInstance } from "../select/helpers/isComponentInstance";
|
|
3
3
|
import { getHighlightFrameElement } from "./helpers/getHighlightFrameElement";
|
|
4
|
+
import { getScreenBounds } from "./helpers/getScreenBounds";
|
|
5
|
+
|
|
6
|
+
const getComponentColor = (): string => {
|
|
7
|
+
return getComputedStyle(document.documentElement).getPropertyValue("--component-color").trim() || "oklch(65.6% 0.241 354.308)";
|
|
8
|
+
};
|
|
4
9
|
|
|
5
10
|
export const refreshHighlightFrame = (node: HTMLElement, nodeProvider: HTMLElement) => {
|
|
6
|
-
|
|
7
|
-
const
|
|
11
|
+
// Batch all DOM reads first (single layout pass)
|
|
12
|
+
const frame = getHighlightFrameElement();
|
|
13
|
+
if (!frame) return;
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
const isInstance = isComponentInstance(node);
|
|
10
16
|
|
|
11
|
-
|
|
17
|
+
// Update SVG dimensions to match current viewport (handles window resize and ensures coordinate system is correct)
|
|
18
|
+
// Use clientWidth/Height to match getBoundingClientRect() coordinate system (excludes scrollbars)
|
|
19
|
+
const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
|
|
20
|
+
const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
|
|
21
|
+
frame.setAttribute("width", viewportWidth.toString());
|
|
22
|
+
frame.setAttribute("height", viewportHeight.toString());
|
|
12
23
|
|
|
13
|
-
|
|
14
|
-
|
|
24
|
+
// Update instance class
|
|
25
|
+
if (isInstance) {
|
|
26
|
+
frame.classList.add("is-instance");
|
|
15
27
|
} else {
|
|
16
|
-
|
|
28
|
+
frame.classList.remove("is-instance");
|
|
17
29
|
}
|
|
18
30
|
|
|
19
|
-
const
|
|
31
|
+
const group = frame.querySelector(".highlight-frame-group") as SVGGElement | null;
|
|
32
|
+
if (!group) return;
|
|
20
33
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
34
|
+
const rect = group.querySelector("rect");
|
|
35
|
+
if (!rect) return;
|
|
36
|
+
|
|
37
|
+
// Update instance color
|
|
38
|
+
if (isInstance) {
|
|
39
|
+
rect.setAttribute("stroke", getComponentColor());
|
|
40
|
+
} else {
|
|
41
|
+
rect.removeAttribute("stroke"); // Use CSS default
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const toolsWrapper = document.body.querySelector(".highlight-frame-tools-wrapper") as HTMLElement | null;
|
|
45
|
+
const nodeTools = toolsWrapper?.querySelector(".node-tools") as HTMLElement | null;
|
|
46
|
+
|
|
47
|
+
const zoom = getCanvasWindowValue(["zoom", "current"]) ?? 1;
|
|
48
|
+
const bounds = getScreenBounds(node);
|
|
49
|
+
|
|
50
|
+
// Calculate all values before any DOM writes
|
|
51
|
+
const { top, left, width, height } = bounds;
|
|
52
|
+
const bottomY = top + height;
|
|
53
|
+
|
|
54
|
+
// Update instance classes on tools wrapper and node tools
|
|
55
|
+
if (toolsWrapper) {
|
|
56
|
+
if (isInstance) {
|
|
57
|
+
toolsWrapper.classList.add("is-instance");
|
|
58
|
+
} else {
|
|
59
|
+
toolsWrapper.classList.remove("is-instance");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (nodeTools) {
|
|
63
|
+
if (isInstance) {
|
|
64
|
+
nodeTools.classList.add("is-instance");
|
|
65
|
+
} else {
|
|
66
|
+
nodeTools.classList.remove("is-instance");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
25
69
|
|
|
26
|
-
|
|
70
|
+
// Batch all DOM writes (single paint pass)
|
|
71
|
+
// Update group transform to move entire group (rect + handles) at once
|
|
72
|
+
group.setAttribute("transform", `translate(${left}, ${top})`);
|
|
73
|
+
|
|
74
|
+
// Update rect dimensions (position is handled by group transform)
|
|
75
|
+
rect.setAttribute("width", width.toString());
|
|
76
|
+
rect.setAttribute("height", height.toString());
|
|
77
|
+
|
|
78
|
+
// Update corner handles positions (relative to group, so only width/height matter)
|
|
79
|
+
const topLeft = group.querySelector(".handle-top-left");
|
|
80
|
+
const topRight = group.querySelector(".handle-top-right");
|
|
81
|
+
const bottomRight = group.querySelector(".handle-bottom-right");
|
|
82
|
+
const bottomLeft = group.querySelector(".handle-bottom-left");
|
|
83
|
+
|
|
84
|
+
const HANDLE_SIZE = 6;
|
|
85
|
+
|
|
86
|
+
// Update handle colors and positions
|
|
87
|
+
const handles = [topLeft, topRight, bottomRight, bottomLeft];
|
|
88
|
+
handles.forEach((handle) => {
|
|
89
|
+
if (handle) {
|
|
90
|
+
if (isInstance) {
|
|
91
|
+
handle.setAttribute("stroke", getComponentColor());
|
|
92
|
+
} else {
|
|
93
|
+
handle.removeAttribute("stroke"); // Use CSS default
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (topLeft) {
|
|
99
|
+
topLeft.setAttribute("x", (-HANDLE_SIZE / 2).toString());
|
|
100
|
+
topLeft.setAttribute("y", (-HANDLE_SIZE / 2).toString());
|
|
101
|
+
}
|
|
102
|
+
if (topRight) {
|
|
103
|
+
topRight.setAttribute("x", (width - HANDLE_SIZE / 2).toString());
|
|
104
|
+
topRight.setAttribute("y", (-HANDLE_SIZE / 2).toString());
|
|
105
|
+
}
|
|
106
|
+
if (bottomRight) {
|
|
107
|
+
bottomRight.setAttribute("x", (width - HANDLE_SIZE / 2).toString());
|
|
108
|
+
bottomRight.setAttribute("y", (height - HANDLE_SIZE / 2).toString());
|
|
109
|
+
}
|
|
110
|
+
if (bottomLeft) {
|
|
111
|
+
bottomLeft.setAttribute("x", (-HANDLE_SIZE / 2).toString());
|
|
112
|
+
bottomLeft.setAttribute("y", (height - HANDLE_SIZE / 2).toString());
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Update tools wrapper position (use calculated bounds, not rect attributes)
|
|
116
|
+
if (toolsWrapper) {
|
|
117
|
+
toolsWrapper.style.left = `${left}px`;
|
|
118
|
+
toolsWrapper.style.top = `${bottomY}px`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (zoom <= 10) {
|
|
122
|
+
nodeProvider.style.setProperty("--tool-opacity", `1`);
|
|
123
|
+
} else {
|
|
124
|
+
nodeProvider.style.setProperty("--tool-opacity", `0`);
|
|
125
|
+
}
|
|
27
126
|
};
|