UNPKG

ivi

Version:

Lightweight Embeddable Web UI Library.

173 lines (162 loc) 5.32 kB
import { type TemplateDescriptor, type VAny, _hN, _hE, _sN, _sE, _T, _t } from "../lib/core.js"; import { type TemplateNode, TemplateNodeType, compileTemplate } from "../template/compiler.js"; import { TEMPLATE_TYPE_HTM, TEMPLATE_TYPE_SVG } from "../template/ir.js"; import { TemplateParserError, formatError } from "../template/parser.js"; import { parseTemplate } from "./parser.js"; const DESCRIPTORS = new WeakMap<TemplateStringsArray, (exprs: any[]) => VAny>(); /** * Creates template with HTML Elements. * * ### Element Syntax: * * - `<div name="value" />` - Static Attribute. * - `<div name />` - Static Attribute. * - `<div name=${expr} />` - Dynamic attribute `element.setAttribute(name, expr)`. * - `<div .name=${expr} />` - Property `element[name] = expr`. * - `<div *name=${expr} />` - Property `element[name] = expr`, diffs against a DOM value. * - `<div ~name="value" />` - Static style `<div style="name:value;">` * - `<div ~name=${expr} />` - Dynamic style `element.style.setProperty(name, expr)` * - `<div @name=${expr} />` - Event `element.addEventListener(name, expr)` * - `<div ${directive} />` - Client-Side Directive `directive(element)` * * @example * * function MyApp(content) { * return html` * <div id="App"> * <h1 class="Title">MyApp</h1> * <div>Content ${content}</div> * </div> * `; * } */ export const html = (strings: TemplateStringsArray, ...exprs: any[]) => { let fn = DESCRIPTORS.get(strings); if (fn === void 0) { let result; try { const tpl = parseTemplate(strings, TEMPLATE_TYPE_HTM); result = compileTemplate(tpl); } catch (e) { if (e instanceof TemplateParserError) { throw Error( "Invalid template" + formatError(strings, e.message, e.staticsOffset, e.textOffset), ); } throw e; } const roots = result.roots; if (roots.length === 1) { const root = prepareRootNode(roots[0], _hE, _hN); fn = (exprs) => createRootNode(root, exprs); } else { const entries = roots.map((root) => prepareRootNode(root, _hE, _hN)); fn = (exprs) => entries.map((root) => createRootNode(root, exprs)); } DESCRIPTORS.set(strings, fn); } return fn(exprs); }; /** * Creates template with SVG Elements. * * ### Element Syntax: * * - `<div name="value" />` - Static Attribute. * - `<div name />` - Static Attribute. * - `<div name=${expr} />` - Dynamic attribute `element.setAttribute(name, expr)`. * - `<div .name=${expr} />` - Property `element[name] = expr`. * - `<div *name=${expr} />` - Property `element[name] = expr`, diffs against a DOM value. * - `<div ~name="value" />` - Static style `<div style="name:value;">` * - `<div ~name=${expr} />` - Dynamic style `element.style.setProperty(name, expr)` * - `<div @name=${expr} />` - Event `element.addEventListener(name, expr)` * - `<div ${directive} />` - Client-Side Directive `directive(element)` * * @example * * function Star(content) { * return svg` * <svg height="210" width="500"> * <polygon * points="100,10 40,198 190,78 10,78 160,198" * ~fill="lime" * ~stroke="purple" * ~stroke-width="5" * ~fill-rule="nonzero" * /> * </svg> * `; * } */ export const svg = (strings: TemplateStringsArray, ...exprs: any[]) => { let fn = DESCRIPTORS.get(strings); if (fn === void 0) { let result; try { const tpl = parseTemplate(strings, TEMPLATE_TYPE_SVG); result = compileTemplate(tpl); } catch (e) { if (e instanceof TemplateParserError) { throw Error( "Invalid template" + formatError(strings, e.message, e.staticsOffset, e.textOffset), ); } throw e; } const roots = result.roots; if (roots.length === 1) { const root = prepareRootNode(roots[0], _sE, _sN); fn = (exprs) => createRootNode(root, exprs); } else { const entries = roots.map((root) => prepareRootNode(root, _hE, _hN)); fn = (exprs) => entries.map((root) => createRootNode(root, exprs)); } DESCRIPTORS.set(strings, fn); } return fn(exprs); }; interface RootNodeBlock { readonly map: number[]; readonly descriptor: TemplateDescriptor; } type RootNode = RootNodeBlock | string | number; const prepareRootNode = (root: TemplateNode, createElement: (t: string) => () => Element, cloneTemplate: (t: string) => () => Element, ) => { switch (root.type) { case TemplateNodeType.Block: const template = root.template; return { map: root.exprs, descriptor: _T( (typeof template === "string") ? createElement(template) : cloneTemplate(template.join("")), root.flags, root.props, root.child, root.state, root.data, ), }; case TemplateNodeType.Text: return root.value; case TemplateNodeType.Expr: return root.value; } }; const createRootNode = (e: RootNode, exprs: any[]) => { if (typeof e === "object") { return _t(e.descriptor, e.map.map((i) => exprs[i])); } if (typeof e === "number") { return exprs[e]; } return e; };