@uistate/core
Version:
Revolutionary DOM-based state management using CSS custom properties - zero dependencies, potential O(1) updates
169 lines (136 loc) • 6.48 kB
JavaScript
const createTemplateManager = (stateManager) => {
const manager = {
handlers: {},
onAction(action, handler) {
this.handlers[action] = handler;
return this;
},
registerActions(actionsMap) {
Object.entries(actionsMap).forEach(([action, handler]) => {
if (typeof handler === 'function') {
this.onAction(action, handler);
} else if (typeof handler === 'object' && handler !== null) {
const { fn, extractId = true, idAttribute = 'id' } = handler;
if (typeof fn !== 'function') {
throw new Error(`Handler for action '${action}' must be a function`);
}
this.onAction(action, (e) => {
if (extractId) {
const target = e.target.closest('[data-action]');
const id = target.dataset[idAttribute] ||
target.dataset.actionId ||
target.dataset.cardId ||
target.dataset.itemId;
fn(id, e, target);
} else {
fn(e);
}
});
} else {
throw new Error(`Invalid handler for action '${action}'`);
}
});
return this;
},
attachDelegation(root = document.body) {
root.addEventListener('click', e => {
const target = e.target.closest('[data-action]');
if (!target) return;
const action = target.dataset.action;
if (!action) return;
const handler = this.handlers[action];
if (typeof handler === 'function') {
handler(e);
} else if (target.dataset.value !== undefined && stateManager) {
stateManager.setState(action, target.dataset.value);
}
});
return this;
},
renderTemplateFromCss(templateName, data = {}) {
const cssTemplate = getComputedStyle(document.documentElement)
.getPropertyValue(`--template-${templateName}`)
.trim()
.replace(/^['"]|['"]$/g, '');
if (!cssTemplate) throw new Error(`Template not found in CSS: --template-${templateName}`);
let html = cssTemplate;
Object.entries(data).forEach(([key, value]) => {
const regex = new RegExp(`{{${key}}}`, 'g');
html = html.replace(regex, value);
});
const temp = document.createElement('div');
temp.innerHTML = html;
return temp.firstElementChild;
},
mount(componentName, container) {
const tpl = document.getElementById(`${componentName}-template`);
if (!tpl) throw new Error(`Template not found: ${componentName}-template`);
const clone = tpl.content.cloneNode(true);
function resolvePlaceholders(fragment) {
Array.from(fragment.querySelectorAll('*')).forEach(el => {
const tag = el.tagName.toLowerCase();
if (tag.endsWith('-placeholder')) {
const name = tag.replace('-placeholder','');
const childTpl = document.getElementById(`${name}-template`);
if (!childTpl) throw new Error(`Template not found: ${name}-template`);
const childClone = childTpl.content.cloneNode(true);
resolvePlaceholders(childClone);
el.replaceWith(childClone);
}
});
}
resolvePlaceholders(clone);
container.appendChild(clone);
return clone.firstElementChild;
},
createComponent(name, renderFn, stateKeys = []) {
if (!stateManager) {
throw new Error('State manager is required for reactive components');
}
let tpl = document.getElementById(`${name}-template`);
if (!tpl) {
tpl = document.createElement('template');
tpl.id = `${name}-template`;
document.body.appendChild(tpl);
}
tpl.innerHTML = renderFn(stateManager);
if (stateKeys.length > 0) {
stateKeys.forEach(key => {
stateManager.observe(key, () => {
tpl.innerHTML = renderFn(stateManager);
});
});
}
return {
mount: (container) => this.mount(name, container)
};
},
applyClassesFromState(element, stateKey, options = {}) {
if (!element) return element;
const {
prefix = '',
clearExisting = false,
namespace = ''
} = typeof options === 'string' ? { prefix: options } : options;
const prefixPath = prefix ? `${prefix}-` : '';
const namespacePath = namespace ? `${namespace}-` : '';
const classString = getComputedStyle(document.documentElement)
.getPropertyValue(`--${namespacePath}${stateKey}-classes`)
.trim()
.replace(/^['"]|['"]$/g, '');
if (classString) {
if (clearExisting) {
element.className = '';
}
classString.split(' ').forEach(cls => {
if (cls) element.classList.add(cls);
});
}
return element;
}
};
return manager;
};
const TemplateManager = createTemplateManager();
export default createTemplateManager;
export { createTemplateManager, TemplateManager };