bausteine
Version:
Design System
241 lines (240 loc) • 9.7 kB
JavaScript
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,
};