UNPKG

typed-dom

Version:

A value-level and type-level DOM builder.

122 lines (112 loc) 8.03 kB
import { El, ElementProxy } from './proxy'; import { Factory, TagNameMap, Attrs, shadow, html, svg, math, define } from './util/dom'; export type API<M extends TagNameMap> = BuilderFunction<M> & { readonly [P in K<M>]: BuilderMethod<M, P>; }; export function API<M extends TagNameMap>( baseFactory: Factory<M>, container?: (el: E<M[K<M>]>) => ShadowRoot | undefined, ): API<M> { return new Proxy<API<M>>((() => 0) as any, handle(baseFactory, container)); } export const Shadow = API<ShadowHostHTMLElementTagNameMap>(html, shadow); export const HTML = API<HTMLElementTagNameMap>(html); export const SVG = API<SVGElementTagNameMap>(svg); export const Math = API<MathMLElementTagNameMap>(math); type K<M> = keyof M & string; type E<V> = Extract<V, Element>; type El_Children_Unit = readonly []; type ElFactory< M extends TagNameMap, T extends keyof M & string = keyof M & string, C extends El.Children = El.Children, > = // Bug: TypeScript: Type U must not affect Type C //<U extends T>(baseFactory: Factory<M>, tag: U, attrs: Attrs, children: C) => M[U]; (baseFactory: Factory<M>, tag: T, attrs: Attrs<E<M[T]>>, children: C) => M[T]; interface BuilderFunction<M extends TagNameMap> { <T extends K<M>, C extends El.Children.Void >(tag: T, attrs: Attrs<E<M[T]>> | undefined, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, El.Children.Void>; <T extends K<M>, C extends El.Children.Void >(tag: T, attrs: Attrs<E<M[T]>> | undefined, children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, El.Children.Void>; <T extends K<M>, C extends El.Children.Node >(tag: T, attrs: Attrs<E<M[T]>> | undefined, children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, C>; <T extends K<M>, C extends El.Children.Text >(tag: T, attrs: Attrs<E<M[T]>> | undefined, children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, El.Children.Text>; <T extends K<M>, C extends El_Children_Unit >(tag: T, attrs: Attrs<E<M[T]>> | undefined, children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, El.Children.Array>; <T extends K<M>, C extends El.Children.Array >(tag: T, attrs: Attrs<E<M[T]>> | undefined, children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, Readonly<C>>; <T extends K<M>, C extends El.Children.Struct>(tag: T, attrs: Attrs<E<M[T]>> | undefined, children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, C>; <T extends K<M>, C extends El.Children.Void >(tag: T, children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, El.Children.Void>; <T extends K<M>, C extends El.Children.Node >(tag: T, children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, C>; <T extends K<M>, C extends El.Children.Text >(tag: T, children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, El.Children.Text>; <T extends K<M>, C extends El_Children_Unit >(tag: T, children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, El.Children.Array>; <T extends K<M>, C extends El.Children.Array >(tag: T, children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, Readonly<C>>; <T extends K<M>, C extends El.Children.Struct>(tag: T, children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, C>; <T extends K<M>, C extends El.Children.Void >(tag: T, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, El.Children.Void>; } interface BuilderMethod<M extends TagNameMap, T extends K<M>> { <C extends El.Children.Void >( attrs: Attrs<E<M[T]>> | undefined, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, El.Children.Void>; <C extends El.Children.Void >( attrs: Attrs<E<M[T]>> | undefined, children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, El.Children.Void>; <C extends El.Children.Node >( attrs: Attrs<E<M[T]>> | undefined, children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, C>; <C extends El.Children.Text >( attrs: Attrs<E<M[T]>> | undefined, children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, El.Children.Text>; <C extends El_Children_Unit >( attrs: Attrs<E<M[T]>> | undefined, children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, El.Children.Array>; <C extends El.Children.Array >( attrs: Attrs<E<M[T]>> | undefined, children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, Readonly<C>>; <C extends El.Children.Struct>( attrs: Attrs<E<M[T]>> | undefined, children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, C>; <C extends El.Children.Void >( children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, El.Children.Void>; <C extends El.Children.Node >( children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, C>; <C extends El.Children.Text >( children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, El.Children.Text>; <C extends El_Children_Unit >( children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, El.Children.Array>; <C extends El.Children.Array >( children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, Readonly<C>>; <C extends El.Children.Struct>( children: C, factory?: ElFactory<M, T, C>): El<T, E<M[T]>, C>; <C extends El.Children.Void >( factory?: ElFactory<M, T, C>): El<T, E<M[T]>, El.Children.Void>; } function handle<M extends TagNameMap>( baseFactory: Factory<M>, container?: (el: Element) => ShadowRoot | undefined, ): ProxyHandler<API<M>> { return { apply(target, _, [tag, ...args]) { return this.get!(target, tag, target)(...args); }, get: (target, prop) => target[prop] || prop in target || typeof prop !== 'string' ? target[prop] : target[prop] = builder(prop as keyof M & string), }; function builder(tag: keyof M & string) { return function build( attrs?: Attrs | El.Children, children?: El.Children, factory?: ElFactory<M, keyof M & string, El.Children>, ): El { if (typeof children === 'function') return build(attrs, undefined, children); if (typeof attrs === 'function') return build(undefined, undefined, attrs); if (isElChildren(attrs)) return build(undefined, attrs, factory); const el = elem(tag, factory, attrs, children); return new ElementProxy(tag, el, children, container?.(el)); }; } function elem( tag: keyof M & string, factory: ElFactory<M, keyof M & string> | undefined, attrs: Attrs | undefined, children: El.Children, ): Element { if(!factory) return baseFactory(tag, attrs) as unknown as Element; const el = define(factory(baseFactory, tag, attrs ?? {}, children) as unknown as Element, attrs); switch (el.tagName) { case tag: case tag.toUpperCase(): return el; default: throw new Error(`Typed-DOM: Expected tag name is "${tag.toLowerCase()}" but actually "${el.tagName.toLowerCase()}"`); } } } function isElChildren(value: Attrs | El.Children): value is El.Children { if (value === undefined) return false; if (value[Symbol.iterator]) return true; assert([value = value as Exclude<typeof value, El.Children.Text | El.Children.Array>]); if (typeof value['nodeType'] === 'number') return true; assert([value = value as Exclude<typeof value, El.Children.Node>]); for (const name of Object.keys(value)) { const val = value[name]; return val !== null && typeof val === 'object'; } return false; }