UNPKG

@uistate/core

Version:

Revolutionary DOM-based state management using CSS custom properties - zero dependencies, potential O(1) updates

182 lines (145 loc) 5.8 kB
import StateSerializer from './stateSerializer.js'; const createCssState = (initialState = {}, serializer = StateSerializer) => { const state = { _sheet: null, _observers: new Map(), _serializer: serializer, _specialHandlers: {}, _eventHandlers: new Map(), init(serializerConfig) { if (!this._sheet) { const style = document.createElement('style'); document.head.appendChild(style); this._sheet = style.sheet; this._addRule(':root {}'); } if (serializerConfig && typeof serializerConfig === 'object') { this._serializer.configure(serializerConfig); } if (initialState && typeof initialState === 'object') { Object.entries(initialState).forEach(([key, value]) => { this.setState(key, value); }); } return this; }, setState(key, value) { const cssValue = this._serializer.serialize(key, value); document.documentElement.style.setProperty(`--${key}`, cssValue); this._serializer.applyToAttributes(key, value); this._notifyObservers(key, value); return value; }, setStates(stateObject) { Object.entries(stateObject).forEach(([key, value]) => { this.setState(key, value); }); return this; }, getState(key) { const value = getComputedStyle(document.documentElement).getPropertyValue(`--${key}`).trim(); if (!value) return ''; return this._serializer.deserialize(key, value); }, observe(key, callback) { if (!this._observers.has(key)) { this._observers.set(key, new Set()); } this._observers.get(key).add(callback); return () => { const observers = this._observers.get(key); if (observers) { observers.delete(callback); } }; }, _notifyObservers(key, value) { const observers = this._observers.get(key); if (observers) { observers.forEach(cb => cb(value)); } }, registerSpecialHandler(stateKey, handlerFn) { this._specialHandlers[stateKey] = handlerFn; return this; }, registerEventBinding(eventType, handler) { this._eventHandlers.set(eventType, handler); return this; }, setupObservers(container = document) { container.querySelectorAll('[data-observe]:not([data-observing])').forEach(el => { const stateKey = el.dataset.observe; this.observe(stateKey, (value) => { if (this._specialHandlers[stateKey]?.observe) { this._specialHandlers[stateKey].observe(value, el); } else if (stateKey.endsWith('-state') && el.hasAttribute('data-state')) { el.dataset.state = value; } else { el.textContent = value; } }); const initialValue = this.getState(stateKey); if (this._specialHandlers[stateKey]?.observe) { this._specialHandlers[stateKey].observe(initialValue, el); } else if (stateKey.endsWith('-state') && el.hasAttribute('data-state')) { el.dataset.state = initialValue; } else { el.textContent = initialValue; } el.dataset.observing = 'true'; }); return this; }, defaultClickHandler(e) { const target = e.target.closest('[data-state-action]'); if (!target) return; const stateAction = target.dataset.stateAction; if (!stateAction) return; if (this._specialHandlers[stateAction]?.action) { this._specialHandlers[stateAction].action(target); return; } if (target.dataset.stateValue !== undefined) { const valueToSet = target.dataset.stateValue; this.setState(stateAction, valueToSet); } }, defaultInputHandler(e) { const target = e.target; const stateAction = target.dataset.stateAction; if (!stateAction) return; if (this._specialHandlers[stateAction]?.action) { this._specialHandlers[stateAction].action(target); } }, setupStateActions(container = document) { this._eventHandlers.forEach((handler, eventType) => { container.addEventListener(eventType, handler); }); if (this._eventHandlers.size === 0) { container.addEventListener('click', (e) => this.defaultClickHandler(e)); container.addEventListener('input', (e) => this.defaultInputHandler(e)); } return this; }, _addRule(rule) { if (this._sheet) { this._sheet.insertRule(rule, this._sheet.cssRules.length); } }, configureSerializer(config) { if (this._serializer.configure) { this._serializer.configure(config); } return this; }, destroy() { this._observers.clear(); } }; return state.init(); }; const UIstate = createCssState(); export { createCssState }; export default UIstate;