h3.min.js
1 2 3 4 5 6 7 8 9 |
/** * H3 v0.11.0 "Keen Klingon" * Copyright 2020 Fabio Cevasco <h3rald@h3rald.com> * * @license MIT * For the full license, see: https://github.com/h3rald/h3/blob/master/LICENSE */ const checkProperties=(e,t)=>{if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const r in e){if(!(r in t))return!1;if(!equal(e[r],t[r]))return!1}return!0},equal=(e,t)=>{if(null===e&&null===t||void 0===e&&void 0===t)return!0;if(void 0===e&&void 0!==t||void 0!==e&&void 0===t||null===e&&null!==t||null!==e&&null===t)return!1;if(e.constructor!==t.constructor)return!1;if("function"==typeof e&&e.toString()!==t.toString())return!1;if([String,Number,Boolean].includes(e.constructor))return e===t;if(e.constructor===Array){if(e.length!==t.length)return!1;for(let r=0;r<e.length;r++)if(!equal(e[r],t[r]))return!1;return!0}return checkProperties(e,t)},selectorRegex=/^([a-z][a-z0-9:_=-]*)?(#[a-z0-9:_=-]+)?(\.[^ ]+)*$/i,[PATCH,INSERT,DELETE]=[-1,-2,-3];let $onrenderCallbacks=[];class VNode{constructor(...e){if(this.type=void 0,this.props={},this.data={},this.id=void 0,this.$html=void 0,this.$onrender=void 0,this.style=void 0,this.value=void 0,this.children=[],this.classList=[],this.eventListeners={},0===e.length)throw new Error("[VNode] No arguments passed to VNode constructor.");if(1===e.length){let t=e[0];if("string"==typeof t)this.processSelector(t);else{if("function"!=typeof t&&("object"!=typeof t||null===t))throw new Error("[VNode] Invalid first argument passed to VNode constructor.");"#text"===t.type?(this.type="#text",this.value=t.value):this.from(this.processVNodeObject(t))}}else if(2===e.length){let[t,r]=e;if("string"!=typeof t)throw new Error("[VNode] Invalid first argument passed to VNode constructor.");if(this.processSelector(t),"string"==typeof r)return void(this.children=[new VNode({type:"#text",value:r})]);if("function"!=typeof r&&("object"!=typeof r||null===r))throw new Error("[VNode] The second argument of a VNode constructor must be an object, an array or a string.");Array.isArray(r)||r instanceof Function||r instanceof VNode?this.processChildren(r):this.processProperties(r)}else{let[t,r,s]=e;if(e.length>3&&(s=e.slice(2)),s=Array.isArray(s)?s:[s],"string"!=typeof t)throw new Error("[VNode] Invalid first argument passed to VNode constructor.");if(this.processSelector(t),r instanceof Function||r instanceof VNode||"string"==typeof r)s=[r].concat(s);else{if("object"!=typeof r||null===r)throw new Error("[VNode] Invalid second argument passed to VNode constructor.");this.processProperties(r)}this.processChildren(s)}}from(e){this.value=e.value,this.type=e.type,this.id=e.id,this.$html=e.$html,this.$onrender=e.$onrender,this.style=e.style,this.data=e.data,this.value=e.value,this.eventListeners=e.eventListeners,this.children=e.children,this.props=e.props,this.classList=e.classList}equal(e,t){return equal(e,void 0===t?this:t)}processProperties(e){this.id=this.id||e.id,this.$html=e.$html,this.$onrender=e.$onrender,this.style=e.style,this.value=e.value,this.data=e.data||{},this.classList=e.classList&&e.classList.length>0?e.classList:this.classList,this.props=e,Object.keys(e).filter(t=>t.startsWith("on")&&e[t]).forEach(t=>{if("function"!=typeof e[t])throw new Error(`[VNode] Event handler specified for ${t} event is not a function.`);this.eventListeners[t.slice(2)]=e[t],delete this.props[t]}),delete this.props.value,delete this.props.$html,delete this.props.$onrender,delete this.props.id,delete this.props.data,delete this.props.style,delete this.props.classList}processSelector(e){if(!e.match(selectorRegex)||0===e.length)throw new Error("[VNode] Invalid selector: "+e);const[,t,r,s]=e.match(selectorRegex);this.type=t,r&&(this.id=r.slice(1)),this.classList=s&&s.split(".").slice(1)||[]}processVNodeObject(e){if(e instanceof VNode)return e;if(e instanceof Function){let t=e();if("string"==typeof t&&(t=new VNode({type:"#text",value:t})),!(t instanceof VNode))throw new Error("[VNode] Function argument does not return a VNode");return t}throw new Error("[VNode] Invalid first argument provided to VNode constructor.")}processChildren(e){const t=Array.isArray(e)?e:[e];this.children=t.map(e=>{if("string"==typeof e)return new VNode({type:"#text",value:e});if("function"==typeof e||"object"==typeof e&&null!==e)return this.processVNodeObject(e);if(e)throw new Error("[VNode] Specified child is not a VNode: "+e)}).filter(e=>e)}render(){if("#text"===this.type)return document.createTextNode(this.value);const e=document.createElement(this.type);return this.id&&(e.id=this.id),Object.keys(this.props).forEach(t=>{"boolean"==typeof this.props[t]&&(this.props[t]?e.setAttribute(t,""):e.removeAttribute(t)),["string","number"].includes(typeof this.props[t])&&e.setAttribute(t,this.props[t]),e[t]=this.props[t]}),Object.keys(this.eventListeners).forEach(t=>{e.addEventListener(t,this.eventListeners[t])}),this.value&&(["textarea","input"].includes(this.type)?e.value=this.value:e.setAttribute("value",this.value)),this.style&&(e.style.cssText=this.style),this.classList.forEach(t=>{e.classList.add(t)}),Object.keys(this.data).forEach(t=>{e.dataset[t]=this.data[t]}),this.children.forEach(t=>{const r=t.render();e.appendChild(r),t.$onrender&&$onrenderCallbacks.push(()=>t.$onrender(r))}),this.$html&&(e.innerHTML=this.$html),e}redraw(e){let{node:t,vnode:r}=e;const s=r,o=this;if(o.constructor!==s.constructor||o.type!==s.type||o.type===s.type&&"#text"===o.type&&o!==s){const e=s.render();return t.parentNode.replaceChild(e,t),s.$onrender&&s.$onrender(e),void o.from(s)}o.id!==s.id&&(t.id=s.id||"",o.id=s.id),o.value!==s.value&&(o.value=s.value,["textarea","input"].includes(o.type)?t.value=s.value||"":t.setAttribute("value",s.value||"")),equal(o.classList,s.classList)||(o.classList.forEach(e=>{s.classList.includes(e)||t.classList.remove(e)}),s.classList.forEach(e=>{o.classList.includes(e)||t.classList.add(e)}),o.classList=s.classList),o.style!==s.style&&(t.style.cssText=s.style||"",o.style=s.style),equal(o.data,s.data)||(Object.keys(o.data).forEach(e=>{s.data[e]?s.data[e]!==o.data[e]&&(t.dataset[e]=s.data[e]):delete t.dataset[e]}),Object.keys(s.data).forEach(e=>{o.data[e]||(t.dataset[e]=s.data[e])}),o.data=s.data),equal(o.props,s.props)||(Object.keys(o.props).forEach(e=>{t[e]=s.props[e],"boolean"==typeof s.props[e]?(o.props[e]=s.props[e],s.props[e]?t.setAttribute(e,""):t.removeAttribute(e)):[null,void 0].includes(s.props[e])?(delete o.props[e],t.removeAttribute(e)):s.props[e]!==o.props[e]&&(o.props[e]=s.props[e],["string","number"].includes(typeof s.props[e])&&t.setAttribute(e,s.props[e]))}),Object.keys(s.props).forEach(e=>{!o.props[e]&&s.props[e]&&(o.props[e]=s.props[e],["string","number"].includes(typeof s.props[e])&&t.setAttribute(e,s.props[e]))})),equal(o.eventListeners,s.eventListeners)||(Object.keys(o.eventListeners).forEach(e=>{s.eventListeners[e]?equal(s.eventListeners[e],o.eventListeners[e])||(t.removeEventListener(e,o.eventListeners[e]),t.addEventListener(e,s.eventListeners[e])):t.removeEventListener(e,o.eventListeners[e])}),Object.keys(s.eventListeners).forEach(e=>{o.eventListeners[e]||t.addEventListener(e,s.eventListeners[e])}),o.eventListeners=s.eventListeners);let i=mapChildren(o,s),n=[...Array(s.children.length).keys()];for(;!equal(i,n);){let e=-1;e:for(const r of i)if(e++,r!==e)switch(r){case PATCH:o.children[e].redraw({node:t.childNodes[e],vnode:s.children[e]});break e;case INSERT:{o.children.splice(e,0,s.children[e]);const r=s.children[e].render();t.insertBefore(r,t.childNodes[e]),s.children[e].$onrender&&s.children[e].$onrender(r);break e}case DELETE:o.children.splice(e,1),t.removeChild(t.childNodes[e]);break e;default:{const s=o.children.splice(r,1)[0];o.children.splice(e,0,s);const i=t.removeChild(t.childNodes[r]);t.insertBefore(i,t.childNodes[e]);break e}}i=mapChildren(o,s),n=[...Array(s.children.length).keys()]}equal(o.$onrender,s.$onrender)||(o.$onrender=s.$onrender),o.$html!==s.$html&&(t.innerHTML=s.$html,o.$html=s.$html,o.$onrender&&o.$onrender(t))}}const mapChildren=(e,t)=>{const r=t.children,s=e.children;let o=[];for(let e=0;e<r.length;e++){let t=PATCH;for(let i=0;i<s.length;i++)if(equal(r[e],s[i])&&!o.includes(i)){t=i;break}t<0&&r.length>=s.length&&o.length>=s.length&&(t=INSERT),o.push(t)}const i=o.filter(e=>e>=0);return s.length>r.length?[...Array(s.length-r.length).keys()].forEach(()=>o.push(DELETE)):i.length===s.length&&(o=o.map(e=>e<0?INSERT:e)),o};class Store{constructor(){this.events={},this.state={}}dispatch(e,t){if("$log"!==e&&this.dispatch("$log",{event:e,data:t}),this.events[e]){this.events[e].forEach(e=>{this.state={...this.state,...e(this.state,t)}})}}on(e,t){return(this.events[e]||(this.events[e]=[])).push(t),()=>{this.events[e]=this.events[e].filter(e=>e!==t)}}}class Route{constructor({path:e,def:t,query:r,parts:s}){if(this.path=e,this.def=t,this.query=r,this.parts=s,this.params={},this.query){this.query.split("&").forEach(e=>{const[t,r]=e.split("=");this.params[decodeURIComponent(t)]=decodeURIComponent(r)})}}}class Router{constructor({element:e,routes:t,store:r,location:s}){if(this.element=e,this.redraw=null,this.store=r,this.location=s||window.location,!t||0===Object.keys(t).length)throw new Error("[Router] No routes defined.");Object.keys(t);this.routes=t}setRedraw(e,t){this.redraw=()=>{e.redraw({node:this.element.childNodes[0],vnode:this.routes[this.route.def](t)}),this.store.dispatch("$redraw")}}async start(){const e=async e=>{const t=this.route,r=e&&e.newURL&&e.newURL.match(/(#.+)$/)&&e.newURL.match(/(#.+)$/)[1]||this.location.hash,s=r.replace(/\?.+$/,"").slice(1),o=r.match(/\?(.+)$/),i=o&&o[1]?o[1]:"",n=s.split("/").slice(1);let a={};for(let e of Object.keys(this.routes)){let t=e.split("/").slice(1),r=!0,o=0;for(a={};r&&t[o];){const e=t[o],s=n[o];e.startsWith(":")&&s?a[e.slice(1)]=s:r=e===s,o++}if(r){this.route=new Route({query:i,path:s,def:e,parts:a});break}}if(!this.route)throw new Error(`[Router] No route matches '${r}'`);let l={};if(t){const e=this.routes[t.def];l=e.teardown&&await e.teardown(e.state)||l}const h=this.routes[this.route.def];if(h.state=l,h.setup&&!1===await h.setup(h.state))return;for(redrawing=!0,this.store.dispatch("$navigation",this.route);this.element.firstChild;)this.element.removeChild(this.element.firstChild);const c=h(h.state),d=c.render();this.element.appendChild(d),this.setRedraw(c,h.state),redrawing=!1,c.$onrender&&c.$onrender(d),$onrenderCallbacks.forEach(e=>e()),$onrenderCallbacks=[],window.scrollTo(0,0),this.store.dispatch("$redraw")};window.addEventListener("hashchange",e),await e()}navigateTo(e,t){let r=Object.keys(t||{}).map(e=>`${encodeURIComponent(e)}=${encodeURIComponent(t[e])}`).join("&");r=r?"?"+r:"",this.location.hash=`#${e}${r}`}}export const h=(...e)=>new VNode(...e);export const h3={};let store=null,router=null,redrawing=!1;h3.init=e=>{let{element:t,routes:r,modules:s,preStart:o,postStart:i,location:n}=e;if(!r){if("function"!=typeof e)throw new Error("[h3.init] The specified argument is not a valid configuration object or component function");r={"/":e}}if(t=t||document.body,!(t&&t instanceof Element))throw new Error("[h3.init] Invalid element specified.");return store=new Store,(s||[]).forEach(e=>{e(store)}),store.dispatch("$init"),router=new Router({element:t,routes:r,store:store,location:n}),Promise.resolve(o&&o()).then(()=>router.start()).then(()=>i&&i())},h3.navigateTo=(e,t)=>{if(!router)throw new Error("[h3.navigateTo] No application initialized, unable to navigate.");return router.navigateTo(e,t)},Object.defineProperty(h3,"route",{get:()=>{if(!router)throw new Error("[h3.route] No application initialized, unable to retrieve current route.");return router.route}}),Object.defineProperty(h3,"state",{get:()=>{if(!store)throw new Error("[h3.state] No application initialized, unable to retrieve current state.");return store.state}}),h3.on=(e,t)=>{if(!store)throw new Error("[h3.on] No application initialized, unable to listen to events.");return store.on(e,t)},h3.dispatch=(e,t)=>{if(!store)throw new Error("[h3.dispatch] No application initialized, unable to dispatch events.");return store.dispatch(e,t)},h3.redraw=e=>{if(!router||!router.redraw)throw new Error("[h3.redraw] No application initialized, unable to redraw.");redrawing||(redrawing=!0,router.redraw(),redrawing=e||!1)},h3.screen=({setup:e,display:t,teardown:r})=>{if(!t||"function"!=typeof t)throw new Error("[h3.screen] No display property specified.");if(e&&"function"!=typeof e)throw new Error("[h3.screen] setup property is not a function.");if(r&&"function"!=typeof r)throw new Error("[h3.screen] teardown property is not a function.");const s=t;return e&&(s.setup=e),r&&(s.teardown=r),s};export default h3; //# sourceMappingURL=h3.js.map |