diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml
index 73cf8d6..ba93c6c 100644
--- a/.github/workflows/node.js.yml
+++ b/.github/workflows/node.js.yml
@@ -12,7 +12,7 @@ jobs:
strategy:
matrix:
- node-version: [16]
+ node-version: [20]
steps:
- uses: actions/checkout@v2
@@ -23,9 +23,9 @@ jobs:
cache: 'npm'
- run: npm ci
- run: npm run build --if-present
- - run: npm test
- - run: npm run coverage --if-present
- - name: Coveralls
- uses: coverallsapp/github-action@master
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
+ # - run: npm test
+ # - run: npm run coverage --if-present
+ # - name: Coveralls
+ # uses: coverallsapp/github-action@master
+ # with:
+ # github-token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 8b2c3c9..d9021aa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
.nyc_output/
coverage/
node_modules/
-
+cjs/*
+!cjs/package.json
diff --git a/README.md b/README.md
index 83ea774..896180c 100644
--- a/README.md
+++ b/README.md
@@ -6,11 +6,11 @@
**Social Media Photo by [Andrii Ganzevych](https://unsplash.com/@odya_kun) on [Unsplash](https://unsplash.com/)**
-_micro html_ is a _~2.5K_ [lighterhtml](https://github.com/WebReflection/lighterhtml#readme) subset to build declarative and reactive UI via template literals tags.
+_micro html_ is a [lighterhtml](https://github.com/WebReflection/lighterhtml#readme) subset to build declarative and reactive UI via template literals tags.
-### š£ Community Announcement
+### š£ uhtml v4 is out
-Please ask questions in the [dedicated discussions repository](https://github.com/WebReflection/discussions), to help the community around this project grow ā„
+Please check the [Release Notes](https://github.com/WebReflection/uhtml/pull/86) to know more about what improved, what changed or broke and what's not there yet (or anymore).
---
diff --git a/V0.md b/V0.md
deleted file mode 100644
index 1599028..0000000
--- a/V0.md
+++ /dev/null
@@ -1,83 +0,0 @@
-# uHTML v0
-
-A micro HTML/SVG render
-
- * no diffing whatsoever, it's just a faster, and smarter `innerHTML` equivalent
- * nodes with a `name="..."` attribute are collected once
- * arrays in holes are joined with a space in between
- * no repeated render, per each node, when same template literal is used
- * a perfect tiny companion for [wickedElements](https://github.com/WebReflection/wicked-elements#readme) or [hookedElements](https://github.com/WebReflection/hooked-elements#readme)
-
-The key of _uhtml_ is size and simplicity: nothing is transformed, transpiled, or mapped, you have full freedom to define layouts and enrich these later on.
-
-```js
-import {render, html, svg} from 'uhtml';
-const {render, html, svg} = require('uhtml');
-// https://unpkg.com/uhtml
-```
-
-## API and Features in a Nutshell
-
-Anything in the template gets in as is, with the exception of arrays, joined via a space, so that classes, as well as list of elements, can get in too.
-
-```js
-import {render, html, svg} from 'uhtml';
-
-const {title, kind} = render(
- document.body,
- html`
-
Hello uHTML!
-
- Welcome to this old adventure!
-
- `
-);
-
-// every name in the template results into an element
-title.style.textDecoration = 'underline';
-kind.textContent = 'new';
-```
-
-Feel free to check this **[live counter demo](https://codepen.io/WebReflection/pen/bGdpEKB)** to better understand how this works.
-
-## F.A.Q.
-
-
-
- How is this any better than innerHTML
?
-
-
- _uhtml_ never pollutes, trash, or recreate, content defined via a template literal.
-
- In case you didn't know, [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) are unique per scope, so that defining some HTML or SVG content for a specific node passed as `render` argument, never replaces the content that was previously there, if the template literal is exactly the same.
-
- Moreover, if you use `innerHTML` for SVG content, that won't work the way you'd expect.
-
- _uhtml_ does indeed the minimum amount of processing to ensure your HTML or SVG content is injected once, and only if the template literal is different from the previous one.
-
-
-
-
-
- Can I use nested html
or svg
in the template ?
-
-
- The _TL;DR_ answer is **no**, 'cause those utilities are there to define the kind of content you want for that specific node, instrumenting few DOM APIs to provide such content within fragments.
-
- This boils down to the inability, or the anti-pattern, to have lists created within a template, unless you take over such list, through a named element, in a way that allows you to update, replace, or drop, such list later on.
-
- The [domdiff](https://github.com/WebReflection/domdiff#readme) module, in such cases, might be a solution, otherwise you are in charge of handling inner lists changes.
-
-
-
-
-
- Should I hydrate each content manually ?
-
-
- The `name` attribute simplifies the retrieval of elements within the template.
- From that time on, you are in charge of populating, or manipulating, anything you like, and per each named node.
-
- Please note that a query such as `[name]` will return anything found in the template, so that name clashing is inevitable, if you use the same attribute within other elements/components defined in your template.
-
-
diff --git a/async.d.ts b/async.d.ts
deleted file mode 100644
index a109c46..0000000
--- a/async.d.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import type { Renderable } from "./index";
-
-export declare function render(
- node: T,
- renderer: (() => Renderable) | Renderable,
-): Promise;
-
-export type { TemplateFunction, Tag, Renderable, html, svg, Hole } from "./index";
\ No newline at end of file
diff --git a/async.js b/async.js
deleted file mode 100644
index 1359ff9..0000000
--- a/async.js
+++ /dev/null
@@ -1,2 +0,0 @@
-self.uhtml=function(e){"use strict";class t extends Map{set(e,t){return super.set(e,t),t}}class n extends WeakMap{set(e,t){return super.set(e,t),t}}const{isArray:r}=Array,s=(e,t)=>{const n=[];for(const{length:l}=e;t{function t(t,n){return e.apply(this,[t].concat(n))}return function(e){return s(arguments,1).then(t.bind(this,e))}};
-/*! (c) Andrea Giammarchi - ISC */const o=/^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i,a=/<([a-z]+[a-z0-9:._-]*)([^>]*?)(\/?)>/g,c=/([^\s\\>"'=]+)\s*=\s*(['"]?)\x01/g,i=/[\x01\x02]/g;const u=(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,{isArray:d}=Array,f=e=>null==e?e:e.valueOf(),p=(e,t)=>{let n,r,s=t.slice(2);return!(t in e)&&(r=t.toLowerCase())in e&&(s=r.slice(2)),t=>{const r=d(t)?t:[t,!1];n!==r[0]&&(n&&e.removeEventListener(s,n,r[1]),(n=r[0])&&e.addEventListener(s,n,r[1]))}};const{isArray:h,prototype:m}=Array,{indexOf:g}=m,{createDocumentFragment:y,createElement:b,createElementNS:v,createTextNode:w,createTreeWalker:x,importNode:N}=new Proxy({},{get:(e,t)=>document[t].bind(document)});let A;const C=(e,t)=>t?(e=>{A||(A=v("http://www.w3.org/2000/svg","svg")),A.innerHTML=e;const t=y();return t.append(...A.childNodes),t})(e):(e=>{const t=b("template");return t.innerHTML=e,t.content})(e),k=({childNodes:e},t)=>e[t],$=(e,t,n)=>((e,t,n,r,s)=>{const l=n.length;let o=t.length,a=l,c=0,i=0,u=null;for(;cs-i){const l=r(t[c],0);for(;i{switch(t[0]){case"?":return((e,t,n)=>r=>{const s=!!f(r);n!==s&&((n=s)?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 p(e,"on"+t.slice(1));case"o":if("n"===t[1])return p(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}`,s=t[n];null==s?e.removeAttribute(r):e.setAttribute(r,s)}})(e)}return((e,t)=>{let n,r=!0;const s=document.createAttributeNS(null,t);return t=>{const l=f(t);n!==l&&(null==(n=l)?r||(e.removeAttributeNode(s),r=!0):(s.value=l,r&&(e.setAttributeNodeNS(s),r=!1)))}})(e,t)};function O(e){const{type:t,path:n}=e,r=n.reduceRight(k,this);return"node"===t?(e=>{let t,n,r=[];const s=l=>{switch(typeof l){case"string":case"number":case"boolean":t!==l&&(t=l,n||(n=w("")),n.data=l,r=$(e,r,[n]));break;case"object":case"undefined":if(null==l){t!=l&&(t=l,r=$(e,r,[]));break}if(h(l)){t=l,0===l.length?r=$(e,r,[]):"object"==typeof l[0]?r=$(e,r,l):s(String(l));break}if(t!==l)if("ELEMENT_NODE"in l)t=l,r=$(e,r,11===l.nodeType?[...l.childNodes]:[l]);else{const e=l.valueOf();e!==l&&s(e)}break;case"function":s(l(e))}};return s})(r):"attr"===t?E(r,e.name):(e=>{let t;return n=>{const r=f(n);t!=r&&(t=r,e.textContent=null==r?"":r)}})(r)}const T=e=>{const t=[];let{parentNode:n}=e;for(;n;)t.push(g.call(n.childNodes,e)),e=n,({parentNode:n}=e);return t},S="isĀµ",L=new n,M=/^(?:textarea|script|style|title|plaintext|xmp)$/,j=(e,t)=>{const n="svg"===e,r=((e,t,n)=>{let r=0;return e.join("").trim().replace(a,((e,t,r,s)=>{let l=t+r.replace(c,"=$2$1").trimEnd();return s.length&&(l+=n||o.test(t)?" /":">"+t),"<"+l+">"})).replace(i,(e=>""===e?"\x3c!--"+t+r+++"--\x3e":t+r++))})(t,S,n),s=C(r,n),l=x(s,129),u=[],d=t.length-1;let f=0,p=`${S}${f}`;for(;f{const{content:n,nodes:r}=L.get(t)||L.set(t,j(e,t)),s=N(n,!0);return{content:s,updates:r.map(O,s)}},P=(e,{type:t,template:n,values:r})=>{const s=D(e,r);let{entry:l}=e;l&&l.template===n&&l.type===t||(e.entry=l=((e,t)=>{const{content:n,updates:r}=B(e,t);return{type:e,template:t,content:n,updates:r,wire:null}})(t,n));const{content:o,updates:a,wire:c}=l;for(let e=0;e{const{firstChild:t,lastChild:n}=e;if(t===n)return n||e;const{childNodes:r}=e,s=[...r];return{ELEMENT_NODE:1,nodeType:111,firstChild:t,lastChild:n,valueOf:()=>(r.length!==s.length&&e.append(...s),e)}})(o))},D=({stack:e},t)=>{const{length:n}=t;for(let r=0;r{const r=new n;return Object.assign(((t,...n)=>new H(e,t,n)),{for(n,s){const l=r.get(n)||r.set(n,new t);return l.get(s)||l.set(s,(t=>(n,...r)=>P(t,{type:e,template:n,values:r}))({stack:[],entry:null,wire:null}))},node:(t,...n)=>P({stack:[],entry:null,wire:null},new H(e,t,n)).valueOf()})},z=new n,R=_("html"),W=_("svg"),{defineProperties:F}=Object,q=e=>{const t=new n;return F(l(e),{for:{value(n,r){const s=e.for(n,r);return t.get(s)||t.set(s,l(s))}},node:{value:l(e.node)}})},G=q(R),I=q(W);return e.Hole=H,e.html=G,e.render=(e,t)=>{const n="function"==typeof t?t():t;return Promise.resolve(n).then((t=>((e,t)=>{const n="function"==typeof t?t():t,r=z.get(e)||z.set(e,{stack:[],entry:null,wire:null}),s=n instanceof H?P(r,n):n;return s!==r.wire&&(r.wire=s,e.replaceChildren(s.valueOf())),e})(e,t)))},e.svg=I,e}({});
diff --git a/cjs/async.js b/cjs/async.js
deleted file mode 100644
index 5128b70..0000000
--- a/cjs/async.js
+++ /dev/null
@@ -1,41 +0,0 @@
-'use strict';
-const {WeakMapSet} = require('@webreflection/mapset');
-
-const asyncTag = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('async-tag'));
-
-const {render: $render, html: $html, svg: $svg} = require('./index.js');
-
-const {defineProperties} = Object;
-
-const tag = original => {
- const wrap = new WeakMapSet;
- return defineProperties(
- asyncTag(original),
- {
- for: {
- value(ref, id) {
- const tag = original.for(ref, id);
- return wrap.get(tag) || wrap.set(tag, asyncTag(tag));
- }
- },
- node: {
- value: asyncTag(original.node)
- }
- }
- );
-};
-
-const html = tag($html);
-exports.html = html;
-const svg = tag($svg);
-exports.svg = svg;
-
-const render = (where, what) => {
- const hole = typeof what === 'function' ? what() : what;
- return Promise.resolve(hole).then(what => $render(where, what));
-};
-exports.render = render;
-
-(m => {
- exports.Hole = m.Hole;
-})(require('./index.js'));
diff --git a/cjs/handlers.js b/cjs/handlers.js
deleted file mode 100644
index 95e222d..0000000
--- a/cjs/handlers.js
+++ /dev/null
@@ -1,150 +0,0 @@
-'use strict';
-const {diffable} = require('@webreflection/uwire');
-
-const {aria, attribute, boolean, event, ref, setter, text} = require('uhandlers');
-const udomdiff = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('udomdiff'));
-
-const {isArray, createTextNode} = require('./utils.js');
-
-// from a generic path, retrieves the exact targeted node
-const reducePath = ({childNodes}, i) => childNodes[i];
-
-// this helper avoid code bloat around handleAnything() callback
-const diff = (comment, oldNodes, newNodes) => udomdiff(
- comment.parentNode,
- // TODO: there is a possible edge case where a node has been
- // removed manually, or it was a keyed one, attached
- // to a shared reference between renders.
- // In this case udomdiff might fail at removing such node
- // as its parent won't be the expected one.
- // The best way to avoid this issue is to filter oldNodes
- // in search of those not live, or not in the current parent
- // anymore, but this would require both a change to uwire,
- // exposing a parentNode from the firstChild, as example,
- // but also a filter per each diff that should exclude nodes
- // that are not in there, penalizing performance quite a lot.
- // As this has been also a potential issue with domdiff,
- // and both lighterhtml and hyperHTML might fail with this
- // very specific edge case, I might as well document this possible
- // "diffing shenanigan" and call it a day.
- oldNodes,
- newNodes,
- diffable,
- comment
-);
-
-// if an interpolation represents a comment, the whole
-// diffing will be related to such comment.
-// This helper is in charge of understanding how the new
-// content for such interpolation/hole should be updated
-const handleAnything = comment => {
- let oldValue, text, nodes = [];
- const anyContent = newValue => {
- switch (typeof newValue) {
- // primitives are handled as text content
- case 'string':
- case 'number':
- case 'boolean':
- if (oldValue !== newValue) {
- oldValue = newValue;
- if (!text)
- text = createTextNode('');
- text.data = newValue;
- nodes = diff(comment, nodes, [text]);
- }
- break;
- // null, and undefined are used to cleanup previous content
- case 'object':
- case 'undefined':
- if (newValue == null) {
- if (oldValue != newValue) {
- oldValue = newValue;
- nodes = diff(comment, nodes, []);
- }
- break;
- }
- // arrays and nodes have a special treatment
- if (isArray(newValue)) {
- oldValue = newValue;
- // arrays can be used to cleanup, if empty
- if (newValue.length === 0)
- nodes = diff(comment, nodes, []);
- // or diffed, if these contains nodes or "wires"
- else if (typeof newValue[0] === 'object')
- nodes = diff(comment, nodes, newValue);
- // in all other cases the content is stringified as is
- else
- anyContent(String(newValue));
- break;
- }
- // if the new value is a DOM node, or a wire, and it's
- // different from the one already live, then it's diffed.
- // if the node is a fragment, it's appended once via its childNodes
- // There is no `else` here, meaning if the content
- // is not expected one, nothing happens, as easy as that.
- if (oldValue !== newValue) {
- if ('ELEMENT_NODE' in newValue) {
- oldValue = newValue;
- nodes = diff(
- comment,
- nodes,
- newValue.nodeType === 11 ?
- [...newValue.childNodes] :
- [newValue]
- );
- }
- else {
- const value = newValue.valueOf();
- if (value !== newValue)
- anyContent(value);
- }
- }
- break;
- case 'function':
- anyContent(newValue(comment));
- break;
- }
- };
- return anyContent;
-};
-
-// attributes can be:
-// * ref=${...} for hooks and other purposes
-// * aria=${...} for aria attributes
-// * ?boolean=${...} for boolean attributes
-// * .dataset=${...} for dataset related attributes
-// * .setter=${...} for Custom Elements setters or nodes with setters
-// such as buttons, details, options, select, etc
-// * @event=${...} to explicitly handle event listeners
-// * onevent=${...} to automatically handle event listeners
-// * generic=${...} to handle an attribute just like an attribute
-const handleAttribute = (node, name/*, svg*/) => {
- switch (name[0]) {
- case '?': return boolean(node, name.slice(1), false);
- case '.': return setter(node, name.slice(1));
- case '@': return event(node, 'on' + name.slice(1));
- case 'o': if (name[1] === 'n') return event(node, name);
- }
-
- switch (name) {
- case 'ref': return ref(node);
- case 'aria': return aria(node);
- }
-
- return attribute(node, name/*, svg*/);
-};
-
-// each mapped update carries the update type and its path
-// the type is either node, attribute, or text, while
-// the path is how to retrieve the related node to update.
-// In the attribute case, the attribute name is also carried along.
-function handlers(options) {
- const {type, path} = options;
- const node = path.reduceRight(reducePath, this);
- return type === 'node' ?
- handleAnything(node) :
- (type === 'attr' ?
- handleAttribute(node, options.name/*, options.svg*/) :
- text(node));
-}
-exports.handlers = handlers;
diff --git a/cjs/index.js b/cjs/index.js
deleted file mode 100644
index 393cc18..0000000
--- a/cjs/index.js
+++ /dev/null
@@ -1,67 +0,0 @@
-'use strict';
-const {MapSet, WeakMapSet} = require('@webreflection/mapset');
-
-const {Hole, createCache, unroll} = require('./rabbit.js');
-
-// both `html` and `svg` template literal tags are polluted
-// with a `for(ref[, id])` and a `node` tag too
-const tag = type => {
- // both `html` and `svg` tags have their own cache
- const keyed = new WeakMapSet;
- // keyed operations always re-use the same cache and unroll
- // the template and its interpolations right away
- const fixed = cache => (template, ...values) => unroll(
- cache,
- {type, template, values}
- );
- return Object.assign(
- // non keyed operations are recognized as instance of Hole
- // during the "unroll", recursively resolved and updated
- (template, ...values) => new Hole(type, template, values),
- {
- // keyed operations need a reference object, usually the parent node
- // which is showing keyed results, and optionally a unique id per each
- // related node, handy with JSON results and mutable list of objects
- // that usually carry a unique identifier
- for(ref, id) {
- const memo = keyed.get(ref) || keyed.set(ref, new MapSet);
- return memo.get(id) || memo.set(id, fixed(createCache()));
- },
- // it is possible to create one-off content out of the box via node tag
- // this might return the single created node, or a fragment with all
- // nodes present at the root level and, of course, their child nodes
- node: (template, ...values) => unroll(createCache(), new Hole(type, template, values)).valueOf()
- }
- );
-};
-
-// each rendered node gets its own cache
-const cache = new WeakMapSet;
-
-// rendering means understanding what `html` or `svg` tags returned
-// and it relates a specific node to its own unique cache.
-// Each time the content to render changes, the node is cleaned up
-// and the new new content is appended, and if such content is a Hole
-// then it's "unrolled" to resolve all its inner nodes.
-const render = (where, what) => {
- const hole = typeof what === 'function' ? what() : what;
- const info = cache.get(where) || cache.set(where, createCache());
- const wire = hole instanceof Hole ? unroll(info, hole) : hole;
- if (wire !== info.wire) {
- info.wire = wire;
- // valueOf() simply returns the node itself, but in case it was a "wire"
- // it will eventually re-append all nodes to its fragment so that such
- // fragment can be re-appended many times in a meaningful way
- // (wires are basically persistent fragments facades with special behavior)
- where.replaceChildren(wire.valueOf());
- }
- return where;
-};
-
-const html = tag('html');
-const svg = tag('svg');
-
-exports.Hole = Hole;
-exports.render = render;
-exports.html = html;
-exports.svg = svg;
diff --git a/cjs/init.js b/cjs/init.js
deleted file mode 100644
index 4abda80..0000000
--- a/cjs/init.js
+++ /dev/null
@@ -1,629 +0,0 @@
-'use strict';
-const {WeakMapSet} = require('@webreflection/mapset');
-const instrument = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('@webreflection/uparser'));
-
-const udomdiff = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('udomdiff'));
-
-module.exports = ({document}) => {
- /**start**/
-const {isArray, prototype} = Array;
-const {indexOf} = prototype;
-
-
-
-const {
- createDocumentFragment,
- createElement,
- createElementNS,
- createTextNode,
- createTreeWalker,
- importNode
-} = new Proxy({}, {
- get: (_, method) => document[method].bind(document)
-});
-
-
-
-const createHTML = html => {
- const template = createElement('template');
- template.innerHTML = html;
- return template.content;
-};
-
-let xml;
-const createSVG = svg => {
- if (!xml) xml = createElementNS('http://www.w3.org/2000/svg', 'svg');
- xml.innerHTML = svg;
- const content = createDocumentFragment();
- content.append(...xml.childNodes);
- return content;
-};
-
-const createContent = (text, svg) => svg ?
- createSVG(text) : createHTML(text);
-
-const ELEMENT_NODE = 1;
-const nodeType = 111;
-
-const remove = ({firstChild, lastChild}) => {
- const range = document.createRange();
- range.setStartAfter(firstChild);
- range.setEndAfter(lastChild);
- range.deleteContents();
- return firstChild;
-};
-
-const diffable = (node, operation) => node.nodeType === nodeType ?
- ((1 / operation) < 0 ?
- (operation ? remove(node) : node.lastChild) :
- (operation ? node.valueOf() : node.firstChild)) :
- node
-;
-
-const persistent = fragment => {
- const {firstChild, lastChild} = fragment;
- if (firstChild === lastChild)
- return lastChild || fragment;
- const {childNodes} = fragment;
- const nodes = [...childNodes];
- return {
- ELEMENT_NODE,
- nodeType,
- firstChild,
- lastChild,
- valueOf() {
- if (childNodes.length !== nodes.length)
- fragment.append(...nodes);
- return fragment;
- }
- };
-};
-
-
-
-// flag for foreign checks (slower path, fast by default)
-let useForeign = false;
-
-class Foreign {
- constructor(handler, value) {
- useForeign = true;
- this._ = (...args) => handler(...args, value);
- }
-}
-
-const foreign = (handler, value) => new Foreign(handler, value);
-
-const aria = node => values => {
- for (const key in values) {
- const name = key === 'role' ? key : `aria-${key}`;
- const value = values[key];
- if (value == null)
- node.removeAttribute(name);
- else
- node.setAttribute(name, value);
- }
-};
-
-const getValue = value => value == null ? value : value.valueOf();
-
-const attribute = (node, name) => {
- let oldValue, orphan = true;
- const attributeNode = document.createAttributeNS(null, name);
- return newValue => {
- const value = useForeign && (newValue instanceof Foreign) ?
- newValue._(node, name) : getValue(newValue);
- if (oldValue !== value) {
- if ((oldValue = value) == null) {
- if (!orphan) {
- node.removeAttributeNode(attributeNode);
- orphan = true;
- }
- }
- else {
- attributeNode.value = value;
- if (orphan) {
- node.setAttributeNodeNS(attributeNode);
- orphan = false;
- }
- }
- }
- };
-};
-
-const boolean = (node, key, oldValue) => newValue => {
- const value = !!getValue(newValue);
- if (oldValue !== value) {
- // when IE won't be around anymore ...
- // node.toggleAttribute(key, oldValue = !!value);
- if ((oldValue = value))
- node.setAttribute(key, '');
- else
- node.removeAttribute(key);
- }
-};
-
-const data = ({dataset}) => values => {
- for (const key in values) {
- const value = values[key];
- if (value == null)
- delete dataset[key];
- else
- dataset[key] = value;
- }
-};
-
-const event = (node, name) => {
- let oldValue, lower, type = name.slice(2);
- if (!(name in node) && (lower = name.toLowerCase()) in node)
- type = lower.slice(2);
- return newValue => {
- const info = isArray(newValue) ? newValue : [newValue, false];
- if (oldValue !== info[0]) {
- if (oldValue)
- node.removeEventListener(type, oldValue, info[1]);
- if (oldValue = info[0])
- node.addEventListener(type, oldValue, info[1]);
- }
- };
-};
-
-const ref = node => {
- let oldValue;
- return value => {
- if (oldValue !== value) {
- oldValue = value;
- if (typeof value === 'function')
- value(node);
- else
- value.current = node;
- }
- };
-};
-
-const setter = (node, key) => key === 'dataset' ?
- data(node) :
- value => {
- node[key] = value;
- };
-
-const text = node => {
- let oldValue;
- return newValue => {
- const value = getValue(newValue);
- if (oldValue != value) {
- oldValue = value;
- node.textContent = value == null ? '' : value;
- }
- };
-};
-
-
-
-
-
-
-
-
-// from a generic path, retrieves the exact targeted node
-const reducePath = ({childNodes}, i) => childNodes[i];
-
-// this helper avoid code bloat around handleAnything() callback
-const diff = (comment, oldNodes, newNodes) => udomdiff(
- comment.parentNode,
- // TODO: there is a possible edge case where a node has been
- // removed manually, or it was a keyed one, attached
- // to a shared reference between renders.
- // In this case udomdiff might fail at removing such node
- // as its parent won't be the expected one.
- // The best way to avoid this issue is to filter oldNodes
- // in search of those not live, or not in the current parent
- // anymore, but this would require both a change to uwire,
- // exposing a parentNode from the firstChild, as example,
- // but also a filter per each diff that should exclude nodes
- // that are not in there, penalizing performance quite a lot.
- // As this has been also a potential issue with domdiff,
- // and both lighterhtml and hyperHTML might fail with this
- // very specific edge case, I might as well document this possible
- // "diffing shenanigan" and call it a day.
- oldNodes,
- newNodes,
- diffable,
- comment
-);
-
-// if an interpolation represents a comment, the whole
-// diffing will be related to such comment.
-// This helper is in charge of understanding how the new
-// content for such interpolation/hole should be updated
-const handleAnything = comment => {
- let oldValue, text, nodes = [];
- const anyContent = newValue => {
- switch (typeof newValue) {
- // primitives are handled as text content
- case 'string':
- case 'number':
- case 'boolean':
- if (oldValue !== newValue) {
- oldValue = newValue;
- if (!text)
- text = createTextNode('');
- text.data = newValue;
- nodes = diff(comment, nodes, [text]);
- }
- break;
- // null, and undefined are used to cleanup previous content
- case 'object':
- case 'undefined':
- if (newValue == null) {
- if (oldValue != newValue) {
- oldValue = newValue;
- nodes = diff(comment, nodes, []);
- }
- break;
- }
- // arrays and nodes have a special treatment
- if (isArray(newValue)) {
- oldValue = newValue;
- // arrays can be used to cleanup, if empty
- if (newValue.length === 0)
- nodes = diff(comment, nodes, []);
- // or diffed, if these contains nodes or "wires"
- else if (typeof newValue[0] === 'object')
- nodes = diff(comment, nodes, newValue);
- // in all other cases the content is stringified as is
- else
- anyContent(String(newValue));
- break;
- }
- // if the new value is a DOM node, or a wire, and it's
- // different from the one already live, then it's diffed.
- // if the node is a fragment, it's appended once via its childNodes
- // There is no `else` here, meaning if the content
- // is not expected one, nothing happens, as easy as that.
- if (oldValue !== newValue) {
- if ('ELEMENT_NODE' in newValue) {
- oldValue = newValue;
- nodes = diff(
- comment,
- nodes,
- newValue.nodeType === 11 ?
- [...newValue.childNodes] :
- [newValue]
- );
- }
- else {
- const value = newValue.valueOf();
- if (value !== newValue)
- anyContent(value);
- }
- }
- break;
- case 'function':
- anyContent(newValue(comment));
- break;
- }
- };
- return anyContent;
-};
-
-// attributes can be:
-// * ref=${...} for hooks and other purposes
-// * aria=${...} for aria attributes
-// * ?boolean=${...} for boolean attributes
-// * .dataset=${...} for dataset related attributes
-// * .setter=${...} for Custom Elements setters or nodes with setters
-// such as buttons, details, options, select, etc
-// * @event=${...} to explicitly handle event listeners
-// * onevent=${...} to automatically handle event listeners
-// * generic=${...} to handle an attribute just like an attribute
-const handleAttribute = (node, name/*, svg*/) => {
- switch (name[0]) {
- case '?': return boolean(node, name.slice(1), false);
- case '.': return setter(node, name.slice(1));
- case '@': return event(node, 'on' + name.slice(1));
- case 'o': if (name[1] === 'n') return event(node, name);
- }
-
- switch (name) {
- case 'ref': return ref(node);
- case 'aria': return aria(node);
- }
-
- return attribute(node, name/*, svg*/);
-};
-
-// each mapped update carries the update type and its path
-// the type is either node, attribute, or text, while
-// the path is how to retrieve the related node to update.
-// In the attribute case, the attribute name is also carried along.
-function handlers(options) {
- const {type, path} = options;
- const node = path.reduceRight(reducePath, this);
- return type === 'node' ?
- handleAnything(node) :
- (type === 'attr' ?
- handleAttribute(node, options.name/*, options.svg*/) :
- text(node));
-};
-
-
-
-
-
-
-
-
-// from a fragment container, create an array of indexes
-// related to its child nodes, so that it's possible
-// to retrieve later on exact node via reducePath
-const createPath = node => {
- const path = [];
- let {parentNode} = node;
- while (parentNode) {
- path.push(indexOf.call(parentNode.childNodes, node));
- node = parentNode;
- ({parentNode} = node);
- }
- return path;
-};
-
-// the prefix is used to identify either comments, attributes, or nodes
-// that contain the related unique id. In the attribute cases
-// isĀµX="attribute-name" will be used to map current X update to that
-// attribute name, while comments will be like , to map
-// the update to that specific comment node, hence its parent.
-// style and textarea will have text content, and are handled
-// directly through text-only updates.
-const prefix = 'isĀµ';
-
-// Template Literals are unique per scope and static, meaning a template
-// should be parsed once, and once only, as it will always represent the same
-// content, within the exact same amount of updates each time.
-// This cache relates each template to its unique content and updates.
-const cache = new WeakMapSet;
-
-// a RegExp that helps checking nodes that cannot contain comments
-const textOnly = /^(?:textarea|script|style|title|plaintext|xmp)$/;
-
-const createCache = () => ({
- stack: [], // each template gets a stack for each interpolation "hole"
-
- entry: null, // each entry contains details, such as:
- // * the template that is representing
- // * the type of node it represents (html or svg)
- // * the content fragment with all nodes
- // * the list of updates per each node (template holes)
- // * the "wired" node or fragment that will get updates
- // if the template or type are different from the previous one
- // the entry gets re-created each time
-
- wire: null // each rendered node represent some wired content and
- // this reference to the latest one. If different, the node
- // will be cleaned up and the new "wire" will be appended
-});
-
-// the entry stored in the rendered node cache, and per each "hole"
-const createEntry = (type, template) => {
- const {content, updates} = mapUpdates(type, template);
- return {type, template, content, updates, wire: null};
-};
-
-// a template is instrumented to be able to retrieve where updates are needed.
-// Each unique template becomes a fragment, cloned once per each other
-// operation based on the same template, i.e. data => html`${data}
`
-const mapTemplate = (type, template) => {
- const svg = type === 'svg';
- const text = instrument(template, prefix, svg);
- const content = createContent(text, svg);
- // once instrumented and reproduced as fragment, it's crawled
- // to find out where each update is in the fragment tree
- const tw = createTreeWalker(content, 1 | 128);
- const nodes = [];
- const length = template.length - 1;
- let i = 0;
- // updates are searched via unique names, linearly increased across the tree
- //
- let search = `${prefix}${i}`;
- while (i < length) {
- const node = tw.nextNode();
- // if not all updates are bound but there's nothing else to crawl
- // it means that there is something wrong with the template.
- if (!node)
- throw `bad template: ${text}`;
- // if the current node is a comment, and it contains isĀµX
- // it means the update should take care of any content
- if (node.nodeType === 8) {
- // The only comments to be considered are those
- // which content is exactly the same as the searched one.
- if (node.data === search) {
- nodes.push({type: 'node', path: createPath(node)});
- search = `${prefix}${++i}`;
- }
- }
- else {
- // if the node is not a comment, loop through all its attributes
- // named isĀµX and relate attribute updates to this node and the
- // attribute name, retrieved through node.getAttribute("isĀµX")
- // the isĀµX attribute will be removed as irrelevant for the layout
- // let svg = -1;
- while (node.hasAttribute(search)) {
- nodes.push({
- type: 'attr',
- path: createPath(node),
- name: node.getAttribute(search)
- });
- node.removeAttribute(search);
- search = `${prefix}${++i}`;
- }
- // if the node was a style, textarea, or others, check its content
- // and if it is then update tex-only this node
- if (
- textOnly.test(node.localName) &&
- node.textContent.trim() === ``
- ){
- node.textContent = '';
- nodes.push({type: 'text', path: createPath(node)});
- search = `${prefix}${++i}`;
- }
- }
- }
- // once all nodes to update, or their attributes, are known, the content
- // will be cloned in the future to represent the template, and all updates
- // related to such content retrieved right away without needing to re-crawl
- // the exact same template, and its content, more than once.
- return {content, nodes};
-};
-
-// if a template is unknown, perform the previous mapping, otherwise grab
-// its details such as the fragment with all nodes, and updates info.
-const mapUpdates = (type, template) => {
- const {content, nodes} = (
- cache.get(template) ||
- cache.set(template, mapTemplate(type, template))
- );
- // clone deeply the fragment
- const fragment = importNode(content, true);
- // and relate an update handler per each node that needs one
- const updates = nodes.map(handlers, fragment);
- // return the fragment and all updates to use within its nodes
- return {content: fragment, updates};
-};
-
-// as html and svg can be nested calls, but no parent node is known
-// until rendered somewhere, the unroll operation is needed to
-// discover what to do with each interpolation, which will result
-// into an update operation.
-const unroll = (info, {type, template, values}) => {
- // interpolations can contain holes and arrays, so these need
- // to be recursively discovered
- const length = unrollValues(info, values);
- let {entry} = info;
- // if the cache entry is either null or different from the template
- // and the type this unroll should resolve, create a new entry
- // assigning a new content fragment and the list of updates.
- if (!entry || (entry.template !== template || entry.type !== type))
- info.entry = (entry = createEntry(type, template));
- const {content, updates, wire} = entry;
- // even if the fragment and its nodes is not live yet,
- // it is already possible to update via interpolations values.
- for (let i = 0; i < length; i++)
- updates[i](values[i]);
- // if the entry was new, or representing a different template or type,
- // create a new persistent entity to use during diffing.
- // This is simply a DOM node, when the template has a single container,
- // as in ``, or a "wire" in `` and similar cases.
- return wire || (entry.wire = persistent(content));
-};
-
-// the stack retains, per each interpolation value, the cache
-// related to each interpolation value, or null, if the render
-// was conditional and the value is not special (Array or Hole)
-const unrollValues = ({stack}, values) => {
- const {length} = values;
- for (let i = 0; i < length; i++) {
- const hole = values[i];
- // each Hole gets unrolled and re-assigned as value
- // so that domdiff will deal with a node/wire, not with a hole
- if (hole instanceof Hole)
- values[i] = unroll(
- stack[i] || (stack[i] = createCache()),
- hole
- );
- // arrays are recursively resolved so that each entry will contain
- // also a DOM node or a wire, hence it can be diffed if/when needed
- else if (isArray(hole))
- unrollValues(stack[i] || (stack[i] = createCache()), hole);
- // if the value is nothing special, the stack doesn't need to retain data
- // this is useful also to cleanup previously retained data, if the value
- // was a Hole, or an Array, but not anymore, i.e.:
- // const update = content => html`${content}
`;
- // update(listOfItems); update(null); update(html`hole`)
- else
- stack[i] = null;
- }
- if (length < stack.length)
- stack.splice(length);
- return length;
-};
-
-/**
- * Holds all details wrappers needed to render the content further on.
- * @constructor
- * @param {string} type The hole type, either `html` or `svg`.
- * @param {string[]} template The template literals used to the define the content.
- * @param {Array} values Zero, one, or more interpolated values to render.
- */
-class Hole {
- constructor(type, template, values) {
- this.type = type;
- this.template = template;
- this.values = values;
- }
-};
-
-
-
-
-
-// both `html` and `svg` template literal tags are polluted
-// with a `for(ref[, id])` and a `node` tag too
-const tag = type => {
- // both `html` and `svg` tags have their own _cache
- const keyed = new WeakMapSet;
- // keyed operations always re-use the same _cache and unroll
- // the template and its interpolations right away
- const fixed = _cache => (template, ...values) => unroll(
- _cache,
- {type, template, values}
- );
- return Object.assign(
- // non keyed operations are recognized as instance of Hole
- // during the "unroll", recursively resolved and updated
- (template, ...values) => new Hole(type, template, values),
- {
- // keyed operations need a reference object, usually the parent node
- // which is showing keyed results, and optionally a unique id per each
- // related node, handy with JSON results and mutable list of objects
- // that usually carry a unique identifier
- for(ref, id) {
- const memo = keyed.get(ref) || keyed.set(ref, new MapSet);
- return memo.get(id) || memo.set(id, fixed(createCache()));
- },
- // it is possible to create one-off content out of the box via node tag
- // this might return the single created node, or a fragment with all
- // nodes present at the root level and, of course, their child nodes
- node: (template, ...values) => unroll(createCache(), new Hole(type, template, values)).valueOf()
- }
- );
-};
-
-// each rendered node gets its own _cache
-const _cache = new WeakMapSet;
-
-// rendering means understanding what `html` or `svg` tags returned
-// and it relates a specific node to its own unique _cache.
-// Each time the content to render changes, the node is cleaned up
-// and the new new content is appended, and if such content is a Hole
-// then it's "unrolled" to resolve all its inner nodes.
-const render = (where, what) => {
- const hole = typeof what === 'function' ? what() : what;
- const info = _cache.get(where) || _cache.set(where, createCache());
- const wire = hole instanceof Hole ? unroll(info, hole) : hole;
- if (wire !== info.wire) {
- info.wire = wire;
- // valueOf() simply returns the node itself, but in case it was a "wire"
- // it will eventually re-append all nodes to its fragment so that such
- // fragment can be re-appended many times in a meaningful way
- // (wires are basically persistent fragments facades with special behavior)
- where.replaceChildren(wire.valueOf());
- }
- return where;
-};
-
-const html = tag('html');
-const svg = tag('svg');
-
-return {Hole, render, html, svg};
-
-/**end**/
-};
diff --git a/cjs/json.js b/cjs/json.js
deleted file mode 100644
index 07028df..0000000
--- a/cjs/json.js
+++ /dev/null
@@ -1,52 +0,0 @@
-'use strict';
-const {MapSet, WeakMapSet} = require('@webreflection/mapset');
-
-const {render: $render, html, svg} = require('./index.js');
-
-// Sender (SW, Worker, postMessage)
-const ids = new WeakMapSet;
-let id = 0;
-
-const tag = type => (template, ...values) => ({
- id: ids.get(template) ||
- ids.set(template, id++),
- type,
- values,
- template
-});
-
-html.json = tag('html');
-svg.json = tag('svg');
-
-// Receiver (onmessage, from SW, Workers, etc)
-const templates = new MapSet;
-
-const unroll = ({type, template, values, id}) => (
- (type === 'svg' ? svg : html).apply(
- null,
- [
- templates.get(id) ||
- templates.set(id, template)
- ].concat(values.map(asJSON))
- )
-);
-
-const asJSON = value => isJSON(value) ? unroll(value) : value;
-
-const isJSON = thing => (
- typeof thing === 'object' &&
- thing !== null &&
- 'id' in thing &&
- 'type' in thing &&
- 'values' in thing &&
- 'template' in thing
-);
-
-const render = (where, what) => $render(
- where,
- isJSON(what) ? unroll(what) : what
-);
-
-exports.render = render;
-exports.html = html;
-exports.svg = svg;
diff --git a/cjs/rabbit.js b/cjs/rabbit.js
deleted file mode 100644
index d38cc8a..0000000
--- a/cjs/rabbit.js
+++ /dev/null
@@ -1,218 +0,0 @@
-'use strict';
-const {WeakMapSet} = require('@webreflection/mapset');
-const instrument = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('@webreflection/uparser'));
-const {persistent} = require('@webreflection/uwire');
-
-const {handlers} = require('./handlers.js');
-const {isArray, indexOf, createContent, createTreeWalker, importNode} = require('./utils.js');
-
-// from a fragment container, create an array of indexes
-// related to its child nodes, so that it's possible
-// to retrieve later on exact node via reducePath
-const createPath = node => {
- const path = [];
- let {parentNode} = node;
- while (parentNode) {
- path.push(indexOf.call(parentNode.childNodes, node));
- node = parentNode;
- ({parentNode} = node);
- }
- return path;
-};
-
-// the prefix is used to identify either comments, attributes, or nodes
-// that contain the related unique id. In the attribute cases
-// isĀµX="attribute-name" will be used to map current X update to that
-// attribute name, while comments will be like , to map
-// the update to that specific comment node, hence its parent.
-// style and textarea will have text content, and are handled
-// directly through text-only updates.
-const prefix = 'isĀµ';
-
-// Template Literals are unique per scope and static, meaning a template
-// should be parsed once, and once only, as it will always represent the same
-// content, within the exact same amount of updates each time.
-// This cache relates each template to its unique content and updates.
-const cache = new WeakMapSet;
-
-// a RegExp that helps checking nodes that cannot contain comments
-const textOnly = /^(?:textarea|script|style|title|plaintext|xmp)$/;
-
-const createCache = () => ({
- stack: [], // each template gets a stack for each interpolation "hole"
-
- entry: null, // each entry contains details, such as:
- // * the template that is representing
- // * the type of node it represents (html or svg)
- // * the content fragment with all nodes
- // * the list of updates per each node (template holes)
- // * the "wired" node or fragment that will get updates
- // if the template or type are different from the previous one
- // the entry gets re-created each time
-
- wire: null // each rendered node represent some wired content and
- // this reference to the latest one. If different, the node
- // will be cleaned up and the new "wire" will be appended
-});
-exports.createCache = createCache;
-
-// the entry stored in the rendered node cache, and per each "hole"
-const createEntry = (type, template) => {
- const {content, updates} = mapUpdates(type, template);
- return {type, template, content, updates, wire: null};
-};
-
-// a template is instrumented to be able to retrieve where updates are needed.
-// Each unique template becomes a fragment, cloned once per each other
-// operation based on the same template, i.e. data => html`${data}
`
-const mapTemplate = (type, template) => {
- const svg = type === 'svg';
- const text = instrument(template, prefix, svg);
- const content = createContent(text, svg);
- // once instrumented and reproduced as fragment, it's crawled
- // to find out where each update is in the fragment tree
- const tw = createTreeWalker(content, 1 | 128);
- const nodes = [];
- const length = template.length - 1;
- let i = 0;
- // updates are searched via unique names, linearly increased across the tree
- //
- let search = `${prefix}${i}`;
- while (i < length) {
- const node = tw.nextNode();
- // if not all updates are bound but there's nothing else to crawl
- // it means that there is something wrong with the template.
- if (!node)
- throw `bad template: ${text}`;
- // if the current node is a comment, and it contains isĀµX
- // it means the update should take care of any content
- if (node.nodeType === 8) {
- // The only comments to be considered are those
- // which content is exactly the same as the searched one.
- if (node.data === search) {
- nodes.push({type: 'node', path: createPath(node)});
- search = `${prefix}${++i}`;
- }
- }
- else {
- // if the node is not a comment, loop through all its attributes
- // named isĀµX and relate attribute updates to this node and the
- // attribute name, retrieved through node.getAttribute("isĀµX")
- // the isĀµX attribute will be removed as irrelevant for the layout
- // let svg = -1;
- while (node.hasAttribute(search)) {
- nodes.push({
- type: 'attr',
- path: createPath(node),
- name: node.getAttribute(search)
- });
- node.removeAttribute(search);
- search = `${prefix}${++i}`;
- }
- // if the node was a style, textarea, or others, check its content
- // and if it is then update tex-only this node
- if (
- textOnly.test(node.localName) &&
- node.textContent.trim() === ``
- ){
- node.textContent = '';
- nodes.push({type: 'text', path: createPath(node)});
- search = `${prefix}${++i}`;
- }
- }
- }
- // once all nodes to update, or their attributes, are known, the content
- // will be cloned in the future to represent the template, and all updates
- // related to such content retrieved right away without needing to re-crawl
- // the exact same template, and its content, more than once.
- return {content, nodes};
-};
-
-// if a template is unknown, perform the previous mapping, otherwise grab
-// its details such as the fragment with all nodes, and updates info.
-const mapUpdates = (type, template) => {
- const {content, nodes} = (
- cache.get(template) ||
- cache.set(template, mapTemplate(type, template))
- );
- // clone deeply the fragment
- const fragment = importNode(content, true);
- // and relate an update handler per each node that needs one
- const updates = nodes.map(handlers, fragment);
- // return the fragment and all updates to use within its nodes
- return {content: fragment, updates};
-};
-
-// as html and svg can be nested calls, but no parent node is known
-// until rendered somewhere, the unroll operation is needed to
-// discover what to do with each interpolation, which will result
-// into an update operation.
-const unroll = (info, {type, template, values}) => {
- // interpolations can contain holes and arrays, so these need
- // to be recursively discovered
- const length = unrollValues(info, values);
- let {entry} = info;
- // if the cache entry is either null or different from the template
- // and the type this unroll should resolve, create a new entry
- // assigning a new content fragment and the list of updates.
- if (!entry || (entry.template !== template || entry.type !== type))
- info.entry = (entry = createEntry(type, template));
- const {content, updates, wire} = entry;
- // even if the fragment and its nodes is not live yet,
- // it is already possible to update via interpolations values.
- for (let i = 0; i < length; i++)
- updates[i](values[i]);
- // if the entry was new, or representing a different template or type,
- // create a new persistent entity to use during diffing.
- // This is simply a DOM node, when the template has a single container,
- // as in ``, or a "wire" in `` and similar cases.
- return wire || (entry.wire = persistent(content));
-};
-exports.unroll = unroll;
-
-// the stack retains, per each interpolation value, the cache
-// related to each interpolation value, or null, if the render
-// was conditional and the value is not special (Array or Hole)
-const unrollValues = ({stack}, values) => {
- const {length} = values;
- for (let i = 0; i < length; i++) {
- const hole = values[i];
- // each Hole gets unrolled and re-assigned as value
- // so that domdiff will deal with a node/wire, not with a hole
- if (hole instanceof Hole)
- values[i] = unroll(
- stack[i] || (stack[i] = createCache()),
- hole
- );
- // arrays are recursively resolved so that each entry will contain
- // also a DOM node or a wire, hence it can be diffed if/when needed
- else if (isArray(hole))
- unrollValues(stack[i] || (stack[i] = createCache()), hole);
- // if the value is nothing special, the stack doesn't need to retain data
- // this is useful also to cleanup previously retained data, if the value
- // was a Hole, or an Array, but not anymore, i.e.:
- // const update = content => html`${content}
`;
- // update(listOfItems); update(null); update(html`hole`)
- else
- stack[i] = null;
- }
- if (length < stack.length)
- stack.splice(length);
- return length;
-};
-
-/**
- * Holds all details wrappers needed to render the content further on.
- * @constructor
- * @param {string} type The hole type, either `html` or `svg`.
- * @param {string[]} template The template literals used to the define the content.
- * @param {Array} values Zero, one, or more interpolated values to render.
- */
-class Hole {
- constructor(type, template, values) {
- this.type = type;
- this.template = template;
- this.values = values;
- }
-}
-exports.Hole = Hole;
diff --git a/cjs/utils.js b/cjs/utils.js
deleted file mode 100644
index 61703ae..0000000
--- a/cjs/utils.js
+++ /dev/null
@@ -1,40 +0,0 @@
-'use strict';
-const {isArray, prototype} = Array;
-const {indexOf} = prototype;
-
-exports.isArray = isArray;
-exports.indexOf = indexOf;
-
-const {
- createDocumentFragment,
- createElement,
- createElementNS,
- createTextNode,
- createTreeWalker,
- importNode
-} = new Proxy({}, {
- get: (_, method) => document[method].bind(document)
-});
-
-exports.createTextNode = createTextNode;
-exports.createTreeWalker = createTreeWalker;
-exports.importNode = importNode;
-
-const createHTML = html => {
- const template = createElement('template');
- template.innerHTML = html;
- return template.content;
-};
-
-let xml;
-const createSVG = svg => {
- if (!xml) xml = createElementNS('http://www.w3.org/2000/svg', 'svg');
- xml.innerHTML = svg;
- const content = createDocumentFragment();
- content.append(...xml.childNodes);
- return content;
-};
-
-const createContent = (text, svg) => svg ?
- createSVG(text) : createHTML(text);
-exports.createContent = createContent;
diff --git a/cjs/x.js b/cjs/x.js
deleted file mode 100644
index acc4e2d..0000000
--- a/cjs/x.js
+++ /dev/null
@@ -1,14 +0,0 @@
-'use strict';
-const {createPragma} = require('jsx2tag');
-const {html} = require('./index.js');
-
-const createElement = createPragma(html);
-self.React = {
- createElement,
- Fragment: createElement
-};
-
-(m => Object.keys(m).map(k => k !== 'default' && (exports[k] = m[k])))
-(require('jsx2tag'));
-(m => Object.keys(m).map(k => k !== 'default' && (exports[k] = m[k])))
-(require('./index.js'));
diff --git a/es.js b/es.js
deleted file mode 100644
index c9d0574..0000000
--- a/es.js
+++ /dev/null
@@ -1,2 +0,0 @@
-self.uhtml=function(e){"use strict";class t extends Map{set(e,t){return super.set(e,t),t}}class n extends WeakMap{set(e,t){return super.set(e,t),t}}
-/*! (c) Andrea Giammarchi - ISC */const r=/^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i,s=/<([a-z]+[a-z0-9:._-]*)([^>]*?)(\/?)>/g,l=/([^\s\\>"'=]+)\s*=\s*(['"]?)\x01/g,o=/[\x01\x02]/g;const a=(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,{isArray:i}=Array,c=e=>null==e?e:e.valueOf(),u=(e,t)=>{let n,r,s=t.slice(2);return!(t in e)&&(r=t.toLowerCase())in e&&(s=r.slice(2)),t=>{const r=i(t)?t:[t,!1];n!==r[0]&&(n&&e.removeEventListener(s,n,r[1]),(n=r[0])&&e.addEventListener(s,n,r[1]))}};const{isArray:d,prototype:p}=Array,{indexOf:f}=p,{createDocumentFragment:h,createElement:m,createElementNS:g,createTextNode:y,createTreeWalker:b,importNode:w}=new Proxy({},{get:(e,t)=>document[t].bind(document)});let v;const x=(e,t)=>t?(e=>{v||(v=g("http://www.w3.org/2000/svg","svg")),v.innerHTML=e;const t=h();return t.append(...v.childNodes),t})(e):(e=>{const t=m("template");return t.innerHTML=e,t.content})(e),N=({childNodes:e},t)=>e[t],C=(e,t,n)=>((e,t,n,r,s)=>{const l=n.length;let o=t.length,a=l,i=0,c=0,u=null;for(;is-c){const l=r(t[i],0);for(;c{switch(t[0]){case"?":return((e,t,n)=>r=>{const s=!!c(r);n!==s&&((n=s)?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 u(e,"on"+t.slice(1));case"o":if("n"===t[1])return u(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}`,s=t[n];null==s?e.removeAttribute(r):e.setAttribute(r,s)}})(e)}return((e,t)=>{let n,r=!0;const s=document.createAttributeNS(null,t);return t=>{const l=c(t);n!==l&&(null==(n=l)?r||(e.removeAttributeNode(s),r=!0):(s.value=l,r&&(e.setAttributeNodeNS(s),r=!1)))}})(e,t)};function k(e){const{type:t,path:n}=e,r=n.reduceRight(N,this);return"node"===t?(e=>{let t,n,r=[];const s=l=>{switch(typeof l){case"string":case"number":case"boolean":t!==l&&(t=l,n||(n=y("")),n.data=l,r=C(e,r,[n]));break;case"object":case"undefined":if(null==l){t!=l&&(t=l,r=C(e,r,[]));break}if(d(l)){t=l,0===l.length?r=C(e,r,[]):"object"==typeof l[0]?r=C(e,r,l):s(String(l));break}if(t!==l)if("ELEMENT_NODE"in l)t=l,r=C(e,r,11===l.nodeType?[...l.childNodes]:[l]);else{const e=l.valueOf();e!==l&&s(e)}break;case"function":s(l(e))}};return s})(r):"attr"===t?A(r,e.name):(e=>{let t;return n=>{const r=c(n);t!=r&&(t=r,e.textContent=null==r?"":r)}})(r)}const $=e=>{const t=[];let{parentNode:n}=e;for(;n;)t.push(f.call(n.childNodes,e)),e=n,({parentNode:n}=e);return t},E="isĀµ",O=new n,T=/^(?:textarea|script|style|title|plaintext|xmp)$/,S=(e,t)=>{const n="svg"===e,a=((e,t,n)=>{let a=0;return e.join("").trim().replace(s,((e,t,s,o)=>{let a=t+s.replace(l,"=$2$1").trimEnd();return o.length&&(a+=n||r.test(t)?" /":">"+t),"<"+a+">"})).replace(o,(e=>""===e?"\x3c!--"+t+a+++"--\x3e":t+a++))})(t,E,n),i=x(a,n),c=b(i,129),u=[],d=t.length-1;let p=0,f=`${E}${p}`;for(;p{const{content:n,nodes:r}=O.get(t)||O.set(t,S(e,t)),s=w(n,!0);return{content:s,updates:r.map(k,s)}},M=(e,{type:t,template:n,values:r})=>{const s=j(e,r);let{entry:l}=e;l&&l.template===n&&l.type===t||(e.entry=l=((e,t)=>{const{content:n,updates:r}=L(e,t);return{type:e,template:t,content:n,updates:r,wire:null}})(t,n));const{content:o,updates:a,wire:i}=l;for(let e=0;e{const{firstChild:t,lastChild:n}=e;if(t===n)return n||e;const{childNodes:r}=e,s=[...r];return{ELEMENT_NODE:1,nodeType:111,firstChild:t,lastChild:n,valueOf:()=>(r.length!==s.length&&e.append(...s),e)}})(o))},j=({stack:e},t)=>{const{length:n}=t;for(let r=0;r{const r=new n;return Object.assign(((t,...n)=>new B(e,t,n)),{for(n,s){const l=r.get(n)||r.set(n,new t);return l.get(s)||l.set(s,(t=>(n,...r)=>M(t,{type:e,template:n,values:r}))({stack:[],entry:null,wire:null}))},node:(t,...n)=>M({stack:[],entry:null,wire:null},new B(e,t,n)).valueOf()})},H=new n,_=D("html"),z=D("svg");return e.Hole=B,e.html=_,e.render=(e,t)=>{const n="function"==typeof t?t():t,r=H.get(e)||H.set(e,{stack:[],entry:null,wire:null}),s=n instanceof B?M(r,n):n;return s!==r.wire&&(r.wire=s,e.replaceChildren(s.valueOf())),e},e.svg=z,e}({});
diff --git a/esm.js b/esm.js
deleted file mode 100644
index c06dc21..0000000
--- a/esm.js
+++ /dev/null
@@ -1,2 +0,0 @@
-class e extends Map{set(e,t){return super.set(e,t),t}}class t extends WeakMap{set(e,t){return super.set(e,t),t}}
-/*! (c) Andrea Giammarchi - ISC */const n=/^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i,r=/<([a-z]+[a-z0-9:._-]*)([^>]*?)(\/?)>/g,s=/([^\s\\>"'=]+)\s*=\s*(['"]?)\x01/g,l=/[\x01\x02]/g;const o=(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,{isArray:a}=Array,i=e=>null==e?e:e.valueOf(),c=(e,t)=>{let n,r,s=t.slice(2);return!(t in e)&&(r=t.toLowerCase())in e&&(s=r.slice(2)),t=>{const r=a(t)?t:[t,!1];n!==r[0]&&(n&&e.removeEventListener(s,n,r[1]),(n=r[0])&&e.addEventListener(s,n,r[1]))}};const{isArray:u,prototype:d}=Array,{indexOf:p}=d,{createDocumentFragment:f,createElement:h,createElementNS:m,createTextNode:g,createTreeWalker:y,importNode:b}=new Proxy({},{get:(e,t)=>document[t].bind(document)});let w;const x=(e,t)=>t?(e=>{w||(w=m("http://www.w3.org/2000/svg","svg")),w.innerHTML=e;const t=f();return t.append(...w.childNodes),t})(e):(e=>{const t=h("template");return t.innerHTML=e,t.content})(e),v=({childNodes:e},t)=>e[t],N=(e,t,n)=>((e,t,n,r,s)=>{const l=n.length;let o=t.length,a=l,i=0,c=0,u=null;for(;is-c){const l=r(t[i],0);for(;c{switch(t[0]){case"?":return((e,t,n)=>r=>{const s=!!i(r);n!==s&&((n=s)?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 c(e,"on"+t.slice(1));case"o":if("n"===t[1])return c(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}`,s=t[n];null==s?e.removeAttribute(r):e.setAttribute(r,s)}})(e)}return((e,t)=>{let n,r=!0;const s=document.createAttributeNS(null,t);return t=>{const l=i(t);n!==l&&(null==(n=l)?r||(e.removeAttributeNode(s),r=!0):(s.value=l,r&&(e.setAttributeNodeNS(s),r=!1)))}})(e,t)};function A(e){const{type:t,path:n}=e,r=n.reduceRight(v,this);return"node"===t?(e=>{let t,n,r=[];const s=l=>{switch(typeof l){case"string":case"number":case"boolean":t!==l&&(t=l,n||(n=g("")),n.data=l,r=N(e,r,[n]));break;case"object":case"undefined":if(null==l){t!=l&&(t=l,r=N(e,r,[]));break}if(u(l)){t=l,0===l.length?r=N(e,r,[]):"object"==typeof l[0]?r=N(e,r,l):s(String(l));break}if(t!==l)if("ELEMENT_NODE"in l)t=l,r=N(e,r,11===l.nodeType?[...l.childNodes]:[l]);else{const e=l.valueOf();e!==l&&s(e)}break;case"function":s(l(e))}};return s})(r):"attr"===t?C(r,e.name):(e=>{let t;return n=>{const r=i(n);t!=r&&(t=r,e.textContent=null==r?"":r)}})(r)}const k=e=>{const t=[];let{parentNode:n}=e;for(;n;)t.push(p.call(n.childNodes,e)),e=n,({parentNode:n}=e);return t},$="isĀµ",E=new t,O=/^(?:textarea|script|style|title|plaintext|xmp)$/,T=(e,t)=>{const o="svg"===e,a=((e,t,o)=>{let a=0;return e.join("").trim().replace(r,((e,t,r,l)=>{let a=t+r.replace(s,"=$2$1").trimEnd();return l.length&&(a+=o||n.test(t)?" /":">"+t),"<"+a+">"})).replace(l,(e=>""===e?"\x3c!--"+t+a+++"--\x3e":t+a++))})(t,$,o),i=x(a,o),c=y(i,129),u=[],d=t.length-1;let p=0,f=`${$}${p}`;for(;p{const{content:n,nodes:r}=E.get(t)||E.set(t,T(e,t)),s=b(n,!0);return{content:s,updates:r.map(A,s)}},L=(e,{type:t,template:n,values:r})=>{const s=M(e,r);let{entry:l}=e;l&&l.template===n&&l.type===t||(e.entry=l=((e,t)=>{const{content:n,updates:r}=S(e,t);return{type:e,template:t,content:n,updates:r,wire:null}})(t,n));const{content:o,updates:a,wire:i}=l;for(let e=0;e{const{firstChild:t,lastChild:n}=e;if(t===n)return n||e;const{childNodes:r}=e,s=[...r];return{ELEMENT_NODE:1,nodeType:111,firstChild:t,lastChild:n,valueOf:()=>(r.length!==s.length&&e.append(...s),e)}})(o))},M=({stack:e},t)=>{const{length:n}=t;for(let r=0;r{const r=new t;return Object.assign(((e,...t)=>new j(n,e,t)),{for(t,s){const l=r.get(t)||r.set(t,new e);return l.get(s)||l.set(s,(e=>(t,...r)=>L(e,{type:n,template:t,values:r}))({stack:[],entry:null,wire:null}))},node:(e,...t)=>L({stack:[],entry:null,wire:null},new j(n,e,t)).valueOf()})},D=new t,_=(e,t)=>{const n="function"==typeof t?t():t,r=D.get(e)||D.set(e,{stack:[],entry:null,wire:null}),s=n instanceof j?L(r,n):n;return s!==r.wire&&(r.wire=s,e.replaceChildren(s.valueOf())),e},z=B("html"),H=B("svg");export{j as Hole,z as html,_ as render,H as svg};
diff --git a/esm/async.js b/esm/async.js
deleted file mode 100644
index 812fb92..0000000
--- a/esm/async.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import {WeakMapSet} from '@webreflection/mapset';
-
-import asyncTag from 'async-tag';
-
-import {render as $render, html as $html, svg as $svg} from './index.js';
-
-const {defineProperties} = Object;
-
-const tag = original => {
- const wrap = new WeakMapSet;
- return defineProperties(
- asyncTag(original),
- {
- for: {
- value(ref, id) {
- const tag = original.for(ref, id);
- return wrap.get(tag) || wrap.set(tag, asyncTag(tag));
- }
- },
- node: {
- value: asyncTag(original.node)
- }
- }
- );
-};
-
-export const html = tag($html);
-export const svg = tag($svg);
-
-export const render = (where, what) => {
- const hole = typeof what === 'function' ? what() : what;
- return Promise.resolve(hole).then(what => $render(where, what));
-};
-
-export {Hole} from './index.js';
diff --git a/esm/create-content.js b/esm/create-content.js
new file mode 100644
index 0000000..e4d2484
--- /dev/null
+++ b/esm/create-content.js
@@ -0,0 +1,23 @@
+import { newRange } from './utils.js';
+
+let template = document.createElement('template'), svg, range;
+
+/**
+ * @param {string} text
+ * @param {boolean} xml
+ * @returns {DocumentFragment}
+ */
+export default (text, xml) => {
+ if (xml) {
+ if (!svg) {
+ svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ range = newRange();
+ range.selectNodeContents(svg);
+ }
+ return range.createContextualFragment(text);
+ }
+ template.innerHTML = text;
+ const { content } = template;
+ template = template.cloneNode(false);
+ return content;
+};
diff --git a/esm/creator.js b/esm/creator.js
new file mode 100644
index 0000000..2a1203a
--- /dev/null
+++ b/esm/creator.js
@@ -0,0 +1,35 @@
+import { COMMENT_NODE } from 'domconstants/constants';
+
+import { PersistentFragment } from './persistent-fragment.js';
+import { detail, parsed } from './literals.js';
+import { empty } from './utils.js';
+
+/**
+ * @param {DocumentFragment} content
+ * @param {number[]} path
+ * @returns {Element}
+ */
+const find = (content, path) => path.reduceRight(childNodesIndex, content);
+const childNodesIndex = (node, i) => node.childNodes[i];
+
+/** @param {(template: TemplateStringsArray, values: any[]) => import("./parser.js").Resolved} parse */
+export default parse => (
+ /** @param {(template: TemplateStringsArray, values: any[]) => import("./literals.js").Parsed} parse */
+ (template, values) => {
+ const { c: content, e: entries, l: length } = parse(template, values);
+ const root = content.cloneNode(true);
+ // reverse loop to avoid missing paths while populating
+ // TODO: is it even worth to pre-populate nodes? see rabbit.js too
+ let current, prev, i = entries.length, details = i ? entries.slice(0) : empty;
+ while (i--) {
+ const { t: type, p: path, u: update, n: name } = entries[i];
+ const node = path === prev ? current : (current = find(root, (prev = path)));
+ const callback = type === COMMENT_NODE ? update() : update;
+ details[i] = detail(callback(node, values[i], name, empty), callback, node, name);
+ }
+ return parsed(
+ length === 1 ? root.firstChild : new PersistentFragment(root),
+ details
+ );
+ }
+);
diff --git a/esm/handler.js b/esm/handler.js
new file mode 100644
index 0000000..e7b1cdf
--- /dev/null
+++ b/esm/handler.js
@@ -0,0 +1,203 @@
+import udomdiff from 'udomdiff';
+import { empty, isArray, set } from './utils.js';
+import { diffFragment } from './persistent-fragment.js';
+import drop from './range.js';
+
+/**
+ * @template T
+ * @param {Element} element
+ * @param {T} value
+ * @returns {T}
+ */
+const aria = (element, value) => {
+ for (const key in value) {
+ const $ = value[key];
+ const name = key === 'role' ? key : `aria-${key}`;
+ if ($ == null) element.removeAttribute(name);
+ else element.setAttribute(name, $);
+ }
+ return value;
+};
+
+let listeners;
+
+/**
+ * @template T
+ * @param {Element} element
+ * @param {T} value
+ * @param {string} name
+ * @returns {T}
+ */
+const at = (element, value, name) => {
+ name = name.slice(1);
+ if (!listeners) listeners = new WeakMap;
+ const known = listeners.get(element) || set(listeners, element, {});
+ let current = known[name];
+ if (current && current[0]) element.removeEventListener(...current);
+ current = isArray(value) ? value : [value, false];
+ known[name] = current;
+ if (current[0]) element.addEventListener(...current);
+ return value;
+};
+
+/**
+ * @template T
+ * @param {Element} element
+ * @param {T} value
+ * @returns {T}
+ */
+const className = (element, value) => direct(element, value, 'className');
+
+/**
+ * @template T
+ * @param {Element} element
+ * @param {T} value
+ * @returns {T}
+ */
+const data = (element, value) => {
+ const { dataset } = element;
+ for (const key in value) {
+ if (value[key] == null) delete dataset[key];
+ else dataset[key] = value[key];
+ }
+ return value;
+};
+
+/**
+ * @template T
+ * @param {Element | CSSStyleDeclaration} ref
+ * @param {T} value
+ * @param {string} name
+ * @returns {T}
+ */
+const direct = (ref, value, name) => (ref[name] = value);
+
+/**
+ * @template T
+ * @param {Element} element
+ * @param {T} value
+ * @param {string} name
+ * @returns {T}
+ */
+const dot = (element, value, name) => direct(element, value, name.slice(1));
+
+/**
+ * @template T
+ * @param {Element} element
+ * @param {T} value
+ * @returns {T}
+ */
+const ref = (element, value) => {
+ if (typeof value === 'function') value(element);
+ else value.current = element;
+ return value;
+};
+
+/**
+ * @template T
+ * @param {Element} element
+ * @param {T} value
+ * @param {string} name
+ * @returns {T}
+ */
+const regular = (element, value, name) => {
+ if (value == null) element.removeAttribute(name);
+ else element.setAttribute(name, value);
+ return value;
+};
+
+/**
+ * @template T
+ * @param {Element} element
+ * @param {T} value
+ * @returns {T}
+ */
+const style = (element, value) => direct(element.style, value, 'cssText');
+
+/**
+ * @template T
+ * @param {Element} element
+ * @param {T} value
+ * @param {string} name
+ * @returns {T}
+ */
+const toggle = (element, value, name) => {
+ element.toggleAttribute(name.slice(1), value);
+ return value;
+};
+
+/**
+ * @template T
+ * @param {Node} node
+ * @param {T} value
+ * @param {string} _
+ * @param {Node[]} prev
+ * @returns {T}
+ */
+export const array = (node, value, _, prev) => {
+ if (value.length)
+ return udomdiff(node.parentNode, prev, value, diffFragment, node);
+ else if (prev.length)
+ drop(prev[0], prev.at(-1), false);
+ return empty;
+};
+
+/**
+ * @param {Element} element
+ * @param {string} name
+ * @returns
+ */
+export const attribute = (element, name) => {
+ switch (name[0]) {
+ case '.': return dot;
+ case '?': return toggle;
+ case '@': return at;
+ default: {
+ switch (name) {
+ case 'aria': return aria;
+ case 'class': return className;
+ case 'data': return data;
+ case 'ref': return ref;
+ case 'style': return style;
+ default: return name in element ? direct : regular;
+ }
+ }
+ }
+};
+
+/**
+ * @template T
+ * @param {Element} element
+ * @param {T} value
+ * @returns {T}
+ */
+export const text = (element, value) => {
+ element.texContent = value == null ? '' : value;
+ return value;
+};
+
+/**
+ * @template T
+ * @this {import("./literals.js").HoleDetails}
+ * @param {Node} node
+ * @param {T} value
+ * @returns {T}
+ */
+export function hole(node, value) {
+ const n = this.n || (this.n = node);
+ switch (typeof value) {
+ case 'string':
+ case 'number':
+ case 'boolean': {
+ if (n !== node) n.replaceWith((this.n = node));
+ this.n.data = value;
+ break;
+ }
+ case 'object':
+ case 'undefined': {
+ n.replaceWith((this.n = value == null ? node : value.valueOf()));
+ break;
+ }
+ }
+ return value;
+};
diff --git a/esm/handlers.js b/esm/handlers.js
deleted file mode 100644
index 2c2e82b..0000000
--- a/esm/handlers.js
+++ /dev/null
@@ -1,148 +0,0 @@
-import {diffable} from '@webreflection/uwire';
-
-import {aria, attribute, boolean, event, ref, setter, text} from 'uhandlers';
-import udomdiff from 'udomdiff';
-
-import {isArray, createTextNode} from './utils.js';
-
-// from a generic path, retrieves the exact targeted node
-const reducePath = ({childNodes}, i) => childNodes[i];
-
-// this helper avoid code bloat around handleAnything() callback
-const diff = (comment, oldNodes, newNodes) => udomdiff(
- comment.parentNode,
- // TODO: there is a possible edge case where a node has been
- // removed manually, or it was a keyed one, attached
- // to a shared reference between renders.
- // In this case udomdiff might fail at removing such node
- // as its parent won't be the expected one.
- // The best way to avoid this issue is to filter oldNodes
- // in search of those not live, or not in the current parent
- // anymore, but this would require both a change to uwire,
- // exposing a parentNode from the firstChild, as example,
- // but also a filter per each diff that should exclude nodes
- // that are not in there, penalizing performance quite a lot.
- // As this has been also a potential issue with domdiff,
- // and both lighterhtml and hyperHTML might fail with this
- // very specific edge case, I might as well document this possible
- // "diffing shenanigan" and call it a day.
- oldNodes,
- newNodes,
- diffable,
- comment
-);
-
-// if an interpolation represents a comment, the whole
-// diffing will be related to such comment.
-// This helper is in charge of understanding how the new
-// content for such interpolation/hole should be updated
-const handleAnything = comment => {
- let oldValue, text, nodes = [];
- const anyContent = newValue => {
- switch (typeof newValue) {
- // primitives are handled as text content
- case 'string':
- case 'number':
- case 'boolean':
- if (oldValue !== newValue) {
- oldValue = newValue;
- if (!text)
- text = createTextNode('');
- text.data = newValue;
- nodes = diff(comment, nodes, [text]);
- }
- break;
- // null, and undefined are used to cleanup previous content
- case 'object':
- case 'undefined':
- if (newValue == null) {
- if (oldValue != newValue) {
- oldValue = newValue;
- nodes = diff(comment, nodes, []);
- }
- break;
- }
- // arrays and nodes have a special treatment
- if (isArray(newValue)) {
- oldValue = newValue;
- // arrays can be used to cleanup, if empty
- if (newValue.length === 0)
- nodes = diff(comment, nodes, []);
- // or diffed, if these contains nodes or "wires"
- else if (typeof newValue[0] === 'object')
- nodes = diff(comment, nodes, newValue);
- // in all other cases the content is stringified as is
- else
- anyContent(String(newValue));
- break;
- }
- // if the new value is a DOM node, or a wire, and it's
- // different from the one already live, then it's diffed.
- // if the node is a fragment, it's appended once via its childNodes
- // There is no `else` here, meaning if the content
- // is not expected one, nothing happens, as easy as that.
- if (oldValue !== newValue) {
- if ('ELEMENT_NODE' in newValue) {
- oldValue = newValue;
- nodes = diff(
- comment,
- nodes,
- newValue.nodeType === 11 ?
- [...newValue.childNodes] :
- [newValue]
- );
- }
- else {
- const value = newValue.valueOf();
- if (value !== newValue)
- anyContent(value);
- }
- }
- break;
- case 'function':
- anyContent(newValue(comment));
- break;
- }
- };
- return anyContent;
-};
-
-// attributes can be:
-// * ref=${...} for hooks and other purposes
-// * aria=${...} for aria attributes
-// * ?boolean=${...} for boolean attributes
-// * .dataset=${...} for dataset related attributes
-// * .setter=${...} for Custom Elements setters or nodes with setters
-// such as buttons, details, options, select, etc
-// * @event=${...} to explicitly handle event listeners
-// * onevent=${...} to automatically handle event listeners
-// * generic=${...} to handle an attribute just like an attribute
-const handleAttribute = (node, name/*, svg*/) => {
- switch (name[0]) {
- case '?': return boolean(node, name.slice(1), false);
- case '.': return setter(node, name.slice(1));
- case '@': return event(node, 'on' + name.slice(1));
- case 'o': if (name[1] === 'n') return event(node, name);
- }
-
- switch (name) {
- case 'ref': return ref(node);
- case 'aria': return aria(node);
- }
-
- return attribute(node, name/*, svg*/);
-};
-
-// each mapped update carries the update type and its path
-// the type is either node, attribute, or text, while
-// the path is how to retrieve the related node to update.
-// In the attribute case, the attribute name is also carried along.
-export function handlers(options) {
- const {type, path} = options;
- const node = path.reduceRight(reducePath, this);
- return type === 'node' ?
- handleAnything(node) :
- (type === 'attr' ?
- handleAttribute(node, options.name/*, options.svg*/) :
- text(node));
-};
diff --git a/esm/index.js b/esm/index.js
index 5728fcd..8dda2c2 100644
--- a/esm/index.js
+++ b/esm/index.js
@@ -1,63 +1,16 @@
-import {MapSet, WeakMapSet} from '@webreflection/mapset';
+/*! (c) Andrea Giammarchi - MIT */
-import {Hole, createCache, unroll} from './rabbit.js';
+import { Hole } from './rabbit.js';
+import render from './render-hole.js';
-// both `html` and `svg` template literal tags are polluted
-// with a `for(ref[, id])` and a `node` tag too
-const tag = type => {
- // both `html` and `svg` tags have their own cache
- const keyed = new WeakMapSet;
- // keyed operations always re-use the same cache and unroll
- // the template and its interpolations right away
- const fixed = cache => (template, ...values) => unroll(
- cache,
- {type, template, values}
- );
- return Object.assign(
- // non keyed operations are recognized as instance of Hole
- // during the "unroll", recursively resolved and updated
- (template, ...values) => new Hole(type, template, values),
- {
- // keyed operations need a reference object, usually the parent node
- // which is showing keyed results, and optionally a unique id per each
- // related node, handy with JSON results and mutable list of objects
- // that usually carry a unique identifier
- for(ref, id) {
- const memo = keyed.get(ref) || keyed.set(ref, new MapSet);
- return memo.get(id) || memo.set(id, fixed(createCache()));
- },
- // it is possible to create one-off content out of the box via node tag
- // this might return the single created node, or a fragment with all
- // nodes present at the root level and, of course, their child nodes
- node: (template, ...values) => unroll(createCache(), new Hole(type, template, values)).valueOf()
- }
- );
-};
+/** @typedef {import("./literals.js").Value} Value */
-// each rendered node gets its own cache
-const cache = new WeakMapSet;
+const tag = svg => (template, ...values) => new Hole(svg, template, values);
-// rendering means understanding what `html` or `svg` tags returned
-// and it relates a specific node to its own unique cache.
-// Each time the content to render changes, the node is cleaned up
-// and the new new content is appended, and if such content is a Hole
-// then it's "unrolled" to resolve all its inner nodes.
-const render = (where, what) => {
- const hole = typeof what === 'function' ? what() : what;
- const info = cache.get(where) || cache.set(where, createCache());
- const wire = hole instanceof Hole ? unroll(info, hole) : hole;
- if (wire !== info.wire) {
- info.wire = wire;
- // valueOf() simply returns the node itself, but in case it was a "wire"
- // it will eventually re-append all nodes to its fragment so that such
- // fragment can be re-appended many times in a meaningful way
- // (wires are basically persistent fragments facades with special behavior)
- where.replaceChildren(wire.valueOf());
- }
- return where;
-};
+/** @type {(template: TemplateStringsArray, ...values:Value[]) => Hole} A tag to render HTML content. */
+const html = tag(false);
-const html = tag('html');
-const svg = tag('svg');
+/** @type {(template: TemplateStringsArray, ...values:Value[]) => Hole} A tag to render SVG content. */
+const svg = tag(true);
-export {Hole, render, html, svg};
+export { Hole, render, html, svg };
diff --git a/esm/init.js b/esm/init.js
deleted file mode 100644
index c9c47e0..0000000
--- a/esm/init.js
+++ /dev/null
@@ -1,628 +0,0 @@
-import {WeakMapSet} from '@webreflection/mapset';
-import instrument from '@webreflection/uparser';
-
-import udomdiff from 'udomdiff';
-
-export default ({document}) => {
- /**start**/
-const {isArray, prototype} = Array;
-const {indexOf} = prototype;
-
-
-
-const {
- createDocumentFragment,
- createElement,
- createElementNS,
- createTextNode,
- createTreeWalker,
- importNode
-} = new Proxy({}, {
- get: (_, method) => document[method].bind(document)
-});
-
-
-
-const createHTML = html => {
- const template = createElement('template');
- template.innerHTML = html;
- return template.content;
-};
-
-let xml;
-const createSVG = svg => {
- if (!xml) xml = createElementNS('http://www.w3.org/2000/svg', 'svg');
- xml.innerHTML = svg;
- const content = createDocumentFragment();
- content.append(...xml.childNodes);
- return content;
-};
-
-const createContent = (text, svg) => svg ?
- createSVG(text) : createHTML(text);
-
-const ELEMENT_NODE = 1;
-const nodeType = 111;
-
-const remove = ({firstChild, lastChild}) => {
- const range = document.createRange();
- range.setStartAfter(firstChild);
- range.setEndAfter(lastChild);
- range.deleteContents();
- return firstChild;
-};
-
-const diffable = (node, operation) => node.nodeType === nodeType ?
- ((1 / operation) < 0 ?
- (operation ? remove(node) : node.lastChild) :
- (operation ? node.valueOf() : node.firstChild)) :
- node
-;
-
-const persistent = fragment => {
- const {firstChild, lastChild} = fragment;
- if (firstChild === lastChild)
- return lastChild || fragment;
- const {childNodes} = fragment;
- const nodes = [...childNodes];
- return {
- ELEMENT_NODE,
- nodeType,
- firstChild,
- lastChild,
- valueOf() {
- if (childNodes.length !== nodes.length)
- fragment.append(...nodes);
- return fragment;
- }
- };
-};
-
-
-
-// flag for foreign checks (slower path, fast by default)
-let useForeign = false;
-
-class Foreign {
- constructor(handler, value) {
- useForeign = true;
- this._ = (...args) => handler(...args, value);
- }
-}
-
-const foreign = (handler, value) => new Foreign(handler, value);
-
-const aria = node => values => {
- for (const key in values) {
- const name = key === 'role' ? key : `aria-${key}`;
- const value = values[key];
- if (value == null)
- node.removeAttribute(name);
- else
- node.setAttribute(name, value);
- }
-};
-
-const getValue = value => value == null ? value : value.valueOf();
-
-const attribute = (node, name) => {
- let oldValue, orphan = true;
- const attributeNode = document.createAttributeNS(null, name);
- return newValue => {
- const value = useForeign && (newValue instanceof Foreign) ?
- newValue._(node, name) : getValue(newValue);
- if (oldValue !== value) {
- if ((oldValue = value) == null) {
- if (!orphan) {
- node.removeAttributeNode(attributeNode);
- orphan = true;
- }
- }
- else {
- attributeNode.value = value;
- if (orphan) {
- node.setAttributeNodeNS(attributeNode);
- orphan = false;
- }
- }
- }
- };
-};
-
-const boolean = (node, key, oldValue) => newValue => {
- const value = !!getValue(newValue);
- if (oldValue !== value) {
- // when IE won't be around anymore ...
- // node.toggleAttribute(key, oldValue = !!value);
- if ((oldValue = value))
- node.setAttribute(key, '');
- else
- node.removeAttribute(key);
- }
-};
-
-const data = ({dataset}) => values => {
- for (const key in values) {
- const value = values[key];
- if (value == null)
- delete dataset[key];
- else
- dataset[key] = value;
- }
-};
-
-const event = (node, name) => {
- let oldValue, lower, type = name.slice(2);
- if (!(name in node) && (lower = name.toLowerCase()) in node)
- type = lower.slice(2);
- return newValue => {
- const info = isArray(newValue) ? newValue : [newValue, false];
- if (oldValue !== info[0]) {
- if (oldValue)
- node.removeEventListener(type, oldValue, info[1]);
- if (oldValue = info[0])
- node.addEventListener(type, oldValue, info[1]);
- }
- };
-};
-
-const ref = node => {
- let oldValue;
- return value => {
- if (oldValue !== value) {
- oldValue = value;
- if (typeof value === 'function')
- value(node);
- else
- value.current = node;
- }
- };
-};
-
-const setter = (node, key) => key === 'dataset' ?
- data(node) :
- value => {
- node[key] = value;
- };
-
-const text = node => {
- let oldValue;
- return newValue => {
- const value = getValue(newValue);
- if (oldValue != value) {
- oldValue = value;
- node.textContent = value == null ? '' : value;
- }
- };
-};
-
-
-
-
-
-
-
-
-// from a generic path, retrieves the exact targeted node
-const reducePath = ({childNodes}, i) => childNodes[i];
-
-// this helper avoid code bloat around handleAnything() callback
-const diff = (comment, oldNodes, newNodes) => udomdiff(
- comment.parentNode,
- // TODO: there is a possible edge case where a node has been
- // removed manually, or it was a keyed one, attached
- // to a shared reference between renders.
- // In this case udomdiff might fail at removing such node
- // as its parent won't be the expected one.
- // The best way to avoid this issue is to filter oldNodes
- // in search of those not live, or not in the current parent
- // anymore, but this would require both a change to uwire,
- // exposing a parentNode from the firstChild, as example,
- // but also a filter per each diff that should exclude nodes
- // that are not in there, penalizing performance quite a lot.
- // As this has been also a potential issue with domdiff,
- // and both lighterhtml and hyperHTML might fail with this
- // very specific edge case, I might as well document this possible
- // "diffing shenanigan" and call it a day.
- oldNodes,
- newNodes,
- diffable,
- comment
-);
-
-// if an interpolation represents a comment, the whole
-// diffing will be related to such comment.
-// This helper is in charge of understanding how the new
-// content for such interpolation/hole should be updated
-const handleAnything = comment => {
- let oldValue, text, nodes = [];
- const anyContent = newValue => {
- switch (typeof newValue) {
- // primitives are handled as text content
- case 'string':
- case 'number':
- case 'boolean':
- if (oldValue !== newValue) {
- oldValue = newValue;
- if (!text)
- text = createTextNode('');
- text.data = newValue;
- nodes = diff(comment, nodes, [text]);
- }
- break;
- // null, and undefined are used to cleanup previous content
- case 'object':
- case 'undefined':
- if (newValue == null) {
- if (oldValue != newValue) {
- oldValue = newValue;
- nodes = diff(comment, nodes, []);
- }
- break;
- }
- // arrays and nodes have a special treatment
- if (isArray(newValue)) {
- oldValue = newValue;
- // arrays can be used to cleanup, if empty
- if (newValue.length === 0)
- nodes = diff(comment, nodes, []);
- // or diffed, if these contains nodes or "wires"
- else if (typeof newValue[0] === 'object')
- nodes = diff(comment, nodes, newValue);
- // in all other cases the content is stringified as is
- else
- anyContent(String(newValue));
- break;
- }
- // if the new value is a DOM node, or a wire, and it's
- // different from the one already live, then it's diffed.
- // if the node is a fragment, it's appended once via its childNodes
- // There is no `else` here, meaning if the content
- // is not expected one, nothing happens, as easy as that.
- if (oldValue !== newValue) {
- if ('ELEMENT_NODE' in newValue) {
- oldValue = newValue;
- nodes = diff(
- comment,
- nodes,
- newValue.nodeType === 11 ?
- [...newValue.childNodes] :
- [newValue]
- );
- }
- else {
- const value = newValue.valueOf();
- if (value !== newValue)
- anyContent(value);
- }
- }
- break;
- case 'function':
- anyContent(newValue(comment));
- break;
- }
- };
- return anyContent;
-};
-
-// attributes can be:
-// * ref=${...} for hooks and other purposes
-// * aria=${...} for aria attributes
-// * ?boolean=${...} for boolean attributes
-// * .dataset=${...} for dataset related attributes
-// * .setter=${...} for Custom Elements setters or nodes with setters
-// such as buttons, details, options, select, etc
-// * @event=${...} to explicitly handle event listeners
-// * onevent=${...} to automatically handle event listeners
-// * generic=${...} to handle an attribute just like an attribute
-const handleAttribute = (node, name/*, svg*/) => {
- switch (name[0]) {
- case '?': return boolean(node, name.slice(1), false);
- case '.': return setter(node, name.slice(1));
- case '@': return event(node, 'on' + name.slice(1));
- case 'o': if (name[1] === 'n') return event(node, name);
- }
-
- switch (name) {
- case 'ref': return ref(node);
- case 'aria': return aria(node);
- }
-
- return attribute(node, name/*, svg*/);
-};
-
-// each mapped update carries the update type and its path
-// the type is either node, attribute, or text, while
-// the path is how to retrieve the related node to update.
-// In the attribute case, the attribute name is also carried along.
-function handlers(options) {
- const {type, path} = options;
- const node = path.reduceRight(reducePath, this);
- return type === 'node' ?
- handleAnything(node) :
- (type === 'attr' ?
- handleAttribute(node, options.name/*, options.svg*/) :
- text(node));
-};
-
-
-
-
-
-
-
-
-// from a fragment container, create an array of indexes
-// related to its child nodes, so that it's possible
-// to retrieve later on exact node via reducePath
-const createPath = node => {
- const path = [];
- let {parentNode} = node;
- while (parentNode) {
- path.push(indexOf.call(parentNode.childNodes, node));
- node = parentNode;
- ({parentNode} = node);
- }
- return path;
-};
-
-// the prefix is used to identify either comments, attributes, or nodes
-// that contain the related unique id. In the attribute cases
-// isĀµX="attribute-name" will be used to map current X update to that
-// attribute name, while comments will be like , to map
-// the update to that specific comment node, hence its parent.
-// style and textarea will have text content, and are handled
-// directly through text-only updates.
-const prefix = 'isĀµ';
-
-// Template Literals are unique per scope and static, meaning a template
-// should be parsed once, and once only, as it will always represent the same
-// content, within the exact same amount of updates each time.
-// This cache relates each template to its unique content and updates.
-const cache = new WeakMapSet;
-
-// a RegExp that helps checking nodes that cannot contain comments
-const textOnly = /^(?:textarea|script|style|title|plaintext|xmp)$/;
-
-const createCache = () => ({
- stack: [], // each template gets a stack for each interpolation "hole"
-
- entry: null, // each entry contains details, such as:
- // * the template that is representing
- // * the type of node it represents (html or svg)
- // * the content fragment with all nodes
- // * the list of updates per each node (template holes)
- // * the "wired" node or fragment that will get updates
- // if the template or type are different from the previous one
- // the entry gets re-created each time
-
- wire: null // each rendered node represent some wired content and
- // this reference to the latest one. If different, the node
- // will be cleaned up and the new "wire" will be appended
-});
-
-// the entry stored in the rendered node cache, and per each "hole"
-const createEntry = (type, template) => {
- const {content, updates} = mapUpdates(type, template);
- return {type, template, content, updates, wire: null};
-};
-
-// a template is instrumented to be able to retrieve where updates are needed.
-// Each unique template becomes a fragment, cloned once per each other
-// operation based on the same template, i.e. data => html`${data}
`
-const mapTemplate = (type, template) => {
- const svg = type === 'svg';
- const text = instrument(template, prefix, svg);
- const content = createContent(text, svg);
- // once instrumented and reproduced as fragment, it's crawled
- // to find out where each update is in the fragment tree
- const tw = createTreeWalker(content, 1 | 128);
- const nodes = [];
- const length = template.length - 1;
- let i = 0;
- // updates are searched via unique names, linearly increased across the tree
- //
- let search = `${prefix}${i}`;
- while (i < length) {
- const node = tw.nextNode();
- // if not all updates are bound but there's nothing else to crawl
- // it means that there is something wrong with the template.
- if (!node)
- throw `bad template: ${text}`;
- // if the current node is a comment, and it contains isĀµX
- // it means the update should take care of any content
- if (node.nodeType === 8) {
- // The only comments to be considered are those
- // which content is exactly the same as the searched one.
- if (node.data === search) {
- nodes.push({type: 'node', path: createPath(node)});
- search = `${prefix}${++i}`;
- }
- }
- else {
- // if the node is not a comment, loop through all its attributes
- // named isĀµX and relate attribute updates to this node and the
- // attribute name, retrieved through node.getAttribute("isĀµX")
- // the isĀµX attribute will be removed as irrelevant for the layout
- // let svg = -1;
- while (node.hasAttribute(search)) {
- nodes.push({
- type: 'attr',
- path: createPath(node),
- name: node.getAttribute(search)
- });
- node.removeAttribute(search);
- search = `${prefix}${++i}`;
- }
- // if the node was a style, textarea, or others, check its content
- // and if it is then update tex-only this node
- if (
- textOnly.test(node.localName) &&
- node.textContent.trim() === ``
- ){
- node.textContent = '';
- nodes.push({type: 'text', path: createPath(node)});
- search = `${prefix}${++i}`;
- }
- }
- }
- // once all nodes to update, or their attributes, are known, the content
- // will be cloned in the future to represent the template, and all updates
- // related to such content retrieved right away without needing to re-crawl
- // the exact same template, and its content, more than once.
- return {content, nodes};
-};
-
-// if a template is unknown, perform the previous mapping, otherwise grab
-// its details such as the fragment with all nodes, and updates info.
-const mapUpdates = (type, template) => {
- const {content, nodes} = (
- cache.get(template) ||
- cache.set(template, mapTemplate(type, template))
- );
- // clone deeply the fragment
- const fragment = importNode(content, true);
- // and relate an update handler per each node that needs one
- const updates = nodes.map(handlers, fragment);
- // return the fragment and all updates to use within its nodes
- return {content: fragment, updates};
-};
-
-// as html and svg can be nested calls, but no parent node is known
-// until rendered somewhere, the unroll operation is needed to
-// discover what to do with each interpolation, which will result
-// into an update operation.
-const unroll = (info, {type, template, values}) => {
- // interpolations can contain holes and arrays, so these need
- // to be recursively discovered
- const length = unrollValues(info, values);
- let {entry} = info;
- // if the cache entry is either null or different from the template
- // and the type this unroll should resolve, create a new entry
- // assigning a new content fragment and the list of updates.
- if (!entry || (entry.template !== template || entry.type !== type))
- info.entry = (entry = createEntry(type, template));
- const {content, updates, wire} = entry;
- // even if the fragment and its nodes is not live yet,
- // it is already possible to update via interpolations values.
- for (let i = 0; i < length; i++)
- updates[i](values[i]);
- // if the entry was new, or representing a different template or type,
- // create a new persistent entity to use during diffing.
- // This is simply a DOM node, when the template has a single container,
- // as in ``, or a "wire" in `` and similar cases.
- return wire || (entry.wire = persistent(content));
-};
-
-// the stack retains, per each interpolation value, the cache
-// related to each interpolation value, or null, if the render
-// was conditional and the value is not special (Array or Hole)
-const unrollValues = ({stack}, values) => {
- const {length} = values;
- for (let i = 0; i < length; i++) {
- const hole = values[i];
- // each Hole gets unrolled and re-assigned as value
- // so that domdiff will deal with a node/wire, not with a hole
- if (hole instanceof Hole)
- values[i] = unroll(
- stack[i] || (stack[i] = createCache()),
- hole
- );
- // arrays are recursively resolved so that each entry will contain
- // also a DOM node or a wire, hence it can be diffed if/when needed
- else if (isArray(hole))
- unrollValues(stack[i] || (stack[i] = createCache()), hole);
- // if the value is nothing special, the stack doesn't need to retain data
- // this is useful also to cleanup previously retained data, if the value
- // was a Hole, or an Array, but not anymore, i.e.:
- // const update = content => html`${content}
`;
- // update(listOfItems); update(null); update(html`hole`)
- else
- stack[i] = null;
- }
- if (length < stack.length)
- stack.splice(length);
- return length;
-};
-
-/**
- * Holds all details wrappers needed to render the content further on.
- * @constructor
- * @param {string} type The hole type, either `html` or `svg`.
- * @param {string[]} template The template literals used to the define the content.
- * @param {Array} values Zero, one, or more interpolated values to render.
- */
-class Hole {
- constructor(type, template, values) {
- this.type = type;
- this.template = template;
- this.values = values;
- }
-};
-
-
-
-
-
-// both `html` and `svg` template literal tags are polluted
-// with a `for(ref[, id])` and a `node` tag too
-const tag = type => {
- // both `html` and `svg` tags have their own _cache
- const keyed = new WeakMapSet;
- // keyed operations always re-use the same _cache and unroll
- // the template and its interpolations right away
- const fixed = _cache => (template, ...values) => unroll(
- _cache,
- {type, template, values}
- );
- return Object.assign(
- // non keyed operations are recognized as instance of Hole
- // during the "unroll", recursively resolved and updated
- (template, ...values) => new Hole(type, template, values),
- {
- // keyed operations need a reference object, usually the parent node
- // which is showing keyed results, and optionally a unique id per each
- // related node, handy with JSON results and mutable list of objects
- // that usually carry a unique identifier
- for(ref, id) {
- const memo = keyed.get(ref) || keyed.set(ref, new MapSet);
- return memo.get(id) || memo.set(id, fixed(createCache()));
- },
- // it is possible to create one-off content out of the box via node tag
- // this might return the single created node, or a fragment with all
- // nodes present at the root level and, of course, their child nodes
- node: (template, ...values) => unroll(createCache(), new Hole(type, template, values)).valueOf()
- }
- );
-};
-
-// each rendered node gets its own _cache
-const _cache = new WeakMapSet;
-
-// rendering means understanding what `html` or `svg` tags returned
-// and it relates a specific node to its own unique _cache.
-// Each time the content to render changes, the node is cleaned up
-// and the new new content is appended, and if such content is a Hole
-// then it's "unrolled" to resolve all its inner nodes.
-const render = (where, what) => {
- const hole = typeof what === 'function' ? what() : what;
- const info = _cache.get(where) || _cache.set(where, createCache());
- const wire = hole instanceof Hole ? unroll(info, hole) : hole;
- if (wire !== info.wire) {
- info.wire = wire;
- // valueOf() simply returns the node itself, but in case it was a "wire"
- // it will eventually re-append all nodes to its fragment so that such
- // fragment can be re-appended many times in a meaningful way
- // (wires are basically persistent fragments facades with special behavior)
- where.replaceChildren(wire.valueOf());
- }
- return where;
-};
-
-const html = tag('html');
-const svg = tag('svg');
-
-return {Hole, render, html, svg};
-
-/**end**/
-};
diff --git a/esm/json.js b/esm/json.js
deleted file mode 100644
index 58b033e..0000000
--- a/esm/json.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import {MapSet, WeakMapSet} from '@webreflection/mapset';
-
-import {render as $render, html, svg} from './index.js';
-
-// Sender (SW, Worker, postMessage)
-const ids = new WeakMapSet;
-let id = 0;
-
-const tag = type => (template, ...values) => ({
- id: ids.get(template) ||
- ids.set(template, id++),
- type,
- values,
- template
-});
-
-html.json = tag('html');
-svg.json = tag('svg');
-
-// Receiver (onmessage, from SW, Workers, etc)
-const templates = new MapSet;
-
-const unroll = ({type, template, values, id}) => (
- (type === 'svg' ? svg : html).apply(
- null,
- [
- templates.get(id) ||
- templates.set(id, template)
- ].concat(values.map(asJSON))
- )
-);
-
-const asJSON = value => isJSON(value) ? unroll(value) : value;
-
-const isJSON = thing => (
- typeof thing === 'object' &&
- thing !== null &&
- 'id' in thing &&
- 'type' in thing &&
- 'values' in thing &&
- 'template' in thing
-);
-
-const render = (where, what) => $render(
- where,
- isJSON(what) ? unroll(what) : what
-);
-
-export {render, html, svg};
diff --git a/esm/keyed.js b/esm/keyed.js
new file mode 100644
index 0000000..5cc5c2c
--- /dev/null
+++ b/esm/keyed.js
@@ -0,0 +1,39 @@
+/*! (c) Andrea Giammarchi - MIT */
+
+import { cache } from './literals.js';
+import { Hole, unroll } from './rabbit.js';
+import { empty, set } from './utils.js';
+import { html, svg } from './index.js';
+import render from './render-any.js';
+
+/** @typedef {import("./literals.js").Cache} Cache */
+/** @typedef {import("./literals.js").Target} Target */
+/** @typedef {import("./literals.js").Value} Value */
+
+/** @typedef {(ref:Object, key:string | number) => Tag} Bound */
+
+/**
+ * @callback Tag
+ * @param {TemplateStringsArray} template
+ * @param {...Value} values
+ * @returns {Target}
+ */
+
+const keyed = new WeakMap;
+const createRef = svg => /** @type {Bound} */ (ref, key) => {
+ /** @type {Tag} */
+ function tag(template, ...values) {
+ return unroll(this, new Hole(svg, template, values));
+ }
+
+ const memo = keyed.get(ref) || set(keyed, ref, new Map);
+ return memo.get(key) || set(memo, key, tag.bind(cache(empty)));
+};
+
+/** @type {Bound} Returns a bound tag to render HTML content. */
+const htmlFor = createRef(false);
+
+/** @type {Bound} Returns a bound tag to render SVG content. */
+const svgFor = createRef(true);
+
+export { Hole, render, html, svg, htmlFor, svgFor };
diff --git a/esm/literals.js b/esm/literals.js
new file mode 100644
index 0000000..07a7d4e
--- /dev/null
+++ b/esm/literals.js
@@ -0,0 +1,90 @@
+import { empty } from './utils.js';
+
+/** @typedef {import("domconstants/constants").ATTRIBUTE_NODE} ATTRIBUTE_NODE */
+/** @typedef {import("domconstants/constants").TEXT_NODE} TEXT_NODE */
+/** @typedef {import("domconstants/constants").COMMENT_NODE} COMMENT_NODE */
+/** @typedef {ATTRIBUTE_NODE | TEXT_NODE | COMMENT_NODE} Type */
+
+/** @typedef {import("./persistent-fragment.js").PersistentFragment} PersistentFragment */
+/** @typedef {import("./rabbit.js").Hole} Hole */
+
+/** @typedef {Node | Element | PersistentFragment} Target */
+/** @typedef {null | undefined | string | number | boolean | Hole} Value */
+/** @typedef {null | undefined | string | number | boolean | Node | Element | PersistentFragment} DOMValue */
+
+/**
+ * @typedef {Object} Entry
+ * @property {Type} type
+ * @property {number[]} path
+ * @property {function} update
+ * @property {string} name
+ */
+
+/**
+ * @param {PersistentFragment} c content retrieved from the template
+ * @param {Entry[]} e entries per each hole in the template
+ * @param {number} l the length of content childNodes
+ * @returns
+ */
+export const cel = (c, e, l) => ({ c, e, l });
+
+/**
+ * @typedef {Object} HoleDetails
+ * @property {null | Node | PersistentFragment} n the current live node, if any and not the `t` one
+ */
+
+/** @type {() => HoleDetails} */
+export const comment = () => ({ n: null });
+
+/**
+ * @typedef {Object} Detail
+ * @property {any} v the current value of the interpolation / hole
+ * @property {function} u the callback to update the value
+ * @property {Node} t the target comment node or element
+ * @property {string} n the name of the attribute, if any
+ */
+
+/**
+ * @param {any} v the current value of the interpolation / hole
+ * @param {function} u the callback to update the value
+ * @param {Node} t the target comment node or element
+ * @param {string} n the name of the attribute, if any
+ * @returns {Detail}
+ */
+export const detail = (v, u, t, n) => ({ v, u, t, n });
+
+/**
+ * @param {Type} t the operation type
+ * @param {number[]} p the path to retrieve the node
+ * @param {function} u the update function
+ * @param {string} n the attribute name, if any
+ * @returns {Entry}
+ */
+export const entry = (t, p, u, n = '') => ({ t, p, u, n });
+
+/**
+ * @typedef {Object} Cache
+ * @property {Cache[]} s the stack of caches per each interpolation / hole
+ * @property {null | TemplateStringsArray} t the cached template
+ * @property {null | Node | PersistentFragment} n the node returned when parsing the template
+ * @property {Detail[]} d the list of updates to perform
+ */
+
+/**
+ * @param {Cache[]} s the cache stack
+ * @returns {Cache}
+ */
+export const cache = s => ({ s, t: null, n: null, d: empty});
+
+/**
+ * @typedef {Object} Parsed
+ * @property {Node | PersistentFragment} n the returned node after parsing the template
+ * @property {Detail[]} d the list of details to update the node
+ */
+
+/**
+ * @param {Node | PersistentFragment} n the returned node after parsing the template
+ * @param {Detail[]} d the list of details to update the node
+ * @returns {Parsed}
+ */
+export const parsed = (n, d) => ({ n, d });
diff --git a/esm/node.js b/esm/node.js
new file mode 100644
index 0000000..0d55594
--- /dev/null
+++ b/esm/node.js
@@ -0,0 +1,21 @@
+/*! (c) Andrea Giammarchi - MIT */
+
+import create from './creator.js';
+import parser from './parser.js';
+import render from './render-node.js';
+
+/** @typedef {import("./literals.js").DOMValue} DOMValue */
+/** @typedef {import("./literals.js").Target} Target */
+
+const tag = svg => {
+ const parse = create(parser(svg));
+ return (template, ...values) => parse(template, values).n;
+};
+
+/** @type {(template: TemplateStringsArray, ...values:DOMValue[]) => Target} A tag to render HTML content. */
+const html = tag(false);
+
+/** @type {(template: TemplateStringsArray, ...values:DOMValue[]) => Target} A tag to render SVG content. */
+const svg = tag(true);
+
+export { render, html, svg };
diff --git a/esm/parser.js b/esm/parser.js
new file mode 100644
index 0000000..373dce1
--- /dev/null
+++ b/esm/parser.js
@@ -0,0 +1,94 @@
+import { ATTRIBUTE_NODE, TEXT_NODE, COMMENT_NODE } from 'domconstants/constants';
+import { TEXT_ELEMENTS } from 'domconstants/re';
+import parser from '@webreflection/uparser';
+
+import { empty, isArray, set } from './utils.js';
+import { cel, comment, entry } from './literals.js';
+
+import { array, attribute, text, hole } from './handler.js';
+import createContent from './create-content.js';
+
+/** @typedef {import("./literals.js").Entry} Entry */
+
+/**
+ * @typedef {Object} Resolved
+ * @property {DocumentFragment} content
+ * @property {Entry[]} entries
+ * @property {function[]} updates
+ * @property {number} length
+ */
+
+/**
+ * @param {Element} node
+ * @returns {number[]}
+ */
+const createPath = node => {
+ const path = [];
+ let parentNode;
+ while ((parentNode = node.parentNode)) {
+ path.push(path.indexOf.call(parentNode.childNodes, node));
+ node = parentNode;
+ }
+ return path;
+};
+
+const boundComment = () => hole.bind(comment());
+const arrayComment = () => array;
+
+/**
+ * @param {TemplateStringsArray} template
+ * @param {boolean} xml
+ * @returns {Resolved}
+ */
+const resolve = (template, values, xml) => {
+ const content = createContent(parser(template, prefix, xml), xml);
+ let entries = empty;
+ const { length } = template;
+ if (length > 1) {
+ const tw = document.createTreeWalker(content, 1 | 128);
+ const replace = [];
+ let i = 0, search = `${prefix}${i++}`;
+ entries = [];
+ while (i < length) {
+ const node = tw.nextNode();
+ if (node.nodeType === COMMENT_NODE) {
+ if (node.data === search) {
+ let update = isArray(values[i - 1]) ? arrayComment : boundComment;
+ if (update === boundComment) replace.push(node);
+ entries.push(entry(COMMENT_NODE, createPath(node), update));
+ search = `${prefix}${i++}`;
+ }
+ }
+ else {
+ let path;
+ while (node.hasAttribute(search)) {
+ if (!path) path = createPath(node);
+ const name = node.getAttribute(search);
+ entries.push(entry(ATTRIBUTE_NODE, path, attribute(node, name), name));
+ node.removeAttribute(search);
+ search = `${prefix}${i++}`;
+ }
+ if (
+ TEXT_ELEMENTS.test(node.localName) &&
+ node.textContent.trim() === ``
+ ) {
+ entries.push(entry(TEXT_NODE, path || createPath(node), text));
+ search = `${prefix}${i++}`;
+ }
+ }
+ }
+ for (i = 0; i < replace.length; i++)
+ replace[i].replaceWith(document.createTextNode(''));
+ }
+ return set(cache, template, cel(content, entries, content.childNodes.length));
+};
+
+/** @type {WeakMap} */
+const cache = new WeakMap;
+const prefix = 'isĀµ';
+
+/**
+ * @param {boolean} xml
+ * @returns {(template: TemplateStringsArray, values: any[]) => Resolved}
+ */
+export default xml => (template, values) => cache.get(template) || resolve(template, values, xml);
diff --git a/esm/persistent-fragment.js b/esm/persistent-fragment.js
new file mode 100644
index 0000000..7856f8e
--- /dev/null
+++ b/esm/persistent-fragment.js
@@ -0,0 +1,46 @@
+import { DOCUMENT_FRAGMENT_NODE } from 'domconstants/constants';
+import custom from 'custom-function/factory';
+import drop from './range.js';
+
+/**
+ * @param {PersistentFragment} fragment
+ * @returns {Node | Element}
+ */
+const remove = ({firstChild, lastChild}) => drop(firstChild, lastChild, true);
+
+let checkType = false;
+
+/**
+ * @param {Node} node
+ * @param {1 | 0 | -0 | -1} operation
+ * @returns {Node}
+ */
+export const diffFragment = (node, operation) => (
+ checkType && node.nodeType === DOCUMENT_FRAGMENT_NODE ?
+ ((1 / operation) < 0 ?
+ (operation ? remove(node) : node.lastChild) :
+ (operation ? node.valueOf() : node.firstChild)) :
+ node
+);
+
+/** @extends {DocumentFragment} */
+export class PersistentFragment extends custom(DocumentFragment) {
+ #nodes;
+ #length;
+ constructor(fragment) {
+ super(fragment);
+ this.#nodes = [...fragment.childNodes];
+ this.#length = this.#nodes.length;
+ checkType = true;
+ }
+ get firstChild() { return this.#nodes[0]; }
+ get lastChild() { return this.#nodes.at(-1); }
+ replaceWith(node) {
+ remove(this).replaceWith(node);
+ }
+ valueOf() {
+ if (this.childNodes.length !== this.#length)
+ this.append(...this.#nodes);
+ return this;
+ }
+}
diff --git a/esm/rabbit.js b/esm/rabbit.js
index 4966a5b..f790952 100644
--- a/esm/rabbit.js
+++ b/esm/rabbit.js
@@ -1,214 +1,71 @@
-import {WeakMapSet} from '@webreflection/mapset';
-import instrument from '@webreflection/uparser';
-import {persistent} from '@webreflection/uwire';
+import { cache } from './literals.js';
+import { empty, isArray } from './utils.js';
+import create from './creator.js';
+import parser from './parser.js';
-import {handlers} from './handlers.js';
-import {isArray, indexOf, createContent, createTreeWalker, importNode} from './utils.js';
+const parseHTML = create(parser(false));
+const parseSVG = create(parser(true));
-// from a fragment container, create an array of indexes
-// related to its child nodes, so that it's possible
-// to retrieve later on exact node via reducePath
-const createPath = node => {
- const path = [];
- let {parentNode} = node;
- while (parentNode) {
- path.push(indexOf.call(parentNode.childNodes, node));
- node = parentNode;
- ({parentNode} = node);
+/**
+ * @param {import("./literals.js").Cache} cache
+ * @param {Hole} hole
+ * @returns {Node}
+ */
+export const unroll = (cache, { s: stack, t: template, v: values }) => {
+ if (values.length && cache.s === empty) cache.s = [];
+ unrollValues(cache, values);
+ if (cache.t !== template) {
+ const { n: node, d: details } = (stack ? parseSVG : parseHTML)(template, values);
+ cache.t = template;
+ cache.n = node;
+ cache.d = details;
}
- return path;
-};
-
-// the prefix is used to identify either comments, attributes, or nodes
-// that contain the related unique id. In the attribute cases
-// isĀµX="attribute-name" will be used to map current X update to that
-// attribute name, while comments will be like , to map
-// the update to that specific comment node, hence its parent.
-// style and textarea will have text content, and are handled
-// directly through text-only updates.
-const prefix = 'isĀµ';
-
-// Template Literals are unique per scope and static, meaning a template
-// should be parsed once, and once only, as it will always represent the same
-// content, within the exact same amount of updates each time.
-// This cache relates each template to its unique content and updates.
-const cache = new WeakMapSet;
-
-// a RegExp that helps checking nodes that cannot contain comments
-const textOnly = /^(?:textarea|script|style|title|plaintext|xmp)$/;
-
-export const createCache = () => ({
- stack: [], // each template gets a stack for each interpolation "hole"
-
- entry: null, // each entry contains details, such as:
- // * the template that is representing
- // * the type of node it represents (html or svg)
- // * the content fragment with all nodes
- // * the list of updates per each node (template holes)
- // * the "wired" node or fragment that will get updates
- // if the template or type are different from the previous one
- // the entry gets re-created each time
-
- wire: null // each rendered node represent some wired content and
- // this reference to the latest one. If different, the node
- // will be cleaned up and the new "wire" will be appended
-});
-
-// the entry stored in the rendered node cache, and per each "hole"
-const createEntry = (type, template) => {
- const {content, updates} = mapUpdates(type, template);
- return {type, template, content, updates, wire: null};
-};
-
-// a template is instrumented to be able to retrieve where updates are needed.
-// Each unique template becomes a fragment, cloned once per each other
-// operation based on the same template, i.e. data => html`${data}
`
-const mapTemplate = (type, template) => {
- const svg = type === 'svg';
- const text = instrument(template, prefix, svg);
- const content = createContent(text, svg);
- // once instrumented and reproduced as fragment, it's crawled
- // to find out where each update is in the fragment tree
- const tw = createTreeWalker(content, 1 | 128);
- const nodes = [];
- const length = template.length - 1;
- let i = 0;
- // updates are searched via unique names, linearly increased across the tree
- //
- let search = `${prefix}${i}`;
- while (i < length) {
- const node = tw.nextNode();
- // if not all updates are bound but there's nothing else to crawl
- // it means that there is something wrong with the template.
- if (!node)
- throw `bad template: ${text}`;
- // if the current node is a comment, and it contains isĀµX
- // it means the update should take care of any content
- if (node.nodeType === 8) {
- // The only comments to be considered are those
- // which content is exactly the same as the searched one.
- if (node.data === search) {
- nodes.push({type: 'node', path: createPath(node)});
- search = `${prefix}${++i}`;
- }
- }
- else {
- // if the node is not a comment, loop through all its attributes
- // named isĀµX and relate attribute updates to this node and the
- // attribute name, retrieved through node.getAttribute("isĀµX")
- // the isĀµX attribute will be removed as irrelevant for the layout
- // let svg = -1;
- while (node.hasAttribute(search)) {
- nodes.push({
- type: 'attr',
- path: createPath(node),
- name: node.getAttribute(search)
- });
- node.removeAttribute(search);
- search = `${prefix}${++i}`;
- }
- // if the node was a style, textarea, or others, check its content
- // and if it is then update tex-only this node
- if (
- textOnly.test(node.localName) &&
- node.textContent.trim() === ``
- ){
- node.textContent = '';
- nodes.push({type: 'text', path: createPath(node)});
- search = `${prefix}${++i}`;
+ else {
+ const { d: details } = cache;
+ for (let i = 0; i < details.length; i++) {
+ const detail = details[i];
+ const value = values[i];
+ const { v: previous } = detail;
+ if (value !== previous) {
+ const { u: update, t: target, n: name } = detail;
+ detail.v = update(target, value, name, previous);
}
}
}
- // once all nodes to update, or their attributes, are known, the content
- // will be cloned in the future to represent the template, and all updates
- // related to such content retrieved right away without needing to re-crawl
- // the exact same template, and its content, more than once.
- return {content, nodes};
-};
-
-// if a template is unknown, perform the previous mapping, otherwise grab
-// its details such as the fragment with all nodes, and updates info.
-const mapUpdates = (type, template) => {
- const {content, nodes} = (
- cache.get(template) ||
- cache.set(template, mapTemplate(type, template))
- );
- // clone deeply the fragment
- const fragment = importNode(content, true);
- // and relate an update handler per each node that needs one
- const updates = nodes.map(handlers, fragment);
- // return the fragment and all updates to use within its nodes
- return {content: fragment, updates};
-};
-
-// as html and svg can be nested calls, but no parent node is known
-// until rendered somewhere, the unroll operation is needed to
-// discover what to do with each interpolation, which will result
-// into an update operation.
-export const unroll = (info, {type, template, values}) => {
- // interpolations can contain holes and arrays, so these need
- // to be recursively discovered
- const length = unrollValues(info, values);
- let {entry} = info;
- // if the cache entry is either null or different from the template
- // and the type this unroll should resolve, create a new entry
- // assigning a new content fragment and the list of updates.
- if (!entry || (entry.template !== template || entry.type !== type))
- info.entry = (entry = createEntry(type, template));
- const {content, updates, wire} = entry;
- // even if the fragment and its nodes is not live yet,
- // it is already possible to update via interpolations values.
- for (let i = 0; i < length; i++)
- updates[i](values[i]);
- // if the entry was new, or representing a different template or type,
- // create a new persistent entity to use during diffing.
- // This is simply a DOM node, when the template has a single container,
- // as in ``, or a "wire" in `` and similar cases.
- return wire || (entry.wire = persistent(content));
+ return cache.n;
};
-// the stack retains, per each interpolation value, the cache
-// related to each interpolation value, or null, if the render
-// was conditional and the value is not special (Array or Hole)
-const unrollValues = ({stack}, values) => {
- const {length} = values;
+/**
+ * @param {Cache} cache
+ * @param {any[]} values
+ * @returns {number}
+ */
+const unrollValues = ({ s: stack }, values) => {
+ const { length } = values;
for (let i = 0; i < length; i++) {
const hole = values[i];
- // each Hole gets unrolled and re-assigned as value
- // so that domdiff will deal with a node/wire, not with a hole
if (hole instanceof Hole)
- values[i] = unroll(
- stack[i] || (stack[i] = createCache()),
- hole
- );
- // arrays are recursively resolved so that each entry will contain
- // also a DOM node or a wire, hence it can be diffed if/when needed
+ values[i] = unroll(stack[i] || (stack[i] = cache(empty)), hole);
else if (isArray(hole))
- unrollValues(stack[i] || (stack[i] = createCache()), hole);
- // if the value is nothing special, the stack doesn't need to retain data
- // this is useful also to cleanup previously retained data, if the value
- // was a Hole, or an Array, but not anymore, i.e.:
- // const update = content => html`${content}
`;
- // update(listOfItems); update(null); update(html`hole`)
+ unrollValues(stack[i] || (stack[i] = cache([])), hole);
else
stack[i] = null;
}
- if (length < stack.length)
- stack.splice(length);
+ if (length < stack.length) stack.splice(length);
return length;
};
/**
- * Holds all details wrappers needed to render the content further on.
+ * Holds all details needed to render the content on a render.
* @constructor
- * @param {string} type The hole type, either `html` or `svg`.
- * @param {string[]} template The template literals used to the define the content.
- * @param {Array} values Zero, one, or more interpolated values to render.
+ * @param {boolean} svg The content type.
+ * @param {TemplateStringsArray} template The template literals used to the define the content.
+ * @param {any[]} values Zero, one, or more interpolated values to render.
*/
export class Hole {
- constructor(type, template, values) {
- this.type = type;
- this.template = template;
- this.values = values;
+ constructor(svg, template, values) {
+ this.s = svg;
+ this.t = template;
+ this.v = values;
}
};
diff --git a/esm/range.js b/esm/range.js
new file mode 100644
index 0000000..56b469c
--- /dev/null
+++ b/esm/range.js
@@ -0,0 +1,19 @@
+import { newRange } from './utils.js';
+
+let range;
+/**
+ * @param {Node | Element} firstChild
+ * @param {Node | Element} lastChild
+ * @param {boolean} preserve
+ * @returns
+ */
+export default (firstChild, lastChild, preserve) => {
+ if (!range) range = newRange();
+ if (preserve)
+ range.setStartAfter(firstChild);
+ else
+ range.setStartBefore(firstChild);
+ range.setEndAfter(lastChild);
+ range.deleteContents();
+ return firstChild;
+};
diff --git a/esm/render-any.js b/esm/render-any.js
new file mode 100644
index 0000000..2044321
--- /dev/null
+++ b/esm/render-any.js
@@ -0,0 +1,25 @@
+/*! (c) Andrea Giammarchi - MIT */
+
+import { cache } from './literals.js';
+import { Hole, unroll } from './rabbit.js';
+import { empty, set } from './utils.js';
+
+/** @type {WeakMap} */
+const known = new WeakMap;
+
+/**
+ * Render with smart updates within a generic container.
+ * @template T
+ * @param {T} where the DOM node where to render content
+ * @param {() => Hole | Hole} what the hole to render
+ * @returns
+ */
+export default (where, what) => {
+ const info = known.get(where) || set(known, where, cache(empty));
+ const hole = typeof what === 'function' ? what() : what;
+ const { n } = info;
+ const node = hole instanceof Hole ? unroll(info, hole) : hole;
+ if (n !== node)
+ where.replaceChildren((info.n = node));
+ return where;
+};
diff --git a/esm/render-hole.js b/esm/render-hole.js
new file mode 100644
index 0000000..0a9e4d8
--- /dev/null
+++ b/esm/render-hole.js
@@ -0,0 +1,24 @@
+/*! (c) Andrea Giammarchi - MIT */
+
+import { cache } from './literals.js';
+import { unroll } from './rabbit.js';
+import { empty, set } from './utils.js';
+
+/** @typedef {import("./rabbit.js").Hole} Hole */
+
+/** @type {WeakMap} */
+const known = new WeakMap;
+
+/**
+ * Render with smart updates within a generic container.
+ * @template T
+ * @param {T} where the DOM node where to render content
+ * @param {() => Hole | Hole} what the hole to render
+ * @returns
+ */
+export default (where, what) => {
+ const info = known.get(where) || set(known, where, cache(empty));
+ if (info.n !== unroll(info, typeof what === 'function' ? what() : what))
+ where.replaceChildren(info.n);
+ return where;
+};
diff --git a/esm/render-node.js b/esm/render-node.js
new file mode 100644
index 0000000..18a701c
--- /dev/null
+++ b/esm/render-node.js
@@ -0,0 +1,13 @@
+/** @typedef {import("./literals.js").Target} Target */
+
+/**
+ * Render directly within a generic container.
+ * @template T
+ * @param {T} where the DOM node where to render content
+ * @param {() => Target | Target} what the node to render
+ * @returns
+ */
+export default (where, what) => {
+ where.replaceChildren(typeof what === 'function' ? what() : what);
+ return where;
+};
diff --git a/esm/utils.js b/esm/utils.js
index c1145db..2225d38 100644
--- a/esm/utils.js
+++ b/esm/utils.js
@@ -1,35 +1,19 @@
-const {isArray, prototype} = Array;
-const {indexOf} = prototype;
+const { isArray } = Array;
+export { isArray };
-export {isArray, indexOf};
+export const empty = [];
-const {
- createDocumentFragment,
- createElement,
- createElementNS,
- createTextNode,
- createTreeWalker,
- importNode
-} = new Proxy({}, {
- get: (_, method) => document[method].bind(document)
-});
+export const newRange = () => document.createRange();
-export {createTextNode, createTreeWalker, importNode};
-
-const createHTML = html => {
- const template = createElement('template');
- template.innerHTML = html;
- return template.content;
-};
-
-let xml;
-const createSVG = svg => {
- if (!xml) xml = createElementNS('http://www.w3.org/2000/svg', 'svg');
- xml.innerHTML = svg;
- const content = createDocumentFragment();
- content.append(...xml.childNodes);
- return content;
+/**
+ * Set the `key` `value` pair to the *Map* or *WeakMap* and returns the `value`
+ * @template T
+ * @param {Map | WeakMap} map
+ * @param {any} key
+ * @param {T} value
+ * @returns {T}
+ */
+export const set = (map, key, value) => {
+ map.set(key, value);
+ return value;
};
-
-export const createContent = (text, svg) => svg ?
- createSVG(text) : createHTML(text);
diff --git a/esm/x.js b/esm/x.js
deleted file mode 100644
index fc57b43..0000000
--- a/esm/x.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import {createPragma} from 'jsx2tag';
-import {html} from './index.js';
-
-const createElement = createPragma(html);
-self.React = {
- createElement,
- Fragment: createElement
-};
-
-export * from 'jsx2tag';
-export * from './index.js';
diff --git a/index.d.ts b/index.d.ts
deleted file mode 100644
index eaddd47..0000000
--- a/index.d.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-export type TemplateFunction = (
- template: TemplateStringsArray,
- ...values: any[]
-) => T;
-
-export interface Tag extends TemplateFunction {
- for(object: object, id?: string): TemplateFunction;
- node: TemplateFunction;
-}
-
-export type Renderable = Hole | HTMLElement | SVGElement;
-
-export declare const html: Tag;
-export declare const svg: Tag;
-export declare function render(
- node: T,
- renderer: (() => Renderable) | Renderable,
-): T;
-
-/**
- * Used for internal purposes, should be created using
- * the `html` or `svg` template tags.
- */
-export declare class Hole {
- constructor(type: string, template: TemplateStringsArray, values: any[]);
- readonly type: string;
- readonly template: TemplateStringsArray;
- readonly values: readonly any[];
-}
diff --git a/index.js b/index.js
index 12a00b0..b14366b 100644
--- a/index.js
+++ b/index.js
@@ -1,804 +1,3 @@
-self.uhtml = (function (exports) {
- 'use strict';
-
- class MapSet extends Map {
- set(key, value) {
- super.set(key, value);
- return value;
- }
- }
-
- class WeakMapSet extends WeakMap {
- set(key, value) {
- super.set(key, value);
- return value;
- }
- }
-
- /*! (c) Andrea Giammarchi - ISC */
- const empty = /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i;
- const elements = /<([a-z]+[a-z0-9:._-]*)([^>]*?)(\/?)>/g;
- const attributes = /([^\s\\>"'=]+)\s*=\s*(['"]?)\x01/g;
- const holes = /[\x01\x02]/g;
-
- // \x01 Node.ELEMENT_NODE
- // \x02 Node.ATTRIBUTE_NODE
-
- /**
- * Given a template, find holes as both nodes and attributes and
- * return a string with holes as either comment nodes or named attributes.
- * @param {string[]} template a template literal tag array
- * @param {string} prefix prefix to use per each comment/attribute
- * @param {boolean} svg enforces self-closing tags
- * @returns {string} X/HTML with prefixed comments or attributes
- */
- var instrument = (template, prefix, svg) => {
- let i = 0;
- return template
- .join('\x01')
- .trim()
- .replace(
- elements,
- (_, name, attrs, selfClosing) => {
- let ml = name + attrs.replace(attributes, '\x02=$2$1').trimEnd();
- if (selfClosing.length)
- ml += (svg || empty.test(name)) ? ' /' : ('>' + name);
- return '<' + ml + '>';
- }
- )
- .replace(
- holes,
- hole => hole === '\x01' ?
- ('') :
- (prefix + i++)
- );
- };
-
- const ELEMENT_NODE = 1;
- const nodeType = 111;
-
- const remove = ({firstChild, lastChild}) => {
- const range = document.createRange();
- range.setStartAfter(firstChild);
- range.setEndAfter(lastChild);
- range.deleteContents();
- return firstChild;
- };
-
- const diffable = (node, operation) => node.nodeType === nodeType ?
- ((1 / operation) < 0 ?
- (operation ? remove(node) : node.lastChild) :
- (operation ? node.valueOf() : node.firstChild)) :
- node
- ;
-
- const persistent = fragment => {
- const {firstChild, lastChild} = fragment;
- if (firstChild === lastChild)
- return lastChild || fragment;
- const {childNodes} = fragment;
- const nodes = [...childNodes];
- return {
- ELEMENT_NODE,
- nodeType,
- firstChild,
- lastChild,
- valueOf() {
- if (childNodes.length !== nodes.length)
- fragment.append(...nodes);
- return fragment;
- }
- };
- };
-
- const {isArray: isArray$1} = Array;
-
- const aria = node => values => {
- for (const key in values) {
- const name = key === 'role' ? key : `aria-${key}`;
- const value = values[key];
- if (value == null)
- node.removeAttribute(name);
- else
- node.setAttribute(name, value);
- }
- };
-
- const getValue = value => value == null ? value : value.valueOf();
-
- const attribute = (node, name) => {
- let oldValue, orphan = true;
- const attributeNode = document.createAttributeNS(null, name);
- return newValue => {
- const value = getValue(newValue);
- if (oldValue !== value) {
- if ((oldValue = value) == null) {
- if (!orphan) {
- node.removeAttributeNode(attributeNode);
- orphan = true;
- }
- }
- else {
- attributeNode.value = value;
- if (orphan) {
- node.setAttributeNodeNS(attributeNode);
- orphan = false;
- }
- }
- }
- };
- };
-
- const boolean = (node, key, oldValue) => newValue => {
- const value = !!getValue(newValue);
- if (oldValue !== value) {
- // when IE won't be around anymore ...
- // node.toggleAttribute(key, oldValue = !!value);
- if ((oldValue = value))
- node.setAttribute(key, '');
- else
- node.removeAttribute(key);
- }
- };
-
- const data = ({dataset}) => values => {
- for (const key in values) {
- const value = values[key];
- if (value == null)
- delete dataset[key];
- else
- dataset[key] = value;
- }
- };
-
- const event = (node, name) => {
- let oldValue, lower, type = name.slice(2);
- if (!(name in node) && (lower = name.toLowerCase()) in node)
- type = lower.slice(2);
- return newValue => {
- const info = isArray$1(newValue) ? newValue : [newValue, false];
- if (oldValue !== info[0]) {
- if (oldValue)
- node.removeEventListener(type, oldValue, info[1]);
- if (oldValue = info[0])
- node.addEventListener(type, oldValue, info[1]);
- }
- };
- };
-
- const ref = node => {
- let oldValue;
- return value => {
- if (oldValue !== value) {
- oldValue = value;
- if (typeof value === 'function')
- value(node);
- else
- value.current = node;
- }
- };
- };
-
- const setter = (node, key) => key === 'dataset' ?
- data(node) :
- value => {
- node[key] = value;
- };
-
- const text = node => {
- let oldValue;
- return newValue => {
- const value = getValue(newValue);
- if (oldValue != value) {
- oldValue = value;
- node.textContent = value == null ? '' : value;
- }
- };
- };
-
- /**
- * ISC License
- *
- * Copyright (c) 2020, Andrea Giammarchi, @WebReflection
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
- * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- * PERFORMANCE OF THIS SOFTWARE.
- */
-
- /**
- * @param {Node} parentNode The container where children live
- * @param {Node[]} a The list of current/live children
- * @param {Node[]} b The list of future children
- * @param {(entry: Node, action: number) => Node} get
- * The callback invoked per each entry related DOM operation.
- * @param {Node} [before] The optional node used as anchor to insert before.
- * @returns {Node[]} The same list of future children.
- */
- var udomdiff = (parentNode, a, b, get, before) => {
- const bLength = b.length;
- let aEnd = a.length;
- let bEnd = bLength;
- let aStart = 0;
- let bStart = 0;
- let map = null;
- while (aStart < aEnd || bStart < bEnd) {
- // append head, tail, or nodes in between: fast path
- if (aEnd === aStart) {
- // we could be in a situation where the rest of nodes that
- // need to be added are not at the end, and in such case
- // the node to `insertBefore`, if the index is more than 0
- // must be retrieved, otherwise it's gonna be the first item.
- const node = bEnd < bLength ?
- (bStart ?
- (get(b[bStart - 1], -0).nextSibling) :
- get(b[bEnd - bStart], 0)) :
- before;
- while (bStart < bEnd)
- parentNode.insertBefore(get(b[bStart++], 1), node);
- }
- // remove head or tail: fast path
- else if (bEnd === bStart) {
- while (aStart < aEnd) {
- // remove the node only if it's unknown or not live
- if (!map || !map.has(a[aStart]))
- parentNode.removeChild(get(a[aStart], -1));
- aStart++;
- }
- }
- // same node: fast path
- else if (a[aStart] === b[bStart]) {
- aStart++;
- bStart++;
- }
- // same tail: fast path
- else if (a[aEnd - 1] === b[bEnd - 1]) {
- aEnd--;
- bEnd--;
- }
- // The once here single last swap "fast path" has been removed in v1.1.0
- // https://github.com/WebReflection/udomdiff/blob/single-final-swap/esm/index.js#L69-L85
- // reverse swap: also fast path
- else if (
- a[aStart] === b[bEnd - 1] &&
- b[bStart] === a[aEnd - 1]
- ) {
- // this is a "shrink" operation that could happen in these cases:
- // [1, 2, 3, 4, 5]
- // [1, 4, 3, 2, 5]
- // or asymmetric too
- // [1, 2, 3, 4, 5]
- // [1, 2, 3, 5, 6, 4]
- const node = get(a[--aEnd], -1).nextSibling;
- parentNode.insertBefore(
- get(b[bStart++], 1),
- get(a[aStart++], -1).nextSibling
- );
- parentNode.insertBefore(get(b[--bEnd], 1), node);
- // mark the future index as identical (yeah, it's dirty, but cheap š)
- // The main reason to do this, is that when a[aEnd] will be reached,
- // the loop will likely be on the fast path, as identical to b[bEnd].
- // In the best case scenario, the next loop will skip the tail,
- // but in the worst one, this node will be considered as already
- // processed, bailing out pretty quickly from the map index check
- a[aEnd] = b[bEnd];
- }
- // map based fallback, "slow" path
- else {
- // the map requires an O(bEnd - bStart) operation once
- // to store all future nodes indexes for later purposes.
- // In the worst case scenario, this is a full O(N) cost,
- // and such scenario happens at least when all nodes are different,
- // but also if both first and last items of the lists are different
- if (!map) {
- map = new Map;
- let i = bStart;
- while (i < bEnd)
- map.set(b[i], i++);
- }
- // if it's a future node, hence it needs some handling
- if (map.has(a[aStart])) {
- // grab the index of such node, 'cause it might have been processed
- const index = map.get(a[aStart]);
- // if it's not already processed, look on demand for the next LCS
- if (bStart < index && index < bEnd) {
- let i = aStart;
- // counts the amount of nodes that are the same in the future
- let sequence = 1;
- while (++i < aEnd && i < bEnd && map.get(a[i]) === (index + sequence))
- sequence++;
- // effort decision here: if the sequence is longer than replaces
- // needed to reach such sequence, which would brings again this loop
- // to the fast path, prepend the difference before a sequence,
- // and move only the future list index forward, so that aStart
- // and bStart will be aligned again, hence on the fast path.
- // An example considering aStart and bStart are both 0:
- // a: [1, 2, 3, 4]
- // b: [7, 1, 2, 3, 6]
- // this would place 7 before 1 and, from that time on, 1, 2, and 3
- // will be processed at zero cost
- if (sequence > (index - bStart)) {
- const node = get(a[aStart], 0);
- while (bStart < index)
- parentNode.insertBefore(get(b[bStart++], 1), node);
- }
- // if the effort wasn't good enough, fallback to a replace,
- // moving both source and target indexes forward, hoping that some
- // similar node will be found later on, to go back to the fast path
- else {
- parentNode.replaceChild(
- get(b[bStart++], 1),
- get(a[aStart++], -1)
- );
- }
- }
- // otherwise move the source forward, 'cause there's nothing to do
- else
- aStart++;
- }
- // this node has no meaning in the future list, so it's more than safe
- // to remove it, and check the next live node out instead, meaning
- // that only the live list index should be forwarded
- else
- parentNode.removeChild(get(a[aStart++], -1));
- }
- }
- return b;
- };
-
- const {isArray, prototype} = Array;
- const {indexOf} = prototype;
-
- const {
- createDocumentFragment,
- createElement,
- createElementNS,
- createTextNode,
- createTreeWalker,
- importNode
- } = new Proxy({}, {
- get: (_, method) => document[method].bind(document)
- });
-
- const createHTML = html => {
- const template = createElement('template');
- template.innerHTML = html;
- return template.content;
- };
-
- let xml;
- const createSVG = svg => {
- if (!xml) xml = createElementNS('http://www.w3.org/2000/svg', 'svg');
- xml.innerHTML = svg;
- const content = createDocumentFragment();
- content.append(...xml.childNodes);
- return content;
- };
-
- const createContent = (text, svg) => svg ?
- createSVG(text) : createHTML(text);
-
- // from a generic path, retrieves the exact targeted node
- const reducePath = ({childNodes}, i) => childNodes[i];
-
- // this helper avoid code bloat around handleAnything() callback
- const diff = (comment, oldNodes, newNodes) => udomdiff(
- comment.parentNode,
- // TODO: there is a possible edge case where a node has been
- // removed manually, or it was a keyed one, attached
- // to a shared reference between renders.
- // In this case udomdiff might fail at removing such node
- // as its parent won't be the expected one.
- // The best way to avoid this issue is to filter oldNodes
- // in search of those not live, or not in the current parent
- // anymore, but this would require both a change to uwire,
- // exposing a parentNode from the firstChild, as example,
- // but also a filter per each diff that should exclude nodes
- // that are not in there, penalizing performance quite a lot.
- // As this has been also a potential issue with domdiff,
- // and both lighterhtml and hyperHTML might fail with this
- // very specific edge case, I might as well document this possible
- // "diffing shenanigan" and call it a day.
- oldNodes,
- newNodes,
- diffable,
- comment
- );
-
- // if an interpolation represents a comment, the whole
- // diffing will be related to such comment.
- // This helper is in charge of understanding how the new
- // content for such interpolation/hole should be updated
- const handleAnything = comment => {
- let oldValue, text, nodes = [];
- const anyContent = newValue => {
- switch (typeof newValue) {
- // primitives are handled as text content
- case 'string':
- case 'number':
- case 'boolean':
- if (oldValue !== newValue) {
- oldValue = newValue;
- if (!text)
- text = createTextNode('');
- text.data = newValue;
- nodes = diff(comment, nodes, [text]);
- }
- break;
- // null, and undefined are used to cleanup previous content
- case 'object':
- case 'undefined':
- if (newValue == null) {
- if (oldValue != newValue) {
- oldValue = newValue;
- nodes = diff(comment, nodes, []);
- }
- break;
- }
- // arrays and nodes have a special treatment
- if (isArray(newValue)) {
- oldValue = newValue;
- // arrays can be used to cleanup, if empty
- if (newValue.length === 0)
- nodes = diff(comment, nodes, []);
- // or diffed, if these contains nodes or "wires"
- else if (typeof newValue[0] === 'object')
- nodes = diff(comment, nodes, newValue);
- // in all other cases the content is stringified as is
- else
- anyContent(String(newValue));
- break;
- }
- // if the new value is a DOM node, or a wire, and it's
- // different from the one already live, then it's diffed.
- // if the node is a fragment, it's appended once via its childNodes
- // There is no `else` here, meaning if the content
- // is not expected one, nothing happens, as easy as that.
- if (oldValue !== newValue) {
- if ('ELEMENT_NODE' in newValue) {
- oldValue = newValue;
- nodes = diff(
- comment,
- nodes,
- newValue.nodeType === 11 ?
- [...newValue.childNodes] :
- [newValue]
- );
- }
- else {
- const value = newValue.valueOf();
- if (value !== newValue)
- anyContent(value);
- }
- }
- break;
- case 'function':
- anyContent(newValue(comment));
- break;
- }
- };
- return anyContent;
- };
-
- // attributes can be:
- // * ref=${...} for hooks and other purposes
- // * aria=${...} for aria attributes
- // * ?boolean=${...} for boolean attributes
- // * .dataset=${...} for dataset related attributes
- // * .setter=${...} for Custom Elements setters or nodes with setters
- // such as buttons, details, options, select, etc
- // * @event=${...} to explicitly handle event listeners
- // * onevent=${...} to automatically handle event listeners
- // * generic=${...} to handle an attribute just like an attribute
- const handleAttribute = (node, name/*, svg*/) => {
- switch (name[0]) {
- case '?': return boolean(node, name.slice(1), false);
- case '.': return setter(node, name.slice(1));
- case '@': return event(node, 'on' + name.slice(1));
- case 'o': if (name[1] === 'n') return event(node, name);
- }
-
- switch (name) {
- case 'ref': return ref(node);
- case 'aria': return aria(node);
- }
-
- return attribute(node, name/*, svg*/);
- };
-
- // each mapped update carries the update type and its path
- // the type is either node, attribute, or text, while
- // the path is how to retrieve the related node to update.
- // In the attribute case, the attribute name is also carried along.
- function handlers(options) {
- const {type, path} = options;
- const node = path.reduceRight(reducePath, this);
- return type === 'node' ?
- handleAnything(node) :
- (type === 'attr' ?
- handleAttribute(node, options.name/*, options.svg*/) :
- text(node));
- }
-
- // from a fragment container, create an array of indexes
- // related to its child nodes, so that it's possible
- // to retrieve later on exact node via reducePath
- const createPath = node => {
- const path = [];
- let {parentNode} = node;
- while (parentNode) {
- path.push(indexOf.call(parentNode.childNodes, node));
- node = parentNode;
- ({parentNode} = node);
- }
- return path;
- };
-
- // the prefix is used to identify either comments, attributes, or nodes
- // that contain the related unique id. In the attribute cases
- // isĀµX="attribute-name" will be used to map current X update to that
- // attribute name, while comments will be like , to map
- // the update to that specific comment node, hence its parent.
- // style and textarea will have text content, and are handled
- // directly through text-only updates.
- const prefix = 'isĀµ';
-
- // Template Literals are unique per scope and static, meaning a template
- // should be parsed once, and once only, as it will always represent the same
- // content, within the exact same amount of updates each time.
- // This cache relates each template to its unique content and updates.
- const cache$1 = new WeakMapSet;
-
- // a RegExp that helps checking nodes that cannot contain comments
- const textOnly = /^(?:textarea|script|style|title|plaintext|xmp)$/;
-
- const createCache = () => ({
- stack: [], // each template gets a stack for each interpolation "hole"
-
- entry: null, // each entry contains details, such as:
- // * the template that is representing
- // * the type of node it represents (html or svg)
- // * the content fragment with all nodes
- // * the list of updates per each node (template holes)
- // * the "wired" node or fragment that will get updates
- // if the template or type are different from the previous one
- // the entry gets re-created each time
-
- wire: null // each rendered node represent some wired content and
- // this reference to the latest one. If different, the node
- // will be cleaned up and the new "wire" will be appended
- });
-
- // the entry stored in the rendered node cache, and per each "hole"
- const createEntry = (type, template) => {
- const {content, updates} = mapUpdates(type, template);
- return {type, template, content, updates, wire: null};
- };
-
- // a template is instrumented to be able to retrieve where updates are needed.
- // Each unique template becomes a fragment, cloned once per each other
- // operation based on the same template, i.e. data => html`${data}
`
- const mapTemplate = (type, template) => {
- const svg = type === 'svg';
- const text = instrument(template, prefix, svg);
- const content = createContent(text, svg);
- // once instrumented and reproduced as fragment, it's crawled
- // to find out where each update is in the fragment tree
- const tw = createTreeWalker(content, 1 | 128);
- const nodes = [];
- const length = template.length - 1;
- let i = 0;
- // updates are searched via unique names, linearly increased across the tree
- //
- let search = `${prefix}${i}`;
- while (i < length) {
- const node = tw.nextNode();
- // if not all updates are bound but there's nothing else to crawl
- // it means that there is something wrong with the template.
- if (!node)
- throw `bad template: ${text}`;
- // if the current node is a comment, and it contains isĀµX
- // it means the update should take care of any content
- if (node.nodeType === 8) {
- // The only comments to be considered are those
- // which content is exactly the same as the searched one.
- if (node.data === search) {
- nodes.push({type: 'node', path: createPath(node)});
- search = `${prefix}${++i}`;
- }
- }
- else {
- // if the node is not a comment, loop through all its attributes
- // named isĀµX and relate attribute updates to this node and the
- // attribute name, retrieved through node.getAttribute("isĀµX")
- // the isĀµX attribute will be removed as irrelevant for the layout
- // let svg = -1;
- while (node.hasAttribute(search)) {
- nodes.push({
- type: 'attr',
- path: createPath(node),
- name: node.getAttribute(search)
- });
- node.removeAttribute(search);
- search = `${prefix}${++i}`;
- }
- // if the node was a style, textarea, or others, check its content
- // and if it is then update tex-only this node
- if (
- textOnly.test(node.localName) &&
- node.textContent.trim() === ``
- ){
- node.textContent = '';
- nodes.push({type: 'text', path: createPath(node)});
- search = `${prefix}${++i}`;
- }
- }
- }
- // once all nodes to update, or their attributes, are known, the content
- // will be cloned in the future to represent the template, and all updates
- // related to such content retrieved right away without needing to re-crawl
- // the exact same template, and its content, more than once.
- return {content, nodes};
- };
-
- // if a template is unknown, perform the previous mapping, otherwise grab
- // its details such as the fragment with all nodes, and updates info.
- const mapUpdates = (type, template) => {
- const {content, nodes} = (
- cache$1.get(template) ||
- cache$1.set(template, mapTemplate(type, template))
- );
- // clone deeply the fragment
- const fragment = importNode(content, true);
- // and relate an update handler per each node that needs one
- const updates = nodes.map(handlers, fragment);
- // return the fragment and all updates to use within its nodes
- return {content: fragment, updates};
- };
-
- // as html and svg can be nested calls, but no parent node is known
- // until rendered somewhere, the unroll operation is needed to
- // discover what to do with each interpolation, which will result
- // into an update operation.
- const unroll = (info, {type, template, values}) => {
- // interpolations can contain holes and arrays, so these need
- // to be recursively discovered
- const length = unrollValues(info, values);
- let {entry} = info;
- // if the cache entry is either null or different from the template
- // and the type this unroll should resolve, create a new entry
- // assigning a new content fragment and the list of updates.
- if (!entry || (entry.template !== template || entry.type !== type))
- info.entry = (entry = createEntry(type, template));
- const {content, updates, wire} = entry;
- // even if the fragment and its nodes is not live yet,
- // it is already possible to update via interpolations values.
- for (let i = 0; i < length; i++)
- updates[i](values[i]);
- // if the entry was new, or representing a different template or type,
- // create a new persistent entity to use during diffing.
- // This is simply a DOM node, when the template has a single container,
- // as in ``, or a "wire" in `` and similar cases.
- return wire || (entry.wire = persistent(content));
- };
-
- // the stack retains, per each interpolation value, the cache
- // related to each interpolation value, or null, if the render
- // was conditional and the value is not special (Array or Hole)
- const unrollValues = ({stack}, values) => {
- const {length} = values;
- for (let i = 0; i < length; i++) {
- const hole = values[i];
- // each Hole gets unrolled and re-assigned as value
- // so that domdiff will deal with a node/wire, not with a hole
- if (hole instanceof Hole)
- values[i] = unroll(
- stack[i] || (stack[i] = createCache()),
- hole
- );
- // arrays are recursively resolved so that each entry will contain
- // also a DOM node or a wire, hence it can be diffed if/when needed
- else if (isArray(hole))
- unrollValues(stack[i] || (stack[i] = createCache()), hole);
- // if the value is nothing special, the stack doesn't need to retain data
- // this is useful also to cleanup previously retained data, if the value
- // was a Hole, or an Array, but not anymore, i.e.:
- // const update = content => html`${content}
`;
- // update(listOfItems); update(null); update(html`hole`)
- else
- stack[i] = null;
- }
- if (length < stack.length)
- stack.splice(length);
- return length;
- };
-
- /**
- * Holds all details wrappers needed to render the content further on.
- * @constructor
- * @param {string} type The hole type, either `html` or `svg`.
- * @param {string[]} template The template literals used to the define the content.
- * @param {Array} values Zero, one, or more interpolated values to render.
- */
- class Hole {
- constructor(type, template, values) {
- this.type = type;
- this.template = template;
- this.values = values;
- }
- }
-
- // both `html` and `svg` template literal tags are polluted
- // with a `for(ref[, id])` and a `node` tag too
- const tag = type => {
- // both `html` and `svg` tags have their own cache
- const keyed = new WeakMapSet;
- // keyed operations always re-use the same cache and unroll
- // the template and its interpolations right away
- const fixed = cache => (template, ...values) => unroll(
- cache,
- {type, template, values}
- );
- return Object.assign(
- // non keyed operations are recognized as instance of Hole
- // during the "unroll", recursively resolved and updated
- (template, ...values) => new Hole(type, template, values),
- {
- // keyed operations need a reference object, usually the parent node
- // which is showing keyed results, and optionally a unique id per each
- // related node, handy with JSON results and mutable list of objects
- // that usually carry a unique identifier
- for(ref, id) {
- const memo = keyed.get(ref) || keyed.set(ref, new MapSet);
- return memo.get(id) || memo.set(id, fixed(createCache()));
- },
- // it is possible to create one-off content out of the box via node tag
- // this might return the single created node, or a fragment with all
- // nodes present at the root level and, of course, their child nodes
- node: (template, ...values) => unroll(createCache(), new Hole(type, template, values)).valueOf()
- }
- );
- };
-
- // each rendered node gets its own cache
- const cache = new WeakMapSet;
-
- // rendering means understanding what `html` or `svg` tags returned
- // and it relates a specific node to its own unique cache.
- // Each time the content to render changes, the node is cleaned up
- // and the new new content is appended, and if such content is a Hole
- // then it's "unrolled" to resolve all its inner nodes.
- const render = (where, what) => {
- const hole = typeof what === 'function' ? what() : what;
- const info = cache.get(where) || cache.set(where, createCache());
- const wire = hole instanceof Hole ? unroll(info, hole) : hole;
- if (wire !== info.wire) {
- info.wire = wire;
- // valueOf() simply returns the node itself, but in case it was a "wire"
- // it will eventually re-append all nodes to its fragment so that such
- // fragment can be re-appended many times in a meaningful way
- // (wires are basically persistent fragments facades with special behavior)
- where.replaceChildren(wire.valueOf());
- }
- return where;
- };
-
- const html = tag('html');
- const svg = tag('svg');
-
- exports.Hole = Hole;
- exports.html = html;
- exports.render = render;
- exports.svg = svg;
-
- return exports;
-
-})({});
+const{isArray:e}=Array,t=[],n=()=>document.createRange(),r=(e,t,n)=>(e.set(t,n),n),s=(e,t,n,r="")=>({t:e,p:t,u:n,n:r}),l=e=>({s:e,t:null,n:null,d:t}),{setPrototypeOf:o}=Object;let c;var i=(e,t,r)=>(c||(c=n()),r?c.setStartAfter(e):c.setStartBefore(e),c.setEndAfter(t),c.deleteContents(),e);const a=({firstChild:e,lastChild:t})=>i(e,t,!0);let u=!1;const h=(e,t)=>u&&11===e.nodeType?1/t<0?t?a(e):e.lastChild:t?e.valueOf():e.firstChild:e;class d extends((e=>{function t(e){return o(e,new.target.prototype)}return t.prototype=e.prototype,t})(DocumentFragment)){#e;#t;constructor(e){super(e),this.#e=[...e.childNodes],this.#t=this.#e.length,u=!0}get firstChild(){return this.#e[0]}get lastChild(){return this.#e.at(-1)}replaceWith(e){a(this).replaceWith(e)}valueOf(){return this.childNodes.length!==this.#t&&this.append(...this.#e),this}}const f=(e,t)=>t.reduceRight(p,e),p=(e,t)=>e.childNodes[t];var g=e=>(n,r)=>{const{c:s,e:l,l:o}=e(n,r),c=s.cloneNode(!0);let i,a,u=l.length,h=u?l.slice(0):t;for(;u--;){const{t:e,p:n,u:s,n:o}=l[u],d=n===a?i:i=f(c,a=n),p=8===e?s():s;h[u]={v:p(d,r[u],o,t),u:p,t:d,n:o}}return((e,t)=>({n:e,d:t}))(1===o?c.firstChild:new d(c),h)};const m=/^(?:plaintext|script|style|textarea|title|xmp)$/i,v=/^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i,x=/<([a-zA-Z0-9]+[a-zA-Z0-9:._-]*)([^>]*?)(\/?)>/g,b=/([^\s\\>"'=]+)\s*=\s*(['"]?)\x01/g,$=/[\x01\x02]/g;const C=(e,t)=>{for(const n in t){const r=t[n],s="role"===n?n:`aria-${n}`;null==r?e.removeAttribute(s):e.setAttribute(s,r)}return t};let w;const y=(t,n,s)=>{s=s.slice(1),w||(w=new WeakMap);const l=w.get(t)||r(w,t,{});let o=l[s];return o&&o[0]&&t.removeEventListener(...o),o=e(n)?n:[n,!1],l[s]=o,o[0]&&t.addEventListener(...o),n},N=(e,t)=>W(e,t,"className"),A=(e,t)=>{const{dataset:n}=e;for(const e in t)null==t[e]?delete n[e]:n[e]=t[e];return t},W=(e,t,n)=>e[n]=t,k=(e,t,n)=>W(e,t,n.slice(1)),E=(e,t)=>("function"==typeof t?t(e):t.current=e,t),O=(e,t,n)=>(null==t?e.removeAttribute(n):e.setAttribute(n,t),t),S=(e,t)=>W(e.style,t,"cssText"),T=(e,t,n)=>(e.toggleAttribute(n.slice(1),t),t),B=(e,n,r,s)=>n.length?((e,t,n,r,s)=>{const l=n.length;let o=t.length,c=l,i=0,a=0,u=null;for(;is-a){const l=r(t[i],0);for(;a{switch(t[0]){case".":return k;case"?":return T;case"@":return y;default:switch(t){case"aria":return C;case"class":return N;case"data":return A;case"ref":return E;case"style":return S;default:return t in e?W:O}}},j=(e,t)=>(e.texContent=null==t?"":t,t);function L(e,t){const n=this.n||(this.n=e);switch(typeof t){case"string":case"number":case"boolean":n!==e&&n.replaceWith(this.n=e),this.n.data=t;break;case"object":case"undefined":n.replaceWith(this.n=null==t?e:t.valueOf())}return t}let z,F,R=document.createElement("template");var Z=(e,t)=>{if(t)return z||(z=document.createElementNS("http://www.w3.org/2000/svg","svg"),F=n(),F.selectNodeContents(z)),F.createContextualFragment(e);R.innerHTML=e;const{content:r}=R;return R=R.cloneNode(!1),r};const D=e=>{const t=[];let n;for(;n=e.parentNode;)t.push(t.indexOf.call(n.childNodes,e)),e=n;return t},H=()=>L.bind({n:null}),P=()=>B,_=(n,l,o)=>{const c=Z(((e,t,n)=>{let r=0;return e.join("").trim().replace(x,((e,t,r,s)=>`<${t}${r.replace(b,"=$2$1").trimEnd()}${s?n||v.test(t)?" /":`>${t}`:""}>`)).replace($,(e=>""===e?`\x3c!--${t+r++}--\x3e`:t+r++))})(n,G,o),o);let i=t;const{length:a}=n;if(a>1){const t=document.createTreeWalker(c,129),n=[];let r=0,o=`${G}${r++}`;for(i=[];r(t,n)=>q.get(t)||_(t,n,e);const J=g(I(!1)),K=g(I(!0)),Q=(e,{s:n,t:r,v:s})=>{if(s.length&&e.s===t&&(e.s=[]),U(e,s),e.t!==r){const{n:t,d:l}=(n?K:J)(r,s);e.t=r,e.n=t,e.d=l}else{const{d:t}=e;for(let e=0;e{const{length:s}=r;for(let o=0;o{const s=X.get(e)||r(X,e,l(t));return s.n!==Q(s,"function"==typeof n?n():n)&&e.replaceChildren(s.n),e};
+/*! (c) Andrea Giammarchi - MIT */const ee=e=>(t,...n)=>new V(e,t,n),te=ee(!1),ne=ee(!0);export{V as Hole,te as html,Y as render,ne as svg};
diff --git a/init.js b/init.js
deleted file mode 100644
index 1869b3c..0000000
--- a/init.js
+++ /dev/null
@@ -1,2 +0,0 @@
-class e extends WeakMap{set(e,t){return super.set(e,t),t}}
-/*! (c) Andrea Giammarchi - ISC */const t=/^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i,n=/<([a-z]+[a-z0-9:._-]*)([^>]*?)(\/?)>/g,r=/([^\s\\>"'=]+)\s*=\s*(['"]?)\x01/g,s=/[\x01\x02]/g;var l=({document:l})=>{const{isArray:o,prototype:a}=Array,{indexOf:i}=a,{createDocumentFragment:c,createElement:u,createElementNS:d,createTextNode:p,createTreeWalker:f,importNode:h}=new Proxy({},{get:(e,t)=>l[t].bind(l)});let g;const m=(e,t)=>t?(e=>{g||(g=d("http://www.w3.org/2000/svg","svg")),g.innerHTML=e;const t=c();return t.append(...g.childNodes),t})(e):(e=>{const t=u("template");return t.innerHTML=e,t.content})(e),y=(e,t)=>111===e.nodeType?1/t<0?t?(({firstChild:e,lastChild:t})=>{const n=l.createRange();return n.setStartAfter(e),n.setEndAfter(t),n.deleteContents(),e})(e):e.lastChild:t?e.valueOf():e.firstChild:e,b=e=>null==e?e:e.valueOf(),w=(e,t)=>{let n,r,s=t.slice(2);return!(t in e)&&(r=t.toLowerCase())in e&&(s=r.slice(2)),t=>{const r=o(t)?t:[t,!1];n!==r[0]&&(n&&e.removeEventListener(s,n,r[1]),(n=r[0])&&e.addEventListener(s,n,r[1]))}},v=({childNodes:e},t)=>e[t],x=(e,t,n)=>((e,t,n,r,s)=>{const l=n.length;let o=t.length,a=l,i=0,c=0,u=null;for(;is-c){const l=r(t[i],0);for(;c{switch(t[0]){case"?":return((e,t,n)=>r=>{const s=!!b(r);n!==s&&((n=s)?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 w(e,"on"+t.slice(1));case"o":if("n"===t[1])return w(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}`,s=t[n];null==s?e.removeAttribute(r):e.setAttribute(r,s)}})(e)}return((e,t)=>{let n,r=!0;const s=l.createAttributeNS(null,t);return t=>{const l=b(t);n!==l&&(null==(n=l)?r||(e.removeAttributeNode(s),r=!0):(s.value=l,r&&(e.setAttributeNodeNS(s),r=!1)))}})(e,t)};function C(e){const{type:t,path:n}=e,r=n.reduceRight(v,this);return"node"===t?(e=>{let t,n,r=[];const s=l=>{switch(typeof l){case"string":case"number":case"boolean":t!==l&&(t=l,n||(n=p("")),n.data=l,r=x(e,r,[n]));break;case"object":case"undefined":if(null==l){t!=l&&(t=l,r=x(e,r,[]));break}if(o(l)){t=l,0===l.length?r=x(e,r,[]):"object"==typeof l[0]?r=x(e,r,l):s(String(l));break}if(t!==l)if("ELEMENT_NODE"in l)t=l,r=x(e,r,11===l.nodeType?[...l.childNodes]:[l]);else{const e=l.valueOf();e!==l&&s(e)}break;case"function":s(l(e))}};return s})(r):"attr"===t?N(r,e.name):(e=>{let t;return n=>{const r=b(n);t!=r&&(t=r,e.textContent=null==r?"":r)}})(r)}const k=e=>{const t=[];let{parentNode:n}=e;for(;n;)t.push(i.call(n.childNodes,e)),e=n,({parentNode:n}=e);return t},$="isĀµ",A=new e,E=/^(?:textarea|script|style|title|plaintext|xmp)$/,O=(e,l)=>{const o="svg"===e,a=((e,l,o)=>{let a=0;return e.join("").trim().replace(n,((e,n,s,l)=>{let a=n+s.replace(r,"=$2$1").trimEnd();return l.length&&(a+=o||t.test(n)?" /":">"+n),"<"+a+">"})).replace(s,(e=>""===e?"\x3c!--"+l+a+++"--\x3e":l+a++))})(l,$,o),i=m(a,o),c=f(i,129),u=[],d=l.length-1;let p=0,h=`${$}${p}`;for(;p{const{content:n,nodes:r}=A.get(t)||A.set(t,O(e,t)),s=h(n,!0);return{content:s,updates:r.map(C,s)}},S=(e,{type:t,template:n,values:r})=>{const s=L(e,r);let{entry:l}=e;l&&l.template===n&&l.type===t||(e.entry=l=((e,t)=>{const{content:n,updates:r}=T(e,t);return{type:e,template:t,content:n,updates:r,wire:null}})(t,n));const{content:o,updates:a,wire:i}=l;for(let e=0;e{const{firstChild:t,lastChild:n}=e;if(t===n)return n||e;const{childNodes:r}=e,s=[...r];return{ELEMENT_NODE:1,nodeType:111,firstChild:t,lastChild:n,valueOf:()=>(r.length!==s.length&&e.append(...s),e)}})(o))},L=({stack:e},t)=>{const{length:n}=t;for(let r=0;r{const n=new e;return Object.assign(((e,...n)=>new M(t,e,n)),{for(e,r){const s=n.get(e)||n.set(e,new MapSet);return s.get(r)||s.set(r,(e=>(n,...r)=>S(e,{type:t,template:n,values:r}))({stack:[],entry:null,wire:null}))},node:(e,...n)=>S({stack:[],entry:null,wire:null},new M(t,e,n)).valueOf()})},B=new e,D=j("html"),H=j("svg");return{Hole:M,render:(e,t)=>{const n="function"==typeof t?t():t,r=B.get(e)||B.set(e,{stack:[],entry:null,wire:null}),s=n instanceof M?S(r,n):n;return s!==r.wire&&(r.wire=s,e.replaceChildren(s.valueOf())),e},html:D,svg:H}};export{l as default};
diff --git a/keyed.js b/keyed.js
new file mode 100644
index 0000000..1e3b0da
--- /dev/null
+++ b/keyed.js
@@ -0,0 +1,3 @@
+const{isArray:e}=Array,t=[],n=()=>document.createRange(),r=(e,t,n)=>(e.set(t,n),n),s=(e,t,n,r="")=>({t:e,p:t,u:n,n:r}),l=e=>({s:e,t:null,n:null,d:t}),{setPrototypeOf:o}=Object;let i;var c=(e,t,r)=>(i||(i=n()),r?i.setStartAfter(e):i.setStartBefore(e),i.setEndAfter(t),i.deleteContents(),e);const a=({firstChild:e,lastChild:t})=>c(e,t,!0);let u=!1;const h=(e,t)=>u&&11===e.nodeType?1/t<0?t?a(e):e.lastChild:t?e.valueOf():e.firstChild:e;class d extends((e=>{function t(e){return o(e,new.target.prototype)}return t.prototype=e.prototype,t})(DocumentFragment)){#e;#t;constructor(e){super(e),this.#e=[...e.childNodes],this.#t=this.#e.length,u=!0}get firstChild(){return this.#e[0]}get lastChild(){return this.#e.at(-1)}replaceWith(e){a(this).replaceWith(e)}valueOf(){return this.childNodes.length!==this.#t&&this.append(...this.#e),this}}const f=(e,t)=>t.reduceRight(p,e),p=(e,t)=>e.childNodes[t];var g=e=>(n,r)=>{const{c:s,e:l,l:o}=e(n,r),i=s.cloneNode(!0);let c,a,u=l.length,h=u?l.slice(0):t;for(;u--;){const{t:e,p:n,u:s,n:o}=l[u],d=n===a?c:c=f(i,a=n),p=8===e?s():s;h[u]={v:p(d,r[u],o,t),u:p,t:d,n:o}}return((e,t)=>({n:e,d:t}))(1===o?i.firstChild:new d(i),h)};const m=/^(?:plaintext|script|style|textarea|title|xmp)$/i,v=/^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i,b=/<([a-zA-Z0-9]+[a-zA-Z0-9:._-]*)([^>]*?)(\/?)>/g,x=/([^\s\\>"'=]+)\s*=\s*(['"]?)\x01/g,$=/[\x01\x02]/g;const w=(e,t)=>{for(const n in t){const r=t[n],s="role"===n?n:`aria-${n}`;null==r?e.removeAttribute(s):e.setAttribute(s,r)}return t};let C;const y=(t,n,s)=>{s=s.slice(1),C||(C=new WeakMap);const l=C.get(t)||r(C,t,{});let o=l[s];return o&&o[0]&&t.removeEventListener(...o),o=e(n)?n:[n,!1],l[s]=o,o[0]&&t.addEventListener(...o),n},N=(e,t)=>W(e,t,"className"),A=(e,t)=>{const{dataset:n}=e;for(const e in t)null==t[e]?delete n[e]:n[e]=t[e];return t},W=(e,t,n)=>e[n]=t,k=(e,t,n)=>W(e,t,n.slice(1)),M=(e,t)=>("function"==typeof t?t(e):t.current=e,t),E=(e,t,n)=>(null==t?e.removeAttribute(n):e.setAttribute(n,t),t),O=(e,t)=>W(e.style,t,"cssText"),S=(e,t,n)=>(e.toggleAttribute(n.slice(1),t),t),T=(e,n,r,s)=>n.length?((e,t,n,r,s)=>{const l=n.length;let o=t.length,i=l,c=0,a=0,u=null;for(;cs-a){const l=r(t[c],0);for(;a{switch(t[0]){case".":return k;case"?":return S;case"@":return y;default:switch(t){case"aria":return w;case"class":return N;case"data":return A;case"ref":return M;case"style":return O;default:return t in e?W:E}}},j=(e,t)=>(e.texContent=null==t?"":t,t);function L(e,t){const n=this.n||(this.n=e);switch(typeof t){case"string":case"number":case"boolean":n!==e&&n.replaceWith(this.n=e),this.n.data=t;break;case"object":case"undefined":n.replaceWith(this.n=null==t?e:t.valueOf())}return t}let z,F,R=document.createElement("template");var Z=(e,t)=>{if(t)return z||(z=document.createElementNS("http://www.w3.org/2000/svg","svg"),F=n(),F.selectNodeContents(z)),F.createContextualFragment(e);R.innerHTML=e;const{content:r}=R;return R=R.cloneNode(!1),r};const D=e=>{const t=[];let n;for(;n=e.parentNode;)t.push(t.indexOf.call(n.childNodes,e)),e=n;return t},H=()=>L.bind({n:null}),P=()=>T,_=(n,l,o)=>{const i=Z(((e,t,n)=>{let r=0;return e.join("").trim().replace(b,((e,t,r,s)=>`<${t}${r.replace(x,"=$2$1").trimEnd()}${s?n||v.test(t)?" /":`>${t}`:""}>`)).replace($,(e=>""===e?`\x3c!--${t+r++}--\x3e`:t+r++))})(n,G,o),o);let c=t;const{length:a}=n;if(a>1){const t=document.createTreeWalker(i,129),n=[];let r=0,o=`${G}${r++}`;for(c=[];r(t,n)=>q.get(t)||_(t,n,e);const J=g(I(!1)),K=g(I(!0)),Q=(e,{s:n,t:r,v:s})=>{if(s.length&&e.s===t&&(e.s=[]),U(e,s),e.t!==r){const{n:t,d:l}=(n?K:J)(r,s);e.t=r,e.n=t,e.d=l}else{const{d:t}=e;for(let e=0;e{const{length:s}=r;for(let o=0;o(t,...n)=>new V(e,t,n),Y=X(!1),ee=X(!0),te=new WeakMap;var ne=(e,n)=>{const s=te.get(e)||r(te,e,l(t)),o="function"==typeof n?n():n,{n:i}=s,c=o instanceof V?Q(s,o):o;return i!==c&&e.replaceChildren(s.n=c),e};
+/*! (c) Andrea Giammarchi - MIT */const re=new WeakMap,se=e=>(n,s)=>{const o=re.get(n)||r(re,n,new Map);return o.get(s)||r(o,s,function(t,...n){return Q(this,new V(e,t,n))}.bind(l(t)))},le=se(!1),oe=se(!0);export{V as Hole,Y as html,le as htmlFor,ne as render,ee as svg,oe as svgFor};
diff --git a/node.js b/node.js
new file mode 100644
index 0000000..169fc97
--- /dev/null
+++ b/node.js
@@ -0,0 +1,3 @@
+const{setPrototypeOf:e}=Object;const{isArray:t}=Array,n=[],r=()=>document.createRange(),s=(e,t,n)=>(e.set(t,n),n);let l;var o=(e,t,n)=>(l||(l=r()),n?l.setStartAfter(e):l.setStartBefore(e),l.setEndAfter(t),l.deleteContents(),e);const i=({firstChild:e,lastChild:t})=>o(e,t,!0);let c=!1;const a=(e,t)=>c&&11===e.nodeType?1/t<0?t?i(e):e.lastChild:t?e.valueOf():e.firstChild:e;class u extends((t=>{function n(t){return e(t,new.target.prototype)}return n.prototype=t.prototype,n})(DocumentFragment)){#e;#t;constructor(e){super(e),this.#e=[...e.childNodes],this.#t=this.#e.length,c=!0}get firstChild(){return this.#e[0]}get lastChild(){return this.#e.at(-1)}replaceWith(e){i(this).replaceWith(e)}valueOf(){return this.childNodes.length!==this.#t&&this.append(...this.#e),this}}const h=(e,t,n,r="")=>({t:e,p:t,u:n,n:r}),d=(e,t)=>t.reduceRight(f,e),f=(e,t)=>e.childNodes[t];const p=/^(?:plaintext|script|style|textarea|title|xmp)$/i,g=/^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i,m=/<([a-zA-Z0-9]+[a-zA-Z0-9:._-]*)([^>]*?)(\/?)>/g,x=/([^\s\\>"'=]+)\s*=\s*(['"]?)\x01/g,b=/[\x01\x02]/g;const v=(e,t)=>{for(const n in t){const r=t[n],s="role"===n?n:`aria-${n}`;null==r?e.removeAttribute(s):e.setAttribute(s,r)}return t};let $;const C=(e,n,r)=>{r=r.slice(1),$||($=new WeakMap);const l=$.get(e)||s($,e,{});let o=l[r];return o&&o[0]&&e.removeEventListener(...o),o=t(n)?n:[n,!1],l[r]=o,o[0]&&e.addEventListener(...o),n},y=(e,t)=>A(e,t,"className"),N=(e,t)=>{const{dataset:n}=e;for(const e in t)null==t[e]?delete n[e]:n[e]=t[e];return t},A=(e,t,n)=>e[n]=t,w=(e,t,n)=>A(e,t,n.slice(1)),W=(e,t)=>("function"==typeof t?t(e):t.current=e,t),k=(e,t,n)=>(null==t?e.removeAttribute(n):e.setAttribute(n,t),t),E=(e,t)=>A(e.style,t,"cssText"),O=(e,t,n)=>(e.toggleAttribute(n.slice(1),t),t),S=(e,t,r,s)=>t.length?((e,t,n,r,s)=>{const l=n.length;let o=t.length,i=l,c=0,a=0,u=null;for(;cs-a){const l=r(t[c],0);for(;a{switch(t[0]){case".":return w;case"?":return O;case"@":return C;default:switch(t){case"aria":return v;case"class":return y;case"data":return N;case"ref":return W;case"style":return E;default:return t in e?A:k}}},B=(e,t)=>(e.texContent=null==t?"":t,t);function M(e,t){const n=this.n||(this.n=e);switch(typeof t){case"string":case"number":case"boolean":n!==e&&n.replaceWith(this.n=e),this.n.data=t;break;case"object":case"undefined":n.replaceWith(this.n=null==t?e:t.valueOf())}return t}let j,L,z=document.createElement("template");var F=(e,t)=>{if(t)return j||(j=document.createElementNS("http://www.w3.org/2000/svg","svg"),L=r(),L.selectNodeContents(j)),L.createContextualFragment(e);z.innerHTML=e;const{content:n}=z;return z=z.cloneNode(!1),n};const R=e=>{const t=[];let n;for(;n=e.parentNode;)t.push(t.indexOf.call(n.childNodes,e)),e=n;return t},Z=()=>M.bind({n:null}),D=()=>S,H=(e,r,l)=>{const o=F(((e,t,n)=>{let r=0;return e.join("").trim().replace(m,((e,t,r,s)=>`<${t}${r.replace(x,"=$2$1").trimEnd()}${s?n||g.test(t)?" /":`>${t}`:""}>`)).replace(b,(e=>""===e?`\x3c!--${t+r++}--\x3e`:t+r++))})(e,_,l),l);let i=n;const{length:c}=e;if(c>1){const e=document.createTreeWalker(o,129),n=[];let s=0,l=`${_}${s++}`;for(i=[];s(e.replaceChildren("function"==typeof t?t():t),e);
+/*! (c) Andrea Giammarchi - MIT */
+const G=e=>{const t=(e=>(t,r)=>{const{c:s,e:l,l:o}=e(t,r),i=s.cloneNode(!0);let c,a,h=l.length,f=h?l.slice(0):n;for(;h--;){const{t:e,p:t,u:s,n:o}=l[h],u=t===a?c:c=d(i,a=t),p=8===e?s():s;f[h]={v:p(u,r[h],o,n),u:p,t:u,n:o}}return{n:1===o?i.firstChild:new u(i),d:f}})((r=e,(e,t)=>P.get(e)||H(e,t,r)));var r;return(e,...n)=>t(e,n).n},I=G(!1),J=G(!0);export{I as html,q as render,J as svg};
diff --git a/package-lock.json b/package-lock.json
index 9553bfe..efbb492 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,33 +1,27 @@
{
"name": "uhtml",
- "version": "3.2.2",
+ "version": "4.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "uhtml",
- "version": "3.2.2",
- "license": "ISC",
+ "version": "4.0.0",
+ "license": "MIT",
"dependencies": {
- "@webreflection/mapset": "^1.0.1",
- "@webreflection/uparser": "^0.2.4",
- "@webreflection/uwire": "^1.2.1",
- "async-tag": "^0.2.0",
- "jsx2tag": "^0.3.1",
- "udomdiff": "^1.1.0",
- "uhandlers": "^0.7.0"
+ "@webreflection/uparser": "^0.3.3",
+ "custom-function": "^1.0.6",
+ "domconstants": "^1.1.6",
+ "udomdiff": "^1.1.0"
},
"devDependencies": {
- "@rollup/plugin-node-resolve": "^15.1.0",
- "@rollup/plugin-terser": "^0.4.3",
- "@ungap/degap": "^0.2.8",
- "ascjs": "^5.0.1",
- "c8": "^8.0.0",
- "drop-babel-typeof": "^1.0.3",
- "linkedom": "^0.14.26",
- "rollup": "^3.25.1",
- "rollup-plugin-includepaths": "^0.2.4",
- "terser": "^5.18.1"
+ "@rollup/plugin-node-resolve": "^15.2.3",
+ "@rollup/plugin-terser": "^0.4.4",
+ "ascjs": "^6.0.3",
+ "c8": "^8.0.1",
+ "linkedom": "^0.16.1",
+ "rollup": "^4.4.0",
+ "typescript": "^5.2.2"
}
},
"node_modules/@babel/parser": {
@@ -122,9 +116,9 @@
"dev": true
},
"node_modules/@rollup/plugin-node-resolve": {
- "version": "15.1.0",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.1.0.tgz",
- "integrity": "sha512-xeZHCgsiZ9pzYVgAo9580eCGqwh/XCEUM9q6iQfGNocjgkufHAqC3exA+45URvhiYV8sBF9RlBai650eNs7AsA==",
+ "version": "15.2.3",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
+ "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==",
"dev": true,
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
@@ -138,7 +132,7 @@
"node": ">=14.0.0"
},
"peerDependencies": {
- "rollup": "^2.78.0||^3.0.0"
+ "rollup": "^2.78.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
@@ -147,9 +141,9 @@
}
},
"node_modules/@rollup/plugin-terser": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.3.tgz",
- "integrity": "sha512-EF0oejTMtkyhrkwCdg0HJ0IpkcaVg1MMSf2olHb2Jp+1mnLM04OhjpJWGma4HobiDTF0WCyViWuvadyE9ch2XA==",
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz",
+ "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==",
"dev": true,
"dependencies": {
"serialize-javascript": "^6.0.1",
@@ -160,7 +154,7 @@
"node": ">=14.0.0"
},
"peerDependencies": {
- "rollup": "^2.x || ^3.x"
+ "rollup": "^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
@@ -169,9 +163,9 @@
}
},
"node_modules/@rollup/pluginutils": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz",
- "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==",
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.5.tgz",
+ "integrity": "sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==",
"dev": true,
"dependencies": {
"@types/estree": "^1.0.0",
@@ -182,7 +176,7 @@
"node": ">=14.0.0"
},
"peerDependencies": {
- "rollup": "^1.20.0||^2.0.0||^3.0.0"
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
@@ -190,16 +184,172 @@
}
}
},
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.4.0.tgz",
+ "integrity": "sha512-AD30wtT58hZZsXIeiksytR6Gm2gofUxn5KqrDBdyzekgxXB9bXN9dqWIEcPfYo9lA9MVRm0lC42LuYGsscRxiA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.4.0.tgz",
+ "integrity": "sha512-PlqvhzFxy5FRTB3wLSsGgPhiakv9jrgfu8tjSojLJFP0CdhfZSRDOFvQ2emWLUEBOSCnjpL63XSuFVMwg59ZtA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.4.0.tgz",
+ "integrity": "sha512-BYmhn1Hebmkmdyn5mBFy7HptowyjtMALyTpywNSNZYigWwyv4L8WQVr0XvOQE7eE6WoKrupSVxtIcGZW8MgZUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.4.0.tgz",
+ "integrity": "sha512-7GXsMiX/giTDBMs/gL3rePLBRC6gV7DT7JQ0lNqoNDe5hm+Gm4NEWky9fwEmer64fIUbOsTiLUsyQ5fDXUbXPA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.4.0.tgz",
+ "integrity": "sha512-kavnkaV50Gu6vESlOAwUad92wYY9mUrcaPmhzOQZKlNFnzWAUYyD/uhHmWvY7Z2chtwhWlng0LvCRBF5QiPO7w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.4.0.tgz",
+ "integrity": "sha512-2hBHEtCjnBTeuLvDAlHRCqsuFQSyAhTQs9vbZEVBTV8ap35pDI1ukPbIVFFCWNvL/KE7xRor5YZFvfyGCfvLnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.4.0.tgz",
+ "integrity": "sha512-u7zy0Ygzl7O5Gvr9TSNSQj+DBzvMJC7rXfyQNgZ13KwkhgJ8z0z+gt2AO4RPd01rZioMQ2/TA24XGGg4xqhd0Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.4.0.tgz",
+ "integrity": "sha512-VvpAdh5SgewmWo8sa5QPYG8aSKH9hU2Kr5+3of0GzBI/8n8PBqhLyvF0DbO+zDW8j5IM8NDebv82MpHrZaD0Cw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.4.0.tgz",
+ "integrity": "sha512-3g6jaXxXVFaDnFoMn2+E3ludGcXFfEr6lDn+S1lh9Qe0JcL9sPt1wGh0g2cKIlb6OakNOFopZqJ5Yub9F7gQlA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.4.0.tgz",
+ "integrity": "sha512-jnoDRkg5Ve6Y1qx2m1+ehouOLQ4ddc15/iQSfFjcDUL6bqLdJJ5c4CKfUy/C6W1oCU4la+hMkveE9GG7ECN7dg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.4.0.tgz",
+ "integrity": "sha512-SoLQmJanozFow8o50ul2a3R+J7nk4pEhrp83PzTSXs5OzOmIZbPSp5kihtQ3f6ypo4MCbmh0V8Ev0bJIEp4Azw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.4.0.tgz",
+ "integrity": "sha512-Zaz6itfQ5sQF5Cia49YDW1ZTr+YfIKzTSb9npLyvQn346n7ulRDOv2J7GnL0zcOJ3cqW7HzG/ZisyO6fH43J9g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
"node_modules/@types/estree": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
- "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
},
"node_modules/@types/istanbul-lib-coverage": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
- "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==",
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
+ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
"dev": true
},
"node_modules/@types/resolve": {
@@ -208,26 +358,13 @@
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
"dev": true
},
- "node_modules/@ungap/degap": {
- "version": "0.2.8",
- "resolved": "https://registry.npmjs.org/@ungap/degap/-/degap-0.2.8.tgz",
- "integrity": "sha512-avav4FVH0W/eyMCAVNHw19Oi7Hv2Ebv5xIhUhagOAuZIQEj6S+C9rVCjXGf8sxdIbwK13woArhsfzeZlYgjCpg==",
- "dev": true
- },
- "node_modules/@webreflection/mapset": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@webreflection/mapset/-/mapset-1.0.1.tgz",
- "integrity": "sha512-cfHPwoviBs7Y/sewLQqE6Ic3XJfUr+LbNEYtR2uW4Od41y5Mg8TTQ8hUb3zBp3cepZTPpwhI6YMnjWk+olqO2w=="
- },
"node_modules/@webreflection/uparser": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/@webreflection/uparser/-/uparser-0.2.4.tgz",
- "integrity": "sha512-4cYSODHAbjsIlvlTLffaN+QiFcNSLTYkRLOrDqpK+m6Bzqyjudq/xHTiSl4/LxeijcQE48nJQuaBnJcnizXxrA=="
- },
- "node_modules/@webreflection/uwire": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@webreflection/uwire/-/uwire-1.2.1.tgz",
- "integrity": "sha512-3FIqIFzqij5NPWKWCQKJhfcRQpfS8RHAcceFosSDDiD6WrMHRAp3QoBqYt7dfrPhJ8Fg2b6T6Ea8ttClzVkZHA=="
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/@webreflection/uparser/-/uparser-0.3.3.tgz",
+ "integrity": "sha512-XxGfo8jr2eVuvP5lrmwjgMAM7QjtZ0ngFD+dd9Fd3GStcEb4QhLlTiqZYF5O3l5k4sU/V6ZiPrVCzCWXWFEmCw==",
+ "dependencies": {
+ "domconstants": "^1.1.6"
+ }
},
"node_modules/acorn": {
"version": "8.9.0",
@@ -266,9 +403,9 @@
}
},
"node_modules/ascjs": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ascjs/-/ascjs-5.0.1.tgz",
- "integrity": "sha512-1d/QdICzpywXiP53/Zz3fMdaC0/BB1ybLf+fK+QrqY8iyXNnWUHUrpmrowueXeswo+O+meJWm43TJSg2ClP3Sg==",
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/ascjs/-/ascjs-6.0.3.tgz",
+ "integrity": "sha512-lAIyi1j7oT0OtF9yFLiRf93LEcK7xTb/gPFwmfdi2T/BQmxJi1YcIES+VnP/kgGJkxq9Oh2DEK6GrZ6l2OVhVQ==",
"dev": true,
"dependencies": {
"@babel/parser": "^7.12.5"
@@ -277,11 +414,6 @@
"ascjs": "bin.js"
}
},
- "node_modules/async-tag": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/async-tag/-/async-tag-0.2.0.tgz",
- "integrity": "sha512-hNstPiQvxVVJdkBjfBsNb3zDEM2IUY3Xp7qaadEnhaGXq1/OFdS+TuwjEJxnIvZRm7e13KrPW74fIeDra7P5vw=="
- },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -323,9 +455,9 @@
}
},
"node_modules/c8": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/c8/-/c8-8.0.0.tgz",
- "integrity": "sha512-XHA5vSfCLglAc0Xt8eLBZMv19lgiBSjnb1FLAQgnwkuhJYEonpilhEB4Ea3jPAbm0FhD6VVJrc0z73jPe7JyGQ==",
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/c8/-/c8-8.0.1.tgz",
+ "integrity": "sha512-EINpopxZNH1mETuI0DzRA4MZpAUH+IFiRhnmFD3vFr3vdrgxqi3VfE3KL0AIL+zDq8rC9bZqwM/VDmmoe04y7w==",
"dev": true,
"dependencies": {
"@bcoe/v8-coverage": "^0.2.3",
@@ -333,13 +465,13 @@
"find-up": "^5.0.0",
"foreground-child": "^2.0.0",
"istanbul-lib-coverage": "^3.2.0",
- "istanbul-lib-report": "^3.0.0",
- "istanbul-reports": "^3.1.4",
+ "istanbul-lib-report": "^3.0.1",
+ "istanbul-reports": "^3.1.6",
"rimraf": "^3.0.2",
"test-exclude": "^6.0.0",
"v8-to-istanbul": "^9.0.0",
- "yargs": "^16.2.0",
- "yargs-parser": "^20.2.9"
+ "yargs": "^17.7.2",
+ "yargs-parser": "^21.1.1"
},
"bin": {
"c8": "bin/c8.js"
@@ -349,14 +481,17 @@
}
},
"node_modules/cliui": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
- "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
"dependencies": {
"string-width": "^4.2.0",
- "strip-ansi": "^6.0.0",
+ "strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
}
},
"node_modules/color-convert": {
@@ -390,9 +525,9 @@
"dev": true
},
"node_modules/convert-source-map": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
- "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true
},
"node_modules/cross-spawn": {
@@ -443,6 +578,11 @@
"integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==",
"dev": true
},
+ "node_modules/custom-function": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/custom-function/-/custom-function-1.0.6.tgz",
+ "integrity": "sha512-styyvwOki/EYr+VBe7/m9xAjq6uKx87SpDKIpFRdTQnofBDSZpBEFc9qJLmaJihjjTeEpAIJ+nz+9fUXj+BPNQ=="
+ },
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
@@ -466,6 +606,11 @@
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
+ "node_modules/domconstants": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/domconstants/-/domconstants-1.1.6.tgz",
+ "integrity": "sha512-CuaDrThJ4VM+LyZ4ax8n52k0KbLJZtffyGkuj1WhpTRRcSfcy/9DfOBa68jenhX96oNUTunblSJEUNC4baFdmQ=="
+ },
"node_modules/domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
@@ -507,15 +652,6 @@
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
- "node_modules/drop-babel-typeof": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/drop-babel-typeof/-/drop-babel-typeof-1.0.3.tgz",
- "integrity": "sha512-nmhRIvZrHSzEv8sc6kqh+2pG7ZAg9ZRjFyY4YavbIzvF/6SYdsH4SDFJAYLWTAh1XoVfdbe3Kmy44h/KB7gv+Q==",
- "dev": true,
- "bin": {
- "drop-babel-typeof": "index.js"
- }
- },
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -661,9 +797,9 @@
"dev": true
},
"node_modules/htmlparser2": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
- "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.0.0.tgz",
+ "integrity": "sha512-uxbSI98wmFT/G4P2zXx4OVx04qWUmyFPrD2/CNepa2Zo3GPNaCaaxElDgwUrwYWkK1nr9fft0Ya8dws8coDLLQ==",
"dev": true,
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
@@ -675,8 +811,8 @@
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
- "domutils": "^3.0.1",
- "entities": "^4.4.0"
+ "domutils": "^3.1.0",
+ "entities": "^4.5.0"
}
},
"node_modules/inflight": {
@@ -744,32 +880,32 @@
"dev": true
},
"node_modules/istanbul-lib-coverage": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz",
- "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==",
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
+ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/istanbul-lib-report": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
- "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
"dev": true,
"dependencies": {
"istanbul-lib-coverage": "^3.0.0",
- "make-dir": "^3.0.0",
+ "make-dir": "^4.0.0",
"supports-color": "^7.1.0"
},
"engines": {
- "node": ">=8"
+ "node": ">=10"
}
},
"node_modules/istanbul-reports": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz",
- "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==",
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz",
+ "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==",
"dev": true,
"dependencies": {
"html-escaper": "^2.0.0",
@@ -779,21 +915,16 @@
"node": ">=8"
}
},
- "node_modules/jsx2tag": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/jsx2tag/-/jsx2tag-0.3.1.tgz",
- "integrity": "sha512-S1ACW3N4yDiE49x9y9f2d+utB3Kci3A3Bx7wb2bK+5sl0B60lZM60pZ8L/A+FA55DYuAy2fgHu1i47yt+xgL9Q=="
- },
"node_modules/linkedom": {
- "version": "0.14.26",
- "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.14.26.tgz",
- "integrity": "sha512-mK6TrydfFA7phrnp+1j57ycBwFI5bGSW6YXlw9acHoqF+mP/y+FooEYYyniOt5Ot57FSKB3iwmnuQ1UUyNLm5A==",
+ "version": "0.16.1",
+ "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.16.1.tgz",
+ "integrity": "sha512-kbK4txFSjGstS8aHkYo3sRSD7viQbE1TAlYRqiqU13oihzzQFp23D1OwW3VdAQJuzqzBB+1qo9Qvp0xOeoVKig==",
"dev": true,
"dependencies": {
"css-select": "^5.1.0",
"cssom": "^0.5.0",
"html-escaper": "^3.0.3",
- "htmlparser2": "^8.0.1",
+ "htmlparser2": "^9.0.0",
"uhyphen": "^0.2.0"
}
},
@@ -818,16 +949,28 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/make-dir": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
- "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
"dev": true,
"dependencies": {
- "semver": "^6.0.0"
+ "semver": "^7.5.3"
},
"engines": {
- "node": ">=8"
+ "node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -992,27 +1135,33 @@
}
},
"node_modules/rollup": {
- "version": "3.25.1",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.1.tgz",
- "integrity": "sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ==",
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.4.0.tgz",
+ "integrity": "sha512-3L67ubCc1Qm49wUodsQ72FM6JmJ9M37d63rGPjxbcKrzNJrwFipl+lDNHeWd6BId09S6Tb9KiBgYKbWhIuqVyg==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
- "node": ">=14.18.0",
+ "node": ">=18.0.0",
"npm": ">=8.0.0"
},
"optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.4.0",
+ "@rollup/rollup-android-arm64": "4.4.0",
+ "@rollup/rollup-darwin-arm64": "4.4.0",
+ "@rollup/rollup-darwin-x64": "4.4.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.4.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.4.0",
+ "@rollup/rollup-linux-arm64-musl": "4.4.0",
+ "@rollup/rollup-linux-x64-gnu": "4.4.0",
+ "@rollup/rollup-linux-x64-musl": "4.4.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.4.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.4.0",
+ "@rollup/rollup-win32-x64-msvc": "4.4.0",
"fsevents": "~2.3.2"
}
},
- "node_modules/rollup-plugin-includepaths": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/rollup-plugin-includepaths/-/rollup-plugin-includepaths-0.2.4.tgz",
- "integrity": "sha512-iZen+XKVExeCzk7jeSZPJKL7B67slZNr8GXSC5ROBXtDGXDBH8wdjMfdNW5hf9kPt+tHyIvWh3wlE9bPrZL24g==",
- "dev": true
- },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -1034,12 +1183,18 @@
]
},
"node_modules/semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
"bin": {
"semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
}
},
"node_modules/serialize-javascript": {
@@ -1185,24 +1340,24 @@
"node": ">=8"
}
},
- "node_modules/uarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/uarray/-/uarray-1.0.0.tgz",
- "integrity": "sha512-LHmiAd5QuAv7pU2vbh+Zq9YOnqVK0H764p2Ozinpfy9ka58OID4IsGLiXsitqH7n0NAIDxvax1A/kDXpii/Ckg=="
+ "node_modules/typescript": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
+ "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
},
"node_modules/udomdiff": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/udomdiff/-/udomdiff-1.1.0.tgz",
"integrity": "sha512-aqjTs5x/wsShZBkVagdafJkP8S3UMGhkHKszsu1cszjjZ7iOp86+Qb3QOFYh01oWjPMy5ZTuxD6hw5uTKxd+VA=="
},
- "node_modules/uhandlers": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/uhandlers/-/uhandlers-0.7.0.tgz",
- "integrity": "sha512-MG6Q6Dc+xIfyFnHU8APpR916XWhnb+m30qVjPXxAmHUaVFmzUAcd6BCWws5LedyG43onCk1RRPNq0N02wcn4NA==",
- "dependencies": {
- "uarray": "^1.0.0"
- }
- },
"node_modules/uhyphen": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz",
@@ -1210,14 +1365,14 @@
"dev": true
},
"node_modules/v8-to-istanbul": {
- "version": "9.1.0",
- "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz",
- "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==",
+ "version": "9.1.3",
+ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz",
+ "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==",
"dev": true,
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.12",
"@types/istanbul-lib-coverage": "^2.0.1",
- "convert-source-map": "^1.6.0"
+ "convert-source-map": "^2.0.0"
},
"engines": {
"node": ">=10.12.0"
@@ -1270,31 +1425,37 @@
"node": ">=10"
}
},
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
"node_modules/yargs": {
- "version": "16.2.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
- "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dev": true,
"dependencies": {
- "cliui": "^7.0.2",
+ "cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
- "string-width": "^4.2.0",
+ "string-width": "^4.2.3",
"y18n": "^5.0.5",
- "yargs-parser": "^20.2.2"
+ "yargs-parser": "^21.1.1"
},
"engines": {
- "node": ">=10"
+ "node": ">=12"
}
},
"node_modules/yargs-parser": {
- "version": "20.2.9",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
- "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true,
"engines": {
- "node": ">=10"
+ "node": ">=12"
}
},
"node_modules/yocto-queue": {
@@ -1387,9 +1548,9 @@
}
},
"@rollup/plugin-node-resolve": {
- "version": "15.1.0",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.1.0.tgz",
- "integrity": "sha512-xeZHCgsiZ9pzYVgAo9580eCGqwh/XCEUM9q6iQfGNocjgkufHAqC3exA+45URvhiYV8sBF9RlBai650eNs7AsA==",
+ "version": "15.2.3",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
+ "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==",
"dev": true,
"requires": {
"@rollup/pluginutils": "^5.0.1",
@@ -1401,9 +1562,9 @@
}
},
"@rollup/plugin-terser": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.3.tgz",
- "integrity": "sha512-EF0oejTMtkyhrkwCdg0HJ0IpkcaVg1MMSf2olHb2Jp+1mnLM04OhjpJWGma4HobiDTF0WCyViWuvadyE9ch2XA==",
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz",
+ "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==",
"dev": true,
"requires": {
"serialize-javascript": "^6.0.1",
@@ -1412,9 +1573,9 @@
}
},
"@rollup/pluginutils": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz",
- "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==",
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.5.tgz",
+ "integrity": "sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==",
"dev": true,
"requires": {
"@types/estree": "^1.0.0",
@@ -1422,16 +1583,100 @@
"picomatch": "^2.3.1"
}
},
+ "@rollup/rollup-android-arm-eabi": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.4.0.tgz",
+ "integrity": "sha512-AD30wtT58hZZsXIeiksytR6Gm2gofUxn5KqrDBdyzekgxXB9bXN9dqWIEcPfYo9lA9MVRm0lC42LuYGsscRxiA==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-android-arm64": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.4.0.tgz",
+ "integrity": "sha512-PlqvhzFxy5FRTB3wLSsGgPhiakv9jrgfu8tjSojLJFP0CdhfZSRDOFvQ2emWLUEBOSCnjpL63XSuFVMwg59ZtA==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-darwin-arm64": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.4.0.tgz",
+ "integrity": "sha512-BYmhn1Hebmkmdyn5mBFy7HptowyjtMALyTpywNSNZYigWwyv4L8WQVr0XvOQE7eE6WoKrupSVxtIcGZW8MgZUA==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-darwin-x64": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.4.0.tgz",
+ "integrity": "sha512-7GXsMiX/giTDBMs/gL3rePLBRC6gV7DT7JQ0lNqoNDe5hm+Gm4NEWky9fwEmer64fIUbOsTiLUsyQ5fDXUbXPA==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.4.0.tgz",
+ "integrity": "sha512-kavnkaV50Gu6vESlOAwUad92wYY9mUrcaPmhzOQZKlNFnzWAUYyD/uhHmWvY7Z2chtwhWlng0LvCRBF5QiPO7w==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.4.0.tgz",
+ "integrity": "sha512-2hBHEtCjnBTeuLvDAlHRCqsuFQSyAhTQs9vbZEVBTV8ap35pDI1ukPbIVFFCWNvL/KE7xRor5YZFvfyGCfvLnA==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-linux-arm64-musl": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.4.0.tgz",
+ "integrity": "sha512-u7zy0Ygzl7O5Gvr9TSNSQj+DBzvMJC7rXfyQNgZ13KwkhgJ8z0z+gt2AO4RPd01rZioMQ2/TA24XGGg4xqhd0Q==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-linux-x64-gnu": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.4.0.tgz",
+ "integrity": "sha512-VvpAdh5SgewmWo8sa5QPYG8aSKH9hU2Kr5+3of0GzBI/8n8PBqhLyvF0DbO+zDW8j5IM8NDebv82MpHrZaD0Cw==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-linux-x64-musl": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.4.0.tgz",
+ "integrity": "sha512-3g6jaXxXVFaDnFoMn2+E3ludGcXFfEr6lDn+S1lh9Qe0JcL9sPt1wGh0g2cKIlb6OakNOFopZqJ5Yub9F7gQlA==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.4.0.tgz",
+ "integrity": "sha512-jnoDRkg5Ve6Y1qx2m1+ehouOLQ4ddc15/iQSfFjcDUL6bqLdJJ5c4CKfUy/C6W1oCU4la+hMkveE9GG7ECN7dg==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.4.0.tgz",
+ "integrity": "sha512-SoLQmJanozFow8o50ul2a3R+J7nk4pEhrp83PzTSXs5OzOmIZbPSp5kihtQ3f6ypo4MCbmh0V8Ev0bJIEp4Azw==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-win32-x64-msvc": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.4.0.tgz",
+ "integrity": "sha512-Zaz6itfQ5sQF5Cia49YDW1ZTr+YfIKzTSb9npLyvQn346n7ulRDOv2J7GnL0zcOJ3cqW7HzG/ZisyO6fH43J9g==",
+ "dev": true,
+ "optional": true
+ },
"@types/estree": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
- "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
},
"@types/istanbul-lib-coverage": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
- "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==",
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
+ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
"dev": true
},
"@types/resolve": {
@@ -1440,26 +1685,13 @@
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
"dev": true
},
- "@ungap/degap": {
- "version": "0.2.8",
- "resolved": "https://registry.npmjs.org/@ungap/degap/-/degap-0.2.8.tgz",
- "integrity": "sha512-avav4FVH0W/eyMCAVNHw19Oi7Hv2Ebv5xIhUhagOAuZIQEj6S+C9rVCjXGf8sxdIbwK13woArhsfzeZlYgjCpg==",
- "dev": true
- },
- "@webreflection/mapset": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@webreflection/mapset/-/mapset-1.0.1.tgz",
- "integrity": "sha512-cfHPwoviBs7Y/sewLQqE6Ic3XJfUr+LbNEYtR2uW4Od41y5Mg8TTQ8hUb3zBp3cepZTPpwhI6YMnjWk+olqO2w=="
- },
"@webreflection/uparser": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/@webreflection/uparser/-/uparser-0.2.4.tgz",
- "integrity": "sha512-4cYSODHAbjsIlvlTLffaN+QiFcNSLTYkRLOrDqpK+m6Bzqyjudq/xHTiSl4/LxeijcQE48nJQuaBnJcnizXxrA=="
- },
- "@webreflection/uwire": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@webreflection/uwire/-/uwire-1.2.1.tgz",
- "integrity": "sha512-3FIqIFzqij5NPWKWCQKJhfcRQpfS8RHAcceFosSDDiD6WrMHRAp3QoBqYt7dfrPhJ8Fg2b6T6Ea8ttClzVkZHA=="
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/@webreflection/uparser/-/uparser-0.3.3.tgz",
+ "integrity": "sha512-XxGfo8jr2eVuvP5lrmwjgMAM7QjtZ0ngFD+dd9Fd3GStcEb4QhLlTiqZYF5O3l5k4sU/V6ZiPrVCzCWXWFEmCw==",
+ "requires": {
+ "domconstants": "^1.1.6"
+ }
},
"acorn": {
"version": "8.9.0",
@@ -1483,19 +1715,14 @@
}
},
"ascjs": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ascjs/-/ascjs-5.0.1.tgz",
- "integrity": "sha512-1d/QdICzpywXiP53/Zz3fMdaC0/BB1ybLf+fK+QrqY8iyXNnWUHUrpmrowueXeswo+O+meJWm43TJSg2ClP3Sg==",
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/ascjs/-/ascjs-6.0.3.tgz",
+ "integrity": "sha512-lAIyi1j7oT0OtF9yFLiRf93LEcK7xTb/gPFwmfdi2T/BQmxJi1YcIES+VnP/kgGJkxq9Oh2DEK6GrZ6l2OVhVQ==",
"dev": true,
"requires": {
"@babel/parser": "^7.12.5"
}
},
- "async-tag": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/async-tag/-/async-tag-0.2.0.tgz",
- "integrity": "sha512-hNstPiQvxVVJdkBjfBsNb3zDEM2IUY3Xp7qaadEnhaGXq1/OFdS+TuwjEJxnIvZRm7e13KrPW74fIeDra7P5vw=="
- },
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -1531,9 +1758,9 @@
"dev": true
},
"c8": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/c8/-/c8-8.0.0.tgz",
- "integrity": "sha512-XHA5vSfCLglAc0Xt8eLBZMv19lgiBSjnb1FLAQgnwkuhJYEonpilhEB4Ea3jPAbm0FhD6VVJrc0z73jPe7JyGQ==",
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/c8/-/c8-8.0.1.tgz",
+ "integrity": "sha512-EINpopxZNH1mETuI0DzRA4MZpAUH+IFiRhnmFD3vFr3vdrgxqi3VfE3KL0AIL+zDq8rC9bZqwM/VDmmoe04y7w==",
"dev": true,
"requires": {
"@bcoe/v8-coverage": "^0.2.3",
@@ -1541,23 +1768,23 @@
"find-up": "^5.0.0",
"foreground-child": "^2.0.0",
"istanbul-lib-coverage": "^3.2.0",
- "istanbul-lib-report": "^3.0.0",
- "istanbul-reports": "^3.1.4",
+ "istanbul-lib-report": "^3.0.1",
+ "istanbul-reports": "^3.1.6",
"rimraf": "^3.0.2",
"test-exclude": "^6.0.0",
"v8-to-istanbul": "^9.0.0",
- "yargs": "^16.2.0",
- "yargs-parser": "^20.2.9"
+ "yargs": "^17.7.2",
+ "yargs-parser": "^21.1.1"
}
},
"cliui": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
- "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
"requires": {
"string-width": "^4.2.0",
- "strip-ansi": "^6.0.0",
+ "strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
}
},
@@ -1589,9 +1816,9 @@
"dev": true
},
"convert-source-map": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
- "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true
},
"cross-spawn": {
@@ -1630,6 +1857,11 @@
"integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==",
"dev": true
},
+ "custom-function": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/custom-function/-/custom-function-1.0.6.tgz",
+ "integrity": "sha512-styyvwOki/EYr+VBe7/m9xAjq6uKx87SpDKIpFRdTQnofBDSZpBEFc9qJLmaJihjjTeEpAIJ+nz+9fUXj+BPNQ=="
+ },
"deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
@@ -1647,6 +1879,11 @@
"entities": "^4.2.0"
}
},
+ "domconstants": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/domconstants/-/domconstants-1.1.6.tgz",
+ "integrity": "sha512-CuaDrThJ4VM+LyZ4ax8n52k0KbLJZtffyGkuj1WhpTRRcSfcy/9DfOBa68jenhX96oNUTunblSJEUNC4baFdmQ=="
+ },
"domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
@@ -1673,12 +1910,6 @@
"domhandler": "^5.0.3"
}
},
- "drop-babel-typeof": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/drop-babel-typeof/-/drop-babel-typeof-1.0.3.tgz",
- "integrity": "sha512-nmhRIvZrHSzEv8sc6kqh+2pG7ZAg9ZRjFyY4YavbIzvF/6SYdsH4SDFJAYLWTAh1XoVfdbe3Kmy44h/KB7gv+Q==",
- "dev": true
- },
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -1784,15 +2015,15 @@
"dev": true
},
"htmlparser2": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
- "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.0.0.tgz",
+ "integrity": "sha512-uxbSI98wmFT/G4P2zXx4OVx04qWUmyFPrD2/CNepa2Zo3GPNaCaaxElDgwUrwYWkK1nr9fft0Ya8dws8coDLLQ==",
"dev": true,
"requires": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
- "domutils": "^3.0.1",
- "entities": "^4.4.0"
+ "domutils": "^3.1.0",
+ "entities": "^4.5.0"
}
},
"inflight": {
@@ -1848,47 +2079,42 @@
"dev": true
},
"istanbul-lib-coverage": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz",
- "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==",
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
+ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
"dev": true
},
"istanbul-lib-report": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
- "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
"dev": true,
"requires": {
"istanbul-lib-coverage": "^3.0.0",
- "make-dir": "^3.0.0",
+ "make-dir": "^4.0.0",
"supports-color": "^7.1.0"
}
},
"istanbul-reports": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz",
- "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==",
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz",
+ "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==",
"dev": true,
"requires": {
"html-escaper": "^2.0.0",
"istanbul-lib-report": "^3.0.0"
}
},
- "jsx2tag": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/jsx2tag/-/jsx2tag-0.3.1.tgz",
- "integrity": "sha512-S1ACW3N4yDiE49x9y9f2d+utB3Kci3A3Bx7wb2bK+5sl0B60lZM60pZ8L/A+FA55DYuAy2fgHu1i47yt+xgL9Q=="
- },
"linkedom": {
- "version": "0.14.26",
- "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.14.26.tgz",
- "integrity": "sha512-mK6TrydfFA7phrnp+1j57ycBwFI5bGSW6YXlw9acHoqF+mP/y+FooEYYyniOt5Ot57FSKB3iwmnuQ1UUyNLm5A==",
+ "version": "0.16.1",
+ "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.16.1.tgz",
+ "integrity": "sha512-kbK4txFSjGstS8aHkYo3sRSD7viQbE1TAlYRqiqU13oihzzQFp23D1OwW3VdAQJuzqzBB+1qo9Qvp0xOeoVKig==",
"dev": true,
"requires": {
"css-select": "^5.1.0",
"cssom": "^0.5.0",
"html-escaper": "^3.0.3",
- "htmlparser2": "^8.0.1",
+ "htmlparser2": "^9.0.0",
"uhyphen": "^0.2.0"
},
"dependencies": {
@@ -1909,13 +2135,22 @@
"p-locate": "^5.0.0"
}
},
+ "lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ },
"make-dir": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
- "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
"dev": true,
"requires": {
- "semver": "^6.0.0"
+ "semver": "^7.5.3"
}
},
"minimatch": {
@@ -2029,20 +2264,26 @@
}
},
"rollup": {
- "version": "3.25.1",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.1.tgz",
- "integrity": "sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ==",
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.4.0.tgz",
+ "integrity": "sha512-3L67ubCc1Qm49wUodsQ72FM6JmJ9M37d63rGPjxbcKrzNJrwFipl+lDNHeWd6BId09S6Tb9KiBgYKbWhIuqVyg==",
"dev": true,
"requires": {
+ "@rollup/rollup-android-arm-eabi": "4.4.0",
+ "@rollup/rollup-android-arm64": "4.4.0",
+ "@rollup/rollup-darwin-arm64": "4.4.0",
+ "@rollup/rollup-darwin-x64": "4.4.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.4.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.4.0",
+ "@rollup/rollup-linux-arm64-musl": "4.4.0",
+ "@rollup/rollup-linux-x64-gnu": "4.4.0",
+ "@rollup/rollup-linux-x64-musl": "4.4.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.4.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.4.0",
+ "@rollup/rollup-win32-x64-msvc": "4.4.0",
"fsevents": "~2.3.2"
}
},
- "rollup-plugin-includepaths": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/rollup-plugin-includepaths/-/rollup-plugin-includepaths-0.2.4.tgz",
- "integrity": "sha512-iZen+XKVExeCzk7jeSZPJKL7B67slZNr8GXSC5ROBXtDGXDBH8wdjMfdNW5hf9kPt+tHyIvWh3wlE9bPrZL24g==",
- "dev": true
- },
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -2050,10 +2291,13 @@
"dev": true
},
"semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
- "dev": true
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^6.0.0"
+ }
},
"serialize-javascript": {
"version": "6.0.1",
@@ -2165,24 +2409,17 @@
"minimatch": "^3.0.4"
}
},
- "uarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/uarray/-/uarray-1.0.0.tgz",
- "integrity": "sha512-LHmiAd5QuAv7pU2vbh+Zq9YOnqVK0H764p2Ozinpfy9ka58OID4IsGLiXsitqH7n0NAIDxvax1A/kDXpii/Ckg=="
+ "typescript": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
+ "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
+ "dev": true
},
"udomdiff": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/udomdiff/-/udomdiff-1.1.0.tgz",
"integrity": "sha512-aqjTs5x/wsShZBkVagdafJkP8S3UMGhkHKszsu1cszjjZ7iOp86+Qb3QOFYh01oWjPMy5ZTuxD6hw5uTKxd+VA=="
},
- "uhandlers": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/uhandlers/-/uhandlers-0.7.0.tgz",
- "integrity": "sha512-MG6Q6Dc+xIfyFnHU8APpR916XWhnb+m30qVjPXxAmHUaVFmzUAcd6BCWws5LedyG43onCk1RRPNq0N02wcn4NA==",
- "requires": {
- "uarray": "^1.0.0"
- }
- },
"uhyphen": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz",
@@ -2190,14 +2427,14 @@
"dev": true
},
"v8-to-istanbul": {
- "version": "9.1.0",
- "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz",
- "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==",
+ "version": "9.1.3",
+ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz",
+ "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==",
"dev": true,
"requires": {
"@jridgewell/trace-mapping": "^0.3.12",
"@types/istanbul-lib-coverage": "^2.0.1",
- "convert-source-map": "^1.6.0"
+ "convert-source-map": "^2.0.0"
}
},
"which": {
@@ -2232,25 +2469,31 @@
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true
},
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
"yargs": {
- "version": "16.2.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
- "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dev": true,
"requires": {
- "cliui": "^7.0.2",
+ "cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
- "string-width": "^4.2.0",
+ "string-width": "^4.2.3",
"y18n": "^5.0.5",
- "yargs-parser": "^20.2.2"
+ "yargs-parser": "^21.1.1"
}
},
"yargs-parser": {
- "version": "20.2.9",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
- "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true
},
"yocto-queue": {
diff --git a/package.json b/package.json
index c147ed7..f5066d6 100644
--- a/package.json
+++ b/package.json
@@ -1,20 +1,16 @@
{
"name": "uhtml",
- "version": "3.2.2",
+ "version": "4.0.0",
"description": "A micro HTML/SVG render",
- "main": "cjs/index.js",
- "types": "index.d.ts",
+ "main": "./cjs/index.js",
"scripts": {
- "build": "node pony.js && npm run cjs && npm run rollup:async && npm run rollup:es && npm run rollup:esm && npm run rollup:babel && npm run rollup:init && drop-babel-typeof ./index.js && npm run test && npm run size",
- "cjs": "rm cjs/*.js && ascjs --no-default esm cjs",
- "rollup:async": "rollup --config rollup/async.config.js && sed -i.bck 's/^var /self./' async.js && rm -rf async.js.bck",
- "rollup:es": "rollup --config rollup/es.config.js && sed -i.bck 's/^var /self./' es.js && rm -rf es.js.bck",
- "rollup:esm": "rollup --config rollup/esm.config.js",
- "rollup:babel": "rollup --config rollup/babel.config.js && sed -i.bck 's/^var /self./' index.js && rm -rf index.js.bck",
- "rollup:init": "rollup --config rollup/init.config.js && sed -i.bck 's/^var /self./' es.js && rm -rf es.js.bck",
- "size": "cat es.js | brotli | wc -c && cat esm.js | brotli | wc -c && cat init.js | brotli | wc -c",
+ "build": "rm -rf cjs/* && npm run cjs && npm run rollup:es && rm -rf types && npm run ts && npm run size",
+ "cjs": "ascjs esm cjs",
+ "rollup:es": "rollup --config rollup/es.config.js",
+ "server": "npx static-handler .",
+ "size": "echo \"index $(cat index.js | brotli | wc -c)\";echo \"keyed $(cat keyed.js | brotli | wc -c)\";echo \"node $(cat node.js | brotli | wc -c)\";",
"test": "node test/coverage.init.js && c8 node test/coverage.js",
- "coverage": "mkdir -p ./coverage; c8 report --reporter=text-lcov > ./coverage/lcov.info"
+ "ts": "tsc -p ."
},
"keywords": [
"micro",
@@ -22,65 +18,41 @@
"render"
],
"author": "Andrea Giammarchi",
- "license": "ISC",
+ "license": "MIT",
"devDependencies": {
- "@rollup/plugin-node-resolve": "^15.1.0",
- "@rollup/plugin-terser": "^0.4.3",
- "@ungap/degap": "^0.2.8",
- "ascjs": "^5.0.1",
- "c8": "^8.0.0",
- "drop-babel-typeof": "^1.0.3",
- "linkedom": "^0.14.26",
- "rollup": "^3.25.1",
- "rollup-plugin-includepaths": "^0.2.4",
- "terser": "^5.18.1"
- },
- "dependencies": {
- "@webreflection/mapset": "^1.0.1",
- "@webreflection/uparser": "^0.2.4",
- "@webreflection/uwire": "^1.2.1",
- "async-tag": "^0.2.0",
- "jsx2tag": "^0.3.1",
- "udomdiff": "^1.1.0",
- "uhandlers": "^0.7.0"
+ "@rollup/plugin-node-resolve": "^15.2.3",
+ "@rollup/plugin-terser": "^0.4.4",
+ "ascjs": "^6.0.3",
+ "c8": "^8.0.1",
+ "linkedom": "^0.16.1",
+ "rollup": "^4.4.0",
+ "typescript": "^5.2.2"
},
"module": "./esm/index.js",
"type": "module",
"exports": {
".": {
- "types": "./index.d.ts",
+ "types": "./types/index.d.ts",
"import": "./esm/index.js",
"default": "./cjs/index.js"
},
- "./async": {
- "types": "./async.d.ts",
- "import": "./esm/async.js",
- "default": "./cjs/async.js"
- },
- "./init": {
- "types": "./index.d.ts",
- "import": "./esm/init.js",
- "default": "./cjs/init.js"
- },
- "./json": {
- "types": "./index.d.ts",
- "import": "./esm/json.js",
- "default": "./cjs/json.js"
+ "./keyed": {
+ "types": "./types/keyed.d.ts",
+ "import": "./esm/keyed.js",
+ "default": "./cjs/keyed.js"
},
- "./jsx": {
- "types": "./index.d.ts",
- "import": "./esm/x.js",
- "default": "./cjs/x.js"
+ "./node": {
+ "types": "./types/node.d.ts",
+ "import": "./esm/node.js",
+ "default": "./cjs/node.js"
},
"./package.json": "./package.json"
},
- "unpkg": "es.js",
- "repository": {
- "type": "git",
- "url": "git+https://github.com/WebReflection/uhtml.git"
- },
- "bugs": {
- "url": "https://github.com/WebReflection/uhtml/issues"
- },
- "homepage": "https://github.com/WebReflection/uhtml#readme"
+ "unpkg": "./keyed.js",
+ "dependencies": {
+ "@webreflection/uparser": "^0.3.3",
+ "custom-function": "^1.0.6",
+ "domconstants": "^1.1.6",
+ "udomdiff": "^1.1.0"
+ }
}
diff --git a/pony.js b/pony.js
deleted file mode 100644
index c9d2013..0000000
--- a/pony.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import {readFileSync, writeFileSync} from 'fs';
-
-const dropIE = s => s.replace(/^import\s+.+/mg, '').replace(/^export\s+/mg, '');
-
-const utils = readFileSync('./esm/utils.js').toString();
-const uwire = readFileSync('./node_modules/@webreflection/uwire/esm/index.js').toString();
-const uhandlers = readFileSync('./node_modules/uhandlers/esm/index.js').toString();
-
-const init = readFileSync('./esm/init.js').toString();
-const handlers = readFileSync('./esm/handlers.js').toString();
-const rabbit = readFileSync('./esm/rabbit.js').toString();
-const index = readFileSync('./esm/index.js').toString();
-
-const outcome = [
- dropIE(utils).replace(/^\{.+\};$/gm, ''),
- dropIE(uwire),
- dropIE(uhandlers),
- dropIE(handlers),
- dropIE(rabbit),
- dropIE(index).replace(/\bcache\b/g, '_cache').replace(/^\{/m, 'return {')
-];
-
-writeFileSync(
- './esm/init.js',
- init.replace(
- /\/\*\*start\*\*\/[\s\S]*?\/\*\*end\*\*\//,
- `/**start**/\n${outcome.join('\n')}\n/**end**/`
- )
-);
diff --git a/rollup/async.config.js b/rollup/async.config.js
deleted file mode 100644
index c272a12..0000000
--- a/rollup/async.config.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import {nodeResolve} from '@rollup/plugin-node-resolve';
-import terser from '@rollup/plugin-terser';
-
-export default {
- input: './esm/async.js',
- plugins: [
- nodeResolve(),
- terser()
- ],
- output: {
- esModule: false,
- exports: 'named',
- file: './async.js',
- format: 'iife',
- name: 'uhtml'
- }
-};
diff --git a/rollup/babel.config.js b/rollup/babel.config.js
deleted file mode 100644
index 83bb631..0000000
--- a/rollup/babel.config.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import {nodeResolve} from '@rollup/plugin-node-resolve';
-
-export default {
- input: './esm/index.js',
- plugins: [
- nodeResolve()
- ],
- output: {
- esModule: false,
- exports: 'named',
- file: './index.js',
- format: 'iife',
- name: 'uhtml'
- }
-};
diff --git a/rollup/es.config.js b/rollup/es.config.js
index 22a2cd3..cbeeaf5 100644
--- a/rollup/es.config.js
+++ b/rollup/es.config.js
@@ -1,17 +1,35 @@
import {nodeResolve} from '@rollup/plugin-node-resolve';
import terser from '@rollup/plugin-terser';
-export default {
- input: './esm/index.js',
- plugins: [
- nodeResolve(),
- terser()
- ],
- output: {
- esModule: false,
- exports: 'named',
- file: './es.js',
- format: 'iife',
- name: 'uhtml'
+const plugins = [
+ nodeResolve(),
+].concat(
+ process.env.NO_MIN ? [] : [terser()]
+);
+
+export default [
+ {
+ plugins,
+ input: './esm/index.js',
+ output: {
+ esModule: true,
+ file: './index.js',
+ },
+ },
+ {
+ plugins,
+ input: './esm/keyed.js',
+ output: {
+ esModule: true,
+ file: './keyed.js',
+ },
+ },
+ {
+ plugins,
+ input: './esm/node.js',
+ output: {
+ esModule: true,
+ file: './node.js',
+ },
}
-};
+];
diff --git a/rollup/esm.config.js b/rollup/esm.config.js
deleted file mode 100644
index 4320c39..0000000
--- a/rollup/esm.config.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import {nodeResolve} from '@rollup/plugin-node-resolve';
-import terser from '@rollup/plugin-terser';
-
-export default {
- input: './esm/index.js',
- plugins: [
- nodeResolve(),
- terser()
- ],
- output: {
- esModule: false,
- file: './esm.js',
- format: 'module'
- }
-};
diff --git a/rollup/init.config.js b/rollup/init.config.js
deleted file mode 100644
index 1b36549..0000000
--- a/rollup/init.config.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import {nodeResolve} from '@rollup/plugin-node-resolve';
-import terser from '@rollup/plugin-terser';
-
-export default {
- input: './esm/init.js',
- plugins: [
- nodeResolve(),
- terser()
- ],
- output: {
- esModule: false,
- file: './init.js',
- format: 'module'
- }
-};
diff --git a/test/coverage.js b/test/coverage.js
index 6f4db77..4760079 100644
--- a/test/coverage.js
+++ b/test/coverage.js
@@ -3,15 +3,17 @@ const {DOMParser, HTMLElement} = require('linkedom');
const document = (new DOMParser).parseFromString('', 'text/html');
globalThis.document = document;
+globalThis.DocumentFragment = document.createDocumentFragment().constructor;
-const {render, html, svg} = require('../cjs');
+const {html: htmlNode} = require('../cjs/node.js');
+const {render, html, svg} = require('../cjs/index');
const {Event} = document.defaultView;
const {body} = document;
-const elementA = html.node`foo
`;
-const elementB = html.node`
+const elementA = htmlNode`foo
`;
+const elementB = htmlNode`
bar
`;
@@ -42,7 +44,7 @@ render(body, html`this is a ${1} ${2} ${3}`);
render(body, html`this is a ${1}`);
let div = document.createElement('div');
-render(div, html.node`this is a test`);
+render(div, htmlNode`this is a test`);
render(div, html.for(body)`this is a test`);
render(div, html.for(body, 1)`this is a test`);
render(div, () => html.for(body)`this is a test`);
diff --git a/test/index.js b/test/index.js
new file mode 100644
index 0000000..e69de29
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..04a45d4
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "module": "NodeNext",
+ "target": "esnext",
+ "moduleResolution": "nodenext",
+ "allowJs": true,
+ "declaration": true,
+ "emitDeclarationOnly": true,
+ "declarationDir": "types"
+ },
+ "include": [
+ "esm/index.js",
+ "esm/exports.js",
+ "esm/xworker.js"
+ ]
+}
diff --git a/types/create-content.d.ts b/types/create-content.d.ts
new file mode 100644
index 0000000..35674ec
--- /dev/null
+++ b/types/create-content.d.ts
@@ -0,0 +1,2 @@
+declare function _default(text: string, xml: boolean): DocumentFragment;
+export default _default;
diff --git a/types/creator.d.ts b/types/creator.d.ts
new file mode 100644
index 0000000..9c659d2
--- /dev/null
+++ b/types/creator.d.ts
@@ -0,0 +1,2 @@
+declare function _default(parse: (template: TemplateStringsArray, values: any[]) => import("./parser.js").Resolved): (template: any, values: any) => import("./literals.js").Parsed;
+export default _default;
diff --git a/types/handler.d.ts b/types/handler.d.ts
new file mode 100644
index 0000000..daf5a68
--- /dev/null
+++ b/types/handler.d.ts
@@ -0,0 +1,22 @@
+/**
+ * @template T
+ * @this {import("./literals.js").HoleDetails}
+ * @param {Node} node
+ * @param {T} value
+ * @returns {T}
+ */
+export function hole(this: import("./literals.js").HoleDetails, node: Node, value: T): T;
+export class hole {
+ /**
+ * @template T
+ * @this {import("./literals.js").HoleDetails}
+ * @param {Node} node
+ * @param {T} value
+ * @returns {T}
+ */
+ constructor(this: import("./literals.js").HoleDetails, node: Node, value: T);
+ n: Object;
+}
+export function array(node: Node, value: T, _: string, prev: Node[]): T;
+export function attribute(element: Element, name: string): (element: Element, value: T, name: string) => T;
+export function text(element: Element, value: T): T;
diff --git a/types/index.d.ts b/types/index.d.ts
new file mode 100644
index 0000000..67c7fe3
--- /dev/null
+++ b/types/index.d.ts
@@ -0,0 +1,8 @@
+export type Value = import("./literals.js").Value;
+import { Hole } from './rabbit.js';
+import render from './render-hole.js';
+/** @type {(template: TemplateStringsArray, ...values:Value[]) => Hole} A tag to render HTML content. */
+export const html: (template: TemplateStringsArray, ...values: Value[]) => Hole;
+/** @type {(template: TemplateStringsArray, ...values:Value[]) => Hole} A tag to render SVG content. */
+export const svg: (template: TemplateStringsArray, ...values: Value[]) => Hole;
+export { Hole, render };
diff --git a/types/literals.d.ts b/types/literals.d.ts
new file mode 100644
index 0000000..814d9da
--- /dev/null
+++ b/types/literals.d.ts
@@ -0,0 +1,82 @@
+export function cel(c: PersistentFragment, e: Entry[], l: number): {
+ c: import("./persistent-fragment.js").PersistentFragment;
+ e: Entry[];
+ l: number;
+};
+/**
+ * @typedef {Object} HoleDetails
+ * @property {null | Node | PersistentFragment} n the current live node, if any and not the `t` one
+ */
+/** @type {() => HoleDetails} */
+export const comment: () => HoleDetails;
+export function detail(v: any, u: Function, t: Node, n: string): Detail;
+export function entry(t: Type, p: number[], u: Function, n?: string): Entry;
+export function cache(s: Cache[]): Cache;
+export function parsed(n: Node | PersistentFragment, d: Detail[]): Parsed;
+export type ATTRIBUTE_NODE = 2;
+export type TEXT_NODE = 3;
+export type COMMENT_NODE = 8;
+export type Type = ATTRIBUTE_NODE | TEXT_NODE | COMMENT_NODE;
+export type PersistentFragment = import("./persistent-fragment.js").PersistentFragment;
+export type Hole = import("./rabbit.js").Hole;
+export type Target = Node | Element | PersistentFragment;
+export type Value = null | undefined | string | number | boolean | Hole;
+export type DOMValue = null | undefined | string | number | boolean | Node | Element | PersistentFragment;
+export type Entry = {
+ type: Type;
+ path: number[];
+ update: Function;
+ name: string;
+};
+export type HoleDetails = {
+ /**
+ * the current live node, if any and not the `t` one
+ */
+ n: null | Node | PersistentFragment;
+};
+export type Detail = {
+ /**
+ * the current value of the interpolation / hole
+ */
+ v: any;
+ /**
+ * the callback to update the value
+ */
+ u: Function;
+ /**
+ * the target comment node or element
+ */
+ t: Node;
+ /**
+ * the name of the attribute, if any
+ */
+ n: string;
+};
+export type Cache = {
+ /**
+ * the stack of caches per each interpolation / hole
+ */
+ s: Cache[];
+ /**
+ * the cached template
+ */
+ t: null | TemplateStringsArray;
+ /**
+ * the node returned when parsing the template
+ */
+ n: null | Node | PersistentFragment;
+ /**
+ * the list of updates to perform
+ */
+ d: Detail[];
+};
+export type Parsed = {
+ /**
+ * the returned node after parsing the template
+ */
+ n: Node | PersistentFragment;
+ /**
+ * the list of details to update the node
+ */
+ d: Detail[];
+};
diff --git a/types/parser.d.ts b/types/parser.d.ts
new file mode 100644
index 0000000..74431ce
--- /dev/null
+++ b/types/parser.d.ts
@@ -0,0 +1,9 @@
+declare function _default(xml: boolean): (template: TemplateStringsArray, values: any[]) => Resolved;
+export default _default;
+export type Entry = import("./literals.js").Entry;
+export type Resolved = {
+ content: DocumentFragment;
+ entries: Entry[];
+ updates: Function[];
+ length: number;
+};
diff --git a/types/persistent-fragment.d.ts b/types/persistent-fragment.d.ts
new file mode 100644
index 0000000..3b410d8
--- /dev/null
+++ b/types/persistent-fragment.d.ts
@@ -0,0 +1,10 @@
+export function diffFragment(node: Node, operation: 1 | 0 | -0 | -1): Node;
+/** @extends {DocumentFragment} */
+export class PersistentFragment extends DocumentFragment {
+ constructor(fragment: any);
+ get firstChild(): any;
+ get lastChild(): any;
+ replaceWith(node: any): void;
+ valueOf(): this;
+ #private;
+}
diff --git a/types/rabbit.d.ts b/types/rabbit.d.ts
new file mode 100644
index 0000000..252e6ce
--- /dev/null
+++ b/types/rabbit.d.ts
@@ -0,0 +1,14 @@
+export function unroll(cache: import("./literals.js").Cache, { s: stack, t: template, v: values }: Hole): Node;
+/**
+ * Holds all details needed to render the content on a render.
+ * @constructor
+ * @param {boolean} svg The content type.
+ * @param {TemplateStringsArray} template The template literals used to the define the content.
+ * @param {any[]} values Zero, one, or more interpolated values to render.
+ */
+export class Hole {
+ constructor(svg: any, template: any, values: any);
+ s: any;
+ t: any;
+ v: any;
+}
diff --git a/types/range.d.ts b/types/range.d.ts
new file mode 100644
index 0000000..d02347f
--- /dev/null
+++ b/types/range.d.ts
@@ -0,0 +1,2 @@
+declare function _default(firstChild: Node | Element, lastChild: Node | Element, preserve: boolean): Element | Node;
+export default _default;
diff --git a/types/render-hole.d.ts b/types/render-hole.d.ts
new file mode 100644
index 0000000..b1e45c0
--- /dev/null
+++ b/types/render-hole.d.ts
@@ -0,0 +1,3 @@
+declare function _default(where: T, what: () => Hole | Hole): T;
+export default _default;
+export type Hole = import("./rabbit.js").Hole;
diff --git a/types/utils.d.ts b/types/utils.d.ts
new file mode 100644
index 0000000..3abe2a5
--- /dev/null
+++ b/types/utils.d.ts
@@ -0,0 +1,4 @@
+export const empty: any[];
+export function newRange(): Range;
+export function set(map: Map | WeakMap, key: any, value: T): T;
+export const isArray: (arg: any) => arg is any[];