├── .gitignore ├── .npmignore ├── .npmrc ├── LICENSE ├── README.md ├── cjs ├── index.js ├── package.json └── x.js ├── es.js ├── esm ├── index.js └── x.js ├── index.js ├── min.js ├── package.json ├── rollup ├── babel.config.js └── es.config.js ├── test ├── index.html ├── index.js └── package.json └── ube.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .nyc_output 3 | coverage/ 4 | node_modules/ 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .nyc_output 3 | .eslintrc.json 4 | .travis.yml 5 | coverage/ 6 | node_modules/ 7 | rollup/ 8 | test/ 9 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2021, Andrea Giammarchi, @WebReflection 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # µbe 2 | 3 | ![lego board with µbe logo](./ube.jpg) 4 | 5 | **Original Social Media Photo by [JOSE LARRAZOLO](https://unsplash.com/@joseadd) on [Unsplash](https://unsplash.com/)** 6 | 7 | A [builtin-elements](https://github.com/WebReflection/builtin-elements#readme) based [µce](https://github.com/WebReflection/uce#readme) alternative: 8 | 9 | * based on [µhtml](https://github.com/WebReflection/uhtml#readme) engine 10 | * requires zero polyfills and it doesn't need a registry 11 | * works **[SSR](https://github.com/WebReflection/ube-ssr)** too (*islands out of the box*) 12 | * roughly *4KB* once minified and gzipped 13 | 14 | ```js 15 | // Some Builtin definition example 16 | import {HTML, render, html} from 'ube'; 17 | 18 | export default class Div extends HTML.Div { 19 | upgradedCallback() { 20 | const {hello} = this.dataset; 21 | render(this, html`Hello ${hello} 👋`); 22 | // this.textContent and this.innerHTML work too 23 | } 24 | } 25 | 26 | 27 | // Some rendering 28 | import {render, html} from 'ube'; 29 | import Div from './div-component.js'; 30 | 31 | render(document.body, html`<${Div} data-hello="µbe" />`); 32 | ``` 33 | 34 | ## Companions 35 | 36 | * [µhtml-intents](https://github.com/WebReflection/uhtml-intents#readme) 37 | * [µhooks](https://github.com/WebReflection/uhooks#readme) 38 | 39 | **[Live Demo](https://codepen.io/WebReflection/pen/gOmaXrZ?editors=0010)** 40 | 41 | ```js 42 | import {HTML, render, html} from 'ube'; 43 | import {hooked, useState} from 'uhooks'; 44 | 45 | class Div extends HTML.Div { 46 | upgradedCallback() { 47 | this.render = hooked(this.render); 48 | this.render(); 49 | } 50 | render() { 51 | const [count, update] = useState(0); 52 | render(this, html` 53 | 56 | `); 57 | } 58 | } 59 | 60 | render(document.body, html`Click test <${Div} />`); 61 | ``` 62 | 63 | ## Previous Work / Similar Libraries 64 | 65 | * [kaboobie](https://github.com/WebReflection/kaboobie/#readme) is the most similar project, but the elements lifecycle is different, as these are replaced once discovered, while *builtin-elements* are real builtin elements with Custom Elements super power, hence more portable, and *SSR* compatible 66 | * [µland](https://github.com/WebReflection/uland#readme), at the core of *kaboobie*, is the one that inspired me the most 67 | * [wicked-elements](https://github.com/WebReflection/wicked-elements#readme) and [hooked-elements](https://github.com/WebReflection/hooked-elements#readme) also work in a similar way, and each element can have multiple definitions, but it requires a registry 68 | 69 | -------------------------------------------------------------------------------- /cjs/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {HTML, SVG, upgrade, downgrade, observer} = require('builtin-elements'); 3 | const {asParams, asStatic} = require('static-params/strict'); 4 | const {render: urender, html: uhtml, svg: usvg} = require('uhtml'); 5 | 6 | const UBE = 'data-ube'; 7 | 8 | const cache = new WeakMap; 9 | const ube = []; 10 | 11 | const indexOf = Class => { 12 | const i = ube.indexOf(Class); 13 | return i < 0 ? (ube.push(Class) - 1) : i; 14 | }; 15 | 16 | const augment = tag => { 17 | function ube() { return reshaped.apply(tag, arguments); } 18 | ube.for = (ref, id) => function () { 19 | return reshaped.apply(tag.for(ref, id), arguments); 20 | }; 21 | return ube; 22 | }; 23 | 24 | function render(where) { 25 | const result = urender.apply(this, arguments); 26 | const be = where.querySelectorAll(`[${UBE}]`); 27 | for (let i = 0, {length} = be; i < length; i++) { 28 | const Class = ube[be[i].getAttribute(UBE)]; 29 | be[i].removeAttribute(UBE); 30 | upgrade(be[i], Class); 31 | } 32 | return result; 33 | } 34 | exports.render = render 35 | 36 | const html = augment(uhtml); 37 | exports.html = html; 38 | const svg = augment(usvg); 39 | exports.svg = svg; 40 | exports.HTML = HTML; 41 | exports.SVG = SVG; 42 | exports.upgrade = upgrade; 43 | exports.downgrade = downgrade; 44 | exports.observer = observer; 45 | 46 | function reshaped(...args) { 47 | const [template] = args; 48 | let known = cache.get(template); 49 | if (known) { 50 | for (let i = 0, {length} = known; i < length; i++) 51 | args[known[i].i] = known[i].v; 52 | } 53 | else { 54 | cache.set(template, known = []); 55 | for (let i = 1, {length} = args; i < length; i++) { 56 | let current = args[i]; 57 | if (typeof current === 'function' && 'tagName' in current) { 58 | const {tagName} = current; 59 | const prev = template[i - 1]; 60 | switch (prev[prev.length - 1]) { 61 | case '<': 62 | current = asStatic(`${tagName} ${UBE}=${indexOf(current)}`); 63 | break; 64 | case '/': 65 | current = asStatic(tagName); 66 | break; 67 | } 68 | known.push({i, v: current}); 69 | args[i] = current; 70 | } 71 | } 72 | } 73 | return this.apply(null, asParams.apply(null, args)); 74 | } 75 | -------------------------------------------------------------------------------- /cjs/package.json: -------------------------------------------------------------------------------- 1 | {"type":"commonjs"} -------------------------------------------------------------------------------- /cjs/x.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {createPragma} = require('jsx2tag'); 3 | const {html} = require('./index.js'); 4 | 5 | const createElement = createPragma(html); 6 | self.React = { 7 | createElement, 8 | Fragment: createElement 9 | }; 10 | 11 | (m => Object.keys(m).map(k => k !== 'default' && (exports[k] = m[k]))) 12 | (require('jsx2tag')); 13 | (m => Object.keys(m).map(k => k !== 'default' && (exports[k] = m[k]))) 14 | (require('./index.js')); 15 | -------------------------------------------------------------------------------- /es.js: -------------------------------------------------------------------------------- 1 | self.ube=function(e){"use strict";const t=!0,n=!1,r="querySelectorAll";function l(e){this.observe(e,{subtree:t,childList:t})}const o="Element",a="attributeChangedCallback",s="connectedCallback",i="disconnectedCallback",c="upgradedCallback",u="downgradedCallback",d={Anchor:"A",DList:"DL",Directory:"Dir",Heading:["H6","H5","H4","H3","H2","H1"],Image:"Img",OList:"OL",Paragraph:"P",TableCaption:"Caption",TableCell:["TH","TD"],TableRow:"TR",UList:"UL",[o]:["Article","Aside","Footer","Header","Main","Nav","Section",o]},{getOwnPropertyNames:p,setPrototypeOf:f}=Object,h=new WeakMap,g=new WeakMap,b=new Set,m=(e,t)=>document.createElementNS(t?"http://www.w3.org/2000/svg":"",e),w=new MutationObserver((e=>{for(let t=0;t{b.has(e.constructor)||(h.delete(e),g.delete(e),u in e&&e.downgradedCallback(),f(e,m(e.tagName,"ownerSVGElement"in e).constructor.prototype))},y=(e,t)=>{if(!(e instanceof t)){v(e),f(e,t.prototype),c in e&&e.upgradedCallback();const{observedAttributes:n}=t;if(n&&a in e){h.set(e,0),w.observe(e,{attributeFilter:n,attributeOldValue:!0,attributes:!0});for(let t=0;t{if(/^(HTML|SVG)/.test(e)){const{$1:t}=RegExp,n="SVG"==t,r=e.slice(t.length,-7)||o,l=n?C:k,a=window[e];b.add(a),[].concat(d[r]||r).forEach((e=>{const t=e.toLowerCase();function o(){return y(m(t,n),this.constructor)}o.tagName=t,o.prototype=a.prototype,l[r]=l[e]=o}))}}));const N=((e,o,a)=>{const s=(n,l,o,a,i)=>{for(let c=0,{length:u}=n;c{for(let r=new Set,l=new Set,o=0,{length:a}=e;o{if(g.has(e)){const n=t?s:i;n in e&&e[n]()}})),$=new WeakMap;function A(e){let t=$.get(e);return t||$.set(e,t=x.apply(null,arguments)),[t.t].concat(t.v.map((e=>arguments[e])))}function x(e){const t=[e[0]],n=[];for(let r=0,l=0,o=1,{length:a}=arguments;onew String(e);var L=e=>({get:t=>e.get(t),set:(t,n)=>(e.set(t,n),n)});const T=/([^\s\\>"'=]+)\s*=\s*(['"]?)$/,E=/^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i,M=/<[a-z][^>]+$/i,O=/>[^<>]*$/,H=/<([a-z]+[a-z0-9:._-]*)([^>]*?)(\/>)/gi,D=/\s+$/,W=(e,t)=>0E.test(t)?e:`<${t}${n.replace(D,"")}>`;const{isArray:V}=Array,{indexOf:j,slice:P}=[],B=(e,t)=>111===e.nodeType?1/t<0?t?(({firstChild:e,lastChild:t})=>{const n=document.createRange();return n.setStartAfter(e),n.setEndAfter(t),n.deleteContents(),e})(e):e.lastChild:t?e.valueOf():e.firstChild:e;const G=(e,t)=>{let n,r=t.slice(2);return!(t in e)&&t.toLowerCase()in e&&(r=r.toLowerCase()),t=>{const l=V(t)?t:[t,!1];n!==l[0]&&(n&&e.removeEventListener(r,n,l[1]),(n=l[0])&&e.addEventListener(r,n,l[1]))}}; 2 | /*! (c) Andrea Giammarchi - ISC */ 3 | self.q=function(e){var t="fragment",n="template",r="content"in o(n)?function(e){var t=o(n);return t.innerHTML=e,t.content}:function(e){var r=o(t),a=o(n),s=null;if(/^[^\S]*?<(col(?:group)?|t(?:head|body|foot|r|d|h))/i.test(e)){var i=RegExp.$1;a.innerHTML=""+e+"
",s=a.querySelectorAll(i)}else a.innerHTML=e,s=a.childNodes;return l(r,s),r};return function(e,t){return("svg"===t?a:r)(e)};function l(e,t){for(var n=t.length;n--;)e.appendChild(t[0])}function o(n){return n===t?e.createDocumentFragment():e.createElementNS("http://www.w3.org/1999/xhtml",n)}function a(e){var n=o(t),r=o("div");return r.innerHTML=''+e+"",l(n,r.firstChild.childNodes),n}}(document);const z=({childNodes:e},t)=>e[t],F=e=>{const t=[];let{parentNode:n}=e;for(;n;)t.push(j.call(n.childNodes,e)),n=(e=n).parentNode;return t},{createTreeWalker:_,importNode:I}=document,U=1!=I.length,J=U?(e,t,n)=>I.call(document,q(e,t,n),!0):q,K=U?e=>_.call(document,e,129,null,!1):e=>_.call(document,e,129),Q=(e,t,n)=>((e,t,n,r,l)=>{const o=n.length;let a=t.length,s=o,i=0,c=0,u=null;for(;il-c){const o=r(t[i],0);for(;c{switch(t[0]){case"?":return((e,t,n)=>r=>{n!==!!r&&((n=!!r)?e.setAttribute(t,""):e.removeAttribute(t))})(e,t.slice(1),!1);case".":return((e,t)=>"dataset"===t?(({dataset:e})=>t=>{for(const n in t){const r=t[n];null==r?delete e[n]:e[n]=r}})(e):n=>{e[t]=n})(e,t.slice(1));case"@":return G(e,"on"+t.slice(1));case"o":if("n"===t[1])return G(e,t)}switch(t){case"ref":return(e=>{let t;return n=>{t!==n&&(t=n,"function"==typeof n?n(e):n.current=e)}})(e);case"aria":return(e=>t=>{for(const n in t){const r="role"===n?n:`aria-${n}`,l=t[n];null==l?e.removeAttribute(r):e.setAttribute(r,l)}})(e)}return((e,t)=>{let n,r=!0;const l=document.createAttributeNS(null,t);return t=>{n!==t&&(n=t,null==n?r||(e.removeAttributeNode(l),r=!0):(l.value=t,r&&(e.setAttributeNodeNS(l),r=!1)))}})(e,t)};function Y(e){const{type:t,path:n}=e,r=n.reduceRight(z,this);return"node"===t?(e=>{let t,n,r=[];const l=o=>{switch(typeof o){case"string":case"number":case"boolean":t!==o&&(t=o,n||(n=document.createTextNode("")),n.data=o,r=Q(e,r,[n]));break;case"object":case"undefined":if(null==o){t!=o&&(t=o,r=Q(e,r,[]));break}if(V(o)){t=o,0===o.length?r=Q(e,r,[]):"object"==typeof o[0]?r=Q(e,r,o):l(String(o));break}t!==o&&"ELEMENT_NODE"in o&&(t=o,r=Q(e,r,11===o.nodeType?P.call(o.childNodes):[o]));break;case"function":l(o(e))}};return l})(r):"attr"===t?X(r,e.name):(e=>{let t;return n=>{t!=n&&(t=n,e.textContent=null==n?"":n)}})(r)}const Z="isµ",ee=L(new WeakMap),te=/^(?:plaintext|script|style|textarea|title|xmp)$/i,ne=(e,t)=>{const n=((e,t,n)=>{const r=[],{length:l}=e;for(let n=1;n`${t}${n-1}=${l||'"'}${r}${l?"":'"'}`)):`${l}\x3c!--${t}${n-1}--\x3e`)}r.push(e[l-1]);const o=r.join("").trim();return n?o:o.replace(H,R)})(t,Z,"svg"===e),r=J(n,e),l=K(r),o=[],a=t.length-1;let s=0,i=`isµ${s}`;for(;s{const{content:n,nodes:r}=ee.get(t)||ee.set(t,ne(e,t)),l=I.call(document,n,!0);return{content:l,updates:r.map(Y,l)}},le=(e,{type:t,template:n,values:r})=>{const{length:l}=r;oe(e,r,l);let{entry:o}=e;o&&o.template===n&&o.type===t||(e.entry=o=((e,t)=>{const{content:n,updates:r}=re(e,t);return{type:e,template:t,content:n,updates:r,wire:null}})(t,n));const{content:a,updates:s,wire:i}=o;for(let e=0;e{const{childNodes:t}=e,{length:n}=t;if(n<2)return n?t[0]:e;const r=P.call(t,0);return{ELEMENT_NODE:1,nodeType:111,firstChild:r[0],lastChild:r[n-1],valueOf(){if(t.length!==n){let t=0;for(;t{for(let r=0;r{const t=L(new WeakMap);return ie(((t,...n)=>new ae(e,t,n)),{for:{value(n,r){const l=t.get(n)||t.set(n,se(null));return l[r]||(l[r]=(t=>(n,...r)=>le(t,{type:e,template:n,values:r}))({stack:[],entry:null,wire:null}))}},node:{value:(t,...n)=>le({stack:[],entry:null,wire:null},{type:e,template:t,values:n}).valueOf()}})},ue=L(new WeakMap),de=(e,t)=>{const n="function"==typeof t?t():t,r=ue.get(e)||ue.set(e,{stack:[],entry:null,wire:null}),l=n instanceof ae?le(r,n):n;return l!==r.wire&&(r.wire=l,e.textContent="",e.appendChild(l.valueOf())),e},pe=ce("html"),fe=ce("svg"),he="data-ube",ge=new WeakMap,be=[],me=e=>{const t=be.indexOf(e);return t<0?be.push(e)-1:t},we=e=>{function t(){return Ce.apply(e,arguments)}return t.for=(t,n)=>function(){return Ce.apply(e.for(t,n),arguments)},t};const ve=we(pe),ye=we(fe);function Ce(...e){const[t]=e;let n=ge.get(t);if(n)for(let t=0,{length:r}=n;t { 11 | const i = ube.indexOf(Class); 12 | return i < 0 ? (ube.push(Class) - 1) : i; 13 | }; 14 | 15 | const augment = tag => { 16 | function ube() { return reshaped.apply(tag, arguments); } 17 | ube.for = (ref, id) => function () { 18 | return reshaped.apply(tag.for(ref, id), arguments); 19 | }; 20 | return ube; 21 | }; 22 | 23 | export function render(where) { 24 | const result = urender.apply(this, arguments); 25 | const be = where.querySelectorAll(`[${UBE}]`); 26 | for (let i = 0, {length} = be; i < length; i++) { 27 | const Class = ube[be[i].getAttribute(UBE)]; 28 | be[i].removeAttribute(UBE); 29 | upgrade(be[i], Class); 30 | } 31 | return result; 32 | } 33 | 34 | export const html = augment(uhtml); 35 | export const svg = augment(usvg); 36 | export {HTML, SVG, upgrade, downgrade, observer}; 37 | 38 | function reshaped(...args) { 39 | const [template] = args; 40 | let known = cache.get(template); 41 | if (known) { 42 | for (let i = 0, {length} = known; i < length; i++) 43 | args[known[i].i] = known[i].v; 44 | } 45 | else { 46 | cache.set(template, known = []); 47 | for (let i = 1, {length} = args; i < length; i++) { 48 | let current = args[i]; 49 | if (typeof current === 'function' && 'tagName' in current) { 50 | const {tagName} = current; 51 | const prev = template[i - 1]; 52 | switch (prev[prev.length - 1]) { 53 | case '<': 54 | current = asStatic(`${tagName} ${UBE}=${indexOf(current)}`); 55 | break; 56 | case '/': 57 | current = asStatic(tagName); 58 | break; 59 | } 60 | known.push({i, v: current}); 61 | args[i] = current; 62 | } 63 | } 64 | } 65 | return this.apply(null, asParams.apply(null, args)); 66 | } 67 | -------------------------------------------------------------------------------- /esm/x.js: -------------------------------------------------------------------------------- 1 | import {createPragma} from 'jsx2tag'; 2 | import {html} from './index.js'; 3 | 4 | const createElement = createPragma(html); 5 | self.React = { 6 | createElement, 7 | Fragment: createElement 8 | }; 9 | 10 | export * from 'jsx2tag'; 11 | export * from './index.js'; 12 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | self.ube = (function (exports) { 2 | 'use strict'; 3 | 4 | /*! (c) Andrea Giammarchi - ISC */ 5 | if (!('isConnected' in Node.prototype)) Object.defineProperty(Node.prototype, 'isConnected', { 6 | configurable: true, 7 | get: function get() { 8 | return !(this.ownerDocument.compareDocumentPosition(this) & this.DOCUMENT_POSITION_DISCONNECTED); 9 | } 10 | }); 11 | 12 | var TRUE = true, 13 | FALSE = false; 14 | var QSA = 'querySelectorAll'; 15 | 16 | function add(node) { 17 | this.observe(node, { 18 | subtree: TRUE, 19 | childList: TRUE 20 | }); 21 | } 22 | /** 23 | * Start observing a generic document or root element. 24 | * @param {Function} callback triggered per each dis/connected node 25 | * @param {Element?} root by default, the global document to observe 26 | * @param {Function?} MO by default, the global MutationObserver 27 | * @returns {MutationObserver} 28 | */ 29 | 30 | 31 | var notify = function notify(callback, root, MO) { 32 | var loop = function loop(nodes, added, removed, connected, pass) { 33 | for (var i = 0, length = nodes.length; i < length; i++) { 34 | var node = nodes[i]; 35 | 36 | if (pass || QSA in node) { 37 | if (connected) { 38 | if (!added.has(node)) { 39 | added.add(node); 40 | removed["delete"](node); 41 | callback(node, connected); 42 | } 43 | } else if (!removed.has(node)) { 44 | removed.add(node); 45 | added["delete"](node); 46 | callback(node, connected); 47 | } 48 | 49 | if (!pass) loop(node[QSA]('*'), added, removed, connected, TRUE); 50 | } 51 | } 52 | }; 53 | 54 | var observer = new (MO || MutationObserver)(function (records) { 55 | for (var added = new Set(), removed = new Set(), i = 0, length = records.length; i < length; i++) { 56 | var _records$i = records[i], 57 | addedNodes = _records$i.addedNodes, 58 | removedNodes = _records$i.removedNodes; 59 | loop(removedNodes, added, removed, FALSE, FALSE); 60 | loop(addedNodes, added, removed, TRUE, FALSE); 61 | } 62 | }); 63 | observer.add = add; 64 | observer.add(root || document); 65 | return observer; 66 | }; 67 | 68 | 69 | 70 | function _defineProperty(obj, key, value) { 71 | if (key in obj) { 72 | Object.defineProperty(obj, key, { 73 | value: value, 74 | enumerable: true, 75 | configurable: true, 76 | writable: true 77 | }); 78 | } else { 79 | obj[key] = value; 80 | } 81 | 82 | return obj; 83 | } 84 | 85 | var CALLBACK = 'Callback'; 86 | var ELEMENT = 'Element'; 87 | var CONSTRUCTOR = 'constructor'; 88 | var PROTOTYPE = 'prototype'; 89 | var ATTRIBUTE_CHANGED_CALLBACK = 'attributeChanged' + CALLBACK; 90 | var CONNECTED_CALLBACK = 'connected' + CALLBACK; 91 | var DISCONNECTED_CALLBACK = 'dis' + CONNECTED_CALLBACK; 92 | var UPGRADED_CALLBACK = 'upgraded' + CALLBACK; 93 | var DOWNGRADED_CALLBACK = 'downgraded' + CALLBACK; 94 | var HTMLSpecial = _defineProperty({ 95 | 'Anchor': 'A', 96 | 'DList': 'DL', 97 | 'Directory': 'Dir', 98 | 'Heading': ['H6', 'H5', 'H4', 'H3', 'H2', 'H1'], 99 | 'Image': 'Img', 100 | 'OList': 'OL', 101 | 'Paragraph': 'P', 102 | 'TableCaption': 'Caption', 103 | 'TableCell': ['TH', 'TD'], 104 | 'TableRow': 'TR', 105 | 'UList': 'UL' 106 | }, ELEMENT, ['Article', 'Aside', 'Footer', 'Header', 'Main', 'Nav', 'Section', ELEMENT]); 107 | 108 | /*! (c) Andrea Giammarchi - ISC */ 109 | var getOwnPropertyNames = Object.getOwnPropertyNames, 110 | setPrototypeOf = Object.setPrototypeOf; 111 | var attributes = new WeakMap(); 112 | var observed = new WeakMap(); 113 | var natives = new Set(); 114 | 115 | var create$1 = function create(tag, isSVG) { 116 | return document.createElementNS(isSVG ? 'http://www.w3.org/2000/svg' : '', tag); 117 | }; 118 | 119 | var AttributesObserver = new MutationObserver(function (records) { 120 | for (var i = 0; i < records.length; i++) { 121 | var _records$i = records[i], 122 | target = _records$i.target, 123 | attributeName = _records$i.attributeName, 124 | oldValue = _records$i.oldValue; 125 | if (attributes.has(target)) target[ATTRIBUTE_CHANGED_CALLBACK](attributeName, oldValue, target.getAttribute(attributeName)); 126 | } 127 | }); 128 | /** 129 | * Set back original element prototype and drops observers. 130 | * @param {Element} target the element to downgrade 131 | */ 132 | 133 | var downgrade = function downgrade(target) { 134 | if (!natives.has(target[CONSTRUCTOR])) { 135 | attributes["delete"](target); 136 | observed["delete"](target); 137 | if (DOWNGRADED_CALLBACK in target) target[DOWNGRADED_CALLBACK](); 138 | setPrototypeOf(target, create$1(target.tagName, 'ownerSVGElement' in target)[CONSTRUCTOR][PROTOTYPE]); 139 | } 140 | }; 141 | /** 142 | * Upgrade an element to a specific class, if not an instance of it already. 143 | * @param {Element} target the element to upgrade 144 | * @param {Function} Class the class the element should be upgraded to 145 | * @returns {Element} the `target` parameter after upgrade 146 | */ 147 | 148 | 149 | var upgrade = function upgrade(target, Class) { 150 | if (!(target instanceof Class)) { 151 | downgrade(target); 152 | setPrototypeOf(target, Class[PROTOTYPE]); 153 | if (UPGRADED_CALLBACK in target) target[UPGRADED_CALLBACK](); 154 | var observedAttributes = Class.observedAttributes; 155 | 156 | if (observedAttributes && ATTRIBUTE_CHANGED_CALLBACK in target) { 157 | attributes.set(target, 0); 158 | AttributesObserver.observe(target, { 159 | attributeFilter: observedAttributes, 160 | attributeOldValue: true, 161 | attributes: true 162 | }); 163 | 164 | for (var i = 0; i < observedAttributes.length; i++) { 165 | var name = observedAttributes[i]; 166 | var value = target.getAttribute(name); 167 | if (value != null) target[ATTRIBUTE_CHANGED_CALLBACK](name, null, value); 168 | } 169 | } 170 | 171 | if (CONNECTED_CALLBACK in target || DISCONNECTED_CALLBACK in target) { 172 | observed.set(target, 0); 173 | if (target.isConnected && CONNECTED_CALLBACK in target) target[CONNECTED_CALLBACK](); 174 | } 175 | } 176 | 177 | return target; 178 | }; 179 | 180 | var SVG = {}; 181 | var HTML = {}; 182 | getOwnPropertyNames(window).forEach(function (name) { 183 | if (/^(HTML|SVG)/.test(name)) { 184 | var Kind = RegExp.$1; 185 | var isSVG = Kind == 'SVG'; 186 | var Class = name.slice(Kind.length, -7) || ELEMENT; 187 | var Namespace = isSVG ? SVG : HTML; 188 | var Native = window[name]; 189 | natives.add(Native); 190 | [].concat(HTMLSpecial[Class] || Class).forEach(function (Tag) { 191 | var tag = Tag.toLowerCase(); 192 | Element.tagName = tag; 193 | Element[PROTOTYPE] = Native[PROTOTYPE]; 194 | Namespace[Class] = Namespace[Tag] = Element; 195 | 196 | function Element() { 197 | return upgrade(create$1(tag, isSVG), this[CONSTRUCTOR]); 198 | } 199 | }); 200 | } 201 | }); 202 | var observer = notify(function (node, connected) { 203 | if (observed.has(node)) { 204 | /* c8 ignore next */ 205 | var method = connected ? CONNECTED_CALLBACK : DISCONNECTED_CALLBACK; 206 | if (method in node) node[method](); 207 | } 208 | }); 209 | 210 | var cache$3 = new WeakMap(); 211 | 212 | function asParams(template) { 213 | var _arguments = arguments; 214 | var known = cache$3.get(template); 215 | if (!known) cache$3.set(template, known = parse.apply(null, arguments)); 216 | return [known.t].concat(known.v.map(function (i) { 217 | return _arguments[i]; 218 | })); 219 | } 220 | 221 | function parse(template) { 222 | var t = [template[0]]; 223 | var v = []; 224 | 225 | for (var c = 0, j = 0, i = 1, length = arguments.length; i < length; i++) { 226 | if (arguments[i] instanceof String) t[c] += arguments[i] + template[i];else { 227 | v[j++] = i; 228 | t[++c] = template[i]; 229 | } 230 | } 231 | 232 | return { 233 | t: t, 234 | v: v 235 | }; 236 | } 237 | 238 | var asStatic = function asStatic(value) { 239 | return new String(value); 240 | }; 241 | 242 | var umap = (function (_) { 243 | return { 244 | // About: get: _.get.bind(_) 245 | // It looks like WebKit/Safari didn't optimize bind at all, 246 | // so that using bind slows it down by 60%. 247 | // Firefox and Chrome are just fine in both cases, 248 | // so let's use the approach that works fast everywhere 👍 249 | get: function get(key) { 250 | return _.get(key); 251 | }, 252 | set: function set(key, value) { 253 | return _.set(key, value), value; 254 | } 255 | }; 256 | }); 257 | 258 | var attr = /([^\s\\>"'=]+)\s*=\s*(['"]?)$/; 259 | var empty = /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i; 260 | var node = /<[a-z][^>]+$/i; 261 | var notNode = />[^<>]*$/; 262 | var selfClosing = /<([a-z]+[a-z0-9:._-]*)([^>]*?)(\/>)/ig; 263 | var trimEnd = /\s+$/; 264 | 265 | var isNode = function isNode(template, i) { 266 | return 0 < i-- && (node.test(template[i]) || !notNode.test(template[i]) && isNode(template, i)); 267 | }; 268 | 269 | var regular = function regular(original, name, extra) { 270 | return empty.test(name) ? original : "<".concat(name).concat(extra.replace(trimEnd, ''), ">"); 271 | }; 272 | 273 | var instrument = (function (template, prefix, svg) { 274 | var text = []; 275 | var length = template.length; 276 | 277 | var _loop = function _loop(i) { 278 | var chunk = template[i - 1]; 279 | text.push(attr.test(chunk) && isNode(template, i) ? chunk.replace(attr, function (_, $1, $2) { 280 | return "".concat(prefix).concat(i - 1, "=").concat($2 || '"').concat($1).concat($2 ? '' : '"'); 281 | }) : "".concat(chunk, "")); 282 | }; 283 | 284 | for (var i = 1; i < length; i++) { 285 | _loop(i); 286 | } 287 | 288 | text.push(template[length - 1]); 289 | var output = text.join('').trim(); 290 | return svg ? output : output.replace(selfClosing, regular); 291 | }); 292 | 293 | var isArray = Array.isArray; 294 | var _ref = [], 295 | indexOf$1 = _ref.indexOf, 296 | slice = _ref.slice; 297 | 298 | var ELEMENT_NODE = 1; 299 | var nodeType = 111; 300 | 301 | var remove = function remove(_ref) { 302 | var firstChild = _ref.firstChild, 303 | lastChild = _ref.lastChild; 304 | var range = document.createRange(); 305 | range.setStartAfter(firstChild); 306 | range.setEndAfter(lastChild); 307 | range.deleteContents(); 308 | return firstChild; 309 | }; 310 | 311 | var diffable = function diffable(node, operation) { 312 | return node.nodeType === nodeType ? 1 / operation < 0 ? operation ? remove(node) : node.lastChild : operation ? node.valueOf() : node.firstChild : node; 313 | }; 314 | var persistent = function persistent(fragment) { 315 | var childNodes = fragment.childNodes; 316 | var length = childNodes.length; 317 | if (length < 2) return length ? childNodes[0] : fragment; 318 | var nodes = slice.call(childNodes, 0); 319 | var firstChild = nodes[0]; 320 | var lastChild = nodes[length - 1]; 321 | return { 322 | ELEMENT_NODE: ELEMENT_NODE, 323 | nodeType: nodeType, 324 | firstChild: firstChild, 325 | lastChild: lastChild, 326 | valueOf: function valueOf() { 327 | if (childNodes.length !== length) { 328 | var i = 0; 329 | 330 | while (i < length) { 331 | fragment.appendChild(nodes[i++]); 332 | } 333 | } 334 | 335 | return fragment; 336 | } 337 | }; 338 | }; 339 | 340 | /** 341 | * ISC License 342 | * 343 | * Copyright (c) 2020, Andrea Giammarchi, @WebReflection 344 | * 345 | * Permission to use, copy, modify, and/or distribute this software for any 346 | * purpose with or without fee is hereby granted, provided that the above 347 | * copyright notice and this permission notice appear in all copies. 348 | * 349 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 350 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 351 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 352 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 353 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 354 | * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 355 | * PERFORMANCE OF THIS SOFTWARE. 356 | */ 357 | 358 | /** 359 | * @param {Node} parentNode The container where children live 360 | * @param {Node[]} a The list of current/live children 361 | * @param {Node[]} b The list of future children 362 | * @param {(entry: Node, action: number) => Node} get 363 | * The callback invoked per each entry related DOM operation. 364 | * @param {Node} [before] The optional node used as anchor to insert before. 365 | * @returns {Node[]} The same list of future children. 366 | */ 367 | var udomdiff = (function (parentNode, a, b, get, before) { 368 | var bLength = b.length; 369 | var aEnd = a.length; 370 | var bEnd = bLength; 371 | var aStart = 0; 372 | var bStart = 0; 373 | var map = null; 374 | 375 | while (aStart < aEnd || bStart < bEnd) { 376 | // append head, tail, or nodes in between: fast path 377 | if (aEnd === aStart) { 378 | // we could be in a situation where the rest of nodes that 379 | // need to be added are not at the end, and in such case 380 | // the node to `insertBefore`, if the index is more than 0 381 | // must be retrieved, otherwise it's gonna be the first item. 382 | var node = bEnd < bLength ? bStart ? get(b[bStart - 1], -0).nextSibling : get(b[bEnd - bStart], 0) : before; 383 | 384 | while (bStart < bEnd) { 385 | parentNode.insertBefore(get(b[bStart++], 1), node); 386 | } 387 | } // remove head or tail: fast path 388 | else if (bEnd === bStart) { 389 | while (aStart < aEnd) { 390 | // remove the node only if it's unknown or not live 391 | if (!map || !map.has(a[aStart])) parentNode.removeChild(get(a[aStart], -1)); 392 | aStart++; 393 | } 394 | } // same node: fast path 395 | else if (a[aStart] === b[bStart]) { 396 | aStart++; 397 | bStart++; 398 | } // same tail: fast path 399 | else if (a[aEnd - 1] === b[bEnd - 1]) { 400 | aEnd--; 401 | bEnd--; 402 | } // The once here single last swap "fast path" has been removed in v1.1.0 403 | // https://github.com/WebReflection/udomdiff/blob/single-final-swap/esm/index.js#L69-L85 404 | // reverse swap: also fast path 405 | else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) { 406 | // this is a "shrink" operation that could happen in these cases: 407 | // [1, 2, 3, 4, 5] 408 | // [1, 4, 3, 2, 5] 409 | // or asymmetric too 410 | // [1, 2, 3, 4, 5] 411 | // [1, 2, 3, 5, 6, 4] 412 | var _node = get(a[--aEnd], -1).nextSibling; 413 | parentNode.insertBefore(get(b[bStart++], 1), get(a[aStart++], -1).nextSibling); 414 | parentNode.insertBefore(get(b[--bEnd], 1), _node); // mark the future index as identical (yeah, it's dirty, but cheap 👍) 415 | // The main reason to do this, is that when a[aEnd] will be reached, 416 | // the loop will likely be on the fast path, as identical to b[bEnd]. 417 | // In the best case scenario, the next loop will skip the tail, 418 | // but in the worst one, this node will be considered as already 419 | // processed, bailing out pretty quickly from the map index check 420 | 421 | a[aEnd] = b[bEnd]; 422 | } // map based fallback, "slow" path 423 | else { 424 | // the map requires an O(bEnd - bStart) operation once 425 | // to store all future nodes indexes for later purposes. 426 | // In the worst case scenario, this is a full O(N) cost, 427 | // and such scenario happens at least when all nodes are different, 428 | // but also if both first and last items of the lists are different 429 | if (!map) { 430 | map = new Map(); 431 | var i = bStart; 432 | 433 | while (i < bEnd) { 434 | map.set(b[i], i++); 435 | } 436 | } // if it's a future node, hence it needs some handling 437 | 438 | 439 | if (map.has(a[aStart])) { 440 | // grab the index of such node, 'cause it might have been processed 441 | var index = map.get(a[aStart]); // if it's not already processed, look on demand for the next LCS 442 | 443 | if (bStart < index && index < bEnd) { 444 | var _i = aStart; // counts the amount of nodes that are the same in the future 445 | 446 | var sequence = 1; 447 | 448 | while (++_i < aEnd && _i < bEnd && map.get(a[_i]) === index + sequence) { 449 | sequence++; 450 | } // effort decision here: if the sequence is longer than replaces 451 | // needed to reach such sequence, which would brings again this loop 452 | // to the fast path, prepend the difference before a sequence, 453 | // and move only the future list index forward, so that aStart 454 | // and bStart will be aligned again, hence on the fast path. 455 | // An example considering aStart and bStart are both 0: 456 | // a: [1, 2, 3, 4] 457 | // b: [7, 1, 2, 3, 6] 458 | // this would place 7 before 1 and, from that time on, 1, 2, and 3 459 | // will be processed at zero cost 460 | 461 | 462 | if (sequence > index - bStart) { 463 | var _node2 = get(a[aStart], 0); 464 | 465 | while (bStart < index) { 466 | parentNode.insertBefore(get(b[bStart++], 1), _node2); 467 | } 468 | } // if the effort wasn't good enough, fallback to a replace, 469 | // moving both source and target indexes forward, hoping that some 470 | // similar node will be found later on, to go back to the fast path 471 | else { 472 | parentNode.replaceChild(get(b[bStart++], 1), get(a[aStart++], -1)); 473 | } 474 | } // otherwise move the source forward, 'cause there's nothing to do 475 | else aStart++; 476 | } // this node has no meaning in the future list, so it's more than safe 477 | // to remove it, and check the next live node out instead, meaning 478 | // that only the live list index should be forwarded 479 | else parentNode.removeChild(get(a[aStart++], -1)); 480 | } 481 | } 482 | 483 | return b; 484 | }); 485 | 486 | var aria = function aria(node) { 487 | return function (values) { 488 | for (var key in values) { 489 | var name = key === 'role' ? key : "aria-".concat(key); 490 | var value = values[key]; 491 | if (value == null) node.removeAttribute(name);else node.setAttribute(name, value); 492 | } 493 | }; 494 | }; 495 | var attribute = function attribute(node, name) { 496 | var oldValue, 497 | orphan = true; 498 | var attributeNode = document.createAttributeNS(null, name); 499 | return function (newValue) { 500 | if (oldValue !== newValue) { 501 | oldValue = newValue; 502 | 503 | if (oldValue == null) { 504 | if (!orphan) { 505 | node.removeAttributeNode(attributeNode); 506 | orphan = true; 507 | } 508 | } else { 509 | attributeNode.value = newValue; 510 | 511 | if (orphan) { 512 | node.setAttributeNodeNS(attributeNode); 513 | orphan = false; 514 | } 515 | } 516 | } 517 | }; 518 | }; 519 | 520 | var _boolean = function _boolean(node, key, oldValue) { 521 | return function (newValue) { 522 | if (oldValue !== !!newValue) { 523 | // when IE won't be around anymore ... 524 | // node.toggleAttribute(key, oldValue = !!newValue); 525 | if (oldValue = !!newValue) node.setAttribute(key, '');else node.removeAttribute(key); 526 | } 527 | }; 528 | }; 529 | var data = function data(_ref) { 530 | var dataset = _ref.dataset; 531 | return function (values) { 532 | for (var key in values) { 533 | var value = values[key]; 534 | if (value == null) delete dataset[key];else dataset[key] = value; 535 | } 536 | }; 537 | }; 538 | var event = function event(node, name) { 539 | var oldValue, 540 | type = name.slice(2); 541 | if (!(name in node) && name.toLowerCase() in node) type = type.toLowerCase(); 542 | return function (newValue) { 543 | var info = isArray(newValue) ? newValue : [newValue, false]; 544 | 545 | if (oldValue !== info[0]) { 546 | if (oldValue) node.removeEventListener(type, oldValue, info[1]); 547 | if (oldValue = info[0]) node.addEventListener(type, oldValue, info[1]); 548 | } 549 | }; 550 | }; 551 | var ref = function ref(node) { 552 | var oldValue; 553 | return function (value) { 554 | if (oldValue !== value) { 555 | oldValue = value; 556 | if (typeof value === 'function') value(node);else value.current = node; 557 | } 558 | }; 559 | }; 560 | var setter = function setter(node, key) { 561 | return key === 'dataset' ? data(node) : function (value) { 562 | node[key] = value; 563 | }; 564 | }; 565 | var text = function text(node) { 566 | var oldValue; 567 | return function (newValue) { 568 | if (oldValue != newValue) { 569 | oldValue = newValue; 570 | node.textContent = newValue == null ? '' : newValue; 571 | } 572 | }; 573 | }; 574 | 575 | /*! (c) Andrea Giammarchi - ISC */ 576 | var createContent = function (document) { 577 | 578 | var FRAGMENT = 'fragment'; 579 | var TEMPLATE = 'template'; 580 | var HAS_CONTENT = ('content' in create(TEMPLATE)); 581 | var createHTML = HAS_CONTENT ? function (html) { 582 | var template = create(TEMPLATE); 583 | template.innerHTML = html; 584 | return template.content; 585 | } : function (html) { 586 | var content = create(FRAGMENT); 587 | var template = create(TEMPLATE); 588 | var childNodes = null; 589 | 590 | if (/^[^\S]*?<(col(?:group)?|t(?:head|body|foot|r|d|h))/i.test(html)) { 591 | var selector = RegExp.$1; 592 | template.innerHTML = '' + html + '
'; 593 | childNodes = template.querySelectorAll(selector); 594 | } else { 595 | template.innerHTML = html; 596 | childNodes = template.childNodes; 597 | } 598 | 599 | append(content, childNodes); 600 | return content; 601 | }; 602 | return function createContent(markup, type) { 603 | return (type === 'svg' ? createSVG : createHTML)(markup); 604 | }; 605 | 606 | function append(root, childNodes) { 607 | var length = childNodes.length; 608 | 609 | while (length--) { 610 | root.appendChild(childNodes[0]); 611 | } 612 | } 613 | 614 | function create(element) { 615 | return element === FRAGMENT ? document.createDocumentFragment() : document.createElementNS('http://www.w3.org/1999/xhtml', element); 616 | } // it could use createElementNS when hasNode is there 617 | // but this fallback is equally fast and easier to maintain 618 | // it is also battle tested already in all IE 619 | 620 | 621 | function createSVG(svg) { 622 | var content = create(FRAGMENT); 623 | var template = create('div'); 624 | template.innerHTML = '' + svg + ''; 625 | append(content, template.firstChild.childNodes); 626 | return content; 627 | } 628 | }(document); 629 | 630 | var reducePath = function reducePath(_ref, i) { 631 | var childNodes = _ref.childNodes; 632 | return childNodes[i]; 633 | }; // from a fragment container, create an array of indexes 634 | // related to its child nodes, so that it's possible 635 | // to retrieve later on exact node via reducePath 636 | 637 | var createPath = function createPath(node) { 638 | var path = []; 639 | var _node = node, 640 | parentNode = _node.parentNode; 641 | 642 | while (parentNode) { 643 | path.push(indexOf$1.call(parentNode.childNodes, node)); 644 | node = parentNode; 645 | parentNode = node.parentNode; 646 | } 647 | 648 | return path; 649 | }; 650 | var _document = document, 651 | createTreeWalker = _document.createTreeWalker, 652 | importNode = _document.importNode; 653 | 654 | var isImportNodeLengthWrong = importNode.length != 1; // IE11 and old Edge discard empty nodes when cloning, potentially 655 | // resulting in broken paths to find updates. The workaround here 656 | // is to import once, upfront, the fragment that will be cloned 657 | // later on, so that paths are retrieved from one already parsed, 658 | // hence without missing child nodes once re-cloned. 659 | 660 | var createFragment = isImportNodeLengthWrong ? function (text, type, normalize) { 661 | return importNode.call(document, createContent(text, type, normalize), true); 662 | } : createContent; // IE11 and old Edge have a different createTreeWalker signature that 663 | // has been deprecated in other browsers. This export is needed only 664 | // to guarantee the TreeWalker doesn't show warnings and, ultimately, works 665 | 666 | var createWalker = isImportNodeLengthWrong ? function (fragment) { 667 | return createTreeWalker.call(document, fragment, 1 | 128, null, false); 668 | } : function (fragment) { 669 | return createTreeWalker.call(document, fragment, 1 | 128); 670 | }; 671 | 672 | var diff = function diff(comment, oldNodes, newNodes) { 673 | return udomdiff(comment.parentNode, // TODO: there is a possible edge case where a node has been 674 | // removed manually, or it was a keyed one, attached 675 | // to a shared reference between renders. 676 | // In this case udomdiff might fail at removing such node 677 | // as its parent won't be the expected one. 678 | // The best way to avoid this issue is to filter oldNodes 679 | // in search of those not live, or not in the current parent 680 | // anymore, but this would require both a change to uwire, 681 | // exposing a parentNode from the firstChild, as example, 682 | // but also a filter per each diff that should exclude nodes 683 | // that are not in there, penalizing performance quite a lot. 684 | // As this has been also a potential issue with domdiff, 685 | // and both lighterhtml and hyperHTML might fail with this 686 | // very specific edge case, I might as well document this possible 687 | // "diffing shenanigan" and call it a day. 688 | oldNodes, newNodes, diffable, comment); 689 | }; // if an interpolation represents a comment, the whole 690 | // diffing will be related to such comment. 691 | // This helper is in charge of understanding how the new 692 | // content for such interpolation/hole should be updated 693 | 694 | 695 | var handleAnything = function handleAnything(comment) { 696 | var oldValue, 697 | text, 698 | nodes = []; 699 | 700 | var anyContent = function anyContent(newValue) { 701 | switch (typeof(newValue)) { 702 | // primitives are handled as text content 703 | case 'string': 704 | case 'number': 705 | case 'boolean': 706 | if (oldValue !== newValue) { 707 | oldValue = newValue; 708 | if (!text) text = document.createTextNode(''); 709 | text.data = newValue; 710 | nodes = diff(comment, nodes, [text]); 711 | } 712 | 713 | break; 714 | // null, and undefined are used to cleanup previous content 715 | 716 | case 'object': 717 | case 'undefined': 718 | if (newValue == null) { 719 | if (oldValue != newValue) { 720 | oldValue = newValue; 721 | nodes = diff(comment, nodes, []); 722 | } 723 | 724 | break; 725 | } // arrays and nodes have a special treatment 726 | 727 | 728 | if (isArray(newValue)) { 729 | oldValue = newValue; // arrays can be used to cleanup, if empty 730 | 731 | if (newValue.length === 0) nodes = diff(comment, nodes, []); // or diffed, if these contains nodes or "wires" 732 | else if (typeof(newValue[0]) === 'object') nodes = diff(comment, nodes, newValue); // in all other cases the content is stringified as is 733 | else anyContent(String(newValue)); 734 | break; 735 | } // if the new value is a DOM node, or a wire, and it's 736 | // different from the one already live, then it's diffed. 737 | // if the node is a fragment, it's appended once via its childNodes 738 | // There is no `else` here, meaning if the content 739 | // is not expected one, nothing happens, as easy as that. 740 | 741 | 742 | if (oldValue !== newValue && 'ELEMENT_NODE' in newValue) { 743 | oldValue = newValue; 744 | nodes = diff(comment, nodes, newValue.nodeType === 11 ? slice.call(newValue.childNodes) : [newValue]); 745 | } 746 | 747 | break; 748 | 749 | case 'function': 750 | anyContent(newValue(comment)); 751 | break; 752 | } 753 | }; 754 | 755 | return anyContent; 756 | }; // attributes can be: 757 | // * ref=${...} for hooks and other purposes 758 | // * aria=${...} for aria attributes 759 | // * ?boolean=${...} for boolean attributes 760 | // * .dataset=${...} for dataset related attributes 761 | // * .setter=${...} for Custom Elements setters or nodes with setters 762 | // such as buttons, details, options, select, etc 763 | // * @event=${...} to explicitly handle event listeners 764 | // * onevent=${...} to automatically handle event listeners 765 | // * generic=${...} to handle an attribute just like an attribute 766 | 767 | 768 | var handleAttribute = function handleAttribute(node, name 769 | /*, svg*/ 770 | ) { 771 | switch (name[0]) { 772 | case '?': 773 | return _boolean(node, name.slice(1), false); 774 | 775 | case '.': 776 | return setter(node, name.slice(1)); 777 | 778 | case '@': 779 | return event(node, 'on' + name.slice(1)); 780 | 781 | case 'o': 782 | if (name[1] === 'n') return event(node, name); 783 | } 784 | 785 | switch (name) { 786 | case 'ref': 787 | return ref(node); 788 | 789 | case 'aria': 790 | return aria(node); 791 | } 792 | 793 | return attribute(node, name 794 | /*, svg*/ 795 | ); 796 | }; // each mapped update carries the update type and its path 797 | // the type is either node, attribute, or text, while 798 | // the path is how to retrieve the related node to update. 799 | // In the attribute case, the attribute name is also carried along. 800 | 801 | 802 | function handlers(options) { 803 | var type = options.type, 804 | path = options.path; 805 | var node = path.reduceRight(reducePath, this); 806 | return type === 'node' ? handleAnything(node) : type === 'attr' ? handleAttribute(node, options.name 807 | /*, options.svg*/ 808 | ) : text(node); 809 | } 810 | 811 | // that contain the related unique id. In the attribute cases 812 | // isµX="attribute-name" will be used to map current X update to that 813 | // attribute name, while comments will be like , to map 814 | // the update to that specific comment node, hence its parent. 815 | // style and textarea will have text content, and are handled 816 | // directly through text-only updates. 817 | 818 | var prefix = 'isµ'; // Template Literals are unique per scope and static, meaning a template 819 | // should be parsed once, and once only, as it will always represent the same 820 | // content, within the exact same amount of updates each time. 821 | // This cache relates each template to its unique content and updates. 822 | 823 | var cache$2 = umap(new WeakMap()); // a RegExp that helps checking nodes that cannot contain comments 824 | 825 | var textOnly = /^(?:plaintext|script|style|textarea|title|xmp)$/i; 826 | var createCache = function createCache() { 827 | return { 828 | stack: [], 829 | // each template gets a stack for each interpolation "hole" 830 | entry: null, 831 | // each entry contains details, such as: 832 | // * the template that is representing 833 | // * the type of node it represents (html or svg) 834 | // * the content fragment with all nodes 835 | // * the list of updates per each node (template holes) 836 | // * the "wired" node or fragment that will get updates 837 | // if the template or type are different from the previous one 838 | // the entry gets re-created each time 839 | wire: null // each rendered node represent some wired content and 840 | // this reference to the latest one. If different, the node 841 | // will be cleaned up and the new "wire" will be appended 842 | 843 | }; 844 | }; // the entry stored in the rendered node cache, and per each "hole" 845 | 846 | var createEntry = function createEntry(type, template) { 847 | var _mapUpdates = mapUpdates(type, template), 848 | content = _mapUpdates.content, 849 | updates = _mapUpdates.updates; 850 | 851 | return { 852 | type: type, 853 | template: template, 854 | content: content, 855 | updates: updates, 856 | wire: null 857 | }; 858 | }; // a template is instrumented to be able to retrieve where updates are needed. 859 | // Each unique template becomes a fragment, cloned once per each other 860 | // operation based on the same template, i.e. data => html`

