UNPKG

infamous

Version:

A CSS3D/WebGL UI library.

131 lines (98 loc) 5.25 kB
import Class from 'lowclass' import Mixin from '../../core/Mixin' export default Mixin(Base => // TODO This is here for now. Make it an extension to // element-behaviors so that it can be applied to any element // generically. Class('DefaultBehaviors').extends( Base, ({ Super }) => ({ static: { // override in subclasses defaultBehaviors: [], }, constructor(...args) { // Use the constructor return value, because we assume Base must be // a HTML element class, in which case this is currently unavoidable // because lowclass currently uses ES5-style constructors. This may // change when lowclass has `class` syntax option. const self = Super(this).constructor(...args) // If no geometry or material behavior is detected, add default ones. // // Deferring to a microtask doesn't work here, we must defer to a // macrotask with setTimeout, so we can detect if the element has // initial behaviors, otherwise the element's initial attributes // haven't been added yet (this is how HTML engines work, see // https://github.com/whatwg/dom/issues/522). // // TODO: If we use setTimeout (macrotask) deferral anywhere (like we do // here), and maybe even with microtask deferral (f.e. Promise), maybe // we should have a single place that initiate this deferral so that // everything in the engine can hook into it. Otherwise if different // call sites use setTimeout, logic will be firing at random and in // different order. setTimeout( () => self._setDefaultBehaviorsIfNeeded(), 0 ) return self }, _setDefaultBehaviorsIfNeeded() { let defaultBehaviors = this.constructor.defaultBehaviors // do nothing if there's no defaults if (!defaultBehaviors) return if (Object.keys(defaultBehaviors).length == 0) return const initialBehaviorNames = Array.from( this.behaviors.keys() ) // small optimization: if there are no initial behaviors and we // have default behaviors, just set the default behaviors. if ( initialBehaviorNames.length == 0 ) { // if not an array, then it's an object. if (! ( defaultBehaviors instanceof Array ) ) defaultBehaviors = Object.keys( defaultBehaviors ) this.setAttribute( 'has', this.getAttribute( 'has' ) + ` ${defaultBehaviors.join(' ')}`) } // otherwise detect which default behavior(s) to add else { let behaviorNamesToAdd = '' // if defaultBehaviors is an array, use default logic to add // behaviors that aren't already added. if (defaultBehaviors instanceof Array) { for (const defaultBehaviorName of defaultBehaviors) { let hasBehavior = false for ( const initialBehaviorName of initialBehaviorNames ) { if ( defaultBehaviorName == initialBehaviorName ) { hasBehavior = true break } } if (hasBehavior) continue else { // TODO programmatic API: //this.behaviors.add('box-geometry') // add a space in front of each name except the first if ( behaviorNamesToAdd ) behaviorNamesToAdd += ' ' behaviorNamesToAdd += defaultBehaviorName } } } // if defaultBehaviors is an object, then behaviors are added // based on conditions. else if (typeof defaultBehaviors == 'object') { const defaultBehaviorNames = Object.keys(defaultBehaviors) for (const defaultBehaviorName of defaultBehaviorNames) { const condition = defaultBehaviors[defaultBehaviorName] if ( (typeof condition == 'function' && condition( initialBehaviorNames ) ) || (typeof condition != 'function' && condition ) ) { // add a space in front of each name except the first if ( behaviorNamesToAdd ) behaviorNamesToAdd += ' ' behaviorNamesToAdd += defaultBehaviorName } } } // add the needed behaviors all at once. if (behaviorNamesToAdd) { let currentHasValue = this.getAttribute('has') if (currentHasValue) currentHasValue += ' ' this.setAttribute( 'has', currentHasValue + behaviorNamesToAdd ) } } }, })) )