UNPKG

uhtml

Version:
1,006 lines (916 loc) • 30.5 kB
'use strict'; // ⚠️ WARNING - THIS FILE IS AN ARTIFACT - DO NOT EDIT /** * @param {Document} document * @returns {import("./keyed.js")} */ module.exports = document => (function () { 'use strict'; const { constructor: DocumentFragment } = document.createDocumentFragment(); /** * 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 = /* c8 ignore start */(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], 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], -0).nextSibling; parentNode.insertBefore( get(b[bStart++], 1), get(a[aStart++], -0).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)); } } /* c8 ignore stop */return b; }; const { isArray } = Array; const { getPrototypeOf, getOwnPropertyDescriptor } = Object; const SVG_NAMESPACE = 'http://www.w3.org/2000/svg'; const empty = []; const newRange = () => document.createRange(); /** * 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} */ const set = (map, key, value) => { map.set(key, value); return value; }; /** * Return a descriptor, if any, for the referenced *Element* * @param {Element} ref * @param {string} prop * @returns */ const gPD = (ref, prop) => { let desc; do { desc = getOwnPropertyDescriptor(ref, prop); } while(!desc && (ref = getPrototypeOf(ref))); return desc; }; /* c8 ignore start */ /** * @param {DocumentFragment} content * @param {number[]} path * @returns {Element} */ const find = (content, path) => path.reduceRight(childNodesIndex, content); const childNodesIndex = (node, i) => node.childNodes[i]; /* c8 ignore stop */ const ELEMENT_NODE = 1; const COMMENT_NODE = 8; const DOCUMENT_FRAGMENT_NODE = 11; const {setPrototypeOf} = Object; /** * @param {Function} Class any base class to extend without passing through it via super() call. * @returns {Function} an extensible class for the passed one. * @example * // creating this very same module utility * import custom from 'custom-function/factory'; * const CustomFunction = custom(Function); * class MyFunction extends CustomFunction {} * const mf = new MyFunction(() => {}); */ var custom = Class => { function Custom(target) { return setPrototypeOf(target, new.target.prototype); } Custom.prototype = Class.prototype; return Custom; }; let range$1; /** * @param {Node | Element} firstChild * @param {Node | Element} lastChild * @param {boolean} preserve * @returns */ var drop = (firstChild, lastChild, preserve) => { if (!range$1) range$1 = newRange(); /* c8 ignore start */ if (preserve) range$1.setStartAfter(firstChild); else range$1.setStartBefore(firstChild); /* c8 ignore stop */ range$1.setEndAfter(lastChild); range$1.deleteContents(); return firstChild; }; /** * @param {PersistentFragment} fragment * @returns {Node | Element} */ const remove = ({firstChild, lastChild}, preserve) => drop(firstChild, lastChild, preserve); let checkType = false; /** * @param {Node} node * @param {1 | 0 | -0 | -1} operation * @returns {Node} */ const diffFragment = /* c8 ignore start */(node, operation) => ( checkType && node.nodeType === DOCUMENT_FRAGMENT_NODE ? ((1 / operation) < 0 ? (operation ? remove(node, true) : node.lastChild) : (operation ? node.valueOf() : node.firstChild)) : node ); const comment = value => document.createComment(value); /** @extends {DocumentFragment} */ class PersistentFragment extends custom(DocumentFragment) { #firstChild = comment('<>'); #lastChild = comment('</>'); #nodes = empty; constructor(fragment) { super(fragment); this.replaceChildren(...[ this.#firstChild, ...fragment.childNodes, this.#lastChild, ]); checkType = true; } get firstChild() { /* c8 ignore stop */return this.#firstChild; } get lastChild() { return this.#lastChild; } get parentNode() { return this.#firstChild.parentNode; } remove() { remove(this, false); } /* c8 ignore start */ replaceWith(node) { remove(this, true).replaceWith(node); } /* c8 ignore stop */ valueOf() { const { parentNode } = this; if (parentNode === this) { if (this.#nodes === empty) this.#nodes = [...this.childNodes]; } else { /* c8 ignore start */ // there are cases where a fragment might be just appended // out of the box without valueOf() invoke (first render). // When these are moved around and lose their parent and, // such parent is not the fragment itself, it's possible there // where changes or mutations in there to take care about. // This is a render-only specific issue but it's tested and // it's worth fixing to me to have more consistent fragments. if (parentNode) { let { firstChild, lastChild } = this; this.#nodes = [firstChild]; while (firstChild !== lastChild) this.#nodes.push((firstChild = firstChild.nextSibling)); } /* c8 ignore stop */ this.replaceChildren(...this.#nodes); } return this; } } const setAttribute = (element, name, value) => element.setAttribute(name, value); /** * @param {Element} element * @param {string} name * @returns {void} */ const removeAttribute = (element, name) => element.removeAttribute(name); /** * @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) removeAttribute(element, name); else setAttribute(element, 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(name, ...current); current = isArray(value) ? value : [value, false]; known[name] = current; if (current[0]) element.addEventListener(name, ...current); return value; }; /** * @template T * @param {import("./literals.js").Detail} detail * @param {T} value * @returns {T} */ const hole = (detail, value) => { const { t: node, n: hole } = detail; let nullish = false; switch (typeof value) { case 'object': if (value !== null) { (hole || node).replaceWith((detail.n = value.valueOf())); break; } case 'undefined': nullish = true; default: node.data = nullish ? '' : value; if (hole) { detail.n = null; hole.replaceWith(node); } break; } return value; }; /** * @template T * @param {Element} element * @param {T} value * @returns {T} */ const className = (element, value) => maybeDirect( element, value, value == null ? 'class' : '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 * @param {string} name * @returns {T} */ const maybeDirect = (element, value, name) => ( value == null ? (removeAttribute(element, name), value) : direct(element, value, name) ); /** * @template T * @param {Element} element * @param {T} value * @returns {T} */ const ref = (element, value) => ( (typeof value === 'function' ? value(element) : (value.current = element)), value ); /** * @template T * @param {Element} element * @param {T} value * @param {string} name * @returns {T} */ const regular = (element, value, name) => ( (value == null ? removeAttribute(element, name) : setAttribute(element, name, value)), value ); /** * @template T * @param {Element} element * @param {T} value * @returns {T} */ const style = (element, value) => ( value == null ? maybeDirect(element, value, 'style') : 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), value ); /** * @param {Node} node * @param {Node[]} value * @param {string} _ * @param {Node[]} prev * @returns {Node[]} */ const array = (node, value, prev) => { // normal diff const { length } = value; node.data = `[${length}]`; if (length) return udomdiff(node.parentNode, prev, value, diffFragment, node); /* c8 ignore start */ switch (prev.length) { case 1: prev[0].remove(); case 0: break; default: drop( diffFragment(prev[0], 0), diffFragment(prev.at(-1), -0), false ); break; } /* c8 ignore stop */ return empty; }; const attr = new Map([ ['aria', aria], ['class', className], ['data', data], ['ref', ref], ['style', style], ]); /** * @param {HTMLElement | SVGElement} element * @param {string} name * @param {boolean} svg * @returns */ const attribute = (element, name, svg) => { switch (name[0]) { case '.': return dot; case '?': return toggle; case '@': return at; default: return ( /* c8 ignore start */ svg || ('ownerSVGElement' in element) /* c8 ignore stop */ ? (name === 'ref' ? ref : regular) : (attr.get(name) || ( name in element ? (name.startsWith('on') ? direct : (gPD(element, name)?.set ? maybeDirect : regular) ) : regular ) ) ); } }; /** * @template T * @param {Element} element * @param {T} value * @returns {T} */ const text = (element, value) => ( (element.textContent = value == null ? '' : value), value ); /** @typedef {import("./persistent-fragment.js").PersistentFragment} PersistentFragment */ /** @typedef {import("./rabbit.js").Hole} Hole */ /** @typedef {unknown} Value */ /** @typedef {Node | Element | PersistentFragment} Target */ /** @typedef {null | undefined | string | number | boolean | Node | Element | PersistentFragment} DOMValue */ /** @typedef {Hole | Node} ArrayValue */ const abc = (a, b, c) => ({ a, b, c }); const bc = (b, c) => ({ b, c }); /** * @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 | null | Node} n the attribute name, if any, or `null` * @property {Cache | ArrayValue[] | null} c the cache value for this detail */ /** * @returns {Detail} */ const detail = (u, t, n, c) => ({ v: empty, u, t, n, c }); /** * @typedef {Object} Entry * @property {number[]} a the path to retrieve the node * @property {function} b the update function * @property {string | null} c the attribute name, if any, or `null` */ /** * @typedef {Object} Cache * @property {null | TemplateStringsArray} a the cached template * @property {null | Node | PersistentFragment} b the node returned when parsing the template * @property {Detail[]} c the list of updates to perform */ /** * @returns {Cache} */ const cache$1 = () => abc(null, null, empty); /** @param {(template: TemplateStringsArray, values: any[]) => import("./parser.js").Resolved} parse */ var create = parse => ( /** * @param {TemplateStringsArray} template * @param {any[]} values * @returns {import("./literals.js").Cache} */ (template, values) => { const { a: fragment, b: entries, c: direct } = parse(template, values); const root = document.importNode(fragment, true); /** @type {import("./literals.js").Detail[]} */ let details = empty; if (entries !== empty) { details = []; for (let current, prev, i = 0; i < entries.length; i++) { const { a: path, b: update, c: name } = entries[i]; const node = path === prev ? current : (current = find(root, (prev = path))); details[i] = detail( update, node, name, update === array ? [] : (update === hole ? cache$1() : null) ); } } return bc( direct ? root.firstChild : new PersistentFragment(root), details, ); } ); const TEXT_ELEMENTS = /^(?:plaintext|script|style|textarea|title|xmp)$/i; const VOID_ELEMENTS = /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i; const elements = /<([a-zA-Z0-9]+[a-zA-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} xml enforces self-closing tags * @returns {string} X/HTML with prefixed comments or attributes */ var parser$1 = (template, prefix, xml) => { let i = 0; return template .join('\x01') .trim() .replace( elements, (_, name, attrs, selfClosing) => `<${ name }${ attrs.replace(attributes, '\x02=$2$1').trimEnd() }${ selfClosing ? ( (xml || VOID_ELEMENTS.test(name)) ? ' /' : `></${name}` ) : '' }>` ) .replace( holes, hole => hole === '\x01' ? `<!--${prefix + i++}-->` : (prefix + i++) ) ; }; let template = document.createElement('template'), svg$1, range; /** * @param {string} text * @param {boolean} xml * @returns {DocumentFragment} */ var createContent = (text, xml) => { if (xml) { if (!svg$1) { svg$1 = document.createElementNS(SVG_NAMESPACE, 'svg'); range = newRange(); range.selectNodeContents(svg$1); } return range.createContextualFragment(text); } template.innerHTML = text; const { content } = template; template = template.cloneNode(false); return content; }; /** @typedef {import("./literals.js").Entry} Entry */ /** * @typedef {Object} Resolved * @param {DocumentFragment} f content retrieved from the template * @param {Entry[]} e entries per each hole in the template * @param {boolean} d direct node to handle */ /** * @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 textNode = () => document.createTextNode(''); /** * @param {TemplateStringsArray} template * @param {boolean} xml * @returns {Resolved} */ const resolve = (template, values, xml) => { const content = createContent(parser$1(template, prefix, xml), xml); const { length } = template; let entries = empty; if (length > 1) { const replace = []; const tw = document.createTreeWalker(content, 1 | 128); let i = 0, search = `${prefix}${i++}`; entries = []; while (i < length) { const node = tw.nextNode(); // these are holes or arrays if (node.nodeType === COMMENT_NODE) { if (node.data === search) { // ⚠️ once array, always array! const update = isArray(values[i - 1]) ? array : hole; if (update === hole) replace.push(node); entries.push(abc(createPath(node), update, null)); search = `${prefix}${i++}`; } } else { let path; // these are attributes while (node.hasAttribute(search)) { if (!path) path = createPath(node); const name = node.getAttribute(search); entries.push(abc(path, attribute(node, name, xml), name)); removeAttribute(node, search); search = `${prefix}${i++}`; } // these are special text-only nodes if ( !xml && TEXT_ELEMENTS.test(node.localName) && node.textContent.trim() === `<!--${search}-->` ) { entries.push(abc(path || createPath(node), text, null)); search = `${prefix}${i++}`; } } } // can't replace holes on the fly or the tree walker fails for (i = 0; i < replace.length; i++) replace[i].replaceWith(textNode()); } // need to decide if there should be a persistent fragment const { childNodes } = content; let { length: len } = childNodes; // html`` or svg`` to signal an empty content // these nodes can be passed directly as never mutated if (len < 1) { len = 1; content.appendChild(textNode()); } // html`${'b'}` or svg`${[]}` cases else if ( len === 1 && // ignore html`static` or svg`static` because // these nodes can be passed directly as never mutated length !== 1 && childNodes[0].nodeType !== ELEMENT_NODE ) { // use a persistent fragment for these cases too len = 0; } return set(cache, template, abc(content, entries, len === 1)); }; /** @type {WeakMap<TemplateStringsArray, Resolved>} */ const cache = new WeakMap; const prefix = 'isµ'; /** * @param {boolean} xml * @returns {(template: TemplateStringsArray, values: any[]) => Resolved} */ var parser = xml => (template, values) => cache.get(template) || resolve(template, values, xml); const createHTML = create(parser(false)); const createSVG = create(parser(true)); /** * @param {import("./literals.js").Cache} info * @param {Hole} hole * @returns {Node} */ const unroll = (info, { s, t, v }) => { if (info.a !== t) { const { b, c } = (s ? createSVG : createHTML)(t, v); info.a = t; info.b = b; info.c = c; } for (let { c } = info, i = 0; i < c.length; i++) { const value = v[i]; const detail = c[i]; switch (detail.u) { case array: detail.v = array( detail.t, unrollValues(detail.c, value), detail.v ); break; case hole: const current = value instanceof Hole ? unroll(detail.c || (detail.c = cache$1()), value) : (detail.c = null, value) ; if (current !== detail.v) detail.v = hole(detail, current); break; default: if (value !== detail.v) detail.v = detail.u(detail.t, value, detail.n, detail.v); break; } } return info.b; }; /** * @param {Cache} cache * @param {any[]} values * @returns {number} */ const unrollValues = (stack, values) => { let i = 0, { length } = values; if (length < stack.length) stack.splice(length); for (; i < length; i++) { const value = values[i]; if (value instanceof Hole) values[i] = unroll(stack[i] || (stack[i] = cache$1()), value); else stack[i] = null; } return values; }; /** * 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. */ class Hole { constructor(svg, template, values) { this.s = svg; this.t = template; this.v = values; } toDOM(info = cache$1()) { return unroll(info, this); } } /*! (c) Andrea Giammarchi - MIT */ /** @typedef {import("./literals.js").Value} Value */ const tag = svg => (template, ...values) => new Hole(svg, template, values); /** @type {(template: TemplateStringsArray, ...values:Value[]) => Hole} A tag to render HTML content. */ const html = tag(false); /** @type {(template: TemplateStringsArray, ...values:Value[]) => Hole} A tag to render SVG content. */ const svg = tag(true); /** @type {WeakMap<Element | DocumentFragment, import("../literals.js").Cache>} */ 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 * @param {boolean} check does a `typeof` check (internal usage). * @returns */ var render = (where, what, check) => { const info = known.get(where) || set(known, where, cache$1()); const { b } = info; const hole = (typeof what === 'function') ? what() : what; const node = hole instanceof Hole ? hole.toDOM(info) : hole; if (b !== node) where.replaceChildren((info.b = node).valueOf()); return where; }; /** @typedef {import("../rabbit.js").Hole} Hole */ /** * 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 */ var keyed$1 = (where, what) => render(where, what); /*! (c) Andrea Giammarchi - MIT */ /** @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 new Hole(svg, template, values).toDOM(this); } const memo = keyed.get(ref) || set(keyed, ref, new Map); return memo.get(key) || set(memo, key, tag.bind(cache$1())); }; /** @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); return { Hole, attr, html, htmlFor, render: keyed$1, svg, svgFor }; })();