@pfern/elements 0.1.11 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +11 -11
- package/README.md +57 -129
- package/elements.js +4 -1942
- package/env.d.ts +24 -0
- package/mathml.js +1 -0
- package/package.json +19 -23
- package/src/core/elements.js +513 -0
- package/src/core/events.js +143 -0
- package/src/core/props.js +177 -0
- package/src/core/tags.js +225 -0
- package/src/core/tick.js +116 -0
- package/src/core/types.js +696 -0
- package/src/helpers.js +73 -0
- package/src/html.js +996 -0
- package/src/mathml.js +340 -0
- package/src/router.js +51 -0
- package/src/ssr.js +175 -0
- package/src/svg.js +407 -0
- package/types/elements.d.ts +4 -1394
- package/types/mathml.d.ts +1 -0
- package/types/src/core/elements.d.ts +29 -0
- package/types/src/core/events.d.ts +15 -0
- package/types/src/core/props.d.ts +9 -0
- package/types/src/core/tags.d.ts +4 -0
- package/types/src/core/tick.d.ts +5 -0
- package/types/src/core/types.d.ts +507 -0
- package/types/src/helpers.d.ts +5 -0
- package/types/src/html.d.ts +802 -0
- package/types/src/mathml.d.ts +264 -0
- package/types/src/router.d.ts +4 -0
- package/types/src/ssr.d.ts +3 -0
- package/types/src/svg.d.ts +348 -0
package/src/mathml.js
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MathML tag helpers (curated).
|
|
3
|
+
*
|
|
4
|
+
* This module is intentionally small and focused on the subset that’s useful
|
|
5
|
+
* for building expression visualizers and teaching tools (e.g. Content MathML
|
|
6
|
+
* trees with `<apply>`, plus a handful of Presentation MathML helpers).
|
|
7
|
+
*
|
|
8
|
+
* The Elements.js runtime supports rendering *any* MathML tag name as a vnode
|
|
9
|
+
* (e.g. `['apply', {}, ...]`). These helpers exist for ergonomics and docs.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const isPropsObject = x =>
|
|
13
|
+
typeof x === 'object'
|
|
14
|
+
&& x !== null
|
|
15
|
+
&& !Array.isArray(x)
|
|
16
|
+
&& !(typeof Node !== 'undefined' && x instanceof Node)
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} tag
|
|
20
|
+
* @returns {import('./core/types.js').ElementsElementHelper<any>}
|
|
21
|
+
*/
|
|
22
|
+
const createTagHelper = tag => (...args) => {
|
|
23
|
+
const hasFirstArg = args.length > 0
|
|
24
|
+
const [propsOrChild, ...children] = args
|
|
25
|
+
const props = hasFirstArg && isPropsObject(propsOrChild) ? propsOrChild : {}
|
|
26
|
+
const actualChildren = !hasFirstArg
|
|
27
|
+
? []
|
|
28
|
+
: props === propsOrChild
|
|
29
|
+
? children
|
|
30
|
+
: [propsOrChild, ...children]
|
|
31
|
+
return /** @type {import('./core/types.js').ElementsVNode} */ (
|
|
32
|
+
[tag, props, ...actualChildren]
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* <math>
|
|
38
|
+
* The root element of a MathML expression.
|
|
39
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/math
|
|
40
|
+
*/
|
|
41
|
+
export const math = createTagHelper('math')
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* <mrow>
|
|
45
|
+
* Groups sub-expressions without introducing visible separators.
|
|
46
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mrow
|
|
47
|
+
*/
|
|
48
|
+
export const mrow = createTagHelper('mrow')
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* <mi>
|
|
52
|
+
* Identifier (variable/function name).
|
|
53
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mi
|
|
54
|
+
*/
|
|
55
|
+
export const mi = createTagHelper('mi')
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* <mo>
|
|
59
|
+
* Operator (e.g. "+", "→", parentheses).
|
|
60
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mo
|
|
61
|
+
*/
|
|
62
|
+
export const mo = createTagHelper('mo')
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* <mn>
|
|
66
|
+
* Number literal.
|
|
67
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mn
|
|
68
|
+
*/
|
|
69
|
+
export const mn = createTagHelper('mn')
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* <mtext>
|
|
73
|
+
* Text inside math layout.
|
|
74
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mtext
|
|
75
|
+
*/
|
|
76
|
+
export const mtext = createTagHelper('mtext')
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* <mspace>
|
|
80
|
+
* Spacing control.
|
|
81
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mspace
|
|
82
|
+
*/
|
|
83
|
+
export const mspace = createTagHelper('mspace')
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* <mstyle>
|
|
87
|
+
* Styling wrapper for MathML subtree.
|
|
88
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mstyle
|
|
89
|
+
*/
|
|
90
|
+
export const mstyle = createTagHelper('mstyle')
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* <menclose>
|
|
94
|
+
* Encloses content (e.g. boxes, circles).
|
|
95
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/menclose
|
|
96
|
+
*/
|
|
97
|
+
export const menclose = createTagHelper('menclose')
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* <mfenced>
|
|
101
|
+
* Convenience fencing wrapper (parentheses, brackets).
|
|
102
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mfenced
|
|
103
|
+
*/
|
|
104
|
+
export const mfenced = createTagHelper('mfenced')
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* <msup>
|
|
108
|
+
* Superscript.
|
|
109
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/msup
|
|
110
|
+
*/
|
|
111
|
+
export const msup = createTagHelper('msup')
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* <msub>
|
|
115
|
+
* Subscript.
|
|
116
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/msub
|
|
117
|
+
*/
|
|
118
|
+
export const msub = createTagHelper('msub')
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* <msubsup>
|
|
122
|
+
* Subscript + superscript.
|
|
123
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/msubsup
|
|
124
|
+
*/
|
|
125
|
+
export const msubsup = createTagHelper('msubsup')
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* <mfrac>
|
|
129
|
+
* Fraction.
|
|
130
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mfrac
|
|
131
|
+
*/
|
|
132
|
+
export const mfrac = createTagHelper('mfrac')
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* <msqrt>
|
|
136
|
+
* Square root.
|
|
137
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/msqrt
|
|
138
|
+
*/
|
|
139
|
+
export const msqrt = createTagHelper('msqrt')
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* <mroot>
|
|
143
|
+
* Root with explicit index.
|
|
144
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mroot
|
|
145
|
+
*/
|
|
146
|
+
export const mroot = createTagHelper('mroot')
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* <munder>
|
|
150
|
+
* Under-script.
|
|
151
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/munder
|
|
152
|
+
*/
|
|
153
|
+
export const munder = createTagHelper('munder')
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* <mover>
|
|
157
|
+
* Over-script.
|
|
158
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mover
|
|
159
|
+
*/
|
|
160
|
+
export const mover = createTagHelper('mover')
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* <munderover>
|
|
164
|
+
* Under + over script.
|
|
165
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/munderover
|
|
166
|
+
*/
|
|
167
|
+
export const munderover = createTagHelper('munderover')
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* <mtable>
|
|
171
|
+
* Table layout.
|
|
172
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mtable
|
|
173
|
+
*/
|
|
174
|
+
export const mtable = createTagHelper('mtable')
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* <mtr>
|
|
178
|
+
* Table row.
|
|
179
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mtr
|
|
180
|
+
*/
|
|
181
|
+
export const mtr = createTagHelper('mtr')
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* <mtd>
|
|
185
|
+
* Table cell.
|
|
186
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mtd
|
|
187
|
+
*/
|
|
188
|
+
export const mtd = createTagHelper('mtd')
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* <semantics>
|
|
192
|
+
* Attach semantic annotations to a presentation tree.
|
|
193
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/semantics
|
|
194
|
+
*/
|
|
195
|
+
export const semantics = createTagHelper('semantics')
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* <annotation>
|
|
199
|
+
* A textual annotation inside <semantics>.
|
|
200
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/annotation
|
|
201
|
+
*/
|
|
202
|
+
export const annotation = createTagHelper('annotation')
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* <annotation-xml>
|
|
206
|
+
* An XML annotation inside <semantics>.
|
|
207
|
+
*
|
|
208
|
+
* Exported as `annotationXml` because of the dash in the tag name.
|
|
209
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/annotation-xml
|
|
210
|
+
*/
|
|
211
|
+
export const annotationXml = createTagHelper('annotation-xml')
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* <apply>
|
|
215
|
+
* Content MathML application node: first child is the operator, remaining
|
|
216
|
+
* children are arguments.
|
|
217
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/apply
|
|
218
|
+
*/
|
|
219
|
+
export const apply = createTagHelper('apply')
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* <ci>
|
|
223
|
+
* Content MathML identifier.
|
|
224
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/ci
|
|
225
|
+
*/
|
|
226
|
+
export const ci = createTagHelper('ci')
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* <cn>
|
|
230
|
+
* Content MathML number.
|
|
231
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/cn
|
|
232
|
+
*/
|
|
233
|
+
export const cn = createTagHelper('cn')
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* <csymbol>
|
|
237
|
+
* Content MathML symbol (often with a content dictionary via `cd`).
|
|
238
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/csymbol
|
|
239
|
+
*/
|
|
240
|
+
export const csymbol = createTagHelper('csymbol')
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* <bind>
|
|
244
|
+
* Content MathML binding node (e.g. lambda).
|
|
245
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/bind
|
|
246
|
+
*/
|
|
247
|
+
export const bind = createTagHelper('bind')
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* <bvar>
|
|
251
|
+
* Bound variable (used under <bind>).
|
|
252
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/bvar
|
|
253
|
+
*/
|
|
254
|
+
export const bvar = createTagHelper('bvar')
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* <lambda>
|
|
258
|
+
* Lambda binder operator (typically inside <bind>).
|
|
259
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/lambda
|
|
260
|
+
*/
|
|
261
|
+
export const lambda = createTagHelper('lambda')
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* <set>
|
|
265
|
+
* Content MathML set constructor (often under <apply>).
|
|
266
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/set
|
|
267
|
+
*/
|
|
268
|
+
export const set = createTagHelper('set')
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* <list>
|
|
272
|
+
* Content MathML list constructor (often under <apply>).
|
|
273
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/list
|
|
274
|
+
*/
|
|
275
|
+
export const list = createTagHelper('list')
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* <vector>
|
|
279
|
+
* Content MathML vector constructor.
|
|
280
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/vector
|
|
281
|
+
*/
|
|
282
|
+
export const vector = createTagHelper('vector')
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* <matrix>
|
|
286
|
+
* Content MathML matrix constructor.
|
|
287
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/matrix
|
|
288
|
+
*/
|
|
289
|
+
export const matrix = createTagHelper('matrix')
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* <matrixrow>
|
|
293
|
+
* Content MathML matrix row.
|
|
294
|
+
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element/matrixrow
|
|
295
|
+
*/
|
|
296
|
+
export const matrixrow = createTagHelper('matrixrow')
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* A map of curated MathML helpers.
|
|
300
|
+
*/
|
|
301
|
+
export const mathml = /** @type {const} */ ({
|
|
302
|
+
math,
|
|
303
|
+
mrow,
|
|
304
|
+
mi,
|
|
305
|
+
mo,
|
|
306
|
+
mn,
|
|
307
|
+
mtext,
|
|
308
|
+
mspace,
|
|
309
|
+
mstyle,
|
|
310
|
+
menclose,
|
|
311
|
+
mfenced,
|
|
312
|
+
msup,
|
|
313
|
+
msub,
|
|
314
|
+
msubsup,
|
|
315
|
+
mfrac,
|
|
316
|
+
msqrt,
|
|
317
|
+
mroot,
|
|
318
|
+
munder,
|
|
319
|
+
mover,
|
|
320
|
+
munderover,
|
|
321
|
+
mtable,
|
|
322
|
+
mtr,
|
|
323
|
+
mtd,
|
|
324
|
+
semantics,
|
|
325
|
+
annotation,
|
|
326
|
+
annotationXml,
|
|
327
|
+
apply,
|
|
328
|
+
ci,
|
|
329
|
+
cn,
|
|
330
|
+
csymbol,
|
|
331
|
+
bind,
|
|
332
|
+
bvar,
|
|
333
|
+
lambda,
|
|
334
|
+
set,
|
|
335
|
+
list,
|
|
336
|
+
vector,
|
|
337
|
+
matrix,
|
|
338
|
+
matrixrow
|
|
339
|
+
})
|
|
340
|
+
|
package/src/router.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Router utilities.
|
|
2
|
+
//
|
|
3
|
+
// `onNavigate(fn)` registers a callback that should re-render your app when the
|
|
4
|
+
// URL changes (via History API navigation or back/forward).
|
|
5
|
+
//
|
|
6
|
+
// The callback is invoked on a microtask after `popstate` so it can safely run
|
|
7
|
+
// outside of any current declarative event update.
|
|
8
|
+
|
|
9
|
+
let current = null
|
|
10
|
+
let installed = false
|
|
11
|
+
let scheduled = false
|
|
12
|
+
|
|
13
|
+
const defer = fn =>
|
|
14
|
+
typeof window.queueMicrotask === 'function'
|
|
15
|
+
? window.queueMicrotask(fn)
|
|
16
|
+
: Promise.resolve().then(fn)
|
|
17
|
+
|
|
18
|
+
const schedule = () => {
|
|
19
|
+
if (typeof current !== 'function') return
|
|
20
|
+
if (scheduled) return
|
|
21
|
+
scheduled = true
|
|
22
|
+
defer(() => {
|
|
23
|
+
scheduled = false
|
|
24
|
+
typeof current === 'function' && current()
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const onPopState = () => schedule()
|
|
29
|
+
|
|
30
|
+
export const hasNavigateHandler = () => typeof current === 'function'
|
|
31
|
+
|
|
32
|
+
export const onNavigate = (fn, { immediate = false } = {}) => {
|
|
33
|
+
current = fn
|
|
34
|
+
|
|
35
|
+
if (typeof window !== 'undefined' && !installed) {
|
|
36
|
+
window.addEventListener('popstate', onPopState)
|
|
37
|
+
installed = true
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
immediate && schedule()
|
|
41
|
+
|
|
42
|
+
return () => {
|
|
43
|
+
if (current !== fn) return
|
|
44
|
+
current = null
|
|
45
|
+
|
|
46
|
+
if (typeof window !== 'undefined' && installed) {
|
|
47
|
+
window.removeEventListener('popstate', onPopState)
|
|
48
|
+
installed = false
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/ssr.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/** @fileoverview Server-side rendering / static site generation helpers.
|
|
2
|
+
*
|
|
3
|
+
* Elements.js treats UI as a pure function tree that produces declarative
|
|
4
|
+
* “vnode” arrays:
|
|
5
|
+
*
|
|
6
|
+
* ['div', { id: 'x' }, 'Hello']
|
|
7
|
+
*
|
|
8
|
+
* In the browser, `render()` evaluates this AST into real DOM nodes. For
|
|
9
|
+
* build-time prerendering (SSG) or server-side rendering (SSR),
|
|
10
|
+
* `toHtmlString()` walks the same AST and prints HTML.
|
|
11
|
+
*
|
|
12
|
+
* Design notes:
|
|
13
|
+
* - This is intentionally “dumb printing”, not hydration. Event handlers and
|
|
14
|
+
* other imperative props (functions) are dropped.
|
|
15
|
+
* - `innerHTML` is treated as an explicit escape hatch: it is inserted verbatim
|
|
16
|
+
* and children are ignored.
|
|
17
|
+
* - Boolean attribute rules mirror DOM assignment in `core/props.js`: -
|
|
18
|
+
* `aria-*` / `data-*` booleans become `"true"` / `"false"`. - other booleans
|
|
19
|
+
* are present when true, omitted when false.
|
|
20
|
+
*
|
|
21
|
+
* The long-term goal is that vnode arrays act as a small, Lisp-like
|
|
22
|
+
* cons-structure / AST: they can be evaluated into DOM, or serialized back into
|
|
23
|
+
* markup, or translated into other equivalent encodings.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const voidTags = new Set([
|
|
27
|
+
'area',
|
|
28
|
+
'base',
|
|
29
|
+
'br',
|
|
30
|
+
'col',
|
|
31
|
+
'embed',
|
|
32
|
+
'hr',
|
|
33
|
+
'img',
|
|
34
|
+
'input',
|
|
35
|
+
'link',
|
|
36
|
+
'meta',
|
|
37
|
+
'param',
|
|
38
|
+
'source',
|
|
39
|
+
'track',
|
|
40
|
+
'wbr'
|
|
41
|
+
])
|
|
42
|
+
|
|
43
|
+
const isObject = x =>
|
|
44
|
+
typeof x === 'object'
|
|
45
|
+
&& x !== null
|
|
46
|
+
&& !Array.isArray(x)
|
|
47
|
+
|
|
48
|
+
const escapeText = s =>
|
|
49
|
+
String(s)
|
|
50
|
+
.replaceAll('&', '&')
|
|
51
|
+
.replaceAll('<', '<')
|
|
52
|
+
.replaceAll('>', '>')
|
|
53
|
+
|
|
54
|
+
const escapeAttr = s =>
|
|
55
|
+
String(s)
|
|
56
|
+
.replaceAll('&', '&')
|
|
57
|
+
.replaceAll('<', '<')
|
|
58
|
+
.replaceAll('>', '>')
|
|
59
|
+
.replaceAll('"', '"')
|
|
60
|
+
.replaceAll('\'', ''')
|
|
61
|
+
|
|
62
|
+
const toKebab = s =>
|
|
63
|
+
String(s)
|
|
64
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
65
|
+
.replaceAll('_', '-')
|
|
66
|
+
.toLowerCase()
|
|
67
|
+
|
|
68
|
+
const styleToString = style => {
|
|
69
|
+
if (!isObject(style)) return ''
|
|
70
|
+
const keys = Object.keys(style)
|
|
71
|
+
if (keys.length === 0) return ''
|
|
72
|
+
|
|
73
|
+
return keys
|
|
74
|
+
.filter(k => style[k] != null && style[k] !== false)
|
|
75
|
+
.map(k => `${k.startsWith('--') ? k : toKebab(k)}:${String(style[k])}`)
|
|
76
|
+
.join(';')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const shouldDropProp = (key, value) => {
|
|
80
|
+
if (key == null) return true
|
|
81
|
+
if (key === 'innerHTML') return true
|
|
82
|
+
if (String(key).startsWith('__')) return true
|
|
83
|
+
if (typeof value === 'function') return true
|
|
84
|
+
if (key === 'ontick') return true
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const attrsToString = props => {
|
|
89
|
+
if (!isObject(props)) return ''
|
|
90
|
+
|
|
91
|
+
const normalized = props.class == null && props.className != null
|
|
92
|
+
? { ...props, class: props.className }
|
|
93
|
+
: props
|
|
94
|
+
|
|
95
|
+
const keys = Object.keys(normalized)
|
|
96
|
+
.filter(k => !shouldDropProp(k, normalized[k]))
|
|
97
|
+
.sort()
|
|
98
|
+
|
|
99
|
+
let out = ''
|
|
100
|
+
for (const key of keys) {
|
|
101
|
+
const value = normalized[key]
|
|
102
|
+
if (value == null || value === false) {
|
|
103
|
+
// Preserve `aria-*` / `data-*` boolean semantics for false.
|
|
104
|
+
if (typeof value === 'boolean'
|
|
105
|
+
&& (key.startsWith('aria-') || key.startsWith('data-')))
|
|
106
|
+
out += ` ${key}="false"`
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (key === 'style' && isObject(value)) {
|
|
111
|
+
const cssText = styleToString(value)
|
|
112
|
+
cssText && (out += ` style="${escapeAttr(cssText)}"`)
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (typeof value === 'boolean') {
|
|
117
|
+
if (key.startsWith('aria-') || key.startsWith('data-')) {
|
|
118
|
+
out += ` ${key}="${value ? 'true' : 'false'}"`
|
|
119
|
+
} else if (value) {
|
|
120
|
+
out += ` ${key}`
|
|
121
|
+
}
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
out += ` ${key}="${escapeAttr(value)}"`
|
|
126
|
+
}
|
|
127
|
+
return out
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const toHtmlStringInner = vnode => {
|
|
131
|
+
const type = typeof vnode
|
|
132
|
+
if (vnode == null || vnode === false) return ''
|
|
133
|
+
if (type === 'string' || type === 'number') return escapeText(vnode)
|
|
134
|
+
|
|
135
|
+
if (!Array.isArray(vnode) || vnode.length === 0) return ''
|
|
136
|
+
|
|
137
|
+
const tag = vnode[0]
|
|
138
|
+
|
|
139
|
+
// Explicit fragments render children without a wrapper tag.
|
|
140
|
+
if (tag === 'fragment') {
|
|
141
|
+
let out = ''
|
|
142
|
+
for (let i = 2; i < vnode.length; i++) out += toHtmlStringInner(vnode[i])
|
|
143
|
+
return out
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (typeof tag !== 'string') return ''
|
|
147
|
+
|
|
148
|
+
/** @type {Record<string, any>} */
|
|
149
|
+
const props = isObject(vnode[1]) ? vnode[1] : {}
|
|
150
|
+
const attrs = attrsToString(props)
|
|
151
|
+
|
|
152
|
+
if (voidTags.has(tag)) return `<${tag}${attrs}>`
|
|
153
|
+
|
|
154
|
+
const hasInnerHtml = 'innerHTML' in props
|
|
155
|
+
const inner = hasInnerHtml
|
|
156
|
+
? String(props.innerHTML ?? '')
|
|
157
|
+
: (() => {
|
|
158
|
+
let out = ''
|
|
159
|
+
for (let i = 2; i < vnode.length; i++) out += toHtmlStringInner(vnode[i])
|
|
160
|
+
return out
|
|
161
|
+
})()
|
|
162
|
+
|
|
163
|
+
return `<${tag}${attrs}>${inner}</${tag}>`
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Serialize a vnode tree to an HTML string (SSR/SSG).
|
|
168
|
+
*
|
|
169
|
+
* @param {import('./core/types.js').ElementsVNode | string | number | null | undefined | false} vnode
|
|
170
|
+
* @param {{ doctype?: boolean }} [options]
|
|
171
|
+
* @returns {string}
|
|
172
|
+
*/
|
|
173
|
+
export const toHtmlString = (vnode, { doctype = false } = {}) =>
|
|
174
|
+
`${doctype ? '<!doctype html>' : ''}${toHtmlStringInner(vnode)}`
|
|
175
|
+
|