tweb/src/components/rangeSelector.ts

176 lines
4.9 KiB
TypeScript

/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import attachGrabListeners, {GrabEvent} from '../helpers/dom/attachGrabListeners';
import clamp from '../helpers/number/clamp';
import safeAssign from '../helpers/object/safeAssign';
export default class RangeSelector {
public container: HTMLDivElement;
protected filled: HTMLDivElement;
protected seek: HTMLInputElement;
public mousedown = false;
protected rect: DOMRect;
protected _removeListeners: () => void;
private events: Partial<{
// onMouseMove: ProgressLine['onMouseMove'],
onMouseDown: RangeSelector['onMouseDown'],
onMouseUp: RangeSelector['onMouseUp'],
onScrub: (value: number) => void
}> = {};
protected decimals: number;
protected step: number;
protected min: number;
protected max: number;
protected withTransition = false;
protected useTransform = false;
protected vertical = false;
constructor(
options: {
step: RangeSelector['step'],
min: RangeSelector['min'],
max: RangeSelector['max'],
withTransition?: RangeSelector['withTransition'],
useTransform?: RangeSelector['useTransform'],
vertical?: RangeSelector['vertical']
},
value = 0
) {
safeAssign(this, options);
this.container = document.createElement('div');
this.container.classList.add('progress-line');
// there is no sense in using transition with transform, because it is updating every frame
if(this.useTransform) {
this.container.classList.add('use-transform');
} else if(this.withTransition) {
this.container.classList.add('with-transition');
}
this.filled = document.createElement('div');
this.filled.classList.add('progress-line__filled');
const seek = this.seek = document.createElement('input');
seek.classList.add('progress-line__seek');
// seek.setAttribute('max', '0');
seek.type = 'range';
seek.step = '' + this.step;
seek.min = '' + this.min;
seek.max = '' + this.max;
seek.value = '' + value;
if(value) {
this.setProgress(value);
}
const stepStr = '' + this.step;
const index = stepStr.indexOf('.');
this.decimals = index === -1 ? 0 : stepStr.length - index - 1;
// this.setListeners();
this.container.append(this.filled, seek);
}
get value() {
return +this.seek.value;
}
public setHandlers(events: RangeSelector['events']) {
this.events = events;
}
protected onMouseMove = (event: GrabEvent) => {
this.scrub(event);
};
protected onMouseDown = (event: GrabEvent) => {
this.rect = this.container.getBoundingClientRect();
this.mousedown = true;
this.scrub(event);
this.container.classList.add('is-focused');
this.events?.onMouseDown && this.events.onMouseDown(event);
};
protected onMouseUp = (event: GrabEvent) => {
this.mousedown = false;
this.container.classList.remove('is-focused');
this.events?.onMouseUp && this.events.onMouseUp(event);
};
public setListeners() {
this.seek.addEventListener('input', this.onInput);
this._removeListeners = attachGrabListeners(this.container, this.onMouseDown, this.onMouseMove, this.onMouseUp);
}
public onInput = () => {
const value = +this.seek.value;
this.setFilled(value);
this.events?.onScrub && this.events.onScrub(value);
};
public setProgress(value: number) {
this.seek.value = '' + value;
this.setFilled(+this.seek.value); // clamp
}
public addProgress(value: number) {
this.seek.value = '' + (+this.seek.value + value);
this.setFilled(+this.seek.value); // clamp
}
public setFilled(value: number) {
let percents = (value - this.min) / (this.max - this.min);
percents = clamp(percents, 0, 1);
// using scaleX and width even with vertical because it will be rotated
if(this.useTransform) {
this.filled.style.transform = `scaleX(${percents})`;
} else {
this.filled.style.width = (percents * 100) + '%';
}
}
protected scrub(event: GrabEvent) {
const rectMax = this.vertical ? this.rect.height : this.rect.width;
const offsetAxisValue = clamp(this.vertical ? -(event.y - this.rect.bottom) : event.x - this.rect.left, 0, rectMax);
let value = this.min + (offsetAxisValue / rectMax * (this.max - this.min));
if((value - this.min) < ((this.max - this.min) / 2)) {
value -= this.step / 10;
}
value = +value.toFixed(this.decimals);
value = clamp(value, this.min, this.max);
// this.seek.value = '' + value;
// this.onInput();
this.setProgress(value);
this.events?.onScrub && this.events.onScrub(value);
return value;
}
public removeListeners() {
if(this._removeListeners) {
this._removeListeners();
this._removeListeners = null;
}
this.seek.removeEventListener('input', this.onInput);
this.events = {};
}
}