Reference Source

coral-spectrum/coral-component-slider/src/scripts/RangedSlider.js

/**
 * Copyright 2019 Adobe. All rights reserved.
 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. You may obtain a copy
 * of the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under
 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
 * OF ANY KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */

import Slider from './Slider';
import range from '../templates/range';
import {commons, transform} from '../../../coral-utils';

/**
 @class Coral.RangedSlider
 @classdesc A Ranged Slider
 @htmltag coral-rangedslider
 @extends {Slider}
 */
class RangedSlider extends Slider {
  /**
   Ranged sliders are always filled.

   @type {Boolean}
   @default true
   @htmlattribute filled
   @htmlattributereflected
   */
  get filled() {
    return true;
  }

  set filled(value) {
    if (!transform.booleanAttr(value)) {
      commons._log('warn', 'Coral.RangedSlider: filled can not be set to false.');
    }
  }

  /**
   This field's current value.
   @type {String}
   @default ""
   @htmlattribute value
   */
  get value() {
    return this.startValue;
  }

  set value(value) {
    this.startValue = value;
  }

  /**
   The starting value of the range.

   @type {String}
   @default '1'
   @emits {change}
   @htmlattribute startValue
   */
  get startValue() {
    return this.values[0] || '1';
  }

  set startValue(value) {
    // Snap value to step
    value = String(this._snapValueToStep(transform.number(value), this.min, this.max, this.step));

    const values = this.values;
    values[0] = value;
    this.values = values;

    // in order to keep the reset value in sync, we need to handle the "startvalue" attribute of the inner input
    const input = this._elements.inputs[0];
    const valueAttribute = this.getAttribute('startvalue') || this.getAttribute('value');
    input[valueAttribute ? 'setAttribute' : 'removeAttribute']('value', valueAttribute);
  }

  /**
   The ending value of the range.

   @type {String}
   @default '100'
   @emits {change}
   @htmlattribute endValue
   */
  get endValue() {
    return this.values[1] || '100';
  }

  set endValue(value) {
    // Snap value to step
    value = String(this._snapValueToStep(transform.number(value), this.min, this.max, this.step));

    const values = this.values;
    values[1] = value;
    this.values = values;

    // in order to keep the reset value in sync, we need to handle the "endvalue" attribute of the inner input
    const input = this._elements.inputs[1];
    const valueAttribute = this.getAttribute('endvalue');
    input[valueAttribute ? 'setAttribute' : 'removeAttribute']('value', valueAttribute);
  }

  /**
   The current values of the ranged slider.

   @type {Array.<String>}
   @default [{@link Coral.RangedSlider#startValue},{@link Coral.RangedSlider#endValue}]
   @emits {change}
   */
  get values() {
    return this._values;
  }

  set values(values) {
    this._values = values;
  }

  /** @private */
  _getHighestValue() {
    return Math.max.apply(null, this.values);
  }

  /** @private */
  _getLowestValue() {
    return Math.min.apply(null, this.values);
  }

  /** @override */
  _updateValue(handle, val) {
    const idx = this._elements.handles.indexOf(handle);

    if (idx === 0) {
      if (val > parseFloat(this.values[1])) {
        val = this.values[1];
      }
      this._elements.rightInput.min = val;
      this._elements.rightHandle.setAttribute('aria-valuemin', val);
    } else {
      if (val < parseFloat(this.values[0])) {
        val = this.values[0];
      }
      this._elements.leftInput.max = val;
      this._elements.leftHandle.setAttribute('aria-valuemax', val);
    }

    const resValue = [this.values[0], this.values[1]];
    resValue[idx] = val;

    const oldValues = this.values;
    this.values = resValue;
    const newValues = this.values;

    if (oldValues.join(':') !== newValues.join(':')) {
      this.trigger('change');
    }
  }

  /** @override */
  _getTemplate() {
    return range;
  }

  /**
   Inherited from {@link BaseFormField#clear}.
   */
  clear() {
    this.startValue = this.min;
    this.endValue = this.max;
  }

  /**
   Inherited from {@link BaseFormField#reset}.
   */
  reset() {
    // since the 'value' property is not reflected, form components use it to restore the initial value. When a
    // component has support for values, this method needs to be overwritten
    const initialStartValue = this.getAttribute('startvalue') || this.getAttribute('value');
    const initialEndValue = this.getAttribute('endvalue');

    this.startValue = transform.string(initialStartValue);
    this.endValue = transform.string(initialEndValue);
  }

  static get _attributePropertyMap() {
    return commons.extend(super._attributePropertyMap, {
      startvalue: 'startValue',
      endvalue: 'endValue'
    });
  }

  /** @ignore */
  static get observedAttributes() {
    return super.observedAttributes.concat([
      'startvalue',
      'endvalue'
    ]);
  }

  /** @ignore */
  render() {
    super.render();

    this.classList.add('_coral-Slider--range');

    // Set filled attribute by default
    this.setAttribute('filled', '');
  }
}

export default RangedSlider;