83 lines
2.5 KiB
TypeScript
83 lines
2.5 KiB
TypeScript
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:
|
|
(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 };
|