class CustomScrubber {
constructor(values, {
format = value => value,
initial = 0,
direction = 1,
delay = null,
autoplay = true,
loop = true,
loopDelay = null,
alternate = false
} = {}) {
this.values = Array.from(values);
this.format = format;
this.direction = direction;
this.delay = delay;
this.autoplay = autoplay;
this.loop = loop;
this.loopDelay = loopDelay;
this.alternate = alternate;
this.form = document.createElement('form');
this.form.style.cssText = "font: 12px var(--sans-serif); font-variant-numeric: tabular-nums; display: flex; height: 33px; align-items: center;";
const button = document.createElement('button');
button.name = 'b';
button.type = 'button';
button.style.cssText = "margin-right: 0.4em; width: 5em;";
this.form.appendChild(button);
this.b = button;
const label = document.createElement('label');
label.style.cssText = "display: flex; align-items: center;";
this.form.appendChild(label);
const input = document.createElement('input');
input.name = 'i';
input.type = 'range';
input.min = 0;
input.max = this.values.length - 1;
input.value = initial;
input.step = 1;
input.style.cssText = "width: 180px;";
label.appendChild(input);
this.i = input;
const output = document.createElement('output');
output.name = 'o';
output.style.cssText = "margin-left: 0.4em;";
label.appendChild(output);
this.o = output;
this.frame = null;
this.timer = null;
this.interval = null;
this.i.oninput = event => {
if (event && event.isTrusted && this.running()) this.stop();
this.value = this.values[this.i.valueAsNumber];
this.o.value = this.format(this.value, this.i.valueAsNumber, this.values);
};
this.b.onclick = () => {
if (this.running()) return this.stop();
this.direction = this.alternate && this.i.valueAsNumber === this.values.length - 1 ? -1 : 1;
this.i.valueAsNumber = (this.i.valueAsNumber + this.direction) % this.values.length;
this.i.dispatchEvent(new CustomEvent("input", { bubbles: true }));
this.start();
};
this.i.oninput();
if (this.autoplay) this.start();
else this.stop();
Inputs.disposal(this.form).then(this.stop);
//return {this, this.form};
}
start() {
this.b.textContent = "Pause";
if (this.delay === null) this.frame = requestAnimationFrame(this.tick.bind(this));
else this.interval = setInterval(this.tick.bind(this), this.delay);
}
stop() {
this.b.textContent = "Play";
if (this.frame !== null) {
cancelAnimationFrame(this.frame);
this.frame = null;
}
if (this.timer !== null) {
clearTimeout(this.timer);
this.timer = null;
}
if (this.interval !== null) {
clearInterval(this.interval);
this.interval = null;
}
}
running() {
return this.frame !== null || this.timer !== null || this.interval !== null;
}
tick() {
if (this.i.valueAsNumber === (this.direction > 0 ? this.values.length - 1 : this.direction < 0 ? 0 : NaN)) {
if (!this.loop) return this.stop();
if (this.alternate) this.direction = -this.direction;
if (this.loopDelay !== null) {
if (this.frame !== null) {
cancelAnimationFrame(this.frame);
this.frame = null;
}
if (this.interval !== null) {
clearInterval(this.interval);
this.interval = null;
}
this.timer = setTimeout(() => {
this.step();
this.start();
}, this.loopDelay);
return;
}
}
if (this.delay === null) {
this.frame = requestAnimationFrame(this.tick.bind(this));
}
this.step();
}
step() {
this.i.valueAsNumber = (this.i.valueAsNumber + this.direction + this.values.length) % this.values.length;
this.i.dispatchEvent(new CustomEvent("input", { bubbles: true }));
}
setValue(val) {
this.i.valueAsNumber = this.values.indexOf(val);
this.i.dispatchEvent(new CustomEvent("input", { bubbles: true }));
}
getValue() {
return this.value;
}
}