UNPKG

@squirrel-forge/ui-core

Version:

A collection of interface, classes, functions and abstracts made for the browser and babel compatible.

229 lines (199 loc) 5.76 kB
/** * Requires */ import { Exception, isPojo, mergeObject } from '@squirrel-forge/ui-util'; /** * Component states exception * @class * @extends Exception */ class ComponentStatesException extends Exception {} /** * @typedef {Object|true} ComponentStateDefinition - Component state definition * @property {undefined|null|boolean} global - By default state is global, set to: false for non global states * @property {undefined|null|string} classOn - CSS class to set when active * @property {undefined|null|string} classOff - CSS class to set when inactive * @property {undefined|null|Array<string>} unsets - Unset given states when this one becomes active */ /** * Component states * @class */ export class ComponentStates { /** * Component element * @private * @property * @type {null} */ #component = null; /** * Component states * @private * @property * @type {null} */ #states = null; /** * States by name * @private * @property * @type {Object} */ #named = {}; /** * Global state * @private * @property * @type {null|string} */ #global = null; /** * Global state attribute * @private * @property * @type {string} */ #attribute = 'data-state'; /** * Constructor * @constructor * @param {UiComponent} component - Component element * @param {null|Object} states - States map */ constructor( component, states = null ) { this.#component = component; if ( states !== null && !isPojo( states ) ) { throw new ComponentStatesException( 'Argument states must be null or a plain Object' ); } else if ( states === null ) { states = { initialized : { classOn : 'initialized' } }; } this.#states = states; } /** * Global state getter * @public * @return {null|string} - Global state */ get global() { return this.#global; } /** * Direct states access * @public * @return {Object} - States data object */ get exposed() { return this.#states; } /** * Extend states * @public * @param {Object} states - States map * @return {void} */ extend( states ) { mergeObject( this.#states, states, true, true, true, false ); } /** * State active * @public * @param {string} name - State name * @return {boolean} - State is active */ is( name ) { return !!this.#named[ name ]; } /** * State defined * @public * @param {string} name - State name * @return {boolean} - State exists */ has( name ) { return !!this.#states[ name ]; } /** * Get state info * @public * @param {string} name - State name * @return {Object} - State info object */ get( name ) { const state = this.#states[ name ]; if ( !state ) throw new ComponentStatesException( 'Unknown state: ' + name ); return state; } /** * Set state by name * @public * @param {string} name - State name * @return {void} */ set( name ) { const state = this.get( name ); const is_global = state === true || state.global !== false; let is_global_changed = false, to = null; const from = this.#global; if ( is_global ) { // Do not set globals unless changed if ( name !== from ) { is_global_changed = true; to = name; } else { // Skip along and ignore the set command return; } } // Complex state options if ( state !== true ) { // Unset any states if ( state.unsets instanceof Array ) { for ( let i = 0; i < state.unsets.length; i++ ) { this.unset( state.unsets[ i ] ); } } // Set/unset any class states if ( state.classOn ) this.#component.dom.classList.add( state.classOn ); if ( state.classOff ) this.#component.dom.classList.remove( state.classOff ); } // Default and global states are both global if ( is_global ) { this.#component.dom.setAttribute( this.#attribute, name ); this.#global = name; } // Set individual state this.#named[ name ] = true; // Dispatch corresponding event if ( is_global_changed ) { this.#component.dispatchEvent( 'state.changed', { from, to } ); } else { this.#component.dispatchEvent( 'state.set', { set : name } ); } } /** * Set state by name * @public * @param {string} name - State name * @return {void} */ unset( name ) { if ( !this.#named[ name ] ) return; const state = this.get( name ); const is_global = state === true || state.global !== false; // Complex state options if ( state !== true ) { // Set/unset any class states if ( state.classOn ) this.#component.dom.classList.remove( state.classOn ); if ( state.classOff ) this.#component.dom.classList.add( state.classOff ); } // Default and global states are both global if ( is_global ) { this.#component.dom.setAttribute( this.#attribute, 'null' ); this.#global = null; } // Set individual state this.#named[ name ] = false; this.#component.dispatchEvent( 'state.unset', { unset : name } ); } }