SolalDR/data-gui

View on GitHub
src/extras/color-controller.ts

Summary

Maintainability
B
4 hrs
Test Coverage
import { controllers } from '../core/controllers';
import { html, customElement, css, query } from 'lit-element'
import { BaseController, ControllerConstructor } from '@/core/controller'
import { NumberController } from '@/controllers/number-controller';
import { Color, isColor } from '@/helpers/color-helper'
import '@/components/elements/input'

/**
 * @category Constructor
 */
export interface ColorControllerConstructor extends ControllerConstructor {
  name?: string
}

/**
 * ColorController is the controller used to handle all different kind of colors.<br>
 * It can take a lot of different format (HSL, RGB, HEX) and types (string, object, number)<br>
 * This controller is build to keep the same input and output formats. <br>
 * ## How to use
 * ``` javascript
 * const target = {
 *   colorHSL: { h: 1, s: 1, l: 1 },
 *   colorHSLA: { h: 1, s: 1, l: 1, a: 0.4 },
 *   colorHEX: "#FFFFFF",
 *   colorHEX2: 0xFFFFFF,
 *   colorRGB: {r: 1, g: 244, b: 1},
 *   colorRGBA: {r: 1, g: 244, b: 1, a: 0.5},
 *   colorRGBA: "rgba(255, 244, 122, 0.5)"
 * }
 *
 * // Method 1
 * group.add('colorHSL', target, {
 *   type: "color"
 * }).on('update', (hslObject) => {
 *   console.log(hslObject.h)
 * })
 *
 * // Method 2
 * group.color('colorRGBA', target).on('update', (rgbaString) => {
 *   myElement.style.color = rgbaString
 * })
 * ```
 *
 * For more information about options or events see {@link BaseController}
 * 
 * @Category Extras controller
 */
@customElement('gui-color-controller')
export class ColorController extends BaseController {
  /**
   * @ignore
   */
  @query('input') input: HTMLInputElement
  private color: Color
  private inputValue: string
  private labelValue: string

  constructor(parameters: ColorControllerConstructor) {
    super(parameters)
  }

  /**
   * @ignore
   */
  firstUpdated() {
    this.input.setAttribute('value', this.color.getHexString())
  }

  /**
   * @ignore
   */
  connectedCallback() {
    this.color = new Color(this.value)
    this.updateInputValue()
    super.connectedCallback()
  }

  protected validate(value: string) {
    return value
  }

  /**
   * @ignore
   */
  static isCompatible(value: unknown, _: string, __: any): boolean {
    return isColor(value)
  }

  protected updateInputValue() {
    const hexValue = this.color.getHexString()
    this.labelValue = this.color.getRgbString()
    if (hexValue.length > 6) {
      this.inputValue = hexValue.substring(0, 7)
    } else {
      this.inputValue = hexValue
    }
  }

  protected onInput(event) {
    this.color.setStyle(event.target.value)
    this.set(this.color.format())
    this.updateInputValue()
    super.onInput(event)
  }

  protected onInputChannel(channel, value) {
    if (channel === 'a') {
      this.color.setAlpha(Number(value))
    } else {
      this.color[channel] = Number(value) / 255
    }
  }

  protected onChangeChannel(channel, value) {
    if (channel === 'a') {
      this.color.setAlpha(Number(value))
    } else {
      this.color[channel] = Number(value) / 255
    }
    this.set(this.color.format())
    this.updateInputValue()
  }

  /**
   * @ignore
   */
  render() {
    const alpha = this.color.alphaEnabled
      ? html`
          <gui-input
            step="0.01"
            class="input-channel input-channel--alpha"
            .value=${String(this.color.a)}
            label="A"
            type="number"
            @change=${event => this.onChangeChannel('a', event.detail)}
            @input=${event => this.onInputChannel('a', event.detail)}
          ></gui-input>
        `
      : ''

    return html`
      <div>
        <label>${this.name}</label>
        <div class="input-container right" .style=${`--value: ${this.labelValue}`}>
          <gui-input
            class="input-channel"
            type="number"
            .value=${String(~~(this.color.r * 255))}
            label="R"
            @change=${event => this.onChangeChannel('r', event.detail)}
            @input=${event => this.onInputChannel('r', event.detail)}
          ></gui-input>
          <gui-input
            class="input-channel"
            type="number"
            .value=${String(~~(this.color.g * 255))}
            label="G"
            @change=${event => this.onChangeChannel('g', event.detail)}
            @input=${event => this.onInputChannel('g', event.detail)}
          ></gui-input>
          <gui-input
            class="input-channel"
            type="number"
            .value=${String(~~(this.color.b * 255))}
            label="B"
            @change=${event => this.onChangeChannel('b', event.detail)}
            @input=${event => this.onInputChannel('b', event.detail)}
          ></gui-input>
          ${alpha}

          <div class="input-color">
            <span></span>
            <input
              type="color"
              .value=${this.inputValue}
              @input=${event => {
                this.onInput(event)
              }}
              @change=${event => {
                this.onChange(event)
              }}
            />
          </div>
        </div>
      </div>
    `
  }

  /**
   * @ignore
   */
  public static styles = css`
    /*minify*/
    ${BaseController.styles}

    :host {
      min-height: 45px;
    }

    .input-color {
      min-width: 100%;
      margin-top: 2px;
    }

    .input-color span {
      position: absolute;
      width: 100%;
      height: 20px;
      border: 1px solid var(--color-bg-secondary);
      box-sizing: border-box;
      background-color: var(--value);
      display: block;
      border-radius: var(--radius);
      box-shadow: var(--shadow);
    }

    .input-color input {
      --value-attr: attr('value');
      width: 100%;
      /* width: 20px; */
      border: 0;
      border-radius: var(--radius);
      padding: 0;
      opacity: 0;
      height: 20px;
    }

    .input-container {
      justify-content: flex-end;
      flex-direction: row;
      flex-wrap: wrap;
    }

    .input-channel:not(:first-child) {
      margin-left: 5px;
    }
  `
}


controllers.splice(
  controllers.indexOf(NumberController) + 1, 0, ColorController
)