@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
JavaScript
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;