URL: http://github.com/WebReflection/uhtml/pull/161.diff
new SyntaxError(`Invalid content: NUL char \\x00 found in template: ${asTemplate(template)}`), - invalid_comment: template => new SyntaxError(`Invalid comment: no closing --> found in template ${asTemplate(template)}`), - invalid_layout: template => new SyntaxError(`Too many closing tags found in template ${asTemplate(template)}`), - invalid_doctype: (template, value) => new SyntaxError(`Invalid doctype: ${value} found in template ${asTemplate(template)}`), - - // DOM ONLY - /* c8 ignore start */ - invalid_template: template => new SyntaxError(`Invalid template - the amount of values does not match the amount of updates: ${asTemplate(template)}`), - invalid_path: (template, path) => new SyntaxError(`Invalid path - unreachable node at the path [${path.join(', ')}] found in template ${asTemplate(template)}`), - invalid_attribute: (template, kind) => new SyntaxError(`Invalid ${kind} attribute in template definition\n${asTemplate(template)}`), - invalid_interpolation: (template, value) => new SyntaxError(`Invalid interpolation - expected hole or array: ${String(value)} found in template ${asTemplate(template)}`), - invalid_hole: value => new SyntaxError(`Invalid interpolation - expected hole: ${String(value)}`), - invalid_key: value => new SyntaxError(`Invalid key attribute or position in template: ${String(value)}`), - invalid_array: value => new SyntaxError(`Invalid array - expected html/svg but found something else: ${String(value)}`), - invalid_component: value => new SyntaxError(`Invalid component: ${String(value)}`), -}; - -const { isArray } = Array; -const { assign, freeze, keys } = Object; -/* c8 ignore stop */ - -// this is an essential ad-hoc DOM facade - - -const ELEMENT = 1; -const ATTRIBUTE$1 = 2; -const TEXT$1 = 3; -const COMMENT$1 = 8; -const DOCUMENT_TYPE = 10; -const FRAGMENT = 11; -const COMPONENT$1 = 42; - -const TEXT_ELEMENTS = new Set([ - 'plaintext', - 'script', - 'style', - 'textarea', - 'title', - 'xmp', -]); - -const VOID_ELEMENTS = new Set([ - 'area', - 'base', - 'br', - 'col', - 'embed', - 'hr', - 'img', - 'input', - 'keygen', - 'link', - 'menuitem', - 'meta', - 'param', - 'source', - 'track', - 'wbr', -]); - -const props = freeze({}); -const children = freeze([]); - -const append = (node, child) => { - if (node.children === children) node.children = []; - node.children.push(child); - child.parent = node; - return child; -}; - -const prop = (node, name, value) => { - if (node.props === props) node.props = {}; - node.props[name] = value; -}; - -const addJSON = (value, comp, json) => { - if (value !== comp) json.push(value); -}; - -const setChildren = (node, json) => { - node.children = json.map(revive, node); -}; - -const setJSON = (node, json, index) => { - switch (json.length) { - case index: setChildren(node, json[index - 1]); - case index - 1: { - const value = json[index - 2]; - if (isArray(value)) setChildren(node, value); - else node.props = assign({}, value); - } - } - return node; -}; - -function revive(json) { - const node = fromJSON(json); - node.parent = this; - return node; -} - -const fromJSON = json => { - switch (json[0]) { - case COMMENT$1: return new Comment(json[1]); - case DOCUMENT_TYPE: return new DocumentType(json[1]); - case TEXT$1: return new Text(json[1]); - case COMPONENT$1: return setJSON(new Component, json, 3); - case ELEMENT: return setJSON(new Element(json[1], !!json[2]), json, 5); - case FRAGMENT: { - const node = new Fragment; - if (1 < json.length) node.children = json[1].map(revive, node); - return node; - } - } -}; - -class Node { - constructor(type) { - this.type = type; - this.parent = null; - } - - toJSON() { - //@ts-ignore - return [this.type, this.data]; - } -} - -class Comment extends Node { - constructor(data) { - super(COMMENT$1); - this.data = data; - } - - toString() { - return ``; - } -} - -class DocumentType extends Node { - constructor(data) { - super(DOCUMENT_TYPE); - this.data = data; - } - - toString() { - return ``; - } -} - -class Text extends Node { - constructor(data) { - super(TEXT$1); - this.data = data; - } - - toString() { - return this.data; - } -} - -class Component extends Node { - constructor() { - super(COMPONENT$1); - this.name = 'template'; - this.props = props; - this.children = children; - } - - toJSON() { - const json = [COMPONENT$1]; - addJSON(this.props, props, json); - addJSON(this.children, children, json); - return json; - } - - toString() { - let attrs = ''; - for (const key in this.props) { - const value = this.props[key]; - if (value != null) { - /* c8 ignore start */ - if (typeof value === 'boolean') { - if (value) attrs += ` ${key}`; - } - else attrs += ` ${key}="${value}"`; - /* c8 ignore stop */ - } - } - return `${this.children.join('')}`; - } -} - -class Element extends Node { - constructor(name, xml = false) { - super(ELEMENT); - this.name = name; - this.xml = xml; - this.props = props; - this.children = children; - } - - toJSON() { - const json = [ELEMENT, this.name, +this.xml]; - addJSON(this.props, props, json); - addJSON(this.children, children, json); - return json; - } - - toString() { - const { xml, name, props, children } = this; - const { length } = children; - let html = `<${name}`; - for (const key in props) { - const value = props[key]; - if (value != null) { - if (typeof value === 'boolean') { - if (value) html += xml ? ` ${key}=""` : ` ${key}`; - } - else html += ` ${key}="${value}"`; - } - } - if (length) { - html += '>'; - for (let text = !xml && TEXT_ELEMENTS.has(name), i = 0; i < length; i++) - html += text ? children[i].data : children[i]; - html += `${name}>`; - } - else if (xml) html += ' />'; - else html += VOID_ELEMENTS.has(name) ? '>' : `>${name}>`; - return html; - } -} - -class Fragment extends Node { - constructor() { - super(FRAGMENT); - this.name = '#fragment'; - this.children = children; - } - - toJSON() { - const json = [FRAGMENT]; - addJSON(this.children, children, json); - return json; - } - - toString() { - return this.children.join(''); - } -} - -//@ts-check - - -const NUL = '\x00'; -const DOUBLE_QUOTED_NUL = `"${NUL}"`; -const SINGLE_QUOTED_NUL = `'${NUL}'`; -const NEXT = /\x00|<[^><\s]+/g; -const ATTRS = /([^\s/>=]+)(?:=(\x00|(?:(['"])[\s\S]*?\3)))?/g; - -// // YAGNI: NUL char in the wild is a shenanigan -// // usage: template.map(safe).join(NUL).trim() -// const NUL_RE = /\x00/g; -// const safe = s => s.replace(NUL_RE, ''); - -/** @typedef {import('../dom/ish.js').Node} Node */ -/** @typedef {import('../dom/ish.js').Element} Element */ -/** @typedef {import('../dom/ish.js').Component} Component */ -/** @typedef {(node: import('../dom/ish.js').Node, type: typeof ATTRIBUTE | typeof TEXT | typeof COMMENT | typeof COMPONENT, path: number[], name: string, hint: unknown) => unknown} update */ -/** @typedef {Element | Component} Container */ - -/** @type {update} */ -const defaultUpdate = (_, type, path, name, hint) => [type, path, name]; - -/** - * @param {Node} node - * @returns {number[]} - */ -const path = node => { - const insideout = []; - while (node.parent) { - switch (node.type) { - /* c8 ignore start */ - case COMPONENT$1: - // fallthrough - /* c8 ignore stop */ - case ELEMENT: { - if (/** @type {Container} */(node).name === 'template') insideout.push(-1); - break; - } - } - insideout.push(node.parent.children.indexOf(node)); - node = node.parent; - } - return insideout; -}; - -/** - * @param {Node} node - * @param {Set