nanosheets
Version:
A minimal spreadsheet editor
1 lines • 6.22 kB
JavaScript
export function NanoSheets(p,{data:h={},afterDataChange:o=()=>null,beforeRedraw:d=()=>null,afterCursorChange:n=()=>null,cellWidth:g=200,cellHeight:b=40,readOnly:x=false,inputStyle:t={font:"inherit",border:"2px solid #00aae1",borderRadius:"2px",transition:"left 0.2s, top 0.2s",padding:"0 8px"},cellStyle:m=(t,e,n,o)=>({padding:"0 10px",borderBottom:"1px solid #dadada",borderRight:"1px solid #dadada",background:o?"#e2f7ff":"white",transition:"background 0.1s",color:"black",outline:"none",whiteSpace:"nowrap"}),containerStyle:e={overflow:"auto",position:"relative",border:"1px solid #dadada"}}){const z=p.getAttribute("style")||"";Object.assign(p.style,e);p.setAttribute("tabindex","-1");const l=t=>t.split("_").map(t=>parseInt(t));let y,w=0;let v=false;let M=[0,0];let A=null;const D=document.createElement("input");D.setAttribute("type","search");Object.assign(D.style,{zIndex:2,lineHeight:b+"px",width:g+"px",height:b+"px",position:"absolute",boxSizing:"border-box"},t);p.appendChild(D);const i=document.createElement("div");i.style.pointerEvents="none";i.style.position="absolute";i.style.width="1px";i.style.height="1px";p.appendChild(i);const s=[0,0];function r(t){Object.keys(t).forEach(t=>{const e=l(t);if(e[0]>s[0])s[0]=e[0];if(e[1]>s[1])s[1]=e[1]});i.style.left=s[0]*g+"px";i.style.top=s[1]*b+"px"}r(h);function B(){const t=p.getBoundingClientRect();y=Math.ceil(t.width/g)+1;w=Math.ceil(t.height/b)+1;const e=y*w;while(p.children.length-2<e){const n=document.createElement("div");p.appendChild(n)}while(p.children.length-2>e){p.removeChild(p.children[2])}}function a(){const n=Math.floor(p.scrollLeft/g);const o=Math.floor(p.scrollTop/b);d(M,A,v);const[t,e]=M;Object.assign(D.style,{position:"absolute",left:g*t+"px",top:b*e+"px"},v?{background:"white",color:"black",pointerEvents:"all"}:{background:"transparent",color:"transparent",pointerEvents:"none"});if(x)D.setAttribute("readonly","");else D.removeAttribute("readonly","");B();const[l,i,r,s]=A||[-1,-1,-1,-1];for(let e=0;e<y;e++){for(let t=0;t<w;t++){const a=n+e;const c=o+t;const f=a+"_"+c;const u=p.children[t*y+e+2];u.style="";Object.assign(u.style,{lineHeight:b+"px",position:"absolute",boxSizing:"border-box",overflow:"hidden",left:g*a+"px",top:b*c+"px",width:g+"px",height:b+"px",...m(a,c,h[f]||"",H(a,l,r)&&H(c,i,s))});u.textContent=h[f]||"";u.setAttribute("cell",f)}}}const c=[];const f=new ResizeObserver(a);function I(){c.forEach(({node:t,args:e})=>t.removeEventListener(...e));f.unobserve(p);p.innerHTML="";p.setAttribute("style",z)}function u(t,...e){t.addEventListener(...e);c.push({node:t,args:e})}f.observe(p);u(p,"scroll",a);a();function E(t){if(x)return;let e=false;for(const n in t){if((h[n]||"")!==(t[n]||"")){if(t[n]!==""){h[n]=t[n]}else{delete h[n]}e=true}}if(e){r(t);o()}}u(D,"focus",()=>{if(!A){const[t,e]=M;A=[t,e,t,e];a()}});u(D,"blur",()=>{O();A=null;a()});function P(){if(v){E({[M.join("_")]:D.value})}}u(D,"keydown",t=>{const[e,n]=M;const o={ArrowRight:[1,0],ArrowLeft:[-1,0],ArrowDown:[0,1],ArrowUp:[0,-1],Enter:[0,1],PageDown:[0,w-1],PageUp:[0,-(w-1)],Home:[-e,0],End:[s[0]-e,0]};const l=o[t.key];if(t.key==="Delete"||t.key==="Backspace"){R();a()}else if(l){O();const[i,r]=l;if(t.shiftKey){A[2]=Math.max(0,A[2]+i);A[3]=Math.max(0,A[3]+r);k(A[2],A[3])}else{C(Math.max(0,e+i),Math.max(0,n+r))}a()}},true);u(D,"input",t=>{if(!v&&D.value){const e=D.value;j(...M);D.value=e;a()}});function k(t,e){const{width:n,height:o}=p.getBoundingClientRect();if(p.scrollLeft+n<(t+1)*g){p.scrollLeft=(t+1)*g-n}else if(t*g<p.scrollLeft){p.scrollLeft=t*g}if(p.scrollTop+o<(e+1)*b){p.scrollTop=(e+1)*b-o}else if(e*b<p.scrollTop){p.scrollTop=e*b}}function C(t,e){M[0]=t;M[1]=e;A=[t,e,t,e];a();k(t,e);n()}function O(){if(v){P();v=false;D.value=""}}function j(t,e){const n=[t,e].join("_");if(!v||M[0]!==t||M[1]!==e){M[0]=t;M[1]=e;D.value=h[n]||"";v=true}a()}let L=0;u(p,"mousedown",t=>{const e=t.target.getAttribute("cell");if(!e||t.buttons!==1)return;t.preventDefault();const[n,o]=l(e);if(e===M.join("_")){if(Date.now()-400<L&&!x){j(n,o)}}else{O();C(n,o);setTimeout(()=>D.focus(),200);L=Date.now()}a()},true);let T=null;u(p,"touchstart",t=>{T=t.target.getAttribute("cell")},true);u(p,"touchend",t=>{const e=t.target.getAttribute("cell");if(e===T){const[n,o]=l(e);if(e===M.join("_")&&!x){j(n,o);setTimeout(()=>{D.select();D.focus()},200)}else{D.blur();O();C(n,o)}}},true);u(p,"mouseenter",t=>{if(t.buttons===1&&A){const e=t.target.getAttribute("cell");if(A&&e){const[n,o]=l(e);A[2]=n;A[3]=o;a()}}},true);u(D,"paste",t=>{if(v)return;t.preventDefault();const e=U((t.clipboardData||window.clipboardData).getData("text/plain"));const o={};const[l,i]=M;e.forEach((t,n)=>t.forEach((t,e)=>o[l+e+"_"+(i+n)]=t));E(o);A=[l,i,l+e[0].length-1,i+e.length-1];a()});function _(t){t.preventDefault();const[n,o,l,i]=A;const r=[];for(let e=Math.min(o,i);e<=Math.max(o,i);e++){const s=[];for(let t=Math.min(n,l);t<=Math.max(n,l);t++){s.push(h[t+"_"+e]||"")}r.push(s)}t.clipboardData.setData("text/plain",K(r))}u(D,"copy",t=>{if(!v){_(t)}});function R(){const n={};const[o,t,l,i]=A;for(let e=Math.min(t,i);e<=Math.max(t,i);e++){for(let t=Math.min(o,l);t<=Math.max(o,l);t++){n[t+"_"+e]=""}}E(n)}u(D,"cut",t=>{if(!v){_(t);R();a()}});function S(t){return t.split('"').length-1}function U(t){let e,n,o,l=[],i=0,r,s,a,c;o=t.split("\n");if(o.length>1&&o[o.length-1]===""){o.pop()}for(e=0,n=o.length;e<n;e+=1){o[e]=o[e].split("\t");for(r=0,s=o[e].length;r<s;r+=1){if(!l[i]){l[i]=[]}if(a&&r===0){c=l[i].length-1;l[i][c]=l[i][c]+"\n"+o[e][0];if(a&&S(o[e][0])&1){a=false;l[i][c]=l[i][c].substring(0,l[i][c].length-1).replace(/""/g,'"')}}else{if(r===s-1&&o[e][r].indexOf('"')===0&&S(o[e][r])&1){l[i].push(o[e][r].substring(1).replace(/""/g,'"'));a=true}else{l[i].push(o[e][r].replace(/""/g,'"'));a=false}}}if(!a){i+=1}}return l}function K(t){let e,n,o,l,i="",r;for(e=0,n=t.length;e<n;e+=1){for(o=0,l=t[e].length;o<l;o+=1){if(o>0){i+="\t"}r=t[e][o];if(typeof r==="string"){if(r.indexOf("\n")>-1){i+='"'+r.replace(/"/g,'""')+'"'}else{i+=r}}else if(r===null||r===void 0){i+=""}else{i+=r}}i+="\n"}return i}function H(t,e,n){return t>=Math.min(e,n)&&t<=Math.max(e,n)}return{destroy:I,scrollTo:k,select:C,redraw(){a();r(h)},data:h,get cursor(){return[...M]},set readOnly(t){x=t;a()}}}