edix
Version:
An experimental, framework agnostic, small (~3kB) contenteditable state manager.
3 lines (2 loc) • 9.89 kB
JavaScript
const t=([t],[e])=>t===e?0:t<e?1:-1,e=(e,n)=>{const r=t(e,n);return 0===r?e[1]===n[1]?0:e[1]<n[1]?1:-1:r},n=t=>1===t.type,r=t=>n(t)?t.text.length:1,o=(t,e,r)=>{const o=t[e];n(r)&&n(o)?t[e]={type:1,text:o.text+r.text}:t.splice(e+1,0,r)},s=(...t)=>{const e=[];for(let n=0;n<t.length;n++){const r=t[n];if(e.length)for(const t of r)o(e,e.length-1,t);else e.push(...r)}return e},i=(t,e)=>{for(let o=0;o<t.length;o++){const s=t[o],i=r(s);if(i>e){const r=t.slice(0,o),i=t.slice(o+1);return n(s)?(r.push({type:1,text:s.text.slice(0,e)}),i.unshift({type:1,text:s.text.slice(e)})):0===e?i.unshift(s):r.push(s),[r,i]}e-=i}return[t,[]]},c=(e,n,r,o)=>[e[0]+r,e[1]+(0===t(e,n)?o-(0===r?0:n[1]):0)],a=(n,r,o)=>1===e(o,n)?[n[0]-o[0]-r[0],n[1]+(0===t(o,n)?r[1]-o[1]:0)]:r,l=(t,e,n,r)=>{const[o]=n,[c]=r||n,a=i(t[n[0]],n[1]),l=a[0],u=r?i(t[r[0]],r[1])[1]:a[1],f=[...e];f.length?(f[0]=s(l,f[0]),f[f.length-1]=s(f[f.length-1],u)):f.push(s(l,u)),t.splice(o,c-o+1,...f)},u=(t,n,[r,o]=n)=>{const s=e(r,o);if(0!==s){const i=-1===s;((t,n,r,o)=>{const[s,i]=n;l(t,[],r,o),1!==e(s,r)&&(n[0]=a(s,r,o)),1!==e(i,r)&&(n[1]=a(i,r,o))})(t,n,i?o:r,i?r:o)}},f=(t,n,o)=>{u(t,n),((t,n,o,s)=>{const[i,a]=n,u=o.length,f=u-1,p=o[u-1].reduce(((t,e)=>t+r(e)),0);l(t,o,s),1!==e(i,s)&&(n[0]=c(i,s,f,p)),1!==e(a,s)&&(n[1]=c(a,s,f,p))})(t,n,o,n[0])},p=(t,e,n)=>{f(t,e,n.split("\n").map((t=>[{type:1,text:t}])))},d=(t,e,n)=>{e[0]=e[1]=n};let y,x,m,g,h=!1;const b=t=>3===t.nodeType,w=t=>1===t.nodeType,D=t=>8===t.nodeType,M=new Set(["DIV","H1","H2","H3","H4","H5","H6","P","PRE","LI","DT","DD","TR"]),k=new Set(["EMBED","IMG","PICTURE","AUDIO","VIDEO","SVG","CANVAS","MATH","IFRAME","OBJECT"]),v=t=>M.has(t.tagName),O=()=>x,E=()=>1===m?x.data.length:2===m?1:0,H=t=>{const e=t.nextSibling;return!!e&&!D(e)},I=t=>{for(;;){if(2===m){const t=x;for(;(x=y.nextNode())&&t.contains(x););}else x=y.nextNode();if(m=null,!x||t&&x===t)break;if(b(x)){if("\n"!==x.data)return m=1;if(H(x))return m=3}else if(w(x)){const t=x.tagName;if("BR"===t){const t=h;if(h=!0,H(x))return m=3;if(!t)return m=5}else{if("false"===x.contentEditable||k.has(t))return m=2;if(g(x)){const t=x.previousElementSibling;if(t&&g(t))return m=4}}}}},R=(t,e,n,r)=>{try{return g=r.t||v,y=e.createTreeWalker(n,5),t(I)}finally{y=x=m=null,h=!1}},{min:S}=Math,T="function"==typeof queueMicrotask?queueMicrotask:t=>{Promise.resolve().then(t)},A=t=>t.ownerDocument,B=t=>A(t).getSelection(),C=(t,e)=>{if(t.rangeCount){const n=t.getRangeAt(0);if(e.contains(n.commonAncestorContainer))return n}},P=(t,e,n)=>{const r=B(t);r.removeAllRanges(),r.addRange(e),n&&(r.collapseToEnd(),r.extend(e.startContainer,e.startOffset))},L=(t,n,[r,o],s,i)=>{const c=e(r,o),a=0===c,l=-1===c,u=l?o:r,f=l?r:o;if(0===u[0]&&0===u[1]&&a&&!n.hasChildNodes()){const e=t.createRange();return e.setStart(n,0),e.setEnd(n,0),P(n,e),!0}const p=V(t,n,u,s,i);if(!p)return!1;const d=a?p:V(t,n,f,s,i);if(!d)return!1;const y=t.createRange();{const[t,e]=p;w(t)?e<1?y.setStartBefore(t):y.setStartAfter(t):y.setStart(t,S(e,t.data.length))}{const[t,e]=d;w(t)?e<1?y.setEndBefore(t):y.setEndAfter(t):y.setEnd(t,S(e,t.data.length))}return P(n,y,l),!0},V=(t,e,[n,r],o,s)=>R((t=>{for(;t();){const t=E();if(r<=t)return[O(),r];r-=t}}),t,o||0===e.childElementCount?e:e.children[n],s),j=(t,e,n,r,o,s)=>{let i,c=n;if(o||0===e.childElementCount)c=e,i=0;else{for(;;){const t=c.parentElement;if(t===e)break;c=t}i=Array.prototype.indexOf.call(e.children,c)}return w(n)&&(n=n.childNodes[r],r=0),R((t=>{let e,o=0;for(;e=t(n);)3===e?(i++,o=0):o+=E();return[i,o+r]}),t,c,s)},F=(t,e,n,r)=>{const o=B(e),s=C(o,e);if(!s)return[[0,0],[0,0]];const{startOffset:i,startContainer:c,endOffset:a,endContainer:l}=s,u=c===l?o.anchorOffset>o.focusOffset:!!(2&o.anchorNode.compareDocumentPosition(o.focusNode));let f,p;if(e!==c||n)f=j(t,e,c,i,n,r),p=o.isCollapsed?f:j(t,e,l,a,n,r);else{if(!(0===i&&0!==a&&e.children.length<=a))return[[0,0],[0,0]];f=[0,0],p=j(t,e,e.lastElementChild,e.lastElementChild.textContent.length,n,r)}return[u?p:f,u?f:p]},_=(t,e,n,r)=>R((t=>{let e,n=null,o="";const s=[],i=t=>{if(n||(n=[]),o&&(n.push({type:1,text:o}),o=""),t){const e=r(t);e&&n.push({type:2,data:e})}},c=()=>{i(),n&&s.push(n),n=null};for(;e=t();)1===e?o+=O().data:2===e?i(O()):3!==e&&4!==e||c();return c(),s}),t,e,n),N=t=>t.reduce(((t,e,n)=>(0!==n&&(t+="\n"),t+e.reduce(((t,e)=>t+(1===e.type?e.text:"")),""))),""),U=()=>"";exports.Delete=u,exports.InsertText=p,exports.editable=(t,{schema:{single:e,js:n,void:o,copy:i,paste:c},isBlock:a,onChange:l})=>{const{contentEditable:y,role:x,ariaMultiLine:m,ariaReadOnly:g}=t,h=t.style.whiteSpace;t.role="textbox",t.style.whiteSpace="pre-wrap",e||(t.ariaMultiLine="true");let b=!1,w=!1,D=!1,M=[[0,0],[0,0]],k=null,v=!1,O=!1,E=!1;const H={t:a},I=()=>{t.contentEditable=b?"false":"true",t.ariaReadOnly=b?"true":null};I();const R=[],S=A(t),P=(t=>{let e=0,n=0;const r=Date.now,o=[t],s=()=>o[e];return{get:s,set:t=>{o[e]=t},undo:()=>e>0?(e--,s()):void 0,redo:()=>e<o.length-1?(e++,s()):void 0,push:t=>{const s=r();0!==e&&s-n<500&&e--,n=s,o[++e]=t,o.splice(e+1),e>500&&(e--,o.shift())}}})([_(S,t,H,o),M]),V=((t,e)=>{let n=!1;const r=[],o=t=>{n&&r.push(...t)},s=new MutationObserver((t=>{o(t),n||e()})),i=()=>{o(s.takeRecords())};return s.observe(t,{characterData:!0,characterDataOldValue:!0,childList:!0,subtree:!0}),{o(t){!n&&t&&i(),n=t},i:()=>(i(),r.splice(0)),l(){r.splice(0),s.disconnect()}}})(t,(()=>{O&&(L(S,t,M,e,H),null!=k&&(clearTimeout(k),k=null))})),N=new Set,U=t=>{N.has(t)||(N.add(t),T((()=>{N.delete(t),t()})))},q=()=>{const n=M;k=setTimeout((()=>{L(S,t,n,e,H)}))},G=(t,o,i)=>{if(!b){e&&([t,o]=((t,[[e,n],[o,i]])=>{let c=0,a=0;for(let n=0;n<t.length;n++)for(const s of t[n]){const t=r(s);n<e&&(c+=t),n<o&&(a+=t)}return[[s(...t)],[[0,c+n],[0,a+i]]]})(t,o)),M=o;const c=P.get()[0];(t.length!==c.length||t.some(((t,e)=>t!==c[e])))&&(P.set([c,i]),P.push([t,o]),l(n(t)))}q()},W=()=>{M=F(S,t,e,H)},J=()=>{const n=V.i();if(V.o(!1),n.length){const r=F(S,t,e,H),s=_(S,t,H,o);let i;for(;i=n.pop();)if("childList"===i.type){const{target:t,removedNodes:e,addedNodes:n,nextSibling:r}=i;for(let n=e.length-1;n>=0;n--)t.insertBefore(e[n],r);for(let e=n.length-1;e>=0;e--)t.removeChild(n[e])}else i.target.nodeValue=i.oldValue;V.i(),D=L(S,t,M,e,H),G(s,r,M)}},K=()=>{if(R.length){const t=[...M],e=[...P.get()[0]];let n;for(;n=R.pop();)n[0](e,t,...n[1]);G(e,t,M)}},X=(t,...e)=>{R.unshift([t,e]),U(K)},Y=t=>{if(!v&&(t.metaKey||t.ctrlKey)&&!t.altKey&&"KeyZ"===t.code&&(t.preventDefault(),V.o(!1),!b)){const e=t.shiftKey?P.redo():P.undo();e&&(M=e[1],l(n(e[0])),q())}},Z=()=>{v||U(J)},z=t=>{switch(t.inputType){case"historyUndo":case"historyRedo":return void t.preventDefault();case"insertLineBreak":case"insertParagraph":if(e)return void t.preventDefault()}V.o(!0)},Q=()=>{v=!0},$=()=>{v=!1,U(J)},tt=()=>{O=!0,W()},et=()=>{O=!1},nt=()=>{D?D=!1:!O||v||E||W()},rt=e=>{const n=(t=>{const e=C(B(t),t);if(e)return e.cloneContents()})(t);n&&i(e,_(S,n,H,o),n)},ot=t=>{const e=c(t);"string"==typeof e?X(p,e):X(f,_(S,e,H,o))},st=t=>{t.preventDefault(),rt(t.clipboardData)},it=t=>{t.preventDefault(),b||(rt(t.clipboardData),X(u))},ct=t=>{t.preventDefault(),ot(t.clipboardData)},at=n=>{n.preventDefault();const r=n.dataTransfer,o=((t,e,{clientX:n,clientY:r},o,s)=>{if(t.caretPositionFromPoint){const i=t.caretPositionFromPoint(n,r);if(i)return j(t,e,i.offsetNode,i.offset,o,s)}else if(t.caretRangeFromPoint){const i=t.caretRangeFromPoint(n,r);if(i)return j(t,e,i.startContainer,i.startOffset,o,s)}})(S,t,n,e,H);r&&o&&(X(d,o),E?X(u,M):t.focus(),ot(r))},lt=t=>{E=!0,rt(t.dataTransfer)},ut=()=>{E=!1};return S.addEventListener("selectionchange",nt),t.addEventListener("keydown",Y),t.addEventListener("input",Z),t.addEventListener("beforeinput",z),t.addEventListener("compositionstart",Q),t.addEventListener("compositionend",$),t.addEventListener("focus",tt),t.addEventListener("blur",et),t.addEventListener("copy",st),t.addEventListener("cut",it),t.addEventListener("paste",ct),t.addEventListener("drop",at),t.addEventListener("dragstart",lt),t.addEventListener("dragend",ut),{dispose:()=>{w||(w=!0,t.contentEditable=y,t.role=x,t.ariaMultiLine=m,t.ariaReadOnly=g,t.style.whiteSpace=h,V.l(),S.removeEventListener("selectionchange",nt),t.removeEventListener("keydown",Y),t.removeEventListener("input",Z),t.removeEventListener("beforeinput",z),t.removeEventListener("compositionstart",Q),t.removeEventListener("compositionend",$),t.removeEventListener("focus",tt),t.removeEventListener("blur",et),t.removeEventListener("copy",st),t.removeEventListener("cut",it),t.removeEventListener("paste",ct),t.removeEventListener("drop",at),t.removeEventListener("dragstart",lt),t.removeEventListener("dragend",ut))},command:X,readonly:t=>{b=t,I()}}},exports.plainSchema=({multiline:t}={})=>({single:!t,js:N,void:()=>{},copy:(t,e)=>{t.setData("text/plain",N(e))},paste:t=>t.getData("text/plain")}),exports.schema=({multiline:t,void:e})=>{const n=Object.entries(e),r=new WeakMap,o=new WeakMap,s=t=>t.reduce(((t,e)=>{if(1===e.type){let n=r.get(e);n||r.set(e,n={type:"text",text:e.text}),t.push(n)}else t.push(o.get(e.data));return t}),[]);return{single:!t,js:t?t=>t.map(s):t=>s(t[0]),void:t=>{for(const[e,r]of n)if(r.is(t)){const n=r.data(t);return o.set(n,{type:e,data:{...n}}),n}},copy:(t,n,r)=>{const s=n.reduce(((t,n,r)=>(0!==r&&(t+="\n"),t+n.reduce(((t,n)=>{if(1===n.type)return t+n.text;const r=o.get(n.data);return t+e[r.type].plain(n.data)}),""))),"");t.setData("text/plain",s);const i=document.createElement("div");i.appendChild(r),t.setData("text/html",i.innerHTML)},paste:t=>{const e=t.getData("text/html");if(e){let t=(new DOMParser).parseFromString(e,"text/html").body,n=!1;for(const e of[...t.childNodes])D(e)?"StartFragment"===e.data?(n=!0,t=new DocumentFragment):"EndFragment"===e.data&&(n=!1):n&&t.appendChild(e);return t}return t.getData("text/plain")}}},exports.voidNode=({is:t,data:e,plain:n=U})=>({is:t,data:e,plain:n});
//# sourceMappingURL=index.js.map