Files
squoosh/src/components/Output/custom-els/RangeInput/index.ts
2018-10-02 11:41:07 +01:00

116 lines
3.2 KiB
TypeScript

import { bind } from '../../../../lib/util';
import './styles.css';
const RETARGETED_EVENTS = ['focus', 'blur'];
const UPDATE_EVENTS = ['input', 'change'];
const REFLECTED_PROPERTIES = ['name', 'min', 'max', 'step', 'value', 'disabled'];
const REFLECTED_ATTRIBUTES = ['name', 'min', 'max', 'step', 'value', 'disabled'];
class RangeInputElement extends HTMLElement {
private _input = document.createElement('input');
private _valueDisplayWrapper = document.createElement('div');
private _valueDisplay = document.createElement('span');
private _ignoreChange = false;
static get observedAttributes() {
return REFLECTED_ATTRIBUTES;
}
constructor() {
super();
this._input.type = 'range';
for (const event of RETARGETED_EVENTS) {
this._input.addEventListener(event, this._retargetEvent, true);
}
for (const event of UPDATE_EVENTS) {
this._input.addEventListener(event, this._update, true);
}
}
get labelPrecision(): string {
return this.getAttribute('label-precision') || '';
}
set labelPrecision(precision: string) {
this.setAttribute('label-precision', precision);
}
connectedCallback() {
if (this._input.parentNode !== this) {
this.appendChild(this._input);
this._valueDisplayWrapper.appendChild(this._valueDisplay);
this.appendChild(this._valueDisplayWrapper);
}
}
attributeChangedCallback(name: string, oldValue: string, newValue: string | null) {
if (this._ignoreChange) return;
if (newValue === null) {
this._input.removeAttribute(name);
} else {
this._input.setAttribute(name, newValue);
}
this._reflectAttributes();
this._update();
}
@bind
private _retargetEvent(event: Event) {
event.stopImmediatePropagation();
const retargetted = new Event(event.type, event);
this.dispatchEvent(retargetted);
}
@bind
private _update() {
const value = parseFloat(this.value || '0');
const min = parseFloat(this.min || '0');
const max = parseFloat(this.max || '100');
const labelPrecision = parseFloat(this.labelPrecision || '0');
const percent = 100 * (value - min) / (max - min);
const displayValue = labelPrecision ? value.toPrecision(labelPrecision) :
Math.round(value).toString();
this.style.setProperty('--value-percent', percent + '%');
this._valueDisplay.textContent = displayValue;
}
private _reflectAttributes() {
this._ignoreChange = true;
for (const attributeName in REFLECTED_ATTRIBUTES) {
const attributeValue = this._input.getAttribute(attributeName);
if (attributeValue === null) continue;
this.setAttribute(attributeName, attributeValue);
}
this._ignoreChange = false;
}
}
interface RangeInputElement {
name: string;
min: string;
max: string;
step: string;
value: string;
disabled: boolean;
}
for (const prop of REFLECTED_PROPERTIES) {
Object.defineProperty(RangeInputElement.prototype, prop, {
get() {
return this._input[prop];
},
set(val) {
this._input[prop] = val;
this._reflectAttributes();
this._update();
},
});
}
export default RangeInputElement;
customElements.define('range-input', RangeInputElement);