sign-pad
Version:
sign-pad web component provides a signature drawing surface and related services
1 lines • 5.53 kB
JavaScript
import{roundTo as t,extractSvgRawData as e,svgToCanvas as n}from"./sign-pad-utils.min.js";export{s as LOCAL_NAME};const s=new URL(import.meta.url).searchParams.get("local-name")||"sign-pad",i="surface",a="http://www.w3.org/2000/svg",r=Symbol("surface"),o=Symbol("active-pointer"),h=Symbol("empty-state"),l=Symbol("current-group"),p=Symbol("current-point"),c=Symbol("current-hop"),d=Symbol("full-diag-size"),u=Symbol("changed-since-active"),f=.4,y="input",w="change",x="empty",v="trim",b="ink",g="fill",m={[v]:!1,[b]:"#000",[g]:"transparent"},$={svg:{defaultOptions:Object.assign({},m)},canvas:{defaultOptions:Object.assign({},m)}},_=document.createElement("template");class M{constructor(t,e){const n=e.x-t.x,s=e.y-t.y;this.angle=Math.atan(s/n);const i=this._calcRect(this.angle,t.w,e.w);this.fpx1=t.x+i.fdx,this.fpy1=t.y+i.fdy,this.fpx2=t.x-i.fdx,this.fpy2=t.y-i.fdy,this.tpx1=e.x+i.tdx,this.tpy1=e.y+i.tdy,this.tpx2=e.x-i.tdx,this.tpy2=e.y-i.tdy}_calcRect(t,e,n){const s=t+Math.PI/2,i=Math.cos(s),a=Math.sin(s);return{fdx:i*e/2,fdy:a*e/2,tdx:i*n/2,tdy:a*n/2}}}_.innerHTML=`\n\t<style>\n\t\t:host {\n\t\t\tdisplay: inline-block;\n\t\t\tmin-width: 300px;\n\t\t\tmin-height: 200px;\n\t\t\twidth: 300px;\n\t\t\theight: 200px;\n\t\t}\n\n\t\t.container {\n\t\t\tposition: relative;\n\t\t\twidth: 100%;\n\t\t\theight: 100%;\n\t\t}\n\n\t\t.${i},\n\t\t[name="background"]::slotted(*) {\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tleft: 0;\n\t\t\twidth: 100%;\n\t\t\theight: 100%;\n\t\t\toutline: none;\n\t\t}\n\n\t\t.${i} {\n\t\t\tfill: currentColor;\n\t\t\ttouch-action: none;\n\t\t}\n\n\t\t[name="background"]::slotted(*) {\n\t\t\tpointer-events: none;\n\t\t}\n\t</style>\n\t<div class="container">\n\t\t<slot name="background"></slot>\n\t\t<svg xmlns="http://www.w3.org/2000/svg" class="${i}" aria-label="signature" tabindex="0"></svg>\n\t</div>\n`,customElements.define(s,class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).appendChild(_.content.cloneNode(!0)),Object.defineProperties(this,{[r]:{value:this.shadowRoot.querySelector(`.${i}`)},[o]:{value:null,writable:!0},[h]:{value:!0,writable:!0},[l]:{value:null,writable:!0},[p]:{value:null,writable:!0},[c]:{value:null,writable:!0},[d]:{value:null,writable:!0},[u]:{value:!1,writable:!0}}),this._setupListeners()}connectedCallback(){this.setAttribute(x,"")}get empty(){return this[h]}clear(){this[h]||(this[r].innerHTML="",this[h]=!0,this[u]=!0,this.setAttribute(x,""),this.dispatchEvent(new Event(y)))}export(t=$.SVG,e){if(!(t in $))throw new Error(`unknown format '${t}'; use one of those: [${Object.keys($).join(", ")}]`);const n=Object.assign({},$[t].defaultOptions,e);switch(t){case"svg":return this._exportSvg(n);case"canvas":return this._exportCanvas(n)}}_setupListeners(){const t=this[r];t.addEventListener("focus",t=>this._onFocus(t)),t.addEventListener("blur",t=>this._onBlur(t)),t.addEventListener("pointerdown",t=>this._drawStart(t)),t.addEventListener("pointermove",t=>this._drawMove(t)),t.addEventListener("pointerup",t=>this._drawEnd(t)),t.addEventListener("pointerleave",t=>this._drawEnd(t)),t.addEventListener("pointercancel",t=>this._drawEnd(t)),t.addEventListener("keyup",t=>this._keyProc(t))}_onFocus(){this[u]=!1}_onBlur(){this[u]&&(this.dispatchEvent(new Event(w,{bubbles:!0,composed:!0})),this[u]=!1)}_drawStart(t){if(!t.isPrimary)return;const e=t.pointerId;this[o]&&e!==this[o]||(t.target.setPointerCapture(e),this[o]=e,this[l]=[],this[p]={x:t.offsetX,y:t.offsetY,w:4},this[c]=null)}_drawEnd(t){t.pointerId===this[o]&&(t.target.releasePointerCapture(this[o]),this[o]=null)}_keyProc(t){switch(t.code){case"Escape":this.clear();break;case"Enter":this.blur()}}_drawMove(t){if(t.pointerId!==this[o])return;const e={x:t.offsetX,y:t.offsetY},n=this[p],s=this._calcDistance(n,e);if(s<4)return;e.w=this._calcWeigth(s);const i=new M(n,e);let a=null,r=null;if(this[c]){let t=i.angle-this[c].angle;Math.abs(t)<Math.PI/4&&(t>Math.PI/2&&(t-=Math.PI),t<-Math.PI/2&&(t+=Math.PI),a=t*f,r=i.angle-a)}this[c]=i,this[p]=e;const d=this[l];d.push(i),d.length>1&&this._paintJoin(d[d.length-2],i),this._paintHop(i,a,r,s),this[u]=!0,this[h]&&(this[h]=!1,this.removeAttribute(x)),this.dispatchEvent(new Event(y))}_calcDistance(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}_calcWeigth(t){let e=this[d];if(!this[d]){const t=this.getBoundingClientRect();e=this[d]=Math.sqrt(Math.pow(t.width,2)+Math.pow(t.height,2))}return Math.max(2,4-t/e*64)}_paintJoin(e,n){const s=document.createElementNS(a,"path");s.setAttribute("d",`M ${t(e.tpx1)} ${t(e.tpy1)} L ${t(n.fpx1)} ${t(n.fpy1)} L ${t(e.tpx2)} ${t(e.tpy2)} L ${t(n.fpx2)} ${t(n.fpy2)} Z`),this[r].appendChild(s)}_paintHop(e,n,s,i){const o=document.createElementNS(a,"path");if(n){const a=i/2/Math.cos(n),r=e.tpx1>=e.fpx1?1:-1,h=Math.cos(s)*a*r,l=Math.sin(s)*a*r;o.setAttribute("d",`M ${t(e.fpx1)} ${t(e.fpy1)} Q ${t(e.fpx1+h)} ${t(e.fpy1+l)} , ${t(e.tpx1)} ${t(e.tpy1)} L ${t(e.tpx2)} ${t(e.tpy2)} Q ${t(e.fpx2+h)} ${t(e.fpy2+l)} , ${t(e.fpx2)} ${t(e.fpy2)} Z`)}else o.setAttribute("d",`M ${t(e.fpx1)} ${t(e.fpy1)} L ${t(e.tpx1)} ${t(e.tpy1)} L ${t(e.tpx2)} ${t(e.tpy2)} L ${t(e.fpx2)} ${t(e.fpy2)} Z`);this[r].appendChild(o)}_exportSvg(t){const n=e(this[r]),s=document.createElementNS(a,"svg");for(const t of n.hops)s.appendChild(t);const i=t[v]?n.drawRect:n.fullRect;return s.setAttribute("viewBox",`${i.x} ${i.y} ${i.w} ${i.h}`),s.setAttribute("fill",t[b]),t[g]!==m[g]&&s.setAttribute("style",`background:${t[g]}`),s}_exportCanvas(t){const s=e(this[r]);return n(s,t[b],t[g],t[v])}});