aboutsummaryrefslogtreecommitdiff
path: root/static/ts/components/input-list.ts
blob: 0afd4999464cd4f3c2eeba45447b805340cea433 (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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
export { InputList }

/*
  TODO allow each item to be a larger unit, possibly containing multiple input
  fields.
*/
class InputList extends HTMLElement {

    el: HTMLInputElement;

    #listeners: [string, (e: Event) => void][] = [];

    constructor() {
        super();
        this.el = this.children[0].cloneNode(true) as HTMLInputElement;
    }

    connectedCallback() {
        for (let child of this.children) {
            child.remove();
        }
        this.addInstance();
    }

    createInstance(): HTMLInputElement {
        let new_el = this.el.cloneNode(true) as HTMLInputElement
        let that = this;
        new_el.addEventListener('input', function() {
            /* TODO .value is empty both if it's actually empty, but also
               for invalid input. Check new_el.validity, and new_el.validationMessage
            */
            if (new_el.value === '') {
                let sibling = (this.previousElementSibling || this.nextElementSibling)
                /* Only remove ourselves if we have siblings
                   Otherwise we just linger */
                if (sibling) {
                    this.remove();
                    (sibling as HTMLInputElement).focus();
                }
            } else {
                if (!this.nextElementSibling) {
                    that.addInstance();
                    // window.setTimeout(() => this.focus())
                    this.focus();
                }
            }
        });

        for (let [type, proc] of this.#listeners) {
            new_el.addEventListener(type, proc);
        }

        return new_el;
    }

    addInstance() {
        let new_el = this.createInstance();
        this.appendChild(new_el);
    }

    get value(): any[] {
        let value_list = []
        for (let child of this.children) {
            value_list.push((child as any).value);
        }
        if (value_list[value_list.length - 1] === '') {
            value_list.pop();
        }
        return value_list
    }

    set value(new_value: any[]) {

        let all_equal = true;
        for (let i = 0; i < this.children.length; i++) {
            let sv = (this.children[i] as any).value
            all_equal
                &&= (sv == new_value[i])
                || (sv === '' && new_value[i] == undefined)
        }
        if (all_equal) return;

        /* Copy our current input elements into a dictionary.
           This allows us to only create new elements where needed
        */
        let values = new Map;
        for (let child of this.children) {
            values.set((child as HTMLInputElement).value, child);
        }

        let output_list: HTMLInputElement[] = []
        for (let value of new_value) {
            let element;
            /* Only create element if needed */
            if ((element = values.get(value))) {
                output_list.push(element)
                /* clear dictionary */
                values.set(value, false);
            } else {
                let new_el = this.createInstance();
                new_el.value = value;
                output_list.push(new_el);
            }
        }
        /* final, trailing, element */
        output_list.push(this.createInstance());

        this.replaceChildren(...output_list);
    }

    addEventListener(type: string, proc: ((e: Event) => void)) {
        // if (type != 'input') throw "Only input supported";

        this.#listeners.push([type, proc])

        for (let child of this.children) {
            child.addEventListener(type, proc);
        }
    }
}