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
|
/*
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] },
placeholder?: string,
disabled?: boolean,
value?: string,
onclick?: (e: E) => void,
onmouseenter?: (e: E) => void,
onmouseleave?: (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.placeholder && e instanceof HTMLInputElement) e.placeholder = o.placeholder;
if (o.disabled && e instanceof HTMLInputElement) e.disabled = true;
if (o.value && e instanceof HTMLInputElement) e.value = o.value;
if (o.onclick) e.addEventListener("click", () => o.onclick!(e))
if (o.onchange) e.addEventListener("change", () => o.onchange!(e))
if (o.onmouseenter) e.addEventListener("mouseenter", () => o.onmouseenter!(e))
if (o.onmouseleave) e.addEventListener("mouseleave", () => o.onmouseleave!(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)
const undo = children.map(c => e_apply(el, c))
//@ts-ignore yeye, new prop
el["jsh_undo"] = () => undo.forEach(f => f())
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);
//@ts-ignore wubbel
const child_undo: undefined | (() => void) = c["jsh_undo"]
if (child_undo) child_undo()
el.removeChild(c)
return { before: p ?? undefined }
}
}
else if (c instanceof OVar) {
let undo_last: () => RedoParams | void;
// TODO if nested, this is a memory leak (only partially fixed)
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) ?? (() => { })
}
|