${data}

` 861 | 862 | 863 | var mapTemplate = function mapTemplate(type, template) { 864 | var text = instrument(template, prefix, type === 'svg'); 865 | var content = createFragment(text, type); // once instrumented and reproduced as fragment, it's crawled 866 | // to find out where each update is in the fragment tree 867 | 868 | var tw = createWalker(content); 869 | var nodes = []; 870 | var length = template.length - 1; 871 | var i = 0; // updates are searched via unique names, linearly increased across the tree 872 | //
873 | 874 | var search = "".concat(prefix).concat(i); 875 | 876 | while (i < length) { 877 | var node = tw.nextNode(); // if not all updates are bound but there's nothing else to crawl 878 | // it means that there is something wrong with the template. 879 | 880 | if (!node) throw "bad template: ".concat(text); // if the current node is a comment, and it contains isµX 881 | // it means the update should take care of any content 882 | 883 | if (node.nodeType === 8) { 884 | // The only comments to be considered are those 885 | // which content is exactly the same as the searched one. 886 | if (node.data === search) { 887 | nodes.push({ 888 | type: 'node', 889 | path: createPath(node) 890 | }); 891 | search = "".concat(prefix).concat(++i); 892 | } 893 | } else { 894 | // if the node is not a comment, loop through all its attributes 895 | // named isµX and relate attribute updates to this node and the 896 | // attribute name, retrieved through node.getAttribute("isµX") 897 | // the isµX attribute will be removed as irrelevant for the layout 898 | // let svg = -1; 899 | while (node.hasAttribute(search)) { 900 | nodes.push({ 901 | type: 'attr', 902 | path: createPath(node), 903 | name: node.getAttribute(search) //svg: svg < 0 ? (svg = ('ownerSVGElement' in node ? 1 : 0)) : svg 904 | 905 | }); 906 | node.removeAttribute(search); 907 | search = "".concat(prefix).concat(++i); 908 | } // if the node was a style, textarea, or others, check its content 909 | // and if it is then update tex-only this node 910 | 911 | 912 | if (textOnly.test(node.tagName) && node.textContent.trim() === "")) { 913 | node.textContent = ''; 914 | nodes.push({ 915 | type: 'text', 916 | path: createPath(node) 917 | }); 918 | search = "".concat(prefix).concat(++i); 919 | } 920 | } 921 | } // once all nodes to update, or their attributes, are known, the content 922 | // will be cloned in the future to represent the template, and all updates 923 | // related to such content retrieved right away without needing to re-crawl 924 | // the exact same template, and its content, more than once. 925 | 926 | 927 | return { 928 | content: content, 929 | nodes: nodes 930 | }; 931 | }; // if a template is unknown, perform the previous mapping, otherwise grab 932 | // its details such as the fragment with all nodes, and updates info. 933 | 934 | 935 | var mapUpdates = function mapUpdates(type, template) { 936 | var _ref = cache$2.get(template) || cache$2.set(template, mapTemplate(type, template)), 937 | content = _ref.content, 938 | nodes = _ref.nodes; // clone deeply the fragment 939 | 940 | 941 | var fragment = importNode.call(document, content, true); // and relate an update handler per each node that needs one 942 | 943 | var updates = nodes.map(handlers, fragment); // return the fragment and all updates to use within its nodes 944 | 945 | return { 946 | content: fragment, 947 | updates: updates 948 | }; 949 | }; // as html and svg can be nested calls, but no parent node is known 950 | // until rendered somewhere, the unroll operation is needed to 951 | // discover what to do with each interpolation, which will result 952 | // into an update operation. 953 | 954 | 955 | var unroll = function unroll(info, _ref2) { 956 | var type = _ref2.type, 957 | template = _ref2.template, 958 | values = _ref2.values; 959 | var length = values.length; // interpolations can contain holes and arrays, so these need 960 | // to be recursively discovered 961 | 962 | unrollValues(info, values, length); 963 | var entry = info.entry; // if the cache entry is either null or different from the template 964 | // and the type this unroll should resolve, create a new entry 965 | // assigning a new content fragment and the list of updates. 966 | 967 | if (!entry || entry.template !== template || entry.type !== type) info.entry = entry = createEntry(type, template); 968 | var _entry = entry, 969 | content = _entry.content, 970 | updates = _entry.updates, 971 | wire = _entry.wire; // even if the fragment and its nodes is not live yet, 972 | // it is already possible to update via interpolations values. 973 | 974 | for (var i = 0; i < length; i++) { 975 | updates[i](values[i]); 976 | } // if the entry was new, or representing a different template or type, 977 | // create a new persistent entity to use during diffing. 978 | // This is simply a DOM node, when the template has a single container, 979 | // as in `

