// @ts-ignore: esbuild custom loader
import styles from './alpha.pcss';
import { CUSTOM_EVENT_COLOR_ALPHA_CHANGED, CUSTOM_EVENT_COLOR_HSV_CHANGED, CUSTOM_EVENT_COLOR_HUE_CHANGED, sendAlphaCustomEvent } from '../../domain/events-provider';
import { getAlphaColorBackground, parseColor } from '../../domain/color-provider';
import { TinyColor } from '@ctrl/tinycolor';
/*
Usage:
------
*/
class Alpha extends HTMLElement {
static get observedAttributes() {
return ['color'];
}
// this id attribute is used for custom events
private cid: string;
private $alpha: HTMLElement | null;
private $color: HTMLElement | null;
private $pointer: HTMLElement | null;
private alpha = 1; // [0, 1]
private hue = 0; // [0, 360]
private saturation = 0; // [0, 1]
private value = 0; // [0, 1]
constructor() {
super();
this.attachShadow({
mode: 'open', // 'closed', 'open',
});
this.onMouseDown = this.onMouseDown.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
this.onChange = this.onChange.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
this.hsvChanged = this.hsvChanged.bind(this);
this.hueChanged = this.hueChanged.bind(this);
this.alphaChanged = this.alphaChanged.bind(this);
}
render(sendEvent = true) {
if (this.$pointer) {
this.$pointer.style.left = `${this.alpha * 100}%`;
}
if (this.$color) {
const color = new TinyColor({
h: this.hue,
s: this.saturation,
v: this.value,
a: this.alpha,
});
this.$color.style.background = getAlphaColorBackground(color);
}
if (sendEvent) {
sendAlphaCustomEvent(this.cid, this.alpha);
}
}
// we need to handle both MouseEvent and TouchEvent --->
// eslint-disable-next-line
onChange(evt: any) {
if (!this.$alpha) return;
if (evt.preventDefault) {
evt.preventDefault();
}
const { width: boxWidth, left: boxLeft } = this.$alpha.getBoundingClientRect();
if (boxWidth === 0) return;
const mouseX = typeof evt.clientX === 'number' ? evt.clientX : evt.touches[0].clientX;
const left = Math.min(Math.max(0, mouseX - boxLeft), boxWidth);
const percent = Math.min(Math.max(0, (left * 100) / boxWidth), 100);
this.alpha = percent / 100;
this.render();
}
onKeyDown(evt: KeyboardEvent) {
this.$pointer?.focus();
switch (evt.key) {
case 'ArrowLeft': {
let percent = this.alpha * 100;
percent = Math.max(0, percent - 1);
this.alpha = percent / 100;
this.render();
break;
}
case 'ArrowRight': {
let percent = this.alpha * 100;
percent = Math.min(100, percent + 1);
this.alpha = percent / 100;
this.render();
break;
}
}
}
hsvChanged(evt: CustomEvent) {
if (!evt || !evt.detail || !evt.detail.cid) return;
// handle only current instance
if (evt.detail.cid !== this.cid) return;
this.saturation = evt.detail.h;
this.hue = evt.detail.s;
this.value = evt.detail.v;
this.render(false);
}
hueChanged(evt: CustomEvent) {
if (!evt || !evt.detail || !evt.detail.cid) return;
// handle only current instance
if (evt.detail.cid !== this.cid) return;
this.hue = evt.detail.h;
this.render(false);
}
alphaChanged(evt: CustomEvent) {
if (!evt || !evt.detail || !evt.detail.cid) return;
// handle only current instance
if (evt.detail.cid !== this.cid) return;
if (this.alpha !== evt.detail.a) {
this.alpha = evt.detail.a;
this.render();
}
}
onMouseDown(evt: MouseEvent) {
if (evt.preventDefault) {
evt.preventDefault();
}
this.onChange(evt);
window.addEventListener('mousemove', this.onChange);
window.addEventListener('mouseup', this.onMouseUp);
window.setTimeout(() => {
this.$pointer?.focus();
}, 0);
}
onMouseUp() {
window.removeEventListener('mousemove', this.onChange);
window.removeEventListener('mouseup', this.onChange);
}
/**
* when the custom element connected to DOM
*/
connectedCallback() {
if (!this.shadowRoot) return;
this.cid = this.getAttribute('cid') || '';
const color = parseColor(this.getAttribute('color'));
const hsv = color.toHsv();
this.alpha = hsv.a;
this.hue = hsv.h;
this.saturation = hsv.s;
this.value = hsv.v;
this.shadowRoot.innerHTML = `
`;
this.$alpha = this.shadowRoot.querySelector('.alpha');
this.$color = this.shadowRoot.querySelector('.color-bg');
this.$pointer = this.shadowRoot.querySelector('.pointer-box');
this.$alpha?.addEventListener('mousedown', this.onMouseDown);
this.$alpha?.addEventListener('mouseup', this.onMouseUp);
this.$alpha?.addEventListener('touchmove', this.onChange);
this.$alpha?.addEventListener('touchstart', this.onChange);
this.$pointer?.addEventListener('keydown', this.onKeyDown);
document.addEventListener(CUSTOM_EVENT_COLOR_HSV_CHANGED, this.hsvChanged);
document.addEventListener(CUSTOM_EVENT_COLOR_HUE_CHANGED, this.hueChanged);
document.addEventListener(CUSTOM_EVENT_COLOR_ALPHA_CHANGED, this.alphaChanged);
}
/**
* when the custom element disconnected from DOM
*/
disconnectedCallback() {
this.$alpha?.removeEventListener('mousedown', this.onMouseDown);
this.$alpha?.removeEventListener('mouseup', this.onMouseUp);
this.$alpha?.removeEventListener('touchmove', this.onChange);
this.$alpha?.removeEventListener('touchstart', this.onChange);
this.$pointer?.removeEventListener('keydown', this.onKeyDown);
document.removeEventListener(CUSTOM_EVENT_COLOR_HSV_CHANGED, this.hsvChanged);
document.removeEventListener(CUSTOM_EVENT_COLOR_HUE_CHANGED, this.hueChanged);
document.removeEventListener(CUSTOM_EVENT_COLOR_ALPHA_CHANGED, this.alphaChanged);
}
/**
* when attributes change
*/
attributeChangedCallback(_attrName: string, _oldVal: string, newVal: string) {
const color = parseColor(newVal);
const hsv = color.toHsv();
this.alpha = hsv.a;
this.hue = hsv.h;
this.saturation = hsv.s;
this.value = hsv.v;
this.render();
}
}
export default Alpha;