UNPKG

@domx/dataelement

Version:

A DataElement base class for handling data state changes

107 lines (90 loc) 3.11 kB
export { RootState }; interface StateMap { [key:string]: StateMap|undefined } interface Root { state:StateMap } declare global { interface Window { [stateProp]: StateMap; } } const stateProp = Symbol(); /** * Used to keep track of a global state tree * for data elements. */ class RootState { /** Initializes/resets the root state */ static init(state:StateMap) { RootState.current = state; } /** Returns the state at the given state path */ static get(path:string):object|null { const value = path.split(".").reduce((state:StateMap|undefined, prop:string) => state !== undefined ? state[prop] : undefined, RootState.current); return value === undefined ? null : value; } /** Sets the state at the given state path */ static set(path:string, value: object):void { setState(RootState.current, path, value); } /** Removes the state at the given state path */ static delete(path:string):void { setState(RootState.current, path, undefined); } /** * Creates a copy of the root state and sets the value at the state path. * Used for intermediate changes before committing. */ static draft(path:string, value: object):StateMap { const copy = JSON.parse(JSON.stringify(RootState.current)); const state = setState(copy, path, value); return state; } /** The current root state */ static get current() { if (!window[stateProp]) { window[stateProp] = {}; } return window[stateProp]; } private static set current(state:StateMap) { window[stateProp] = state; } static snapshot(name:string) { window.dispatchEvent(new CustomEvent("rootstate-snapshot", { detail: { name, state: RootState.current } })); } } /** Used for setting, deleting, and drafting state */ const setState = (state:StateMap, path:string, value:object|undefined):StateMap => { let currentState = state; const pathParts = path.split("."); let deleteElStatePath:string|null = null; pathParts.forEach((prop:string, index:number) => { if (index === pathParts.length - 1) { if (value === undefined) { delete currentState[prop]; // if empty, then delete that part as well if (Object.keys(currentState).length === 0) { deleteElStatePath = [...pathParts].splice(0, pathParts.length - 1).join("."); } } else { currentState[prop] = { ...value } as StateMap; } } else if (currentState[prop] === undefined) { currentState[prop] = {}; } currentState = currentState[prop] as StateMap; }); if (deleteElStatePath) { return setState(RootState.current, deleteElStatePath, undefined); } return state; };