2026-05-14 17:12:01 +08:00
|
|
|
// src/component.js
|
|
|
|
|
import { NewState } from './observer.js';
|
|
|
|
|
import { $, $$ } from './dom-utils.js';
|
|
|
|
|
|
|
|
|
|
const _components = new Map();
|
|
|
|
|
const _pendingTemplates = [];
|
|
|
|
|
|
|
|
|
|
export const Component = {
|
|
|
|
|
getTemplate: name => document.querySelector(`template[component="${name.toUpperCase()}"]`),
|
|
|
|
|
register: (name, setupFunc, templateNode = null, ...globalNodes) => {
|
|
|
|
|
_components.set(name.toUpperCase(), setupFunc);
|
|
|
|
|
if (document.readyState !== 'loading') Component._addTemplate(name, templateNode, globalNodes);
|
|
|
|
|
else _pendingTemplates.push([name, templateNode, globalNodes]);
|
|
|
|
|
},
|
|
|
|
|
exists: (name) => _components.has(name.toUpperCase()),
|
|
|
|
|
getSetupFunction: (name) => _components.get(name.toUpperCase()),
|
|
|
|
|
_addTemplate: (name, templateNode, globalNodes) => {
|
|
|
|
|
if (templateNode) {
|
|
|
|
|
const template = document.createElement('TEMPLATE');
|
|
|
|
|
template.setAttribute('component', name.toUpperCase());
|
|
|
|
|
template.content.appendChild(templateNode);
|
|
|
|
|
document.body.appendChild(template);
|
|
|
|
|
}
|
|
|
|
|
if (globalNodes) globalNodes.forEach(node => document.body.appendChild(node));
|
|
|
|
|
},
|
|
|
|
|
_initPending: () => {
|
|
|
|
|
_pendingTemplates.forEach(([name, templateNode, globalNodes]) => Component._addTemplate(name, templateNode, globalNodes));
|
|
|
|
|
_pendingTemplates.length = 0;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export function _mergeNode(from, to, scanObj, exists = {}) {
|
2026-05-15 00:57:35 +08:00
|
|
|
if (from.attributes) {
|
2026-05-18 18:52:38 +08:00
|
|
|
Array.from(from.attributes).forEach(attr => {
|
|
|
|
|
if (attr.name === 'class') return;
|
|
|
|
|
if (attr.name === 'style') {
|
|
|
|
|
if (to.hasAttribute('style')) to.setAttribute('style', `${attr.value}; ${to.getAttribute('style')}`);
|
|
|
|
|
else to.setAttribute('style', attr.value);
|
|
|
|
|
} else if (!to.hasAttribute(attr.name)) {
|
|
|
|
|
to.setAttribute(attr.name, attr.value);
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-05-15 00:57:35 +08:00
|
|
|
}
|
2026-05-18 18:52:38 +08:00
|
|
|
to.classList.add(...from.classList);
|
2026-05-14 17:12:01 +08:00
|
|
|
Array.from(from.childNodes).forEach(child => to.appendChild(child));
|
2026-05-15 00:57:35 +08:00
|
|
|
if (from.tagName && Component.exists(from.tagName)) _makeComponent(from.tagName, to, scanObj, exists);
|
2026-05-14 17:12:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function _makeComponent(name, node, scanObj, exists = {}) {
|
|
|
|
|
if (exists[name]) return;
|
|
|
|
|
exists[name] = true;
|
2026-05-18 18:52:38 +08:00
|
|
|
if (scanObj.thisObj) {
|
|
|
|
|
Array.from(node.attributes).forEach(attr => {
|
|
|
|
|
if ((attr.name.startsWith('$') || attr.name.startsWith('st-')) && attr.value.includes('this.')) {
|
|
|
|
|
attr.value = attr.value.replace(/\bthis\./g, 'this.parent.');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-05-14 17:12:01 +08:00
|
|
|
const componentFunc = Component.getSetupFunction(name);
|
|
|
|
|
const slots = {};
|
|
|
|
|
Array.from(node.childNodes).forEach(child => {
|
|
|
|
|
if (child.nodeType === Node.ELEMENT_NODE && child.hasAttribute('slot')) {
|
|
|
|
|
slots[child.getAttribute('slot')] = child;
|
|
|
|
|
child.removeAttribute('slot');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
node.innerHTML = '';
|
|
|
|
|
node.state = NewState(node.state || {});
|
|
|
|
|
const template = Component.getTemplate(name);
|
|
|
|
|
if (template) {
|
|
|
|
|
const tplnode = template.content.cloneNode(true);
|
|
|
|
|
if (tplnode.childNodes.length) {
|
2026-05-18 18:52:38 +08:00
|
|
|
const rootNode = Array.from(tplnode.childNodes).find(n => n.nodeType === Node.ELEMENT_NODE);
|
|
|
|
|
if (rootNode) _mergeNode(rootNode, node, scanObj, exists);
|
2026-05-14 17:12:01 +08:00
|
|
|
$$(node, '[slot-id]').forEach(placeholder => {
|
|
|
|
|
const slotName = placeholder.getAttribute('slot-id');
|
2026-05-18 18:52:38 +08:00
|
|
|
if (slots[slotName]) {
|
2026-05-14 17:12:01 +08:00
|
|
|
placeholder.removeAttribute('slot-id');
|
|
|
|
|
placeholder.innerHTML = '';
|
2026-05-18 18:52:38 +08:00
|
|
|
_mergeNode(slots[slotName], placeholder, scanObj, exists);
|
2026-05-14 17:12:01 +08:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-18 18:52:38 +08:00
|
|
|
if (componentFunc) componentFunc(node);
|
2026-05-14 17:12:01 +08:00
|
|
|
}
|