@webjsdev/core 0.7.50 → 0.7.51

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 (57) hide show
  1. package/dist/webjs-core-browser.js +5 -5
  2. package/dist/webjs-core-browser.js.map +3 -3
  3. package/dist/webjs-core-testing.js +20 -20
  4. package/dist/webjs-core-testing.js.map +3 -3
  5. package/dist/webjs-core.js +7 -7
  6. package/dist/webjs-core.js.map +3 -3
  7. package/index-browser.js +1 -1
  8. package/index.d.ts +1 -1
  9. package/index.js +1 -1
  10. package/package.json +1 -1
  11. package/src/component/lifecycle.js +1481 -0
  12. package/src/component/reactive.js +171 -0
  13. package/src/component/server-element.js +248 -0
  14. package/src/component.js +2 -1896
  15. package/src/render-client/parts.js +2007 -0
  16. package/src/render-client/reconciler.js +388 -0
  17. package/src/render-client/template-compiler.js +525 -0
  18. package/src/render-client.js +1 -2938
  19. package/src/render-server/attrs.js +160 -0
  20. package/src/render-server/dsd.js +445 -0
  21. package/src/render-server/html-scan.js +402 -0
  22. package/src/render-server/slots.js +213 -0
  23. package/src/render-server/stream.js +163 -0
  24. package/src/render-server/template-renderer.js +923 -0
  25. package/src/render-server/text.js +163 -0
  26. package/src/render-server.js +6 -2405
  27. package/src/router-client/anchors.js +44 -0
  28. package/src/router-client/boundaries.js +221 -0
  29. package/src/router-client/constants.js +187 -0
  30. package/src/router-client/diagnostics.js +377 -0
  31. package/src/router-client/dom-differ.js +709 -0
  32. package/src/router-client/dom-parse.js +150 -0
  33. package/src/router-client/events.js +139 -0
  34. package/src/router-client/fetch-apply.js +387 -0
  35. package/src/router-client/form-encoder.js +135 -0
  36. package/src/router-client/frames.js +329 -0
  37. package/src/router-client/head-merge.js +344 -0
  38. package/src/router-client/nav-error.js +105 -0
  39. package/src/router-client/navigator.js +781 -0
  40. package/src/router-client/prefetch.js +824 -0
  41. package/src/router-client/scroll.js +259 -0
  42. package/src/router-client/snapshot-cache.js +97 -0
  43. package/src/router-client/state.js +92 -0
  44. package/src/router-client/stream.js +206 -0
  45. package/src/router-client/swap.js +451 -0
  46. package/src/router-client/upgrade.js +261 -0
  47. package/src/router-client/view-transition.js +223 -0
  48. package/src/router-client.d.ts +10 -1
  49. package/src/router-client.js +75 -5345
  50. package/src/slot/assignment.js +304 -0
  51. package/src/slot/interception.js +558 -0
  52. package/src/slot/polyfills.js +365 -0
  53. package/src/slot/project.js +432 -0
  54. package/src/slot/sensors.js +303 -0
  55. package/src/slot/state.js +245 -0
  56. package/src/slot/symbols.js +85 -0
  57. package/src/slot.js +7 -2199
