-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjsx.tsx
140 lines (124 loc) · 3.75 KB
/
jsx.tsx
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
type classDefSingle = string | { [key: string]: boolean };
type classDef = classDefSingle | classDefSingle[];
type styleDef = string | { [key: string]: any };
export type JSXData = {
class?: classDef;
style?: styleDef;
__html?: string;
[other: string]: any;
} | null;
function evalClassDefSingle(def: classDefSingle): string {
if (typeof def === "string") return def;
else
return Object.keys(def)
.filter((key) => def[key])
.join(" ");
}
function evalClassDef(def: classDef): string {
if (Array.isArray(def)) return def.map(evalClassDefSingle).join(" ");
else return evalClassDefSingle(def);
}
function evalStyleDef(def: styleDef): string {
if (typeof def === "string") return def;
else
return Object.keys(def)
.map((key) => `${key}: ${def[key]}`)
.join("; ");
}
type RecursiveElement =
| Element
| string
| boolean
| null
| undefined
| RecursiveElement[];
type NonrecursiveElement = Element | string;
type RecursiveElementSSR =
| string
| boolean
| null
| undefined
| RecursiveElementSSR[];
function flatten(e: RecursiveElement[]): NonrecursiveElement[] {
return e
.filter((e) => e !== null && e !== undefined && e !== false)
.flatMap((e) => {
if (Array.isArray(e)) return flatten(e);
else if (e === true) return ["true"];
return [e];
}) as NonrecursiveElement[];
}
function flattenSSR(e: RecursiveElementSSR[]): string[] {
return flatten(e) as string[];
}
export function jsxFactory(
ns?: string,
): (tag: string, data: JSXData, ...children: RecursiveElement[]) => Element {
return (tag, data, ...children) => {
const el =
ns !== undefined
? document.createElementNS(ns, tag)
: document.createElement(tag);
if (data?.class !== undefined)
el.setAttribute("class", evalClassDef(data.class));
if (data?.style !== undefined)
el.setAttribute("style", evalStyleDef(data.style));
if (data !== null)
for (const key in data) {
if (key === "class" || key === "style" || key === "__html") continue;
el.setAttribute(key, data[key]);
}
if (data?.__html !== undefined) el.innerHTML = data.__html;
else el.append(...flatten(children));
return el;
};
}
export function jsxFactorySSR(
ns?: string,
): (tag: string, data: JSXData, ...children: RecursiveElementSSR[]) => string {
return (tag, data, ...children) => {
let str = ns !== undefined ? `<${tag} xmlns="${ns}"` : `<${tag}`;
if (data?.class !== undefined)
str += ` class="${evalClassDef(data.class)}"`;
if (data?.style !== undefined)
str += ` style="${evalStyleDef(data.style)}"`;
if (data !== null)
for (const key in data) {
if (key === "class" || key === "style" || key === "__html") continue;
str += ` ${key}="${data[key]}"`;
}
str += ">";
// TODO: assert: __html and children are mutually exclusive
if (data?.__html !== undefined) str += data.__html;
else str += flattenSSR(children).join("");
str += `</${tag}>`;
return str;
};
}
const SSR = import.meta.env.SSR;
const factory = SSR ? jsxFactorySSR : jsxFactory;
export const jsx = factory();
export const jsxSVG = factory("http://www.w3.org/2000/svg");
export function clone<T extends Element>(input: T): T {
if (SSR)
return input; // T is actually string
else return input.cloneNode(true) as T;
}
export namespace jsx {
export namespace JSX {
export type Element = HTMLElement;
export interface IntrinsicElements {
// TODO: restrict to HTML elements
[elemName: string]: JSXData;
}
}
}
export namespace jsxSVG {
export namespace JSX {
export type Element = SVGElement;
export interface IntrinsicElements {
// TODO: restrict to HTML elements
[elemName: string]: JSXData;
}
}
}