UNPKG

@uistate/core

Version:

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

173 lines (141 loc) 5.85 kB
function escapeCssValue(value) { if (typeof value !== 'string') return value; return value.replace(/[^\x20-\x7E]|[!;{}:()[\]/@,'"]/g, function(char) { const hex = char.charCodeAt(0).toString(16); return '\\' + hex + ' '; }); } function unescapeCssValue(value) { if (typeof value !== 'string') return value; if (!value.includes('\\')) return value; return value.replace(/\\([0-9a-f]{1,6})\s?/gi, function(match, hex) { return String.fromCharCode(parseInt(hex, 16)); }); } function createSerializer(config = {}) { const defaultConfig = { mode: 'hybrid', debug: false, complexThreshold: 3, preserveTypes: true, }; const options = { ...defaultConfig, ...config }; const serializer = { config: options, configure(newConfig) { Object.assign(this.config, newConfig); if (this.config.debug) { console.log('StateSerializer config updated:', this.config); } }, serialize(key, value) { if (value === null || value === undefined) { return ''; } const valueType = typeof value; const isComplex = valueType === 'object' && (Array.isArray(value) || (Object.keys(value).length >= this.config.complexThreshold)); if (this.config.mode === 'escape' || (this.config.mode === 'hybrid' && !isComplex)) { if (valueType === 'string') { return escapeCssValue(value); } else if (valueType === 'object') { const jsonStr = JSON.stringify(value); return escapeCssValue(jsonStr); } else { return String(value); } } else { return JSON.stringify(value); } }, deserialize(key, value) { if (!value) return ''; if (this.config.mode !== 'escape' && ((value.startsWith('{') && value.endsWith('}')) || (value.startsWith('[') && value.endsWith(']')))) { try { return JSON.parse(value); } catch (e) { if (this.config.debug) { console.warn(`Failed to parse JSON for key "${key}":`, value); } } } const unescaped = unescapeCssValue(value); if (this.config.mode !== 'escape' && ((unescaped.startsWith('{') && unescaped.endsWith('}')) || (unescaped.startsWith('[') && unescaped.endsWith(']')))) { try { return JSON.parse(unescaped); } catch (e) { } } return unescaped; }, serializeForAttribute(key, value) { if (value === null || value === undefined) return null; if (typeof value === 'object') { return this.serialize(key, value); } return String(value); }, applyToAttributes(key, value, element = document.documentElement) { if (value === null || value === undefined) { element.removeAttribute(`data-${key}`); return; } if (typeof value === 'object') { element.setAttribute(`data-${key}`, this.serialize(key, value)); if (!Array.isArray(value)) { Object.entries(value).forEach(([propKey, propValue]) => { const attributeKey = `data-${key}-${propKey.toLowerCase()}`; if (propValue !== null && propValue !== undefined) { if (typeof propValue === 'object') { element.setAttribute( attributeKey, this.serialize(`${key}.${propKey}`, propValue) ); } else { element.setAttribute(attributeKey, propValue); } } else { element.removeAttribute(attributeKey); } }); } } else { element.setAttribute(`data-${key}`, value); } }, needsComplexSerialization(value) { return typeof value === 'object' && value !== null; }, setStateWithCss(uistate, path, value) { uistate.setState(path, value); const cssPath = path.replace(/\./g, '-'); const serialized = this.serialize(path, value); document.documentElement.style.setProperty(`--${cssPath}`, serialized); const segments = path.split('.'); if (segments.length === 1) { document.documentElement.dataset[path] = typeof value === 'object' ? JSON.stringify(value) : value; } return value; }, getStateFromCss(uistate, path) { const value = uistate.getState(path); if (value !== undefined) return value; const cssPath = path.replace(/\./g, '-'); const cssValue = getComputedStyle(document.documentElement) .getPropertyValue(`--${cssPath}`).trim(); return cssValue ? this.deserialize(path, cssValue) : undefined; } }; return serializer; } const StateSerializer = createSerializer(); export default StateSerializer; export { createSerializer, escapeCssValue, unescapeCssValue };