Reference Source

coral-spectrum/coral-component-colorinput/src/scripts/ColorInputColorProperties.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 {BaseComponent} from '../../../coral-base-component';
import BaseColorInputAbstractSubview from './BaseColorInputAbstractSubview';
import Color from './Color';
import '../../../coral-component-button';
import '../../../coral-component-textfield';
import './ColorInputSlider';
import propertiesSubview from '../templates/colorProperties';
import {commons, i18n} from '../../../coral-utils';
import {Decorator} from '../../../coral-decorator';

const CLASSNAME = '_coral-ColorInput-colorProperties';

/**
 @class Coral.ColorInput.ColorProperties
 @classdesc A ColorInput Color properties component
 @htmltag coral-colorinput-colorproperties
 @extends {HTMLElement}
 @extends {BaseComponent}
 @extends {BaseColorInputAbstractSubview}
 */
const ColorInputColorProperties = Decorator(class extends BaseColorInputAbstractSubview(BaseComponent(HTMLElement)) {
  /** @ignore */
  constructor() {
    super();

    this._delegateEvents(commons.extend(this._events, {
      'change [handle="redSlider"]': '_onRedSliderChange',
      'change [handle="greenSlider"]': '_onGreenSliderChange',
      'change [handle="blueSlider"]': '_onBlueSliderChange',
      'change [handle="alphaSlider"]': '_onAlphaSliderChange',
      'change ._coral-ColorInput-editHex': '_onChangeHex',
      'change ._coral-ColorInput-editRgba': '_onChangeRgba'
    }));

    // Templates
    this._elements = {};
    propertiesSubview.call(this._elements, {commons, i18n});
  }

  /** @ignore */
  _onColorInputChange() {
    const newColor = this._colorinput.valueAsColor;
    const colorPreview = this._elements.colorPreview2;
    let rgba;

    if (!newColor) {
      // update the colorPreview background color, state, and label
      colorPreview.setAttribute('aria-pressed', 'true');
      colorPreview.setAttribute('aria-label', i18n.get('Color not set'));

      // reset Hex value to empty
      this._elements.hexInput.value = '';
    } else {
      rgba = newColor.rgba;

      // update the colorPreview background color, state, and label
      colorPreview.style.backgroundColor = newColor.rgbValue;
      colorPreview.setAttribute('aria-pressed', 'false');
      colorPreview.setAttribute('aria-label', i18n.get('{value}, Color', {
        value: parseFloat(rgba.a) === 1 ? newColor.value : newColor.rgbaValue
      }));

      // update the Hex input value
      this._elements.hexInput.value = newColor.hexValue.substr(1);
    }

    const prefixes = ['red', 'green', 'blue', 'alpha'];
    const prefixesLength = prefixes.length;
    let prefix;
    let abbr;
    let isAlpha;
    let val;

    // update rgba slider and input values
    for (let i = 0 ; i < prefixesLength ; i++) {
      prefix = prefixes[i];
      abbr = prefix.substr(0, 1);
      isAlpha = i === prefixesLength - 1;

      // default slider and input value
      val = isAlpha ? 100 : 127;

      // with new color, get appropriate RGBA value
      if (newColor) {
        if (!rgba) {
          val = '';
        } else if (isAlpha) {
          val = parseInt(rgba[abbr] * 100, 10);
        } else {
          val = rgba[abbr];
        }
      }

      // update the slider and input values
      this._elements[`${prefix}Slider`].value = this._elements[`${prefix}Input`].value = val;
    }

    if (colorPreview === document.activeElement) {
      // force blur and focus on colorButton so that new color or state is announced
      colorPreview.blur();

      // delay focus by 100ms so that screen reader has time to adjust to label with updated color value
      window.setTimeout(() => {
        colorPreview.focus();
      }, 100);
    }
  }

  /** @ignore */
  _onRedSliderChange(event) {
    this._elements.redInput.value = this._elements.redSlider.value;
    this._onChangeRgba(event);
  }

  /** @ignore */
  _onGreenSliderChange(event) {
    this._elements.greenInput.value = this._elements.greenSlider.value;
    this._onChangeRgba(event);
  }

  /** @ignore */
  _onBlueSliderChange(event) {
    this._elements.blueInput.value = this._elements.blueSlider.value;
    this._onChangeRgba(event);
  }

  /** @ignore */
  _onAlphaSliderChange(event) {
    this._elements.alphaInput.value = this._elements.alphaSlider.value;
    this._onChangeRgba(event);
  }

  /** @ignore */
  _onChangeHex(event) {
    event.stopPropagation();

    // Value of hexInput field is without '#'.
    const value = `#${this._elements.hexInput.value}`;
    const color = new Color();
    color.value = value;

    if (color.hex === null) {
      // no valid color value
      this._elements.hexInput.value = '';
      // Save last valid color
      if (this._colorinput.valueAsColor !== null) {
        this.constructor._lastValidColor = this._colorinput.valueAsColor;
      }
      this._colorinput._setActiveColor(null);
    } else {
      this._colorinput._setActiveColor(color);
    }
  }

  /** @ignore */
  _onChangeRgba(event) {
    event.stopPropagation();

    const r = parseInt(this._elements.redInput.value, 10);
    const g = parseInt(this._elements.greenInput.value, 10);
    const b = parseInt(this._elements.blueInput.value, 10);
    const a = parseInt(this._elements.alphaInput.value, 10);

    let colorValid = true;
    if (isNaN(r) || r < 0 || r > 255) {
      colorValid = false;
      this._elements.redInput.value = '';
    }

    if (isNaN(g) || g < 0 || g > 255) {
      colorValid = false;
      this._elements.greenInput.value = '';
    }

    if (isNaN(b) || b < 0 || b > 255) {
      colorValid = false;
      this._elements.blueInput.value = '';
    }

    if (isNaN(a) || a < 0 || a > 100) {
      colorValid = false;
      this._elements.alphaInput.value = '';
    }

    if (colorValid) {
      const color = new Color();
      color.rgba = {
        r: r,
        g: g,
        b: b,
        a: a / 100
      };
      this._colorinput._setActiveColor(color);
    }
  }

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

    this.classList.add(CLASSNAME);

    // Support cloneNode
    const subview = this.querySelector('._coral-ColorInput-propertiesSubview');
    if (subview) {
      subview.remove();
    }

    this.appendChild(this._elements.propertiesSubview);
  }
});

export default ColorInputColorProperties;