/** * Engine Module * Component Management and DOM Engine. */ import { Util, $, $$ } from './utils.js'; import { NewState, _setActiveBinding, _setNoWriteBack, _onNotifyUpdate } from './observer.js'; import { _runCode, _returnCode, setDisableRunCodeError } from './core.js'; // --- Component Logic --- 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 => { 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)) { const isClass = ['$class', 'st-class'].includes(attr.name); const isStyle = ['$style', 'st-style'].includes(attr.name); if (isClass || isStyle) { const oldVal = to.getAttribute(attr.name); const newVal = attr.value; const delimiter = isClass ? " " : "; "; const oldExpr = oldVal.includes('${') ? oldVal : `\${${oldVal}}`; const newExpr = newVal.includes('${') ? newVal : `\${${newVal}}`; to.setAttribute(attr.name, `${newExpr}${delimiter}${oldExpr}`); } } else { to.setAttribute(attr.name, attr.value); } }); } const toClassList = [...to.classList]; to.className = ''; to.classList.add(...from.classList); to.classList.add(...toClassList); const target = to.tagName === 'TEMPLATE' ? to.content : to; const sourceNodes = from.tagName === 'TEMPLATE' ? from.content.childNodes : from.childNodes; Array.from(sourceNodes).forEach(child => target.appendChild(child)); if (from.tagName && Component.exists(from.tagName)) _makeComponent(from.tagName, to, scanObj, exists); } // Slots may be declared inside a rendering template (for example `$each`). // `querySelectorAll` does not cross a