foldkit 0.147.0 → 0.148.1
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/dist/buildToken.d.ts +3 -0
- package/dist/buildToken.d.ts.map +1 -0
- package/dist/buildToken.js +21 -0
- package/dist/controlledDomState.d.ts +25 -0
- package/dist/controlledDomState.d.ts.map +1 -0
- package/dist/controlledDomState.js +240 -0
- package/dist/cssStyleProperties.d.ts +6 -0
- package/dist/cssStyleProperties.d.ts.map +1 -0
- package/dist/cssStyleProperties.js +91 -0
- package/dist/domReflection.d.ts +73 -0
- package/dist/domReflection.d.ts.map +1 -0
- package/dist/domReflection.js +557 -0
- package/dist/experimental/server/host.d.ts +102 -8
- package/dist/experimental/server/host.d.ts.map +1 -1
- package/dist/experimental/server/host.js +203 -13
- package/dist/experimental/server/public.d.ts +2 -2
- package/dist/experimental/server/public.d.ts.map +1 -1
- package/dist/experimental/server/public.js +1 -1
- package/dist/experimental/server/serialize.d.ts +15 -5
- package/dist/experimental/server/serialize.d.ts.map +1 -1
- package/dist/experimental/server/serialize.js +373 -144
- package/dist/experimental/server/server.d.ts +55 -14
- package/dist/experimental/server/server.d.ts.map +1 -1
- package/dist/experimental/server/server.js +546 -22
- package/dist/experimental/server/template.d.ts +8 -0
- package/dist/experimental/server/template.d.ts.map +1 -1
- package/dist/experimental/server/template.js +437 -2
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +436 -29
- package/dist/hydrate.d.ts +1 -1
- package/dist/hydrate.d.ts.map +1 -1
- package/dist/hydrate.js +449 -121
- package/dist/hydrationMarkers.d.ts +15 -0
- package/dist/hydrationMarkers.d.ts.map +1 -0
- package/dist/hydrationMarkers.js +72 -0
- package/dist/nativeInnerHtml.d.ts +13 -0
- package/dist/nativeInnerHtml.d.ts.map +1 -0
- package/dist/nativeInnerHtml.js +30 -0
- package/dist/propertyProvenance.d.ts +33 -0
- package/dist/propertyProvenance.d.ts.map +1 -0
- package/dist/propertyProvenance.js +78 -0
- package/dist/propsModule.d.ts.map +1 -1
- package/dist/propsModule.js +149 -15
- package/dist/runtime/public.d.ts +1 -1
- package/dist/runtime/public.d.ts.map +1 -1
- package/dist/runtime/runtime.d.ts +33 -6
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +240 -27
- package/dist/snabbdom/attributes.d.ts.map +1 -1
- package/dist/snabbdom/attributes.js +65 -37
- package/dist/snabbdom/class.d.ts.map +1 -1
- package/dist/snabbdom/class.js +61 -5
- package/dist/snabbdom/style.d.ts.map +1 -1
- package/dist/snabbdom/style.js +57 -34
- package/dist/test/apps/attributes.d.ts +1 -0
- package/dist/test/apps/attributes.d.ts.map +1 -1
- package/dist/test/apps/attributes.js +8 -1
- package/dist/test/apps/login.js +1 -1
- package/dist/test/matchers.d.ts.map +1 -1
- package/dist/test/matchers.js +2 -1
- package/dist/test/scene.d.ts.map +1 -1
- package/dist/test/scene.js +2 -1
- package/package.json +2 -2
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
import { CSS_STYLE_PROPERTIES } from './cssStyleProperties.js';
|
|
2
|
+
// The one description of how a typed attribute builder's DOM property relates
|
|
3
|
+
// to the HTML attribute it is named after.
|
|
4
|
+
//
|
|
5
|
+
// Two mechanisms read this. A server render turns the property into attribute
|
|
6
|
+
// text a parser will read back, and a client render assigns the property to a
|
|
7
|
+
// live element. They agree only where the property is a reflecting IDL
|
|
8
|
+
// attribute of that element, so the same tables decide what the serializer
|
|
9
|
+
// emits and what the builders refuse to build. Keeping one copy is what stops
|
|
10
|
+
// the two from drifting: a property added to the serializer alone would be
|
|
11
|
+
// served as markup no client render reproduces.
|
|
12
|
+
/** DOM properties whose attribute is present or absent rather than valued.
|
|
13
|
+
*
|
|
14
|
+
* @internal */
|
|
15
|
+
export const BOOLEAN_PROPERTIES = new Set([
|
|
16
|
+
'autofocus',
|
|
17
|
+
'autoplay',
|
|
18
|
+
'checked',
|
|
19
|
+
'controls',
|
|
20
|
+
'disabled',
|
|
21
|
+
'formNoValidate',
|
|
22
|
+
'hidden',
|
|
23
|
+
'inert',
|
|
24
|
+
'isMap',
|
|
25
|
+
'loop',
|
|
26
|
+
'multiple',
|
|
27
|
+
'muted',
|
|
28
|
+
'noValidate',
|
|
29
|
+
'open',
|
|
30
|
+
'playsInline',
|
|
31
|
+
'readOnly',
|
|
32
|
+
'required',
|
|
33
|
+
'reversed',
|
|
34
|
+
'selected',
|
|
35
|
+
]);
|
|
36
|
+
/** DOM properties whose name differs from the attribute they reflect.
|
|
37
|
+
*
|
|
38
|
+
* @internal */
|
|
39
|
+
export const RENAMED_PROPERTY_ATTRIBUTES = {
|
|
40
|
+
colSpan: 'colspan',
|
|
41
|
+
dateTime: 'datetime',
|
|
42
|
+
formAction: 'formaction',
|
|
43
|
+
formEnctype: 'formenctype',
|
|
44
|
+
formMethod: 'formmethod',
|
|
45
|
+
formNoValidate: 'formnovalidate',
|
|
46
|
+
formTarget: 'formtarget',
|
|
47
|
+
htmlFor: 'for',
|
|
48
|
+
isMap: 'ismap',
|
|
49
|
+
maxLength: 'maxlength',
|
|
50
|
+
minLength: 'minlength',
|
|
51
|
+
noValidate: 'novalidate',
|
|
52
|
+
playsInline: 'playsinline',
|
|
53
|
+
readOnly: 'readonly',
|
|
54
|
+
rowSpan: 'rowspan',
|
|
55
|
+
tabIndex: 'tabindex',
|
|
56
|
+
};
|
|
57
|
+
/** DOM properties whose value is written as the same-named attribute verbatim.
|
|
58
|
+
*
|
|
59
|
+
* @internal */
|
|
60
|
+
export const PASSTHROUGH_PROPERTIES = new Set([
|
|
61
|
+
'accept',
|
|
62
|
+
'action',
|
|
63
|
+
'alt',
|
|
64
|
+
'autocomplete',
|
|
65
|
+
'cite',
|
|
66
|
+
'cols',
|
|
67
|
+
'dir',
|
|
68
|
+
'download',
|
|
69
|
+
'enctype',
|
|
70
|
+
'high',
|
|
71
|
+
'href',
|
|
72
|
+
'id',
|
|
73
|
+
'label',
|
|
74
|
+
'lang',
|
|
75
|
+
'low',
|
|
76
|
+
'max',
|
|
77
|
+
'method',
|
|
78
|
+
'min',
|
|
79
|
+
'name',
|
|
80
|
+
'optimum',
|
|
81
|
+
'pattern',
|
|
82
|
+
'placeholder',
|
|
83
|
+
'poster',
|
|
84
|
+
'preload',
|
|
85
|
+
'rel',
|
|
86
|
+
'rows',
|
|
87
|
+
'size',
|
|
88
|
+
'span',
|
|
89
|
+
'src',
|
|
90
|
+
'start',
|
|
91
|
+
'step',
|
|
92
|
+
'target',
|
|
93
|
+
'title',
|
|
94
|
+
'type',
|
|
95
|
+
'value',
|
|
96
|
+
'wrap',
|
|
97
|
+
]);
|
|
98
|
+
/** The property names that are universal HTML global attributes.
|
|
99
|
+
*
|
|
100
|
+
* @internal */
|
|
101
|
+
export const GLOBAL_ATTRIBUTE_PROPERTIES = new Set([
|
|
102
|
+
'autofocus',
|
|
103
|
+
'id',
|
|
104
|
+
'title',
|
|
105
|
+
'lang',
|
|
106
|
+
'dir',
|
|
107
|
+
'tabIndex',
|
|
108
|
+
'hidden',
|
|
109
|
+
'inert',
|
|
110
|
+
'draggable',
|
|
111
|
+
]);
|
|
112
|
+
// The properties an SVG or MathML element carries as reflecting IDL attributes,
|
|
113
|
+
// so that assigning one on the client leaves the element in the state parsing
|
|
114
|
+
// the served attribute produces.
|
|
115
|
+
//
|
|
116
|
+
// Everything else is an HTML interface member the foreign element does not
|
|
117
|
+
// have. Assigning `title` or `href` to an SVG element sets an expando or, since
|
|
118
|
+
// bundled code is strict, throws on a readonly `SVGAnimatedString`, while the
|
|
119
|
+
// serializer would have written an attribute; the two never agree. Measured in
|
|
120
|
+
// Chromium across the whole table above on `svg:a`, `svg:rect`, and
|
|
121
|
+
// `math:mtext`: these three reflect on all of them and nothing else reflects on
|
|
122
|
+
// any of them.
|
|
123
|
+
/** @internal */
|
|
124
|
+
export const FOREIGN_REPRESENTABLE_PROPERTIES = new Set([
|
|
125
|
+
'autofocus',
|
|
126
|
+
'id',
|
|
127
|
+
'tabIndex',
|
|
128
|
+
]);
|
|
129
|
+
/** The HTML attribute a typed builder's DOM property reflects, or `undefined`
|
|
130
|
+
* when the property is not one the tables above describe.
|
|
131
|
+
*
|
|
132
|
+
* @internal */
|
|
133
|
+
export const reflectedAttributeName = (propertyName) => {
|
|
134
|
+
const renamed = RENAMED_PROPERTY_ATTRIBUTES[propertyName];
|
|
135
|
+
if (renamed !== undefined) {
|
|
136
|
+
return renamed;
|
|
137
|
+
}
|
|
138
|
+
if (BOOLEAN_PROPERTIES.has(propertyName) ||
|
|
139
|
+
PASSTHROUGH_PROPERTIES.has(propertyName) ||
|
|
140
|
+
propertyName === 'draggable') {
|
|
141
|
+
return propertyName;
|
|
142
|
+
}
|
|
143
|
+
return undefined;
|
|
144
|
+
};
|
|
145
|
+
// HTML attribute names are ASCII case-insensitive: a parser lowercases them,
|
|
146
|
+
// and `setAttribute` on an HTML element does the same. `h.Attribute('MULTIPLE',
|
|
147
|
+
// '')` and `h.Attribute('multiple', '')` therefore name one attribute, and any
|
|
148
|
+
// analysis that compares names verbatim reads the first as an unrelated one.
|
|
149
|
+
// Foreign content is left alone, where `viewBox` and `preserveAspectRatio` are
|
|
150
|
+
// case-sensitive names of their own.
|
|
151
|
+
const ASCII_UPPER = /[A-Z]/g;
|
|
152
|
+
/** @internal */
|
|
153
|
+
export const toHtmlAttributeName = (name) => name.replace(ASCII_UPPER, letter => letter.toLowerCase());
|
|
154
|
+
const SVG_ATTRIBUTE_ADJUSTMENTS = {
|
|
155
|
+
attributename: 'attributeName',
|
|
156
|
+
attributetype: 'attributeType',
|
|
157
|
+
basefrequency: 'baseFrequency',
|
|
158
|
+
baseprofile: 'baseProfile',
|
|
159
|
+
calcmode: 'calcMode',
|
|
160
|
+
clippathunits: 'clipPathUnits',
|
|
161
|
+
diffuseconstant: 'diffuseConstant',
|
|
162
|
+
edgemode: 'edgeMode',
|
|
163
|
+
filterunits: 'filterUnits',
|
|
164
|
+
glyphref: 'glyphRef',
|
|
165
|
+
gradienttransform: 'gradientTransform',
|
|
166
|
+
gradientunits: 'gradientUnits',
|
|
167
|
+
kernelmatrix: 'kernelMatrix',
|
|
168
|
+
kernelunitlength: 'kernelUnitLength',
|
|
169
|
+
keypoints: 'keyPoints',
|
|
170
|
+
keysplines: 'keySplines',
|
|
171
|
+
keytimes: 'keyTimes',
|
|
172
|
+
lengthadjust: 'lengthAdjust',
|
|
173
|
+
limitingconeangle: 'limitingConeAngle',
|
|
174
|
+
markerheight: 'markerHeight',
|
|
175
|
+
markerunits: 'markerUnits',
|
|
176
|
+
markerwidth: 'markerWidth',
|
|
177
|
+
maskcontentunits: 'maskContentUnits',
|
|
178
|
+
maskunits: 'maskUnits',
|
|
179
|
+
numoctaves: 'numOctaves',
|
|
180
|
+
pathlength: 'pathLength',
|
|
181
|
+
patterncontentunits: 'patternContentUnits',
|
|
182
|
+
patterntransform: 'patternTransform',
|
|
183
|
+
patternunits: 'patternUnits',
|
|
184
|
+
pointsatx: 'pointsAtX',
|
|
185
|
+
pointsaty: 'pointsAtY',
|
|
186
|
+
pointsatz: 'pointsAtZ',
|
|
187
|
+
preservealpha: 'preserveAlpha',
|
|
188
|
+
preserveaspectratio: 'preserveAspectRatio',
|
|
189
|
+
primitiveunits: 'primitiveUnits',
|
|
190
|
+
refx: 'refX',
|
|
191
|
+
refy: 'refY',
|
|
192
|
+
repeatcount: 'repeatCount',
|
|
193
|
+
repeatdur: 'repeatDur',
|
|
194
|
+
requiredextensions: 'requiredExtensions',
|
|
195
|
+
requiredfeatures: 'requiredFeatures',
|
|
196
|
+
specularconstant: 'specularConstant',
|
|
197
|
+
specularexponent: 'specularExponent',
|
|
198
|
+
spreadmethod: 'spreadMethod',
|
|
199
|
+
startoffset: 'startOffset',
|
|
200
|
+
stddeviation: 'stdDeviation',
|
|
201
|
+
stitchtiles: 'stitchTiles',
|
|
202
|
+
surfacescale: 'surfaceScale',
|
|
203
|
+
systemlanguage: 'systemLanguage',
|
|
204
|
+
tablevalues: 'tableValues',
|
|
205
|
+
targetx: 'targetX',
|
|
206
|
+
targety: 'targetY',
|
|
207
|
+
textlength: 'textLength',
|
|
208
|
+
viewbox: 'viewBox',
|
|
209
|
+
viewtarget: 'viewTarget',
|
|
210
|
+
xchannelselector: 'xChannelSelector',
|
|
211
|
+
ychannelselector: 'yChannelSelector',
|
|
212
|
+
zoomandpan: 'zoomAndPan',
|
|
213
|
+
};
|
|
214
|
+
export const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
|
|
215
|
+
export const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
|
|
216
|
+
export const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
|
|
217
|
+
/** The attribute name produced by parsing HTML source in the given namespace.
|
|
218
|
+
*
|
|
219
|
+
* @internal
|
|
220
|
+
*/
|
|
221
|
+
export const parsedAttributeName = (namespace, name) => {
|
|
222
|
+
const lowerName = toHtmlAttributeName(name);
|
|
223
|
+
if (namespace === SVG_NAMESPACE) {
|
|
224
|
+
return SVG_ATTRIBUTE_ADJUSTMENTS[lowerName] ?? lowerName;
|
|
225
|
+
}
|
|
226
|
+
if (namespace === MATHML_NAMESPACE && lowerName === 'definitionurl') {
|
|
227
|
+
return 'definitionURL';
|
|
228
|
+
}
|
|
229
|
+
return lowerName;
|
|
230
|
+
};
|
|
231
|
+
const HTML_PROPERTY_ELEMENTS = {
|
|
232
|
+
accept: new Set(['input']),
|
|
233
|
+
action: new Set(['form']),
|
|
234
|
+
alt: new Set(['area', 'img', 'input']),
|
|
235
|
+
autocomplete: new Set(['form', 'input', 'select', 'textarea']),
|
|
236
|
+
autoplay: new Set(['audio', 'video']),
|
|
237
|
+
checked: new Set(['input']),
|
|
238
|
+
cite: new Set(['blockquote', 'del', 'ins', 'q']),
|
|
239
|
+
cols: new Set(['textarea']),
|
|
240
|
+
colSpan: new Set(['td', 'th']),
|
|
241
|
+
controls: new Set(['audio', 'video']),
|
|
242
|
+
dateTime: new Set(['del', 'ins', 'time']),
|
|
243
|
+
disabled: new Set([
|
|
244
|
+
'button',
|
|
245
|
+
'fieldset',
|
|
246
|
+
'input',
|
|
247
|
+
'link',
|
|
248
|
+
'optgroup',
|
|
249
|
+
'option',
|
|
250
|
+
'select',
|
|
251
|
+
'textarea',
|
|
252
|
+
]),
|
|
253
|
+
download: new Set(['a', 'area']),
|
|
254
|
+
enctype: new Set(['form']),
|
|
255
|
+
formAction: new Set(['button', 'input']),
|
|
256
|
+
formEnctype: new Set(['button', 'input']),
|
|
257
|
+
formMethod: new Set(['button', 'input']),
|
|
258
|
+
formNoValidate: new Set(['button', 'input']),
|
|
259
|
+
formTarget: new Set(['button', 'input']),
|
|
260
|
+
high: new Set(['meter']),
|
|
261
|
+
href: new Set(['a', 'area', 'base', 'link']),
|
|
262
|
+
htmlFor: new Set(['label', 'output', 'script']),
|
|
263
|
+
isMap: new Set(['img']),
|
|
264
|
+
label: new Set(['optgroup', 'option', 'track']),
|
|
265
|
+
loop: new Set(['audio', 'video']),
|
|
266
|
+
low: new Set(['meter']),
|
|
267
|
+
max: new Set(['input', 'meter', 'progress']),
|
|
268
|
+
maxLength: new Set(['input', 'textarea']),
|
|
269
|
+
method: new Set(['form']),
|
|
270
|
+
min: new Set(['input', 'meter']),
|
|
271
|
+
minLength: new Set(['input', 'textarea']),
|
|
272
|
+
multiple: new Set(['input', 'select']),
|
|
273
|
+
muted: new Set(['audio', 'video']),
|
|
274
|
+
name: new Set([
|
|
275
|
+
'a',
|
|
276
|
+
'button',
|
|
277
|
+
'details',
|
|
278
|
+
'embed',
|
|
279
|
+
'fieldset',
|
|
280
|
+
'form',
|
|
281
|
+
'iframe',
|
|
282
|
+
'img',
|
|
283
|
+
'input',
|
|
284
|
+
'map',
|
|
285
|
+
'meta',
|
|
286
|
+
'object',
|
|
287
|
+
'output',
|
|
288
|
+
'param',
|
|
289
|
+
'select',
|
|
290
|
+
'slot',
|
|
291
|
+
'textarea',
|
|
292
|
+
]),
|
|
293
|
+
noValidate: new Set(['form']),
|
|
294
|
+
open: new Set(['details', 'dialog']),
|
|
295
|
+
optimum: new Set(['meter']),
|
|
296
|
+
pattern: new Set(['input']),
|
|
297
|
+
placeholder: new Set(['input', 'textarea']),
|
|
298
|
+
playsInline: new Set(['video']),
|
|
299
|
+
poster: new Set(['video']),
|
|
300
|
+
preload: new Set(['audio', 'video']),
|
|
301
|
+
readOnly: new Set(['input', 'textarea']),
|
|
302
|
+
rel: new Set(['a', 'area', 'form', 'link']),
|
|
303
|
+
required: new Set(['input', 'select', 'textarea']),
|
|
304
|
+
reversed: new Set(['ol']),
|
|
305
|
+
rows: new Set(['textarea']),
|
|
306
|
+
rowSpan: new Set(['td', 'th']),
|
|
307
|
+
selected: new Set(['option']),
|
|
308
|
+
size: new Set(['hr', 'input', 'select']),
|
|
309
|
+
span: new Set(['col', 'colgroup']),
|
|
310
|
+
src: new Set([
|
|
311
|
+
'audio',
|
|
312
|
+
'embed',
|
|
313
|
+
'iframe',
|
|
314
|
+
'img',
|
|
315
|
+
'input',
|
|
316
|
+
'script',
|
|
317
|
+
'source',
|
|
318
|
+
'track',
|
|
319
|
+
'video',
|
|
320
|
+
]),
|
|
321
|
+
start: new Set(['ol']),
|
|
322
|
+
step: new Set(['input']),
|
|
323
|
+
target: new Set(['a', 'area', 'base', 'form', 'link']),
|
|
324
|
+
type: new Set([
|
|
325
|
+
'a',
|
|
326
|
+
'button',
|
|
327
|
+
'embed',
|
|
328
|
+
'input',
|
|
329
|
+
'li',
|
|
330
|
+
'link',
|
|
331
|
+
'object',
|
|
332
|
+
'ol',
|
|
333
|
+
'param',
|
|
334
|
+
'script',
|
|
335
|
+
'source',
|
|
336
|
+
'style',
|
|
337
|
+
'ul',
|
|
338
|
+
]),
|
|
339
|
+
value: new Set([
|
|
340
|
+
'button',
|
|
341
|
+
'data',
|
|
342
|
+
'input',
|
|
343
|
+
'li',
|
|
344
|
+
'meter',
|
|
345
|
+
'option',
|
|
346
|
+
'output',
|
|
347
|
+
'param',
|
|
348
|
+
'progress',
|
|
349
|
+
'select',
|
|
350
|
+
'textarea',
|
|
351
|
+
]),
|
|
352
|
+
wrap: new Set(['textarea']),
|
|
353
|
+
};
|
|
354
|
+
/** Whether assigning a typed property can be represented by parsed HTML for
|
|
355
|
+
* the target element.
|
|
356
|
+
*
|
|
357
|
+
* @internal
|
|
358
|
+
*/
|
|
359
|
+
export const isHtmlPropertyRepresentable = (tagName, propertyName) => GLOBAL_ATTRIBUTE_PROPERTIES.has(propertyName) ||
|
|
360
|
+
(Object.hasOwn(HTML_PROPERTY_ELEMENTS, propertyName) &&
|
|
361
|
+
HTML_PROPERTY_ELEMENTS[propertyName]?.has(tagName.toLowerCase()) === true);
|
|
362
|
+
const CANONICAL_NUMBER_PROPERTIES = {
|
|
363
|
+
li: new Set(['value']),
|
|
364
|
+
meter: new Set(['high', 'low', 'max', 'min', 'optimum', 'value']),
|
|
365
|
+
progress: new Set(['max', 'value']),
|
|
366
|
+
};
|
|
367
|
+
/** The attribute spelling produced by assigning a typed property on a fresh
|
|
368
|
+
* element.
|
|
369
|
+
*
|
|
370
|
+
* @internal
|
|
371
|
+
*/
|
|
372
|
+
export const serializedHtmlPropertyValue = (tagName, propertyName, value) => {
|
|
373
|
+
const lowerTagName = tagName.toLowerCase();
|
|
374
|
+
if (lowerTagName === 'textarea' && propertyName === 'cols' && value === 0) {
|
|
375
|
+
return '20';
|
|
376
|
+
}
|
|
377
|
+
if (lowerTagName === 'textarea' && propertyName === 'rows' && value === 0) {
|
|
378
|
+
return '2';
|
|
379
|
+
}
|
|
380
|
+
if (CANONICAL_NUMBER_PROPERTIES[lowerTagName]?.has(propertyName) === true) {
|
|
381
|
+
return String(Number(value));
|
|
382
|
+
}
|
|
383
|
+
return String(value);
|
|
384
|
+
};
|
|
385
|
+
const STYLE_LIFECYCLE_KEYS = new Set([
|
|
386
|
+
'delayed',
|
|
387
|
+
'destroy',
|
|
388
|
+
'remove',
|
|
389
|
+
]);
|
|
390
|
+
const CUSTOM_STYLE_PROPERTY_NAME = /^--(?:[A-Za-z0-9_-]|[^\u0000-\u007f])+$/;
|
|
391
|
+
const STYLE_PROPERTY_NAME = /^-?[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
392
|
+
const canonicalStylePropertyName = (name) => name.startsWith('Webkit') ? `webkit${name.slice('Webkit'.length)}` : name;
|
|
393
|
+
const cssDeclarationName = (name) => {
|
|
394
|
+
if (name.startsWith('--')) {
|
|
395
|
+
return name;
|
|
396
|
+
}
|
|
397
|
+
if (name === 'cssFloat') {
|
|
398
|
+
return 'float';
|
|
399
|
+
}
|
|
400
|
+
const canonicalName = canonicalStylePropertyName(name);
|
|
401
|
+
const kebabName = canonicalName
|
|
402
|
+
.replace(ASCII_UPPER, letter => `-${letter}`)
|
|
403
|
+
.toLowerCase();
|
|
404
|
+
if (kebabName.startsWith('webkit-') || kebabName.startsWith('ms-')) {
|
|
405
|
+
return `-${kebabName}`;
|
|
406
|
+
}
|
|
407
|
+
return kebabName;
|
|
408
|
+
};
|
|
409
|
+
const CSS_DECLARATION_PROPERTIES = new Set(Array.from(CSS_STYLE_PROPERTIES, cssDeclarationName));
|
|
410
|
+
const hasUnsafeStyleSyntax = (value) => {
|
|
411
|
+
let quote;
|
|
412
|
+
let parentheses = 0;
|
|
413
|
+
let isComment = false;
|
|
414
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
415
|
+
const character = value.charAt(index);
|
|
416
|
+
const next = value.charAt(index + 1);
|
|
417
|
+
if (isComment) {
|
|
418
|
+
if (character === '*' && next === '/') {
|
|
419
|
+
isComment = false;
|
|
420
|
+
index += 1;
|
|
421
|
+
}
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
if (character === '\\') {
|
|
425
|
+
if (next === '') {
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
index += 1;
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
if (quote !== undefined) {
|
|
432
|
+
if (character === quote) {
|
|
433
|
+
quote = undefined;
|
|
434
|
+
}
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
if (character === '/' && next === '*') {
|
|
438
|
+
isComment = true;
|
|
439
|
+
index += 1;
|
|
440
|
+
}
|
|
441
|
+
else if (character === '"' || character === "'") {
|
|
442
|
+
quote = character;
|
|
443
|
+
}
|
|
444
|
+
else if (character === '(') {
|
|
445
|
+
parentheses += 1;
|
|
446
|
+
}
|
|
447
|
+
else if (character === ')') {
|
|
448
|
+
if (parentheses === 0) {
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
parentheses -= 1;
|
|
452
|
+
}
|
|
453
|
+
else if (parentheses === 0 && (character === ';' || character === '!')) {
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return quote !== undefined || parentheses !== 0 || isComment;
|
|
458
|
+
};
|
|
459
|
+
/** Refuses style entries whose property assignment and serialized declaration
|
|
460
|
+
* can describe different CSS.
|
|
461
|
+
*
|
|
462
|
+
* @internal
|
|
463
|
+
*/
|
|
464
|
+
export const assertStyleIsRepresentable = (style) => {
|
|
465
|
+
const declarationOwners = new Map();
|
|
466
|
+
for (const name of Object.keys(style)) {
|
|
467
|
+
if (STYLE_LIFECYCLE_KEYS.has(name)) {
|
|
468
|
+
throw new Error(`[foldkit] h.Style property ${JSON.stringify(name)} is a Snabbdom ` +
|
|
469
|
+
'lifecycle control rather than a CSS property. Foldkit h.Style ' +
|
|
470
|
+
'accepts only declarations applied while the element exists.');
|
|
471
|
+
}
|
|
472
|
+
if (name === 'cssText' ||
|
|
473
|
+
(name.startsWith('--')
|
|
474
|
+
? !CUSTOM_STYLE_PROPERTY_NAME.test(name)
|
|
475
|
+
: !STYLE_PROPERTY_NAME.test(name))) {
|
|
476
|
+
throw new Error(`[foldkit] h.Style property ${JSON.stringify(name)} cannot be ` +
|
|
477
|
+
'represented as one inline CSS declaration. Use a CSS property ' +
|
|
478
|
+
'name without declaration delimiters.');
|
|
479
|
+
}
|
|
480
|
+
const canonicalName = canonicalStylePropertyName(name);
|
|
481
|
+
if (!name.startsWith('--') &&
|
|
482
|
+
!CSS_STYLE_PROPERTIES.has(canonicalName) &&
|
|
483
|
+
!CSS_DECLARATION_PROPERTIES.has(name)) {
|
|
484
|
+
throw new Error(`[foldkit] h.Style property ${JSON.stringify(name)} is not a ` +
|
|
485
|
+
'CSSStyleDeclaration property supported by Foldkit. Use a known ' +
|
|
486
|
+
'camel-case or declaration name, or a custom property beginning --.');
|
|
487
|
+
}
|
|
488
|
+
const declarationName = cssDeclarationName(name);
|
|
489
|
+
const existingOwner = declarationOwners.get(declarationName);
|
|
490
|
+
if (existingOwner !== undefined) {
|
|
491
|
+
throw new Error(`[foldkit] h.Style properties ${JSON.stringify(existingOwner)} and ` +
|
|
492
|
+
`${JSON.stringify(name)} both name the ${JSON.stringify(declarationName)} ` +
|
|
493
|
+
'CSS declaration. Keep one spelling so the server, fresh client, ' +
|
|
494
|
+
'and hydration have one owner for it.');
|
|
495
|
+
}
|
|
496
|
+
declarationOwners.set(declarationName, name);
|
|
497
|
+
const value = style[name];
|
|
498
|
+
if (typeof value !== 'string') {
|
|
499
|
+
throw new Error(`[foldkit] h.Style value for ${JSON.stringify(name)} must be a ` +
|
|
500
|
+
'string. Browser CSSOM assignment coerces other values while server ' +
|
|
501
|
+
'serialization cannot preserve the same authored declaration.');
|
|
502
|
+
}
|
|
503
|
+
if (hasUnsafeStyleSyntax(value)) {
|
|
504
|
+
throw new Error(`[foldkit] h.Style value for ${JSON.stringify(name)} contains a ` +
|
|
505
|
+
'unsafe CSS declaration syntax. Assigning one DOM style ' +
|
|
506
|
+
'property rejects that value, while serializing it into a style ' +
|
|
507
|
+
'attribute could activate additional or !important declarations. ' +
|
|
508
|
+
'Keep the value to one declaration.');
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
/** The CSS declaration name corresponding to a CSSStyleDeclaration property.
|
|
513
|
+
*
|
|
514
|
+
* @internal
|
|
515
|
+
*/
|
|
516
|
+
export const serializedStylePropertyName = (name) => {
|
|
517
|
+
return cssDeclarationName(name);
|
|
518
|
+
};
|
|
519
|
+
/** A style object keyed by the declaration names shared by CSSOM and HTML.
|
|
520
|
+
*
|
|
521
|
+
* @internal
|
|
522
|
+
*/
|
|
523
|
+
export const normalizedStyleProperties = (style) => {
|
|
524
|
+
assertStyleIsRepresentable(style);
|
|
525
|
+
return Object.fromEntries(Object.entries(style).map(([name, value]) => [
|
|
526
|
+
cssDeclarationName(name),
|
|
527
|
+
String(value),
|
|
528
|
+
]));
|
|
529
|
+
};
|
|
530
|
+
/** Inline style source whose parsed declarations match fresh CSSOM property
|
|
531
|
+
* assignment, excluding Snabbdom lifecycle controls.
|
|
532
|
+
*
|
|
533
|
+
* @internal
|
|
534
|
+
*/
|
|
535
|
+
export const serializedStyleAttribute = (style) => {
|
|
536
|
+
const declarations = Object.entries(style).flatMap(([name, value]) => STYLE_LIFECYCLE_KEYS.has(name) || typeof value !== 'string' || value === ''
|
|
537
|
+
? []
|
|
538
|
+
: [`${cssDeclarationName(name)}: ${value}`]);
|
|
539
|
+
const serialized = declarations.join('; ');
|
|
540
|
+
return serialized === '' ? undefined : serialized;
|
|
541
|
+
};
|
|
542
|
+
/** The value of an HTML attribute written under any ASCII casing, or
|
|
543
|
+
* `undefined` when no spelling of it is present.
|
|
544
|
+
*
|
|
545
|
+
* @internal */
|
|
546
|
+
export const htmlAttributeValue = (attributes, name) => {
|
|
547
|
+
if (attributes === undefined) {
|
|
548
|
+
return undefined;
|
|
549
|
+
}
|
|
550
|
+
let value;
|
|
551
|
+
for (const written of Object.keys(attributes)) {
|
|
552
|
+
if (toHtmlAttributeName(written) === name) {
|
|
553
|
+
value = attributes[written];
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return value;
|
|
557
|
+
};
|
|
@@ -13,21 +13,82 @@
|
|
|
13
13
|
*/
|
|
14
14
|
export declare const acceptsHtml: (acceptHeader: string | undefined) => boolean;
|
|
15
15
|
/**
|
|
16
|
-
* Merges
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* is not duplicated, and otherwise
|
|
16
|
+
* Merges a field name into an existing `Vary` header value, parsing it as a
|
|
17
|
+
* comma-separated, case-insensitive list of field names. `Vary: *` already
|
|
18
|
+
* varies on everything and is returned unchanged, a token already present (in
|
|
19
|
+
* any case, and distinct from a longer name that merely starts with it, such as
|
|
20
|
+
* `Accept-Language` beside `Accept`) is not duplicated, and otherwise the field
|
|
21
|
+
* name is appended.
|
|
22
|
+
*
|
|
23
|
+
* A host whose response depends on a request header must declare that header in
|
|
24
|
+
* `Vary` so a shared cache does not serve one client's representation to
|
|
25
|
+
* another. The dev host, reference server, and scaffold merge through this one
|
|
26
|
+
* helper rather than assigning `Vary`, which would drop the fields already
|
|
27
|
+
* there.
|
|
28
|
+
*
|
|
29
|
+
* @experimental Ships from `foldkit/experimental/server`; expect breaking changes while the API settles.
|
|
30
|
+
*/
|
|
31
|
+
export declare const varyWith: (existing: string | undefined, fieldName: string) => string;
|
|
32
|
+
/**
|
|
33
|
+
* Merges the `Accept` field name into an existing `Vary` header value.
|
|
21
34
|
*
|
|
22
35
|
* A page host that negotiates HTML on the `Accept` header must declare that in
|
|
23
36
|
* `Vary` so a shared cache does not serve one representation in place of the
|
|
24
|
-
* other.
|
|
25
|
-
* helper so a comma-joined field-name string like `Accept-Language` is never
|
|
26
|
-
* mistaken for the `Accept` field.
|
|
37
|
+
* other.
|
|
27
38
|
*
|
|
28
39
|
* @experimental Ships from `foldkit/experimental/server`; expect breaking changes while the API settles.
|
|
29
40
|
*/
|
|
30
41
|
export declare const varyWithAccept: (existing: string | undefined) => string;
|
|
42
|
+
/**
|
|
43
|
+
* Resolves a request target against the origin the host serves, returning the
|
|
44
|
+
* absolute URL to hand the server entry, or `undefined` when the target names
|
|
45
|
+
* a different origin.
|
|
46
|
+
*
|
|
47
|
+
* A request target is not a URL. HTTP allows origin-form (`/page?q=1`) and
|
|
48
|
+
* absolute-form (`http://host/page`), and a client can send a network-path
|
|
49
|
+
* reference (`//elsewhere.example/page`) that resolves against no scheme at
|
|
50
|
+
* all. Resolving one of those against the host origin silently adopts the
|
|
51
|
+
* origin the client wrote, so an entry that derives redirects, canonical URLs,
|
|
52
|
+
* cookie domains, or tenant selection from `Request.url` would take them from
|
|
53
|
+
* the request rather than from the deployment. Only a target that resolves to
|
|
54
|
+
* the host's own origin is accepted; a host behind a proxy that must serve a
|
|
55
|
+
* different public origin passes that origin in explicitly.
|
|
56
|
+
*
|
|
57
|
+
* @experimental Ships from `foldkit/experimental/server`; expect breaking changes while the API settles.
|
|
58
|
+
*/
|
|
59
|
+
export declare const resolveRequestUrl: (requestTarget: string, origin: string) => string | undefined;
|
|
60
|
+
/** How a host should read a request that no static file answered.
|
|
61
|
+
*
|
|
62
|
+
* `PathAsset`: the path names an asset, whatever the request headers say, so a
|
|
63
|
+
* refusal is the same for every client and needs no `Vary`.
|
|
64
|
+
*
|
|
65
|
+
* `DestinationAsset`: the path could be a page, and only the request's
|
|
66
|
+
* `Sec-Fetch-Dest` says otherwise. A refusal here depends on that header, so it
|
|
67
|
+
* has to declare it in `Vary` before a shared cache may store it: a cross-site
|
|
68
|
+
* script request would otherwise seed a cached 404 for a real page.
|
|
69
|
+
*
|
|
70
|
+
* `Page`: nothing marks it as an asset, so the usual `Accept` negotiation
|
|
71
|
+
* decides.
|
|
72
|
+
*
|
|
73
|
+
* @experimental Ships from `foldkit/experimental/server`; expect breaking changes while the API settles.
|
|
74
|
+
*/
|
|
75
|
+
export type RequestClassification = 'PathAsset' | 'DestinationAsset' | 'Page';
|
|
76
|
+
/**
|
|
77
|
+
* Classifies a request that no static file answered, from its path and, when
|
|
78
|
+
* the client sent one, its `Sec-Fetch-Dest`.
|
|
79
|
+
*
|
|
80
|
+
* A host answers a static miss by content negotiation, and a browser requests
|
|
81
|
+
* scripts, stylesheets, and images with `Accept: */*`, which accepts HTML. A
|
|
82
|
+
* request for a hashed asset that is no longer deployed would therefore be
|
|
83
|
+
* answered with the application shell at 200, so a stale bundle reads as a
|
|
84
|
+
* blank page rather than the 404 it is.
|
|
85
|
+
*
|
|
86
|
+
* The path is read first, so a request the URL alone settles is never made to
|
|
87
|
+
* depend on a header the client may or may not send.
|
|
88
|
+
*
|
|
89
|
+
* @experimental Ships from `foldkit/experimental/server`; expect breaking changes while the API settles.
|
|
90
|
+
*/
|
|
91
|
+
export declare const classifyRequest: (requestUrl: string, fetchDestination?: string) => RequestClassification;
|
|
31
92
|
/**
|
|
32
93
|
* Decides whether a request path resolves to the `index.html` template, the
|
|
33
94
|
* way a static file server resolves it: percent-decoded, backslashes and
|
|
@@ -39,4 +100,37 @@ export declare const varyWithAccept: (existing: string | undefined) => string;
|
|
|
39
100
|
* @experimental Ships from `foldkit/experimental/server`; expect breaking changes while the API settles.
|
|
40
101
|
*/
|
|
41
102
|
export declare const resolvesToIndexHtml: (requestUrl: string) => boolean;
|
|
103
|
+
/**
|
|
104
|
+
* The answer a host gives for the methods it cannot hand to a server entry.
|
|
105
|
+
*
|
|
106
|
+
* The WHATWG `Request` constructor rejects `CONNECT`, `TRACE`, and `TRACK`, so
|
|
107
|
+
* an entry can never be handed one: forwarding it turns a malformed request
|
|
108
|
+
* into a 500. A host refuses them itself, with `405` and an `Allow` header
|
|
109
|
+
* naming what it does forward.
|
|
110
|
+
*
|
|
111
|
+
* On Node only `TRACE` reaches this rule. The HTTP parser rejects `TRACK` with
|
|
112
|
+
* a 400 before any handler runs, and `CONNECT` arrives on its own event rather
|
|
113
|
+
* than as an ordinary request. Both are named anyway, so a host on another
|
|
114
|
+
* runtime refuses them rather than handing one to `new Request`.
|
|
115
|
+
*
|
|
116
|
+
* Every other method reaches the entry, `OPTIONS` included. A preflight is a
|
|
117
|
+
* question about a resource, so the application answers it: the entry can allow
|
|
118
|
+
* one origin for one route and refuse it for another, which no host-level
|
|
119
|
+
* policy could express. The Vite dev host applies Vite's CORS option only to
|
|
120
|
+
* Vite-owned modules and assets. An application preflight therefore reaches
|
|
121
|
+
* the entry rather than middleware a deployed host has no counterpart for.
|
|
122
|
+
*
|
|
123
|
+
* @experimental Ships from `foldkit/experimental/server`; expect breaking changes while the API settles.
|
|
124
|
+
*/
|
|
125
|
+
export declare const HOST_METHOD_ANSWERS: Readonly<{
|
|
126
|
+
refusedStatus: number;
|
|
127
|
+
allow: string;
|
|
128
|
+
}>;
|
|
129
|
+
/**
|
|
130
|
+
* Whether a host refuses this method itself instead of calling the server
|
|
131
|
+
* entry. See {@link HOST_METHOD_ANSWERS}.
|
|
132
|
+
*
|
|
133
|
+
* @experimental Ships from `foldkit/experimental/server`; expect breaking changes while the API settles.
|
|
134
|
+
*/
|
|
135
|
+
export declare const isHostSettledMethod: (method: string) => boolean;
|
|
42
136
|
//# sourceMappingURL=host.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../../src/experimental/server/host.ts"],"names":[],"mappings":"AA+DA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GAAI,cAAc,MAAM,GAAG,SAAS,KAAG,OAqB9D,CAAA;AAED
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../../src/experimental/server/host.ts"],"names":[],"mappings":"AA+DA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GAAI,cAAc,MAAM,GAAG,SAAS,KAAG,OAqB9D,CAAA;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,QAAQ,GACnB,UAAU,MAAM,GAAG,SAAS,EAC5B,WAAW,MAAM,KAChB,MAgBF,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GAAI,UAAU,MAAM,GAAG,SAAS,KAAG,MAChC,CAAA;AAE9B;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,GAC5B,eAAe,MAAM,EACrB,QAAQ,MAAM,KACb,MAAM,GAAG,SAqBX,CAAA;AA4ED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,qBAAqB,GAAG,WAAW,GAAG,kBAAkB,GAAG,MAAM,CAAA;AAE7E;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,eAAe,GAC1B,YAAY,MAAM,EAClB,mBAAmB,MAAM,KACxB,qBA+BF,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAAI,YAAY,MAAM,KAAG,OAkBxD,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC;IACzC,aAAa,EAAE,MAAM,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;CACd,CAGA,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,QAAQ,MAAM,KAAG,OAKpD,CAAA"}
|