`, or a "wire" in `

` and similar cases. 980 | 981 | 982 | return wire || (entry.wire = persistent(content)); 983 | }; // the stack retains, per each interpolation value, the cache 984 | // related to each interpolation value, or null, if the render 985 | // was conditional and the value is not special (Array or Hole) 986 | 987 | var unrollValues = function unrollValues(_ref3, values, length) { 988 | var stack = _ref3.stack; 989 | 990 | for (var i = 0; i < length; i++) { 991 | var hole = values[i]; // each Hole gets unrolled and re-assigned as value 992 | // so that domdiff will deal with a node/wire, not with a hole 993 | 994 | if (hole instanceof Hole) values[i] = unroll(stack[i] || (stack[i] = createCache()), hole); // arrays are recursively resolved so that each entry will contain 995 | // also a DOM node or a wire, hence it can be diffed if/when needed 996 | else if (isArray(hole)) unrollValues(stack[i] || (stack[i] = createCache()), hole, hole.length); // if the value is nothing special, the stack doesn't need to retain data 997 | // this is useful also to cleanup previously retained data, if the value 998 | // was a Hole, or an Array, but not anymore, i.e.: 999 | // const update = content => html`
${content}
`; 1000 | // update(listOfItems); update(null); update(html`hole`) 1001 | else stack[i] = null; 1002 | } 1003 | 1004 | if (length < stack.length) stack.splice(length); 1005 | }; 1006 | /** 1007 | * Holds all details wrappers needed to render the content further on. 1008 | * @constructor 1009 | * @param {string} type The hole type, either `html` or `svg`. 1010 | * @param {string[]} template The template literals used to the define the content. 1011 | * @param {Array} values Zero, one, or more interpolated values to render. 1012 | */ 1013 | 1014 | 1015 | function Hole(type, template, values) { 1016 | this.type = type; 1017 | this.template = template; 1018 | this.values = values; 1019 | } 1020 | 1021 | var create = Object.create, 1022 | defineProperties = Object.defineProperties; // both `html` and `svg` template literal tags are polluted 1023 | // with a `for(ref[, id])` and a `node` tag too 1024 | 1025 | var tag = function tag(type) { 1026 | // both `html` and `svg` tags have their own cache 1027 | var keyed = umap(new WeakMap()); // keyed operations always re-use the same cache and unroll 1028 | // the template and its interpolations right away 1029 | 1030 | var fixed = function fixed(cache) { 1031 | return function (template) { 1032 | for (var _len = arguments.length, values = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 1033 | values[_key - 1] = arguments[_key]; 1034 | } 1035 | 1036 | return unroll(cache, { 1037 | type: type, 1038 | template: template, 1039 | values: values 1040 | }); 1041 | }; 1042 | }; 1043 | 1044 | return defineProperties( // non keyed operations are recognized as instance of Hole 1045 | // during the "unroll", recursively resolved and updated 1046 | function (template) { 1047 | for (var _len2 = arguments.length, values = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { 1048 | values[_key2 - 1] = arguments[_key2]; 1049 | } 1050 | 1051 | return new Hole(type, template, values); 1052 | }, { 1053 | "for": { 1054 | // keyed operations need a reference object, usually the parent node 1055 | // which is showing keyed results, and optionally a unique id per each 1056 | // related node, handy with JSON results and mutable list of objects 1057 | // that usually carry a unique identifier 1058 | value: function value(ref, id) { 1059 | var memo = keyed.get(ref) || keyed.set(ref, create(null)); 1060 | return memo[id] || (memo[id] = fixed(createCache())); 1061 | } 1062 | }, 1063 | node: { 1064 | // it is possible to create one-off content out of the box via node tag 1065 | // this might return the single created node, or a fragment with all 1066 | // nodes present at the root level and, of course, their child nodes 1067 | value: function value(template) { 1068 | for (var _len3 = arguments.length, values = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { 1069 | values[_key3 - 1] = arguments[_key3]; 1070 | } 1071 | 1072 | return unroll(createCache(), { 1073 | type: type, 1074 | template: template, 1075 | values: values 1076 | }).valueOf(); 1077 | } 1078 | } 1079 | }); 1080 | }; // each rendered node gets its own cache 1081 | 1082 | 1083 | var cache$1 = umap(new WeakMap()); // rendering means understanding what `html` or `svg` tags returned 1084 | // and it relates a specific node to its own unique cache. 1085 | // Each time the content to render changes, the node is cleaned up 1086 | // and the new new content is appended, and if such content is a Hole 1087 | // then it's "unrolled" to resolve all its inner nodes. 1088 | 1089 | var render$1 = function render(where, what) { 1090 | var hole = typeof what === 'function' ? what() : what; 1091 | var info = cache$1.get(where) || cache$1.set(where, createCache()); 1092 | var wire = hole instanceof Hole ? unroll(info, hole) : hole; 1093 | 1094 | if (wire !== info.wire) { 1095 | info.wire = wire; 1096 | where.textContent = ''; // valueOf() simply returns the node itself, but in case it was a "wire" 1097 | // it will eventually re-append all nodes to its fragment so that such 1098 | // fragment can be re-appended many times in a meaningful way 1099 | // (wires are basically persistent fragments facades with special behavior) 1100 | 1101 | where.appendChild(wire.valueOf()); 1102 | } 1103 | 1104 | return where; 1105 | }; 1106 | 1107 | var html$1 = tag('html'); 1108 | var svg$1 = tag('svg'); 1109 | 1110 | var UBE = 'data-ube'; 1111 | var cache = new WeakMap(); 1112 | var ube = []; 1113 | 1114 | var indexOf = function indexOf(Class) { 1115 | var i = ube.indexOf(Class); 1116 | return i < 0 ? ube.push(Class) - 1 : i; 1117 | }; 1118 | 1119 | var augment = function augment(tag) { 1120 | function ube() { 1121 | return reshaped.apply(tag, arguments); 1122 | } 1123 | 1124 | ube["for"] = function (ref, id) { 1125 | return function () { 1126 | return reshaped.apply(tag["for"](ref, id), arguments); 1127 | }; 1128 | }; 1129 | 1130 | return ube; 1131 | }; 1132 | 1133 | function render(where) { 1134 | var result = render$1.apply(this, arguments); 1135 | var be = where.querySelectorAll("[".concat(UBE, "]")); 1136 | 1137 | for (var i = 0, length = be.length; i < length; i++) { 1138 | var Class = ube[be[i].getAttribute(UBE)]; 1139 | be[i].removeAttribute(UBE); 1140 | upgrade(be[i], Class); 1141 | } 1142 | 1143 | return result; 1144 | } 1145 | var html = augment(html$1); 1146 | var svg = augment(svg$1); 1147 | 1148 | function reshaped() { 1149 | for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 1150 | args[_key] = arguments[_key]; 1151 | } 1152 | 1153 | var template = args[0]; 1154 | var known = cache.get(template); 1155 | 1156 | if (known) { 1157 | for (var i = 0, _known = known, length = _known.length; i < length; i++) { 1158 | args[known[i].i] = known[i].v; 1159 | } 1160 | } else { 1161 | cache.set(template, known = []); 1162 | 1163 | for (var _i = 1, _length = args.length; _i < _length; _i++) { 1164 | var current = args[_i]; 1165 | 1166 | if (typeof current === 'function' && 'tagName' in current) { 1167 | var _current = current, 1168 | tagName = _current.tagName; 1169 | var prev = template[_i - 1]; 1170 | 1171 | switch (prev[prev.length - 1]) { 1172 | case '<': 1173 | current = asStatic("".concat(tagName, " ").concat(UBE, "=").concat(indexOf(current))); 1174 | break; 1175 | 1176 | case '/': 1177 | current = asStatic(tagName); 1178 | break; 1179 | } 1180 | 1181 | known.push({ 1182 | i: _i, 1183 | v: current 1184 | }); 1185 | args[_i] = current; 1186 | } 1187 | } 1188 | } 1189 | 1190 | return this.apply(null, asParams.apply(null, args)); 1191 | } 1192 | 1193 | exports.HTML = HTML; 1194 | exports.SVG = SVG; 1195 | exports.downgrade = downgrade; 1196 | exports.html = html; 1197 | exports.observer = observer; 1198 | exports.render = render; 1199 | exports.svg = svg; 1200 | exports.upgrade = upgrade; 1201 | 1202 | return exports; 1203 | 1204 | }({})); 1205 | -------------------------------------------------------------------------------- /min.js: -------------------------------------------------------------------------------- 1 | self.ube=function(e){"use strict"; 2 | /*! (c) Andrea Giammarchi - ISC */"isConnected"in Node.prototype||Object.defineProperty(Node.prototype,"isConnected",{configurable:!0,get:function(){return!(this.ownerDocument.compareDocumentPosition(this)&this.DOCUMENT_POSITION_DISCONNECTED)}});var t=!0,n=!1,r="querySelectorAll";function a(e){this.observe(e,{subtree:t,childList:t})}var o,i,c,u="Element",l="attributeChangedCallback",f="connectedCallback",s="disconnectedCallback",d="upgradedCallback",v="downgradedCallback",p=(c=["Article","Aside","Footer","Header","Main","Nav","Section",u],(i=u)in(o={Anchor:"A",DList:"DL",Directory:"Dir",Heading:["H6","H5","H4","H3","H2","H1"],Image:"Img",OList:"OL",Paragraph:"P",TableCaption:"Caption",TableCell:["TH","TD"],TableRow:"TR",UList:"UL"})?Object.defineProperty(o,i,{value:c,enumerable:!0,configurable:!0,writable:!0}):o[i]=c,o),h=Object.getOwnPropertyNames,g=Object.setPrototypeOf,b=new WeakMap,m=new WeakMap,w=new Set,y=function(e,t){return document.createElementNS(t?"http://www.w3.org/2000/svg":"",e)},C=new MutationObserver((function(e){for(var t=0;t"'=]+)\s*=\s*(['"]?)$/,D=/^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i,j=/<[a-z][^>]+$/i,P=/>[^<>]*$/,W=/<([a-z]+[a-z0-9:._-]*)([^>]*?)(\/>)/gi,$=/\s+$/,R=function e(t,n){return 0")},I=function(e,t,n){for(var r=[],a=e.length,o=function(n){var a=e[n-1];r.push(H.test(a)&&R(e,n)?a.replace(H,(function(e,r,a){return"".concat(t).concat(n-1,"=").concat(a||'"').concat(r).concat(a?"":'"')})):"".concat(a,"\x3c!--").concat(t).concat(n-1,"--\x3e"))},i=1;i"+e+"",c=i.querySelectorAll(u)}else i.innerHTML=e,c=i.childNodes;return a(r,c),r};return function(e,t){return("svg"===t?i:r)(e)};function a(e,t){for(var n=t.length;n--;)e.appendChild(t[0])}function o(n){return n===t?e.createDocumentFragment():e.createElementNS("http://www.w3.org/1999/xhtml",n)}function i(e){var n=o(t),r=o("div");return r.innerHTML=''+e+"",a(n,r.firstChild.childNodes),n}}(document),J=function(e,t){return e.childNodes[t]},K=function(e){for(var t=[],n=e.parentNode;n;)t.push(G.call(n.childNodes,e)),n=(e=n).parentNode;return t},Q=document,X=Q.createTreeWalker,Y=Q.importNode,Z=1!=Y.length,ee=Z?function(e,t,n){return Y.call(document,U(e,t,n),!0)}:U,te=Z?function(e){return X.call(document,e,129,null,!1)}:function(e){return X.call(document,e,129)},ne=function(e,t,n){return function(e,t,n,r,a){for(var o=n.length,i=t.length,c=o,u=0,l=0,f=null;up-l)for(var b=r(t[u],0);l1?n-1:0),a=1;a1?r-1:0),o=1;o1?n-1:0),a=1;a 2 | 3 | 4 | 5 | 6 | ube 7 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | require('../cjs'); -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | {"type":"commonjs"} -------------------------------------------------------------------------------- /ube.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebReflection/ube/e7f10caa1f31b5796da0f878ca44e5f50758b28b/ube.jpg --------------------------------------------------------------------------------