foldkit 0.147.0 → 0.148.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.
Files changed (61) hide show
  1. package/dist/buildToken.d.ts +3 -0
  2. package/dist/buildToken.d.ts.map +1 -0
  3. package/dist/buildToken.js +21 -0
  4. package/dist/controlledDomState.d.ts +25 -0
  5. package/dist/controlledDomState.d.ts.map +1 -0
  6. package/dist/controlledDomState.js +240 -0
  7. package/dist/cssStyleProperties.d.ts +6 -0
  8. package/dist/cssStyleProperties.d.ts.map +1 -0
  9. package/dist/cssStyleProperties.js +91 -0
  10. package/dist/domReflection.d.ts +73 -0
  11. package/dist/domReflection.d.ts.map +1 -0
  12. package/dist/domReflection.js +557 -0
  13. package/dist/experimental/server/host.d.ts +102 -8
  14. package/dist/experimental/server/host.d.ts.map +1 -1
  15. package/dist/experimental/server/host.js +203 -13
  16. package/dist/experimental/server/public.d.ts +2 -2
  17. package/dist/experimental/server/public.d.ts.map +1 -1
  18. package/dist/experimental/server/public.js +1 -1
  19. package/dist/experimental/server/serialize.d.ts +15 -5
  20. package/dist/experimental/server/serialize.d.ts.map +1 -1
  21. package/dist/experimental/server/serialize.js +367 -144
  22. package/dist/experimental/server/server.d.ts +55 -14
  23. package/dist/experimental/server/server.d.ts.map +1 -1
  24. package/dist/experimental/server/server.js +544 -21
  25. package/dist/experimental/server/template.d.ts +8 -0
  26. package/dist/experimental/server/template.d.ts.map +1 -1
  27. package/dist/experimental/server/template.js +437 -2
  28. package/dist/html/index.d.ts.map +1 -1
  29. package/dist/html/index.js +436 -29
  30. package/dist/hydrate.d.ts +1 -1
  31. package/dist/hydrate.d.ts.map +1 -1
  32. package/dist/hydrate.js +449 -121
  33. package/dist/hydrationMarkers.d.ts +15 -0
  34. package/dist/hydrationMarkers.d.ts.map +1 -0
  35. package/dist/hydrationMarkers.js +70 -0
  36. package/dist/nativeInnerHtml.d.ts +13 -0
  37. package/dist/nativeInnerHtml.d.ts.map +1 -0
  38. package/dist/nativeInnerHtml.js +30 -0
  39. package/dist/propertyProvenance.d.ts +33 -0
  40. package/dist/propertyProvenance.d.ts.map +1 -0
  41. package/dist/propertyProvenance.js +78 -0
  42. package/dist/propsModule.d.ts.map +1 -1
  43. package/dist/propsModule.js +149 -15
  44. package/dist/runtime/public.d.ts +1 -1
  45. package/dist/runtime/public.d.ts.map +1 -1
  46. package/dist/runtime/runtime.d.ts +30 -6
  47. package/dist/runtime/runtime.d.ts.map +1 -1
  48. package/dist/runtime/runtime.js +230 -27
  49. package/dist/snabbdom/attributes.d.ts.map +1 -1
  50. package/dist/snabbdom/attributes.js +65 -37
  51. package/dist/snabbdom/style.d.ts.map +1 -1
  52. package/dist/snabbdom/style.js +53 -34
  53. package/dist/test/apps/attributes.d.ts +1 -0
  54. package/dist/test/apps/attributes.d.ts.map +1 -1
  55. package/dist/test/apps/attributes.js +8 -1
  56. package/dist/test/apps/login.js +1 -1
  57. package/dist/test/matchers.d.ts.map +1 -1
  58. package/dist/test/matchers.js +2 -1
  59. package/dist/test/scene.d.ts.map +1 -1
  60. package/dist/test/scene.js +2 -1
  61. 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 the `Accept` field name into an existing `Vary` header value, parsing
17
- * it as a comma-separated, case-insensitive list of field names. `Vary: *`
18
- * already varies on everything and is returned unchanged, an existing `Accept`
19
- * token (in any case, and distinct from `Accept-Language` or `Accept-Encoding`)
20
- * is not duplicated, and otherwise `Accept` is appended.
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. The dev host, reference server, and scaffold merge through this one
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;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,cAAc,GAAI,UAAU,MAAM,GAAG,SAAS,KAAG,MAgB7D,CAAA;AAiBD;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAAI,YAAY,MAAM,KAAG,OAkBxD,CAAA"}
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"}