state/src/component.js

87 lines
3.7 KiB
JavaScript
Raw Normal View History

// src/component.js
import { NewState } from './observer.js';
import { $, $$ } from './dom-utils.js';
import { _scanTree } from './dom.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 = {}) {
if (from.attributes) {
Array.from(from.attributes).forEach(attr => attr.name !== 'class' && to.setAttribute(attr.name, attr.value));
}
if (from.classList) {
to.classList.add(...from.classList);
}
Array.from(from.childNodes).forEach(child => to.appendChild(child));
if (from.tagName && Component.exists(from.tagName)) _makeComponent(from.tagName, to, scanObj, exists);
}
export function _makeComponent(name, node, scanObj, exists = {}) {
if (exists[name]) return;
exists[name] = true;
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.'); });
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) {
const rootNode = tplnode.children[0];
_mergeNode(rootNode, node, scanObj, exists);
$$(node, '[slot-id]').forEach(placeholder => {
const slotName = placeholder.getAttribute('slot-id');
const slotSource = slots[slotName];
if (slotSource) {
placeholder.removeAttribute('slot-id');
placeholder.innerHTML = '';
if (slotSource.tagName === 'TEMPLATE') {
Array.from(slotSource.content.childNodes).forEach(child => placeholder.appendChild(child.cloneNode(true)));
} else {
_mergeNode(slotSource, placeholder, scanObj, exists);
}
}
});
}
}
if (componentFunc) {
try {
componentFunc(node);
} catch (e) {
console.error('Error in component setupFunc for', name, e);
}
}
}