UNPKG

bausteine

Version:
241 lines (240 loc) 9.7 kB
import { render, internals, isServerSide, reactive, unset, } from "hydro-js"; export default function createWebComponent(name, template, { extendsFrom, reflects = false } = {}) { const extendsFromClass = (extendsFrom ? MyHTMLElementTagNameMap[extendsFrom] : window.HTMLElement); if (!window.customElements.get(name)) { window.customElements.define(name, class hydroElement extends extendsFromClass { #observer; #props = {}; #unmount; constructor() { super(); if (reflects) { this.#observer = new window.MutationObserver((mutationList) => this.attributeChangedCallback(mutationList)); } } connectedCallback() { const current = new window.DocumentFragment(); current.append(...this.childNodes); const props = {}; let first; if (internals.allNodeChanges.has(this)) { internals.allNodeChanges.get(this).reduce((acc, curr) => { const proxy = curr.at(3); const key = curr.at(2); const resolved = internals.hydroToReactive.get(proxy); if (resolved) { first = resolved; acc[key] = resolved; } else if (key) { acc[key] = reactive(this.getAttribute(key)); } return acc; }, props); } else { for (const attr of this.getAttributeNames()) { props[attr] = reactive(this.getAttribute(attr)); } } first ??= {}; this.#props = Object.values(props).every((prop) => prop === first) ? first : props; const templateRes = template({ root: this, props: this.#props, }); this.#unmount = render(templateRes, this.appendChild(document.createElement("span")), false); for (const slot of this.querySelectorAll("slot")) { const slotName = slot.getAttribute("name"); const replacer = current.querySelector(`[slot="${slotName}"], [data-slot="${slotName}"]`); if (replacer) { replacer.setAttribute("data-slot", slotName ?? ""); render(replacer, slot, false); } } for (const unusedSlot of current.querySelectorAll("[slot], [data-slot]")) { unusedSlot.remove(); } const defaultSlot = this.querySelector("slot:not([name]), [data-default-slot]"); if (defaultSlot && current.childNodes.length) { if (isServerSide()) { for (const child of current.children) { child.setAttribute("data-default-slot", ""); } render(current, defaultSlot, false); } else { const prevDefaultSlot = current.querySelectorAll("[data-default-slot]"); if (prevDefaultSlot.length === 1) { render(prevDefaultSlot[0], defaultSlot, false); } else if (prevDefaultSlot.length > 1) { const prevDefaultSlotFragment = new DocumentFragment(); for (const slot of prevDefaultSlot) { prevDefaultSlotFragment.appendChild(slot); } render(prevDefaultSlotFragment, defaultSlot, false); } else if (current.childNodes.length) { render(current, defaultSlot, false); } } } this.#observer?.observe(this, { attributes: true, attributeOldValue: true, }); } // Inform about external changes attributeChangedCallback(mutationList) { for (const mutation of mutationList) { const key = mutation.attributeName; const prop = this.#props[key]; if (!prop) continue; const elemValue = this.getAttribute(key); // Trigger for manual change if (elemValue === null) { prop.setter(internals.boolAttrList.includes(key) ? false : null); } else if (elemValue === "") { prop.setter(true); } else { prop.setter(elemValue); } } } disconnectedCallback() { this.#observer?.disconnect(); for (const key in this.#props) { unset(this.#props[key]); } this.#unmount(); } // eslint-disable-next-line @typescript-eslint/no-explicit-any observe(name, fn) { this.#props[name]?.observe(name, fn); } }, extendsFrom ? { extends: extendsFrom } : undefined); } } const MyHTMLElementTagNameMap = { a: window.HTMLAnchorElement, abbr: window.HTMLElement, address: window.HTMLElement, area: window.HTMLAreaElement, article: window.HTMLElement, aside: window.HTMLElement, audio: window.HTMLAudioElement, b: window.HTMLElement, base: window.HTMLBaseElement, bdi: window.HTMLElement, bdo: window.HTMLElement, blockquote: window.HTMLQuoteElement, body: window.HTMLBodyElement, br: window.HTMLBRElement, button: window.HTMLButtonElement, canvas: window.HTMLCanvasElement, caption: window.HTMLTableCaptionElement, cite: window.HTMLElement, code: window.HTMLElement, col: window.HTMLTableColElement, colgroup: window.HTMLTableColElement, data: window.HTMLDataElement, // Safari bug datalist: Object.prototype.hasOwnProperty.call(window, "HTMLDataListElement") ? window.HTMLDataListElement : window.HTMLElement, dd: window.HTMLElement, del: window.HTMLModElement, details: window.HTMLDetailsElement, dfn: window.HTMLElement, dialog: window.HTMLDialogElement, div: window.HTMLDivElement, dl: window.HTMLDListElement, dt: window.HTMLElement, em: window.HTMLElement, embed: window.HTMLEmbedElement, fieldset: window.HTMLFieldSetElement, figcaption: window.HTMLElement, figure: window.HTMLElement, footer: window.HTMLElement, form: window.HTMLFormElement, h1: window.HTMLHeadingElement, h2: window.HTMLHeadingElement, h3: window.HTMLHeadingElement, h4: window.HTMLHeadingElement, h5: window.HTMLHeadingElement, h6: window.HTMLHeadingElement, head: window.HTMLHeadElement, header: window.HTMLElement, hgroup: window.HTMLElement, hr: window.HTMLHRElement, html: window.HTMLHtmlElement, i: window.HTMLElement, iframe: window.HTMLIFrameElement, img: window.HTMLImageElement, input: window.HTMLInputElement, ins: window.HTMLModElement, kbd: window.HTMLElement, label: window.HTMLLabelElement, legend: window.HTMLLegendElement, li: window.HTMLLIElement, link: window.HTMLLinkElement, main: window.HTMLElement, map: window.HTMLMapElement, mark: window.HTMLElement, menu: window.HTMLMenuElement, meta: window.HTMLMetaElement, meter: window.HTMLMeterElement, nav: window.HTMLElement, noscript: window.HTMLElement, object: window.HTMLObjectElement, ol: window.HTMLOListElement, optgroup: window.HTMLOptGroupElement, option: window.HTMLOptionElement, output: window.HTMLOutputElement, p: window.HTMLParagraphElement, picture: window.HTMLPictureElement, pre: window.HTMLPreElement, progress: window.HTMLProgressElement, q: window.HTMLQuoteElement, rp: window.HTMLElement, rt: window.HTMLElement, ruby: window.HTMLElement, s: window.HTMLElement, samp: window.HTMLElement, script: window.HTMLScriptElement, search: window.HTMLElement, section: window.HTMLElement, select: window.HTMLSelectElement, slot: window.HTMLSlotElement, small: window.HTMLElement, source: window.HTMLSourceElement, span: window.HTMLSpanElement, strong: window.HTMLElement, style: window.HTMLStyleElement, sub: window.HTMLElement, summary: window.HTMLElement, sup: window.HTMLElement, table: window.HTMLTableElement, tbody: window.HTMLTableSectionElement, td: window.HTMLTableCellElement, template: window.HTMLTemplateElement, textarea: window.HTMLTextAreaElement, tfoot: window.HTMLTableSectionElement, th: window.HTMLTableCellElement, thead: window.HTMLTableSectionElement, time: window.HTMLTimeElement, title: window.HTMLTitleElement, tr: window.HTMLTableRowElement, track: window.HTMLTrackElement, u: window.HTMLElement, ul: window.HTMLUListElement, var: window.HTMLElement, video: window.HTMLVideoElement, wbr: window.HTMLElement, };