aboutsummaryrefslogtreecommitdiff
path: root/src/element.ts
blob: b3fb20cfce58024a086bb0a674c169d72a4fbceb (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
/*
    This file is part of jshelper (https://codeberg.org/metamuffin/jshelper)
    which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
    Copyright (C) 2023 metamuffin <metamuffin.org>
*/
import { OVar } from "./observable.ts";

interface Opts<E> {
    class?: string[] | string,
    id?: string,
    src?: string,
    for?: string,
    type?: string,
    href?: string,
    style?: { [key in keyof CSSStyleDeclaration]?: CSSStyleDeclaration[key] }
    onclick?: (e: E) => void,
    onchange?: (e: E) => void,
}

function apply_opts<E extends HTMLElement>(e: E, o: Opts<E>): (() => void) | void {
    if (o.id) e.id = o.id
    if (o.for) (e as unknown as HTMLLabelElement).htmlFor = o.for
    if (o.type && e instanceof HTMLInputElement) e.type = o.type
    if (o.href && e instanceof HTMLAnchorElement) e.href = o.href;
    if (o.onclick) e.addEventListener("click", () => o.onclick!(e))
    if (o.onchange) e.addEventListener("change", () => o.onchange!(e))
    if (typeof o?.class == "string") e.classList.add(o.class)
    if (typeof o?.class == "object") e.classList.add(...o.class)
    if (o.style) for (const k in o.style) { const v = o.style[k]; if (v !== undefined) e.style[k] = v }
}

type EEl<K extends keyof HTMLElementTagNameMap> = string
    | HTMLElement
    | Opts<HTMLElementTagNameMap[K]>
    | OVar<Opts<HTMLElementTagNameMap[K]>>
    | OVar<string>
    | OVar<HTMLElement>
    | OVar<OVar<HTMLDivElement>>
    | OVar<HTMLUListElement> // is this possible with dudplication?
    | OVar<HTMLOListElement>
    | OVar<HTMLInputElement>
    | OVar<HTMLPreElement>
    | OVar<HTMLCanvasElement>
    | OVar<HTMLVideoElement>
    | OVar<HTMLImageElement>
    | OVar<HTMLDetailsElement>
    | OVar<HTMLSpanElement>
    | OVar<HTMLSelectElement>
    | OVar<HTMLLabelElement>
    | OVar<HTMLButtonElement>
    | OVar<HTMLDivElement>
    | OVar<HTMLParagraphElement>
    | undefined;

export function e<K extends keyof HTMLElementTagNameMap>(name: K, ...children: EEl<K>[]): HTMLElementTagNameMap[K] {
    const el = document.createElement(name)
    for (const c of children) e_apply(el, c)
    return el
}

interface RedoParams { before?: HTMLElement }
function e_apply<K extends keyof HTMLElementTagNameMap, C extends EEl<K>>(el: HTMLElementTagNameMap[K], c: C, redo?: RedoParams): () => RedoParams | void {
    if (typeof c == "undefined") {
        return () => { }
    }
    if (typeof c == "string") {
        el.append(c)
        return () => {
            el.textContent = ""
        }
    }
    else if (c instanceof HTMLElement) {
        el.insertBefore(c, redo?.before ?? null)
        return () => {
            const p = c.nextSibling as (HTMLElement | void);
            el.removeChild(c)
            return { before: p ?? undefined }
        }
    }
    else if (c instanceof OVar) {
        let undo_last: () => RedoParams | void;
        return c.onchangeinit(val => {
            let redo_param = undefined;
            if (undo_last) redo_param = undo_last()
            undo_last = e_apply(el, val, redo_param ?? undefined)
        })
    }
    else return apply_opts(el, c) ?? (() => { })
}