-
Notifications
You must be signed in to change notification settings - Fork 692
/
jsx.ts
173 lines (154 loc) · 4.27 KB
/
jsx.ts
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/**
* Custom JSX module designed specifically for TypeDoc's needs.
* When overriding a default TypeDoc theme output, your implementation must create valid {@link Element}
* instances, which can be most easily done by using TypeDoc's JSX implementation. To use it, set up
* your tsconfig with the following compiler options:
* ```json
* {
* "jsx": "react",
* "jsxFactory": "JSX.createElement",
* "jsxFragmentFactory": "JSX.Fragment"
* }
* ```
* @module
*/
import { escapeHtml } from "./html";
import type {
IntrinsicElements,
JsxElement,
JsxChildren,
JsxComponent,
} from "./jsx.elements";
import { JsxFragment as Fragment } from "./jsx.elements";
// Backwards compatibility until 0.24
export type {
JsxElement as Element,
JsxChildren as Children,
JsxComponent,
} from "./jsx.elements";
export { JsxFragment as Fragment } from "./jsx.elements";
/**
* Used to inject HTML directly into the document.
*/
export function Raw(_props: { html: string }) {
// This is handled specially by the renderElement function. Instead of being
// called, the tag is compared to this function and the `html` prop will be
// returned directly.
return null;
}
/**
* TypeScript's rules for looking up the JSX.IntrinsicElements and JSX.Element
* interfaces are incredibly strange. It will find them if they are included as
* a namespace under the createElement function, or globally, or, apparently, if
* a JSX namespace is declared at the same scope as the factory function.
* Hide this in the docs, hopefully someday TypeScript improves this and allows
* looking adjacent to the factory function and we can get rid of this phantom namespace.
* @hidden
*/
export declare namespace JSX {
export { IntrinsicElements, JsxElement as Element };
}
const voidElements = new Set([
"area",
"base",
"br",
"col",
"embed",
"hr",
"img",
"input",
"link",
"meta",
"param",
"source",
"track",
"wbr",
]);
const blockElements = new Set([
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"div",
"section",
"nav",
"details",
"p",
"ul",
"ol",
"li",
]);
/**
* JSX factory function to create an "element" that can later be rendered with {@link renderElement}
* @param tag
* @param props
* @param children
*/
export function createElement(
tag: typeof Fragment | string | JsxComponent<any>,
props: object | null,
...children: JsxChildren[]
): JsxElement {
return { tag, props, children };
}
export function renderElement(element: JsxElement | null | undefined): string {
if (!element) {
return "";
}
const { tag, props, children } = element;
if (typeof tag === "function") {
if (tag === Raw) {
return String((props as any).html);
}
return renderElement(tag(Object.assign({ children }, props)));
}
const html: string[] = [];
if (tag !== Fragment) {
if (blockElements.has(tag)) {
html.push("\n");
}
html.push("<", tag);
for (const [key, val] of Object.entries(props ?? {})) {
if (val == null) continue;
if (typeof val == "boolean") {
if (val) {
html.push(" ", key);
}
} else {
html.push(" ", key, "=", JSON.stringify(val));
}
}
}
let hasChildren = false;
if (children.length) {
hasChildren = true;
if (tag !== Fragment) html.push(">");
renderChildren(children);
}
if (tag !== Fragment) {
if (!hasChildren) {
if (voidElements.has(tag)) {
html.push("/>");
} else {
html.push("></", tag, ">");
}
} else {
html.push("</", tag, ">");
}
}
return html.join("");
function renderChildren(children: JsxChildren[]) {
for (const child of children) {
if (!child) continue;
if (Array.isArray(child)) {
renderChildren(child);
} else if (typeof child === "string" || typeof child === "number") {
html.push(escapeHtml(child.toString()));
} else {
html.push(renderElement(child));
}
}
}
}