This commit is contained in:
Morten Olsen
2025-09-21 08:43:15 +02:00
commit 0ab9a27493
18 changed files with 5379 additions and 0 deletions

82
lib/html.ts Normal file
View File

@@ -0,0 +1,82 @@
import { expandTemplateStringFromStore, storeSymbol, type Store } from './store.js';
const toHtmlElement = (input: unknown): HTMLElement | Text => {
if (input instanceof HTMLElement || input instanceof Text) {
return input;
}
const toHtmlProperty = Object.getOwnPropertyDescriptor(input, 'toHtmlElement');
if (toHtmlProperty && typeof toHtmlProperty.value === 'function') {
const elm = toHtmlProperty.value();
return elm;
}
if (typeof input === 'object') {
return document.createTextNode(JSON.stringify(input));
}
if (input === undefined) {
return document.createTextNode('');
}
return document.createTextNode(String(input));
};
const html = (tag: string | (() => HTMLElement)) => {
const render =
(options: Record<string, unknown>) =>
(strings: TemplateStringsArray, ...params: unknown[]) => {
const fn = (elm: HTMLElement) => {
for (const [event, listener] of Object.entries(options)) {
if (typeof listener === 'function') {
elm.addEventListener(event, () => listener());
}
}
if ('style' in options) {
for (const [name, value] of Object.entries(options.style as any)) {
(elm.style as any)[name] = value;
}
}
const elements = expandTemplateStringFromStore(strings, ...params);
const htmlElements = elements.map(toHtmlElement).filter(Boolean);
elm.innerHTML = '';
for (const element of htmlElements) {
elm.appendChild(element);
}
};
return Object.assign(fn, {
toHtmlElement: () => {
const root = typeof tag === 'function' ? tag() : document.createElement(tag);
fn(root);
return root;
},
});
};
return Object.assign((strings: TemplateStringsArray, ...params: unknown[]) => render({})(strings, ...params), {
$:
(props: Record<string, unknown>) =>
(strings: TemplateStringsArray, ...params: unknown[]) =>
render(props)(strings, ...params),
});
};
type Html = ReturnType<ReturnType<typeof html>>;
const div = html('div');
const h1 = html('h1');
const p = html('p');
const button = html('button');
const scope = (...stores: Store[]) =>
html(() => {
const elm = document.createElement('div');
elm.style.display = 'content';
const storeMap = new Map();
(elm as any)[storeSymbol] = storeMap;
for (const store of stores) {
storeMap.set(store, store.create());
}
return elm;
});
export type { Html };
export { html, div, h1, p, button, scope };