idiomorph
Version:
an id-based DOM morphing library
1 lines • 9.35 kB
JavaScript
var Idiomorph=function(){"use strict";const e=()=>{};const n={morphStyle:"outerHTML",callbacks:{beforeNodeAdded:e,afterNodeAdded:e,beforeNodeMorphed:e,afterNodeMorphed:e,beforeNodeRemoved:e,afterNodeRemoved:e,beforeAttributeUpdated:e},head:{style:"merge",shouldPreserve:e=>e.getAttribute("im-preserve")==="true",shouldReAppend:e=>e.getAttribute("im-re-append")==="true",shouldRemove:e,afterHeadMorphed:e},restoreFocus:true};function t(t,e,n={}){t=d(t);const r=f(e);const o=u(t,r,n);const i=a(o,()=>{return c(o,t,r,e=>{if(e.morphStyle==="innerHTML"){s(e,t,r);return Array.from(t.childNodes)}else{return l(e,t,r)}})});o.pantry.remove();return i}function l(e,t,n){const r=f(t);s(e,r,n,t,t.nextSibling);return Array.from(r.childNodes)}function a(e,t){if(!e.config.restoreFocus)return t();let n=document.activeElement;if(!(n instanceof HTMLInputElement||n instanceof HTMLTextAreaElement)){return t()}const{id:r,selectionStart:o,selectionEnd:i}=n;const l=t();if(r&&r!==document.activeElement?.id){n=e.target.querySelector(`[id="${r}"]`);n?.focus()}if(n&&!n.selectionEnd&&i){n.setSelectionRange(o,i)}return l}const s=function(){function e(e,t,n,r=null,o=null){if(t instanceof HTMLTemplateElement&&n instanceof HTMLTemplateElement){t=t.content;n=n.content}r||=t.firstChild;for(const i of n.childNodes){if(r&&r!=o){const a=d(e,i,r,o);if(a){if(a!==r){m(e,r,a)}p(a,i,e);r=a.nextSibling;continue}}if(i instanceof Element&&e.persistentIds.has(i.id)){const s=h(t,i.id,r,e);p(s,i,e);r=s.nextSibling;continue}const l=u(t,i,r,e);if(l){r=l.nextSibling}}while(r&&r!=o){const c=r;r=r.nextSibling;f(e,c)}}function u(e,t,n,r){if(r.callbacks.beforeNodeAdded(t)===false)return null;if(r.idMap.has(t)){const o=document.createElement(t.tagName);e.insertBefore(o,n);p(o,t,r);r.callbacks.afterNodeAdded(o);return o}else{const i=document.importNode(t,true);e.insertBefore(i,n);r.callbacks.afterNodeAdded(i);return i}}const d=function(){function e(e,t,n,r){let o=null;let i=t.nextSibling;let l=0;let a=n;while(a&&a!=r){if(c(a,t)){if(s(e,a,t)){return a}if(o===null){if(!e.idMap.has(a)){o=a}}}if(o===null&&i&&c(a,i)){l++;i=i.nextSibling;if(l>=2){o=undefined}}if(a.contains(document.activeElement))break;a=a.nextSibling}return o||null}function s(e,t,n){let r=e.idMap.get(t);let o=e.idMap.get(n);if(!o||!r)return false;for(const i of r){if(o.has(i)){return true}}return false}function c(e,t){const n=e;const r=t;return n.nodeType===r.nodeType&&n.tagName===r.tagName&&(!n.id||n.id===r.id)}return e}();function f(e,t){if(e.idMap.has(t)){l(e.pantry,t,null)}else{if(e.callbacks.beforeNodeRemoved(t)===false)return;t.parentNode?.removeChild(t);e.callbacks.afterNodeRemoved(t)}}function m(t,e,n){let r=e;while(r&&r!==n){let e=r;r=r.nextSibling;f(t,e)}return r}function h(e,t,n,r){const o=r.target.id===t&&r.target||r.target.querySelector(`[id="${t}"]`)||r.pantry.querySelector(`[id="${t}"]`);i(o,r);l(e,o,n);return o}function i(t,n){const r=t.id;while(t=t.parentNode){let e=n.idMap.get(t);if(e){e.delete(r);if(!e.size){n.idMap.delete(t)}}}}function l(t,n,r){if(t.moveBefore){try{t.moveBefore(n,r)}catch(e){t.insertBefore(n,r)}}else{t.insertBefore(n,r)}}return e}();const p=function(){function e(e,t,n){if(n.ignoreActive&&e===document.activeElement){return null}if(n.callbacks.beforeNodeMorphed(e,t)===false){return e}if(e instanceof HTMLHeadElement&&n.head.ignore){}else if(e instanceof HTMLHeadElement&&n.head.style!=="morph"){m(e,t,n)}else{r(e,t,n);if(!f(e,n)){s(n,e,t)}}n.callbacks.afterNodeMorphed(e,t);return e}function r(e,t,n){let r=t.nodeType;if(r===1){const o=e;const i=t;const l=o.attributes;const a=i.attributes;for(const s of a){if(d(s.name,o,"update",n)){continue}if(o.getAttribute(s.name)!==s.value){o.setAttribute(s.name,s.value)}}for(let e=l.length-1;0<=e;e--){const c=l[e];if(!c)continue;if(!i.hasAttribute(c.name)){if(d(c.name,o,"remove",n)){continue}o.removeAttribute(c.name)}}if(!f(o,n)){u(o,i,n)}}if(r===8||r===3){if(e.nodeValue!==t.nodeValue){e.nodeValue=t.nodeValue}}}function u(n,r,o){if(n instanceof HTMLInputElement&&r instanceof HTMLInputElement&&r.type!=="file"){let e=r.value;let t=n.value;i(n,r,"checked",o);i(n,r,"disabled",o);if(!r.hasAttribute("value")){if(!d("value",n,"remove",o)){n.value="";n.removeAttribute("value")}}else if(t!==e){if(!d("value",n,"update",o)){n.setAttribute("value",e);n.value=e}}}else if(n instanceof HTMLOptionElement&&r instanceof HTMLOptionElement){i(n,r,"selected",o)}else if(n instanceof HTMLTextAreaElement&&r instanceof HTMLTextAreaElement){let e=r.value;let t=n.value;if(d("value",n,"update",o)){return}if(e!==t){n.value=e}if(n.firstChild&&n.firstChild.nodeValue!==e){n.firstChild.nodeValue=e}}}function i(e,t,n,r){const o=t[n],i=e[n];if(o!==i){const l=d(n,e,"update",r);if(!l){e[n]=t[n]}if(o){if(!l){e.setAttribute(n,"")}}else{if(!d(n,e,"remove",r)){e.removeAttribute(n)}}}}function d(e,t,n,r){if(e==="value"&&r.ignoreActiveValue&&t===document.activeElement){return true}return r.callbacks.beforeAttributeUpdated(e,t,n)===false}function f(e,t){return!!t.ignoreActiveValue&&e===document.activeElement&&e!==document.body}return e}();function c(t,e,n,r){if(t.head.block){const o=e.querySelector("head");const i=n.querySelector("head");if(o&&i){const l=m(o,i,t);return Promise.all(l).then(()=>{const e=Object.assign(t,{head:{block:false,ignore:true}});return r(e)})}}return r(t)}function m(e,t,r){let o=[];let i=[];let l=[];let a=[];let s=new Map;for(const n of t.children){s.set(n.outerHTML,n)}for(const u of e.children){let e=s.has(u.outerHTML);let t=r.head.shouldReAppend(u);let n=r.head.shouldPreserve(u);if(e||n){if(t){i.push(u)}else{s.delete(u.outerHTML);l.push(u)}}else{if(r.head.style==="append"){if(t){i.push(u);a.push(u)}}else{if(r.head.shouldRemove(u)!==false){i.push(u)}}}}a.push(...s.values());let c=[];for(const d of a){let n=document.createRange().createContextualFragment(d.outerHTML).firstChild;if(r.callbacks.beforeNodeAdded(n)!==false){if("href"in n&&n.href||"src"in n&&n.src){let t;let e=new Promise(function(e){t=e});n.addEventListener("load",function(){t()});c.push(e)}e.appendChild(n);r.callbacks.afterNodeAdded(n);o.push(n)}}for(const f of i){if(r.callbacks.beforeNodeRemoved(f)!==false){e.removeChild(f);r.callbacks.afterNodeRemoved(f)}}r.head.afterHeadMorphed(e,{added:o,kept:l,removed:i});return c}const u=function(){function e(e,t,n){const{persistentIds:r,idMap:o}=d(e,t);const i=a(n);const l=i.morphStyle||"outerHTML";if(!["innerHTML","outerHTML"].includes(l)){throw`Do not understand how to morph style ${l}`}return{target:e,newContent:t,config:i,morphStyle:l,ignoreActive:i.ignoreActive,ignoreActiveValue:i.ignoreActiveValue,restoreFocus:i.restoreFocus,idMap:o,persistentIds:r,pantry:s(),callbacks:i.callbacks,head:i.head}}function a(e){let t=Object.assign({},n);Object.assign(t,e);t.callbacks=Object.assign({},n.callbacks,e.callbacks);t.head=Object.assign({},n.head,e.head);return t}function s(){const e=document.createElement("div");e.hidden=true;document.body.insertAdjacentElement("afterend",e);return e}function c(e){let t=Array.from(e.querySelectorAll("[id]"));if(e.id){t.push(e)}return t}function u(n,e,r,t){for(const o of t){if(e.has(o.id)){let t=o;while(t){let e=n.get(t);if(e==null){e=new Set;n.set(t,e)}e.add(o.id);if(t===r)break;t=t.parentElement}}}}function d(e,t){const n=c(e);const r=c(t);const o=f(n,r);let i=new Map;u(i,o,e,n);const l=t.__idiomorphRoot||t;u(i,o,l,r);return{persistentIds:o,idMap:i}}function f(e,t){let n=new Set;let r=new Map;for(const{id:i,tagName:l}of e){if(r.has(i)){n.add(i)}else{r.set(i,l)}}let o=new Set;for(const{id:i,tagName:l}of t){if(o.has(i)){n.add(i)}else if(r.get(i)===l){o.add(i)}}for(const i of n){o.delete(i)}return o}return e}();const{normalizeElement:d,normalizeParent:f}=function(){const o=new WeakSet;function e(e){if(e instanceof Document){return e.documentElement}else{return e}}function r(e){if(e==null){return document.createElement("div")}else if(typeof e==="string"){return r(l(e))}else if(o.has(e)){return e}else if(e instanceof Node){if(e.parentNode){return new i(e)}else{const t=document.createElement("div");t.append(e);return t}}else{const t=document.createElement("div");for(const n of[...e]){t.append(n)}return t}}class i{constructor(e){this.originalNode=e;this.realParentNode=e.parentNode;this.previousSibling=e.previousSibling;this.nextSibling=e.nextSibling}get childNodes(){const e=[];let t=this.previousSibling?this.previousSibling.nextSibling:this.realParentNode.firstChild;while(t&&t!=this.nextSibling){e.push(t);t=t.nextSibling}return e}querySelectorAll(r){return this.childNodes.reduce((t,e)=>{if(e instanceof Element){if(e.matches(r))t.push(e);const n=e.querySelectorAll(r);for(let e=0;e<n.length;e++){t.push(n[e])}}return t},[])}insertBefore(e,t){return this.realParentNode.insertBefore(e,t)}moveBefore(e,t){return this.realParentNode.moveBefore(e,t)}get __idiomorphRoot(){return this.originalNode}}function l(n){let r=new DOMParser;let e=n.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim,"");if(e.match(/<\/html>/)||e.match(/<\/head>/)||e.match(/<\/body>/)){let t=r.parseFromString(n,"text/html");if(e.match(/<\/html>/)){o.add(t);return t}else{let e=t.firstChild;if(e){o.add(e)}return e}}else{let e=r.parseFromString("<body><template>"+n+"</template></body>","text/html");let t=e.body.querySelector("template").content;o.add(t);return t}}return{normalizeElement:e,normalizeParent:r}}();return{morph:t,defaults:n}}();