widgets/toggle.js

/*
 * This file is part of AUX.
 *
 * AUX is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * AUX is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General
 * Public License along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

import { addClass } from './../utils/dom.js';
import { Button } from './button.js';
import { defineRender } from '../renderer.js';

function toggle(O) {
  if (this.userset('state', !O.state) === false) return;
  this.emit('toggled', O.state);
}
function pressStart(e) {
  const O = this.options;
  if (O.press || O.delay || (!O.toggle && !O.press)) toggle.call(this, O);
}
function pressEnd(e) {
  const O = this.options;
  if (
    (O.press && e.timeStamp > this.__time_stamp + O.press) ||
    (!O.press && !O.delay) ||
    (!O.toggle && !O.press)
  )
    toggle.call(this, O);
}
function pressCancel(e) {
  const O = this.options;
  if (O.press || (!O.toggle && !O.press)) toggle.call(this, O);
}

/**
 * A toggle button. The toggle button can either be pressed (which means that it will
 * switch its state as long as it is pressed) or toggled permanently. Its behavior is
 * controlled by the two options <code>press</code> and <code>toggle</code>.
 *
 * @class Toggle
 *
 * @extends Button
 *
 * @param {Object} [options={ }] - An object containing initial options.
 *
 * @property {Boolean} [options.toggle=true] - If true, the button is toggled on click.
 * @property {Integer} [options.press=0] - Controls press behavior. If <code>options.toggle</code>
 *   is <code>false</code> and this option is <code>0</code>, the toggle button will toggle until
 *   released. If <code>options.toggle</code> is true and this option is a positive integer, it is
 *   interpreted as a milliseconds timeout. When pressing a button longer than this timeout, it will
 *   be toggled until released, otherwise it will be toggled permanently.
 * @property {Integer} [options.delay=0] - Delay all actions for n milliseconds. While actions are
 *   delayed, the widget has class <code>.aux-delayed</code>. Use to force users to press the button
 *   for a certain amount of time before it actually gets toggled.
 * @property {String|Boolean} [options.icon_active=false] - An optional icon which is only displayed
 *   when the button toggle state is <code>true</code>. Please note that this option only works if `icon` is also set.
 * @property {String|Boolean} [options.label_active=false] - An optional label which is only displayed
 *   when the button toggle state is <code>true</code>. Please note that this option only works if `label` is also set.
 */
export class Toggle extends Button {
  static get _options() {
    return {
      label_active: 'string',
      icon_active: 'string',
      press: 'int',
      toggle: 'boolean',
    };
  }

  static get options() {
    return {
      label_active: false,
      icon_active: false,
      icon_inactive: false,
      press: 0,
      toggle: true,
    };
  }

  static get static_events() {
    return {
      press_start: pressStart,
      press_end: pressEnd,
      press_cancel: pressCancel,
    };
  }

  static get renderers() {
    return [
      defineRender(['state'], function (state) {
        // FIXME: the dependencies seem wrong here.
        const O = this.options;
        let tmp = (state && O.label_active) || O.label;
        if (tmp) this.label.set('label', tmp || '');
        tmp = (state && O.icon_active) || O.icon;
        if (tmp) this.icon.set('icon', tmp || '');
        this.getFocusTargets()[0].setAttribute('aria-pressed', state);
      }),
    ];
  }

  draw(O, element) {
    addClass(element, 'aux-toggle');
    super.draw(O, element);
  }

  /**
   * Toggle the button state.
   *
   * @method Toggle#toggle
   *
   * @emits Toggle#toggled
   */
  toggle() {
    toggle.call(this, this.options);
    /**
     * Is fired when the button was toggled.
     *
     * @event Toggle#toggled
     *
     * @param {boolean} state - The state of the {@link Toggle}.
     */
  }
}