text: /** @module Controller */
  
  /**
   * Base class for all controllers.
   */
  export default class Controller {
  
  	constructor( parent, object, property, className, widgetTag = 'div' ) {
  
  		/**
  		 * The GUI that contains this controller.
  		 * @type {GUI}
  		 */
  		this.parent = parent;
  
  		/**
  		 * The object this controller will modify.
  		 * @type {object}
  		 */
  		this.object = object;
  
  		/**
  		 * The name of the property to control.
  		 * @type {string}
  		 */
  		this.property = property;
  
  		/**
  		 * Used to determine if the controller is disabled.
  		 * Use `controller.disable( true|false )` to modify this value
  		 * @type {boolean}
  		 */
  		this._disabled = false;
  
  		/**
  		 * The value of `object[ property ]` when the controller was created.
  		 * @type {any}
  		 */
  		this.initialValue = this.getValue();
  
  		/**
  		 * The outermost container DOM element for this controller.
  		 * @type {HTMLElement}
  		 */
  		this.domElement = document.createElement( 'label' );
  		this.domElement.classList.add( 'controller' );
  		this.domElement.classList.add( className );
  
  		/**
  		 * The DOM element that contains the controller's name.
  		 * @type {HTMLElement}
  		 */
  		this.$name = document.createElement( 'div' );
  		this.$name.classList.add( 'name' );
  
  		Controller.nextNameID = Controller.nextNameID || 0;
  		this.$name.id = `lil-gui-name-${++Controller.nextNameID}`;
  
  		/**
  		 * The DOM element that contains the controller's "widget" (which differs by controller type).
  		 * @type {HTMLElement}
  		 */
  		this.$widget = document.createElement( widgetTag );
  		this.$widget.classList.add( 'widget' );
  
  		/**
  		 * The DOM element that receives the disabled attribute when using disable()
  		 * @type {HTMLElement}
  		 */
  		this.$disable = this.$widget;
  
  		this.domElement.appendChild( this.$name );
  		this.domElement.appendChild( this.$widget );
  
  		this.parent.children.push( this );
  		this.parent.controllers.push( this );
  
  		this.parent.$children.appendChild( this.domElement );
  
  		this._listenCallback = this._listenCallback.bind( this );
  
  		this.name( property );
  
  	}
  
  	/**
  	 * Sets the name of the controller and its label in the GUI.
  	 * @param {string} name
  	 * @returns {this}
  	 */
  	name( name ) {
  		/**
  		 * The controller's name. Use `controller.name( 'Name' )` to modify this value.
  		 * @type {string}
  		 */
  		this._name = name;
  		this.$name.innerHTML = name;
  		return this;
  	}
  
  	/**
  	 * Pass a function to be called whenever the value is modified by this controller.
  	 * The function receives the new value as its first parameter. The value of `this` will be the
  	 * controller.
  	 * @param {Function} callback
  	 * @returns {this}
  	 * @example
  	 * const controller = gui.add( object, 'property' );
  	 *
  	 * controller.onChange( function( v ) {
  	 * 	console.log( 'The value is now ' + v );
  	 * 	console.assert( this === controller );
  	 * } );
  	 */
  	onChange( callback ) {
  		/**
  		 * Used to access the function bound to `onChange` events. Don't modify this value directly.
  		 * Use the `controller.onChange( callback )` method instead.
  		 * @type {Function}
  		 */
  		this._onChange = callback;
  		return this;
  	}
  
  	/**
  	 * Calls the onChange methods of this controller and its parent GUI.
  	 * @protected
  	 */
  	_callOnChange() {
  
  		this.parent._callOnChange( this );
  
  		if ( this._onChange !== undefined ) {
  			this._onChange.call( this, this.getValue() );
  		}
  
  		this._changed = true;
  
  	}
  
  	/**
  	 * Pass a function to be called after this controller has been modified and loses focus.
  	 * @param {Function} callback
  	 * @returns {this}
  	 * @example
  	 * const controller = gui.add( object, 'property' );
  	 *
  	 * controller.onFinishChange( function( v ) {
  	 * 	console.log( 'Changes complete: ' + v );
  	 * 	console.assert( this === controller );
  	 * } );
  	 */
  	onFinishChange( callback ) {
  		/**
  		 * Used to access the function bound to `onFinishChange` events. Don't modify this value
  		 * directly. Use the `controller.onFinishChange( callback )` method instead.
  		 * @type {Function}
  		 */
  		this._onFinishChange = callback;
  		return this;
  	}
  
  	/**
  	 * Should be called by Controller when its widgets lose focus.
  	 * @protected
  	 */
  	_callOnFinishChange() {
  
  		if ( this._changed ) {
  
  			this.parent._callOnFinishChange( this );
  
  			if ( this._onFinishChange !== undefined ) {
  				this._onFinishChange.call( this, this.getValue() );
  			}
  
  		}
  
  		this._changed = false;
  
  	}
  
  	/**
  	 * Sets the controller back to its initial value.
  	 * @returns {this}
  	 */
  	reset() {
  		this.setValue( this.initialValue );
  		this._callOnFinishChange();
  		return this;
  	}
  
  	/**
  	 * Enables this controller.
  	 * @param {boolean} enabled
  	 * @returns {this}
  	 * @example
  	 * controller.enable();
  	 * controller.enable( false ); // disable
  	 * controller.enable( controller._disabled ); // toggle
  	 */
  	enable( enabled = true ) {
  		return this.disable( !enabled );
  	}
  
  	/**
  	 * Disables this controller.
  	 * @param {boolean} disabled
  	 * @returns {this}
  	 * @example
  	 * controller.disable();
  	 * controller.disable( false ); // enable
  	 * controller.disable( !controller._disabled ); // toggle
  	 */
  	disable( disabled = true ) {
  
  		if ( disabled === this._disabled ) return this;
  
  		this._disabled = disabled;
  
  		this.domElement.classList.toggle( 'disabled', disabled );
  		this.$disable.toggleAttribute( 'disabled', disabled );
  
  		return this;
  
  	}
  
  	/**
  	 * Destroys this controller and replaces it with a new option controller. Provided as a more
  	 * descriptive syntax for `gui.add`, but primarily for compatibility with dat.gui.
  	 *
  	 * Use caution, as this method will destroy old references to this controller. It will also
  	 * change controller order if called out of sequence, moving the option controller to the end of
  	 * the GUI.
  	 * @example
  	 * // safe usage
  	 *
  	 * gui.add( object1, 'property' ).options( [ 'a', 'b', 'c' ] );
  	 * gui.add( object2, 'property' );
  	 *
  	 * // danger
  	 *
  	 * const c = gui.add( object1, 'property' );
  	 * gui.add( object2, 'property' );
  	 *
  	 * c.options( [ 'a', 'b', 'c' ] );
  	 * // controller is now at the end of the GUI even though it was added first
  	 *
  	 * assert( c.parent.children.indexOf( c ) === -1 )
  	 * // c references a controller that no longer exists
  	 *
  	 * @param {object|Array} options
  	 * @returns {Controller}
  	 */
  	options( options ) {
  		const controller = this.parent.add( this.object, this.property, options );
  		controller.name( this._name );
  		this.destroy();
  		return controller;
  	}
  
  	/**
  	 * Sets the minimum value. Only works on number controllers.
  	 * @param {number} min
  	 * @returns {this}
  	 */
  	// eslint-disable-next-line no-unused-vars
  	min( min ) {
  		return this;
  	}
  
  	/**
  	 * Sets the maximum value. Only works on number controllers.
  	 * @param {number} max
  	 * @returns {this}
  	 */
  	// eslint-disable-next-line no-unused-vars
  	max( max ) {
  		return this;
  	}
  
  	/**
  	 * Sets the step. Only works on number controllers.
  	 * @param {number} step
  	 * @returns {this}
  	 */
  	// eslint-disable-next-line no-unused-vars
  	step( step ) {
  		return this;
  	}
  
  	/**
  	 * Calls `updateDisplay()` every animation frame. Pass `false` to stop listening.
  	 * @param {boolean} listen
  	 * @returns {this}
  	 */
  	listen( listen = true ) {
  
  		/**
  		 * Used to determine if the controller is currently listening. Don't modify this value
  		 * directly. Use the `controller.listen( true|false )` method instead.
  		 * @type {boolean}
  		 */
  		this._listening = listen;
  
  		if ( this._listenCallbackID !== undefined ) {
  			cancelAnimationFrame( this._listenCallbackID );
  			this._listenCallbackID = undefined;
  		}
  
  		if ( this._listening ) {
  			this._listenCallback();
  		}
  
  		return this;
  
  	}
  
  	_listenCallback() {
  
  		this._listenCallbackID = requestAnimationFrame( this._listenCallback );
  
  		// To prevent framerate loss, make sure the value has changed before updating the display.
  		// Note: save() is used here instead of getValue() only because of ColorController. The !== operator
  		// won't work for color objects or arrays, but ColorController.save() always returns a string.
  
  		const curValue = this.save();
  
  		if ( curValue !== this._listenPrevValue ) {
  			this.updateDisplay();
  		}
  
  		this._listenPrevValue = curValue;
  
  	}
  
  	/**
  	 * Returns `object[ property ]`.
  	 * @returns {any}
  	 */
  	getValue() {
  		return this.object[ this.property ];
  	}
  
  	/**
  	 * Sets the value of `object[ property ]`, invokes any `onChange` handlers and updates the display.
  	 * @param {any} value
  	 * @returns {this}
  	 */
  	setValue( value ) {
  		this.object[ this.property ] = value;
  		this._callOnChange();
  		this.updateDisplay();
  		return this;
  	}
  
  	/**
  	 * Updates the display to keep it in sync with the current value. Useful for updating your
  	 * controllers when their values have been modified outside of the GUI.
  	 * @returns {this}
  	 */
  	updateDisplay() {
  		return this;
  	}
  
  	load( value ) {
  		this.setValue( value );
  		this._callOnFinishChange();
  		return this;
  	}
  
  	save() {
  		return this.getValue();
  	}
  
  	/**
  	 * Destroys this controller and removes it from the parent GUI.
  	 */
  	destroy() {
  		this.listen( false );
  		this.parent.children.splice( this.parent.children.indexOf( this ), 1 );
  		this.parent.controllers.splice( this.parent.controllers.indexOf( this ), 1 );
  		this.parent.$children.removeChild( this.domElement );
  	}
  
  }
  