aboutsummaryrefslogtreecommitdiff
path: root/static/components/slider.ts
blob: 48abc91b01b3930872a88d022a25bc25f4ae4da6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
export { SliderInput }

import { makeElement } from '../lib'

const dflt = {
    min: 0,
    max: 100,
    step: 1,
}

type Attribute = 'min' | 'max' | 'step'

class SliderInput extends HTMLElement {

    /* value a string since javascript kind of expects that */
    #value = "0";
    min = 0;
    max = 100;
    step = 1;

    readonly slider: HTMLInputElement;
    readonly textIn: HTMLInputElement;

    constructor(min?: number, max?: number, step?: number, value?: number) {
        super();

        this.min = min || parseFloat(this.getAttribute('min') || "" + dflt['min']);
        this.max = max || parseFloat(this.getAttribute('max') || "" + dflt['max']);
        this.step = step || parseFloat(this.getAttribute('step') || "" + dflt['step']);
        // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/range#value
        const defaultValue
            = (this.max < this.min)
                ? this.min
                : this.min + (this.max - this.min) / 2;

        this.slider = makeElement('input', {
            type: 'range',
            min: this.min,
            max: this.max,
            step: this.step,
            value: this.value,
        }) as HTMLInputElement
        this.textIn = makeElement('input', {
            type: 'number',
            min: this.min,
            max: this.max,
            step: this.step,
            value: this.value,
        }) as HTMLInputElement

        this.slider.addEventListener('input', e => this.propagate(e));
        this.textIn.addEventListener('input', e => this.propagate(e));

        /* MUST be after sub components are bound */
        this.value = "" + (value || this.getAttribute('value') || defaultValue);
    }

    connectedCallback() {
        this.replaceChildren(this.slider, this.textIn);
    }


    static get observedAttributes(): Attribute[] {
        return ['min', 'max', 'step']
    }

    attributeChangedCallback(name: Attribute, _?: string, to?: string): void {
        if (to) {
            this.slider.setAttribute(name, to);
            this.textIn.setAttribute(name, to);
        } else {
            this.slider.removeAttribute(name);
            this.textIn.removeAttribute(name);
        }
        this[name] = parseFloat(to || "" + dflt[name])
    }

    propagate(e: Event) {
        this.value = (e.target as HTMLInputElement).value;
        if (e instanceof InputEvent && this.oninput) {
            this.oninput(e);
        }
    }

    set value(value: string) {
        this.slider.value = value;
        this.textIn.value = value;
        this.#value = value;
    }

    get value(): string {
        return this.#value;
    }

    /* TODO do we want to implement this?
     * oninput directly on the component already works
     * /
    addEventListener(type: string, proc: ((e: Event) => void)) {
    }
    */
}