@@ -0,0 +1,163 @@
1
+ import NAMED_ENTITIES, { LEGACY_NAMES } from '../html-entities.js';
2
+
3
+ /**
4
+ * Name-case conversion and HTML character-reference decoding.
5
+ *
6
+ * A leaf like `html-scan.js`, and for the same reason: `template-renderer.js`
7
+ * needs `kebabCase` to serialize property-binding names, and taking it from
8
+ * here rather than from `dsd.js` is half of what keeps the two acyclic.
9
+ */
10
+
11
+ /** @param {string} s */
12
+ export function camelCase(s) {
13
+ return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
14
+ }
15
+
16
+ /**
17
+ * Inverse of camelCase. `userName` -> `user-name`, `userID` -> `user-i-d`.
18
+ * Used to serialize property-binding names into HTML attribute names,
19
+ * which are case-insensitive in the parser. The original JS property
20
+ * name is recovered via camelCase() on the consumer side.
21
+ *
22
+ * @param {string} s
23
+ */
24
+ export function kebabCase(s) {
25
+ return s.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
26
+ }
27
+
28
+ /** windows-1252 mappings the HTML tokenizer applies to the C1 range. */
29
+ const C1_REPLACEMENTS = {
30
+ 0x80: 0x20ac, 0x82: 0x201a, 0x83: 0x0192, 0x84: 0x201e, 0x85: 0x2026,
31
+ 0x86: 0x2020, 0x87: 0x2021, 0x88: 0x02c6, 0x89: 0x2030, 0x8a: 0x0160,
32
+ 0x8b: 0x2039, 0x8c: 0x0152, 0x8e: 0x017d, 0x91: 0x2018, 0x92: 0x2019,
33
+ 0x93: 0x201c, 0x94: 0x201d, 0x95: 0x2022, 0x96: 0x2013, 0x97: 0x2014,
34
+ 0x98: 0x02dc, 0x99: 0x2122, 0x9a: 0x0161, 0x9b: 0x203a, 0x9c: 0x0153,
35
+ 0x9e: 0x017e, 0x9f: 0x0178,
36
+ };
37
+
38
+ // The trailing `;` is OPTIONAL on the named arm, because a browser decodes a
39
+ // legacy semicolon-less reference too. The callback decides which of the two
40
+ // forms it has; see `decodeNamed`.
41
+ const CHAR_REF = /&(?:#(\d+);?|#[xX]([0-9a-fA-F]+);?|([a-zA-Z][a-zA-Z0-9]*)(;?))/g;
42
+ // A Map, not the imported object, so a name that collides with something on
43
+ // `Object.prototype` misses instead of returning a function. See `decodeNamed`.
44
+ const NAMED = new Map(Object.entries(NAMED_ENTITIES));
45
+ const LEGACY = new Set(LEGACY_NAMES);
46
+
47
+ /**
48
+ * Decode HTML character references in an attribute value.
49
+ *
50
+ * `parseAttrs` hands back the literal characters between the quote marks, so
51
+ * nothing has decoded them yet, while a browser decodes EVERY reference before
52
+ * any reader sees the value. This closes that gap (#1341): the full WHATWG
53
+ * named table plus decimal and hexadecimal numeric references, with the
54
+ * tokenizer's own numeric fix-ups (null, a surrogate, and anything past
55
+ * U+10FFFF become U+FFFD; the C1 range maps through windows-1252). It replaced
56
+ * a three-entity `unescapeAttr`, which left a `String`-typed prop undecoded
57
+ * entirely and turned `&lt;script&gt;` into the half-decoded `<script&gt;`.
58
+ *
59
+ * SINGLE PASS on purpose. A replacement is never rescanned, so `&amp;lt;`
60
+ * decodes to the literal `&lt;` and never to `<`. The old function got that
61
+ * from replacing `&amp;` last, which does not generalise past three entities.
62
+ *
63
+ * The 106 legacy semicolon-LESS names are covered too, because a browser really
64
+ * does decode them and measurably: Chromium, Firefox, and WebKit all hand a
65
+ * reader U+00A0 for `s="&nbsp"`. Leaving them literal would have been a value
66
+ * divergence of exactly the kind this function exists to remove, not a
67
+ * harmless non-goal. The rule that governs them, in an ATTRIBUTE value, is a
68
+ * one-character LOOKAHEAD rather than deep tokenizer state, which is why it is
69
+ * implementable here. `decodeNamed` carries the rule and the reason it takes
70
+ * the shape it does; the short version is that `&nbsp` at the end of a value
71
+ * decodes while `&nbspx`, `&nbsp=x`, and `&notin` stay literal, all three
72
+ * verified against the three engines.
73
+ *
74
+ * The same function also decodes `data-webjs-fallback`, which is MARKUP, where
75
+ * the tokenizer applies no such carve-out. Using the attribute rule there is
76
+ * deliberate and strictly conservative: that payload is written by
77
+ * `escapeAttr`, which emits only `&amp;` / `&quot;` / `&lt;`, so every
78
+ * reference in it is semicolon-terminated and never reaches this path.
79
+ *
80
+ * @param {string} s
81
+ * @returns {string}
82
+ */
83
+ export function decodeAttrEntities(s) {
84
+ // Load bearing, not a micro-optimisation: skipping the scan for the common
85
+ // no-`&` value is what makes this cheaper per attribute than the three
86
+ // chained `replace` calls it replaced, which paid for three passes always.
87
+ if (s.indexOf('&') === -1) return s;
88
+ return s.replace(CHAR_REF, (match, dec, hex, name, semi, offset) => {
89
+ if (dec !== undefined) return fromCodePoint(parseInt(dec, 10));
90
+ if (hex !== undefined) return fromCodePoint(parseInt(hex, 16));
91
+ return decodeNamed(match, name, semi === ';', s[offset + match.length]);
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Resolve one named reference, semicolon-terminated or legacy.
97
+ *
98
+ * The table is read through a `Map` rather than by indexing the imported
99
+ * object. An object-literal lookup resolves through `Object.prototype`, so
100
+ * `&constructor;` / `&toString;` / `&hasOwnProperty;` and four more returned a
101
+ * FUNCTION instead of `undefined` and threw on the spread in
102
+ * `codePointsToString`, a path `seedServerAttrs` reaches for every attribute of
103
+ * every custom element, which rendered the whole component as an SSR error box.
104
+ * A browser leaves those literal, since they are not named references, so the
105
+ * throw was a divergence of exactly the kind this file exists to remove. A
106
+ * `Map` has no prototype chain to fall through, which closes the shape rather
107
+ * than guarding one call site.
108
+ *
109
+ * WHY THERE IS NO LONGEST-PREFIX LOOP. The tokenizer consumes the longest name
110
+ * in the table, so `&notin` is `&not` followed by `in`. Here that always
111
+ * collapses to the whole name: `CHAR_REF` captures `[a-zA-Z][a-zA-Z0-9]*`, so a
112
+ * prefix SHORTER than the name is by construction followed by an ASCII
113
+ * alphanumeric, which is precisely when the attribute carve-out declines to
114
+ * decode. A loop over shorter prefixes could therefore only ever return
115
+ * `match`, which is what falling through to the end already does. `&notin`
116
+ * stays literal either way, verified against the three engines, though note it
117
+ * gets there by not being a legacy name rather than by the carve-out.
118
+ *
119
+ * That same greediness means an ASCII alphanumeric character can never follow
120
+ * the match: whatever follows is by construction not `[a-zA-Z0-9]`, or the
121
+ * capture would have eaten it. So only the `=` check is needed for the
122
+ * attribute carve-out. `&nbspx` is literal because `nbspx` is not a legacy
123
+ * name, NOT because of the lookahead; `&nbsp=x` is the shape the lookahead
124
+ * actually decides.
125
+ *
126
+ * @param {string} match the whole matched reference, returned unchanged when nothing decodes
127
+ * @param {string} name the name, with no `&` and no `;`
128
+ * @param {boolean} hadSemi
129
+ * @param {string|undefined} nextChar the character after the match, if any
130
+ * @returns {string}
131
+ */
132
+ export function decodeNamed(match, name, hadSemi, nextChar) {
133
+ if (hadSemi) {
134
+ const cp = NAMED.get(name);
135
+ return cp === undefined ? match : codePointsToString(cp);
136
+ }
137
+ if (!LEGACY.has(name)) return match;
138
+ // The attribute carve-out: a legacy name decodes only when what follows is
139
+ // neither `=` nor an ASCII alphanumeric. Nothing following at all (the end of
140
+ // the value) decodes, which is the common `s="&nbsp"` shape. Because CHAR_REF
141
+ // captures greedily, what follows is never an ASCII alphanumeric, so only `=`
142
+ // needs to be checked here.
143
+ if (nextChar === '=') return match;
144
+ // Every legacy name is in the table under the same name, asserted by a test
145
+ // rather than left to trust, so this cannot miss.
146
+ return codePointsToString(NAMED.get(name));
147
+ }
148
+
149
+ /** @param {number|number[]} cp */
150
+ export function codePointsToString(cp) {
151
+ return typeof cp === 'number' ? String.fromCodePoint(cp) : String.fromCodePoint(...cp);
152
+ }
153
+
154
+ /**
155
+ * The tokenizer's numeric character reference fix-ups.
156
+ * @param {number} n
157
+ * @returns {string}
158
+ */
159
+ export function fromCodePoint(n) {
160
+ if (n === 0 || n > 0x10ffff || (n >= 0xd800 && n <= 0xdfff)) return '\uFFFD';
161
+ if (C1_REPLACEMENTS[n] !== undefined) return String.fromCodePoint(C1_REPLACEMENTS[n]);
162
+ return String.fromCodePoint(n);
163
+ }