import {isCapsule, ICapsule} from "./Capsule"; import {ISubscription} from "./Publisher"; import Rung, {FunctionalRung} from "./Rung"; export type IRenderAttributes = Partial<{ [K in keyof HTMLElementTagNameMap[T]]: HTMLElementTagNameMap[T][K] | ICapsule }> & { classes?: string[], saveTo?: ICapsule, }; type IdSelector = `#${ string }`; export function bootstrap(app: Rung, id: IdSelector) { const rootNode = document.querySelector(id); if (!rootNode) { throw new Error(`No node was found with the id ${id} to attach to`); } else { rootNode.appendChild(app.render()); } } export function frag(subs?: Node[]): DocumentFragment { const frag = document.createDocumentFragment(); if (subs) { attachSubs(frag, subs); } return frag; } export function q(text: string): Text { return document.createTextNode(text); } type InstantiationType = FunctionalRung | keyof HTMLElementTagNameMap; type Props = T extends FunctionalRung ? Attributes : T extends keyof HTMLElementTagNameMap ? IRenderAttributes : never; export type SubNode = Rung | Node | ICapsule; export function h(type: T, attributes?: Props, ...subNodes: SubNode[]): HTMLElementTagNameMap[T]; export function h, U extends Props, V extends ReturnType>(type: T, attributes?: U, ...subNodes: SubNode[]): V; export function h(type: T, attributes?: Props | null, ...subNodes: SubNode[]) { if (typeof type === "function") { return type(attributes, subNodes); } else { return createStandardElement(type, attributes ?? {}, subNodes); } } function createStandardElement( type: T, attributes: IRenderAttributes | null, subNodes: SubNode[] ): HTMLElementTagNameMap[T] { const element = document.createElement(type); if (attributes) { if (attributes.classes) { element.classList.add(...attributes.classes); } if (attributes.saveTo) { attributes.saveTo.val = element; } applyAttributes(element, attributes); } attachSubs(element, subNodes); return element; } function nodeCapsuleWatcher(newVal: T extends ICapsule ? U : never, textNode: Text, sub: ISubscription): void { if (!textNode.parentNode) { sub.unbind(); textNode.remove(); } else { textNode.replaceWith(newVal?.toString() ?? q("[dead ref]")); } } function attachSubs(node: Element | DocumentFragment, subNodes: SubNode[]): void { for (let i = 0; i < subNodes.length; i++) { const subNode = subNodes[i]; if (subNode instanceof Rung) { node.append(subNode.render()); } else if (isCapsule(subNode)) { const textNode = q(subNode.toString()); const sub = subNode.watch((newVal) => nodeCapsuleWatcher(newVal, textNode, sub)); node.append(textNode); } else { node.append(subNode); } } } function applyAttributes(element: HTMLElement, attributes: IRenderAttributes): void { for (const key in attributes) { if (Object.prototype.hasOwnProperty.call(attributes, key)) { const attribute = (attributes as Record)[key]; if (attribute) { if (isCapsule(attribute)) { const attributeAsCapsule = attribute as ICapsule; const elementWithAttributeKey = element as unknown as Record; elementWithAttributeKey[key] = attributeAsCapsule.val; attribute.watch((newVal) => elementWithAttributeKey[key] = newVal); } else { (element as unknown as Record)[key] = attribute; } } } } }