tungsten 0.2.24 → 0.3.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 (87) hide show
  1. package/dist/ContentNode.d.ts +7 -0
  2. package/dist/ContentNode.d.ts.map +1 -1
  3. package/dist/Instance/Instance.d.ts.map +1 -1
  4. package/dist/Instance/Instance.js +7 -5
  5. package/dist/Instance/gotoRedirect.sxyt.d.ts +2 -0
  6. package/dist/Instance/gotoRedirect.sxyt.d.ts.map +1 -0
  7. package/dist/Instance/gotoRedirect.sxyt.js +148 -0
  8. package/dist/Servers/DevServer.d.ts +7 -0
  9. package/dist/Servers/DevServer.d.ts.map +1 -1
  10. package/dist/Servers/DevServer.js +38 -1
  11. package/dist/Servers/routing.d.ts.map +1 -1
  12. package/dist/Servers/routing.js +10 -1
  13. package/dist/build/compileJavascript.d.ts.map +1 -1
  14. package/dist/build/compileJavascript.js +86 -1
  15. package/dist/build/compileStyles.d.ts +1 -1
  16. package/dist/build/compileStyles.d.ts.map +1 -1
  17. package/dist/build/compileStyles.js +10 -4
  18. package/dist/build/compileUserApp.d.ts.map +1 -1
  19. package/dist/build/compileUserApp.js +7 -2
  20. package/dist/components/RawHtml.d.ts +36 -0
  21. package/dist/components/RawHtml.d.ts.map +1 -0
  22. package/dist/components/RawHtml.js +30 -0
  23. package/dist/components/RawHtml.sxyt.d.ts +2 -0
  24. package/dist/components/RawHtml.sxyt.d.ts.map +1 -0
  25. package/dist/components/RawHtml.sxyt.js +104 -0
  26. package/dist/dev/liveReloadHub.d.ts +6 -0
  27. package/dist/dev/liveReloadHub.d.ts.map +1 -0
  28. package/dist/dev/liveReloadHub.js +12 -0
  29. package/dist/helpers/getFetchCompatibleHeaders.d.ts.map +1 -1
  30. package/dist/hydrate/hydrate.d.ts.map +1 -1
  31. package/dist/hydrate/hydrate.js +11 -4
  32. package/dist/hydrate/reconcileHydratedAttributes.d.ts +24 -0
  33. package/dist/hydrate/reconcileHydratedAttributes.d.ts.map +1 -0
  34. package/dist/hydrate/reconcileHydratedAttributes.js +73 -0
  35. package/dist/hydrate/reconcileHydratedAttributes.sxyt.d.ts +2 -0
  36. package/dist/hydrate/reconcileHydratedAttributes.sxyt.d.ts.map +1 -0
  37. package/dist/hydrate/reconcileHydratedAttributes.sxyt.js +173 -0
  38. package/dist/indexInner.d.ts +4 -0
  39. package/dist/indexInner.d.ts.map +1 -1
  40. package/dist/indexInner.js +3 -0
  41. package/dist/innerHtmlFromProps.d.ts +16 -0
  42. package/dist/innerHtmlFromProps.d.ts.map +1 -0
  43. package/dist/innerHtmlFromProps.js +31 -0
  44. package/dist/innerHtmlFromProps.sxyt.d.ts +2 -0
  45. package/dist/innerHtmlFromProps.sxyt.d.ts.map +1 -0
  46. package/dist/innerHtmlFromProps.sxyt.js +99 -0
  47. package/dist/postcssPlugins/TransformCssNames.d.ts +14 -1
  48. package/dist/postcssPlugins/TransformCssNames.d.ts.map +1 -1
  49. package/dist/postcssPlugins/TransformCssNames.js +97 -95
  50. package/dist/rawHtml/HtmlAllowList.d.ts +17 -0
  51. package/dist/rawHtml/HtmlAllowList.d.ts.map +1 -0
  52. package/dist/rawHtml/HtmlAllowList.js +1 -0
  53. package/dist/rawHtml/decodeHtmlEntities.d.ts +8 -0
  54. package/dist/rawHtml/decodeHtmlEntities.d.ts.map +1 -0
  55. package/dist/rawHtml/decodeHtmlEntities.js +105 -0
  56. package/dist/rawHtml/htmlParsingTags.d.ts +23 -0
  57. package/dist/rawHtml/htmlParsingTags.d.ts.map +1 -0
  58. package/dist/rawHtml/htmlParsingTags.js +55 -0
  59. package/dist/rawHtml/parseHtml.d.ts +35 -0
  60. package/dist/rawHtml/parseHtml.d.ts.map +1 -0
  61. package/dist/rawHtml/parseHtml.js +214 -0
  62. package/dist/rawHtml/parseHtml.sxyt.d.ts +2 -0
  63. package/dist/rawHtml/parseHtml.sxyt.d.ts.map +1 -0
  64. package/dist/rawHtml/parseHtml.sxyt.js +239 -0
  65. package/dist/rawHtml/safeHtmlAllowList.d.ts +18 -0
  66. package/dist/rawHtml/safeHtmlAllowList.d.ts.map +1 -0
  67. package/dist/rawHtml/safeHtmlAllowList.js +47 -0
  68. package/dist/rawHtml/tokenizeHtml.d.ts +25 -0
  69. package/dist/rawHtml/tokenizeHtml.d.ts.map +1 -0
  70. package/dist/rawHtml/tokenizeHtml.js +148 -0
  71. package/dist/render/processHead.d.ts.map +1 -1
  72. package/dist/render/processHead.js +10 -0
  73. package/dist/render/processProps.d.ts.map +1 -1
  74. package/dist/render/processProps.js +14 -2
  75. package/dist/render/processProps.sxyt.d.ts +2 -0
  76. package/dist/render/processProps.sxyt.d.ts.map +1 -0
  77. package/dist/render/processProps.sxyt.js +90 -0
  78. package/dist/render/renderingTreeToHtml.d.ts.map +1 -1
  79. package/dist/render/renderingTreeToHtml.js +8 -0
  80. package/dist/routing/RouteTypes.d.ts +1 -1
  81. package/dist/routing/RouteTypes.d.ts.map +1 -1
  82. package/dist/routing/routeSymbols.d.ts +9 -0
  83. package/dist/routing/routeSymbols.d.ts.map +1 -0
  84. package/dist/routing/routeSymbols.js +4 -0
  85. package/dist/update/integrateUpdateVdom.d.ts.map +1 -1
  86. package/dist/update/integrateUpdateVdom.js +28 -7
  87. package/package.json +3 -3
@@ -0,0 +1,30 @@
1
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
2
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
3
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
4
+ import { TungstenSimpleComponent } from "../Components.js";
5
+ import { parseHtml } from "../rawHtml/parseHtml.js";
6
+ import { safeHtmlAllowList } from "../rawHtml/safeHtmlAllowList.js";
7
+ /**
8
+ * Renders a string of HTML as real content.
9
+ *
10
+ * The markup is parsed into an ordinary Tungsten tree, so it renders, hydrates
11
+ * and reconciles like any other content — unlike `dangerouslySetInnerHTML`,
12
+ * which hands the browser a region Tungsten cannot see into.
13
+ *
14
+ * Note that the parsed elements are not part of any component's scoped CSS, so
15
+ * component styles reach them only through `:i()` / `:ignore()`.
16
+ */
17
+ export class RawHtml extends TungstenSimpleComponent {
18
+ constructor(...args) {
19
+ super(...args);
20
+ _defineProperty(this, "content", ({
21
+ html,
22
+ sanitize,
23
+ allowList,
24
+ safe
25
+ }) => parseHtml(html, {
26
+ sanitize,
27
+ allowList: allowList ?? (safe === true ? safeHtmlAllowList : undefined)
28
+ }));
29
+ }
30
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=RawHtml.sxyt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RawHtml.sxyt.d.ts","sourceRoot":"","sources":["../../src/components/RawHtml.sxyt.ts"],"names":[],"mappings":""}
@@ -0,0 +1,104 @@
1
+ import { expect } from 'chai';
2
+ import { Window } from 'happy-dom';
3
+ import { describe } from 'sxy-test-runner';
4
+ import { RawHtml } from "./RawHtml.js";
5
+ import { createTestApp } from "../testing/index.js";
6
+ // Mounts RawHtml and returns the markup it actually put in the page. The parser
7
+ // itself is covered in rawHtml/parseHtml.sxyt.ts; what is under test here is
8
+ // that the component hands its props to the parser correctly.
9
+ const mount = async props => {
10
+ const window = new Window();
11
+ const testApp = await createTestApp(RawHtml, {
12
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
13
+ document: window.document,
14
+ props
15
+ });
16
+ try {
17
+ return {
18
+ html: testApp.container.innerHTML,
19
+ text: testApp.container.textContent,
20
+ elements: testApp.container.querySelectorAll('*').length
21
+ };
22
+ } finally {
23
+ await testApp.unmount();
24
+ }
25
+ };
26
+ const render = async props => (await mount(props)).html;
27
+ describe('RawHtml', ({
28
+ it
29
+ }) => {
30
+ it('renders parsed markup as real elements', async () => {
31
+ const html = await render({
32
+ html: '<p>Bonuses for <strong>this month</strong>.</p>'
33
+ });
34
+ expect(html).to.contain('<p>');
35
+ expect(html).to.contain('<strong>this month</strong>');
36
+ });
37
+ it('renders nothing for empty markup', async () => {
38
+ // a component always leaves its delimiter comments behind, so the check
39
+ // is that it contributed no content, not that the container is empty
40
+ const {
41
+ text,
42
+ elements
43
+ } = await mount({
44
+ html: ''
45
+ });
46
+ expect(text).to.equal('');
47
+ expect(elements).to.equal(0);
48
+ });
49
+ it('drops scripts by default', async () => {
50
+ const html = await render({
51
+ html: '<p>a</p><script>alert(1)</script>'
52
+ });
53
+ expect(html).to.contain('<p>a</p>');
54
+ expect(html).to.not.contain('script');
55
+ expect(html).to.not.contain('alert');
56
+ });
57
+ it('keeps scripts when sanitizing is turned off', async () => {
58
+ expect(await render({
59
+ html: '<div><script>x()</script></div>',
60
+ sanitize: false
61
+ })).to.contain('<script>x()</script>');
62
+ });
63
+ // `safe` is the shortcut for Tungsten's built-in allow list
64
+ it('applies the built-in allow list when safe is set', async () => {
65
+ const html = await render({
66
+ html: '<p style="position:fixed">a</p><iframe src="//evil"></iframe>',
67
+ safe: true
68
+ });
69
+ expect(html).to.contain('a');
70
+ expect(html).to.not.contain('iframe');
71
+ // the safe list excludes style, which can disguise a page without script
72
+ expect(html).to.not.contain('position:fixed');
73
+ });
74
+ it('does not apply an allow list unless asked', async () => {
75
+ const html = await render({
76
+ html: '<p style="position:fixed">a</p>'
77
+ });
78
+ expect(html).to.contain('position:fixed');
79
+ });
80
+ it('lets an explicit allowList win over safe', async () => {
81
+ const onlyEmphasis = {
82
+ tags: ['em'],
83
+ attributes: []
84
+ };
85
+ // <strong> is on the safe list but not on this one, so if allowList is
86
+ // being applied it is unwrapped; if safe won, it would survive
87
+ const html = await render({
88
+ html: '<em>keep</em><strong>unwrap</strong>',
89
+ safe: true,
90
+ allowList: onlyEmphasis
91
+ });
92
+ expect(html).to.contain('<em>keep</em>');
93
+ expect(html).to.not.contain('<strong>');
94
+ expect(html).to.contain('unwrap');
95
+ });
96
+ it('renders whatever it is given without throwing', async () => {
97
+ const malformed = ['<p>unclosed', '</p>stray', '<p class="unterminated>x', '<<>>', '<!-- open'];
98
+ for (const html of malformed) {
99
+ expect(await render({
100
+ html
101
+ })).to.be.a('string');
102
+ }
103
+ });
104
+ });
@@ -0,0 +1,6 @@
1
+ export type ReloadEventKind = 'js' | 'css';
2
+ type ReloadListener = (kind: ReloadEventKind) => void;
3
+ export declare function onReloadEvent(listener: ReloadListener): () => void;
4
+ export declare function emitReloadEvent(kind: ReloadEventKind): void;
5
+ export {};
6
+ //# sourceMappingURL=liveReloadHub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"liveReloadHub.d.ts","sourceRoot":"","sources":["../../src/dev/liveReloadHub.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,KAAK,CAAA;AAE1C,KAAK,cAAc,GAAG,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAA;AAIrD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,IAAI,CAGlE;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAE3D"}
@@ -0,0 +1,12 @@
1
+ // A process-wide pub/sub so the JS and CSS watchers (compileJavascript, compileStyles),
2
+ // which run and are started before the dev server exists, can notify DevServer's
3
+ // SSE clients whenever a rebuild completes and the browser should refresh. Dev mode only.
4
+ // This is a plain auto-reload signal, not module-level hot patching.
5
+ const listeners = new Set();
6
+ export function onReloadEvent(listener) {
7
+ listeners.add(listener);
8
+ return () => listeners.delete(listener);
9
+ }
10
+ export function emitReloadEvent(kind) {
11
+ for (const listener of listeners) listener(kind);
12
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"getFetchCompatibleHeaders.d.ts","sourceRoot":"","sources":["../../src/helpers/getFetchCompatibleHeaders.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAKtC,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,WAYrD"}
1
+ {"version":3,"file":"getFetchCompatibleHeaders.d.ts","sourceRoot":"","sources":["../../src/helpers/getFetchCompatibleHeaders.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAKtC,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAY/D"}
@@ -1 +1 @@
1
- {"version":3,"file":"hydrate.d.ts","sourceRoot":"","sources":["../../src/hydrate/hydrate.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAQnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AA0BxD,OAAO,EAAE,yBAAyB,EAAoD,MAAM,mBAAmB,CAAA;AAmC/G,wBAAsB,OAAO,CACzB,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,yBAAyB,GAAG,IAAI,EAC/C,cAAc,EAAE,cAAc,iBAyDjC"}
1
+ {"version":3,"file":"hydrate.d.ts","sourceRoot":"","sources":["../../src/hydrate/hydrate.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAQnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AA0BxD,OAAO,EAAE,yBAAyB,EAAoD,MAAM,mBAAmB,CAAA;AAoC/G,wBAAsB,OAAO,CACzB,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,yBAAyB,GAAG,IAAI,EAC/C,cAAc,EAAE,cAAc,iBAyDjC"}
@@ -28,9 +28,10 @@ import { initStores } from 'tungsten/Servers/initStores.js';
28
28
  import { MatchedRoute } from "../routing/MatchedRoute.js";
29
29
  import { isDev } from "../isDev.js";
30
30
  import { warn } from "../appOutput.js";
31
+ import { reconcileHydratedAttributes } from "./reconcileHydratedAttributes.js";
31
32
  import { jsx as _jsx } from "tungsten/jsxRuntime.js";
32
33
  function debug(...texts) {
33
- //console.log(...texts)
34
+ console.log(...texts);
34
35
  //console.log(getStackTrace().split('\n')[2])
35
36
  }
36
37
  function makeDefaultHydrateMeta() {
@@ -546,11 +547,17 @@ async function hydrateNodeComponent(node, parentElement, domPointer, instance, m
546
547
  // export type HTMLBindableElement = HTMLButtonElement | HTMLDataElement | HTMLInputElement | HTMLLIElement
547
548
  // | HTMLMeterElement | HTMLOptionElement | HTMLParamElement | HTMLProgressElement | HTMLSelectElement | HTMLTextAreaElement
548
549
  function hydrateAttributes(node, domNode) {
549
- // !!! We should check whether attributes match here
550
- // Show errors in dev mode
551
- // Auto fix in prod?
552
550
  // handle bind attributes and merge together event handlers
553
551
  processAttributes(node);
552
+ // Bring the server's attributes into line with what this render produced.
553
+ // Runs in production as well as development: leaving a known-wrong value in
554
+ // place is not better in production, it is only quieter. Only genuine
555
+ // differences are written, so a page that matches costs reads and no
556
+ // writes. The report is dev-only, because a mismatch is a bug in the
557
+ // application's rendering rather than something a user can act on.
558
+ if (ContentNodeElementIsHtml(node)) {
559
+ reconcileHydratedAttributes(node, domNode, isDev ? mismatch => warn(`hydration mismatch on <${node[$type]}>:` + ` ${mismatch.name} rendered as ${JSON.stringify(mismatch.expected)}` + ` but the server sent ${JSON.stringify(mismatch.actual)}.` + ` The rendered value has been applied.`) : undefined);
560
+ }
554
561
  debug('--\nhydrateAttributes', domNode);
555
562
  for (const key in node[$props]) {
556
563
  if (StringIsHtmlEvent(key)) {
@@ -0,0 +1,24 @@
1
+ import type { ContentNodeHtml } from '../ContentNode.js';
2
+ export type AttributeMismatch = {
3
+ name: string;
4
+ /** The value the client rendered, or null when it renders no such attribute. */
5
+ expected: string | null;
6
+ /** The value the server's markup carried, or null when it carried none. */
7
+ actual: string | null;
8
+ };
9
+ /**
10
+ * Brings a server-rendered element's attributes into line with what the client
11
+ * has just rendered, and reports every difference.
12
+ *
13
+ * Hydration attaches to markup the server produced. Any attribute whose value
14
+ * the client works out differently — a stored preference, a media query, a time
15
+ * zone — would otherwise keep the server's value for the life of the page,
16
+ * because nothing else revisits it until an unrelated update happens to
17
+ * reconcile that element.
18
+ *
19
+ * Only genuine differences are written. The comparison runs the props through
20
+ * `processProps()`, the same function that produced the markup being compared
21
+ * against, so a matching element costs one attribute read each and no writes.
22
+ */
23
+ export declare function reconcileHydratedAttributes(node: ContentNodeHtml, domNode: HTMLElement, onMismatch?: (mismatch: AttributeMismatch) => void): void;
24
+ //# sourceMappingURL=reconcileHydratedAttributes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcileHydratedAttributes.d.ts","sourceRoot":"","sources":["../../src/hydrate/reconcileHydratedAttributes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAIxD,MAAM,MAAM,iBAAiB,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,gFAAgF;IAChF,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,2EAA2E;IAC3E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB,CAAA;AA6BD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,2BAA2B,CACvC,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE,WAAW,EACpB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,iBAAiB,KAAK,IAAI,GACnD,IAAI,CAkCN"}
@@ -0,0 +1,73 @@
1
+ import { $props } from "../contentNodeSymbols.js";
2
+ import { processProps } from "../render/processProps.js";
3
+ /**
4
+ * The attribute value a render produces for a prop, or null for "no attribute".
5
+ *
6
+ * These are the rules both renderers already use — `renderingNodeToHtml()` on
7
+ * the server and `addAttributes()` on the client — so that a value which
8
+ * round-trips unchanged compares equal here.
9
+ */
10
+ function attributeValue(value) {
11
+ if (typeof value === 'boolean') return value ? '' : null;
12
+ if (value === undefined || value === null) return null;
13
+ return String(value);
14
+ }
15
+ function removeAttribute(domNode, name) {
16
+ // checked is both an attribute and live state, and the attribute alone only
17
+ // sets the default. Clear both, or the box stays ticked.
18
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
19
+ if (name === 'checked') domNode.checked = false;
20
+ domNode.removeAttribute(name);
21
+ }
22
+ function setAttribute(domNode, name, value) {
23
+ domNode.setAttribute(name, value);
24
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
25
+ if (name === 'checked') domNode.checked = true;
26
+ }
27
+ /**
28
+ * Brings a server-rendered element's attributes into line with what the client
29
+ * has just rendered, and reports every difference.
30
+ *
31
+ * Hydration attaches to markup the server produced. Any attribute whose value
32
+ * the client works out differently — a stored preference, a media query, a time
33
+ * zone — would otherwise keep the server's value for the life of the page,
34
+ * because nothing else revisits it until an unrelated update happens to
35
+ * reconcile that element.
36
+ *
37
+ * Only genuine differences are written. The comparison runs the props through
38
+ * `processProps()`, the same function that produced the markup being compared
39
+ * against, so a matching element costs one attribute read each and no writes.
40
+ */
41
+ export function reconcileHydratedAttributes(node, domNode, onMismatch) {
42
+ // A copy: processProps() rewrites what it is given, and these props stay in
43
+ // the content tree for the next update to diff against.
44
+ const expected = processProps({
45
+ ...node[$props]
46
+ });
47
+ const handled = new Set();
48
+ for (const name in expected) {
49
+ handled.add(name.toLowerCase());
50
+ const value = attributeValue(expected[name]);
51
+ const actual = domNode.getAttribute(name);
52
+ if (value === actual) continue;
53
+ if (value === null) removeAttribute(domNode, name);else setAttribute(domNode, name, value);
54
+ onMismatch?.({
55
+ name,
56
+ expected: value,
57
+ actual
58
+ });
59
+ }
60
+ // Anything the markup carries that this render does not produce. The list
61
+ // is live, so the names are taken before any of them are removed.
62
+ const present = [...domNode.attributes].map(attribute => attribute.name);
63
+ for (const name of present) {
64
+ if (handled.has(name.toLowerCase())) continue;
65
+ const actual = domNode.getAttribute(name);
66
+ removeAttribute(domNode, name);
67
+ onMismatch?.({
68
+ name,
69
+ expected: null,
70
+ actual
71
+ });
72
+ }
73
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=reconcileHydratedAttributes.sxyt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcileHydratedAttributes.sxyt.d.ts","sourceRoot":"","sources":["../../src/hydrate/reconcileHydratedAttributes.sxyt.ts"],"names":[],"mappings":""}
@@ -0,0 +1,173 @@
1
+ import { describe } from 'sxy-test-runner';
2
+ import { expect } from 'chai';
3
+ import { Window } from 'happy-dom';
4
+ import { reconcileHydratedAttributes } from "./reconcileHydratedAttributes.js";
5
+ import { $props, $type } from "../contentNodeSymbols.js";
6
+ const window = new Window();
7
+ const node = (type, props) => ({
8
+ [$type]: type,
9
+ [$props]: props
10
+ });
11
+ /** Builds an element from markup the server could have produced. */
12
+ const element = html => {
13
+ const host = window.document.createElement('div');
14
+ host.innerHTML = html;
15
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
16
+ return host.firstElementChild;
17
+ };
18
+ const attributesOf = domNode => Object.fromEntries([...domNode.attributes].map(attribute => [attribute.name, attribute.value]));
19
+ /** Reconciles and returns what changed. */
20
+ const reconcile = (type, props, html) => {
21
+ const domNode = element(html);
22
+ const mismatches = [];
23
+ reconcileHydratedAttributes(node(type, props), domNode, mismatch => mismatches.push(mismatch));
24
+ return {
25
+ domNode,
26
+ mismatches,
27
+ attributes: attributesOf(domNode)
28
+ };
29
+ };
30
+ describe('reconcileHydratedAttributes()', ({
31
+ it
32
+ }) => {
33
+ it('patches an attribute whose rendered value differs from the markup', () => {
34
+ const {
35
+ attributes,
36
+ mismatches
37
+ } = reconcile('div', {
38
+ style: {
39
+ backgroundColor: '#123456'
40
+ }
41
+ }, '<div style="background-color: #bae3ff;"></div>');
42
+ expect(attributes.style).to.equal('background-color: #123456;');
43
+ expect(mismatches.map(mismatch => mismatch.name)).to.deep.equal(['style']);
44
+ });
45
+ it('leaves a matching attribute untouched and reports nothing', () => {
46
+ const {
47
+ attributes,
48
+ mismatches
49
+ } = reconcile('input', {
50
+ name: 'ref',
51
+ title: 'Reference'
52
+ }, '<input name="ref" title="Reference">');
53
+ expect(attributes).to.deep.equal({
54
+ name: 'ref',
55
+ title: 'Reference'
56
+ });
57
+ expect(mismatches).to.deep.equal([]);
58
+ });
59
+ it('adds an attribute the markup is missing', () => {
60
+ const {
61
+ attributes
62
+ } = reconcile('a', {
63
+ href: '/x',
64
+ title: 't'
65
+ }, '<a href="/x"></a>');
66
+ expect(attributes).to.deep.equal({
67
+ href: '/x',
68
+ title: 't'
69
+ });
70
+ });
71
+ it('removes an attribute the render no longer produces', () => {
72
+ const {
73
+ attributes,
74
+ mismatches
75
+ } = reconcile('a', {
76
+ href: '/x'
77
+ }, '<a href="/x" title="gone"></a>');
78
+ expect(attributes).to.deep.equal({
79
+ href: '/x'
80
+ });
81
+ expect(mismatches.map(mismatch => mismatch.name)).to.deep.equal(['title']);
82
+ });
83
+ it('treats a true boolean as a present, empty attribute', () => {
84
+ expect(reconcile('button', {
85
+ disabled: true
86
+ }, '<button></button>').attributes).to.deep.equal({
87
+ disabled: ''
88
+ });
89
+ });
90
+ it('removes an attribute whose rendered value is false, null or undefined', () => {
91
+ expect(reconcile('button', {
92
+ disabled: false
93
+ }, '<button disabled></button>').attributes).to.deep.equal({});
94
+ expect(reconcile('a', {
95
+ href: null
96
+ }, '<a href="/x"></a>').attributes).to.deep.equal({});
97
+ expect(reconcile('a', {
98
+ href: undefined
99
+ }, '<a href="/x"></a>').attributes).to.deep.equal({});
100
+ });
101
+ // The comparison has to apply the same transformation the server used, or
102
+ // every element looks mismatched and the whole document gets rewritten.
103
+ it('applies the server prop transform, so className compares against class', () => {
104
+ const {
105
+ attributes,
106
+ mismatches
107
+ } = reconcile('div', {
108
+ className: 'frame Qtc_frame'
109
+ }, '<div class="frame Qtc_frame"></div>');
110
+ expect(attributes).to.deep.equal({
111
+ class: 'frame Qtc_frame'
112
+ });
113
+ expect(mismatches).to.deep.equal([]);
114
+ });
115
+ it('ignores event handlers rather than writing them as attributes', () => {
116
+ const {
117
+ attributes,
118
+ mismatches
119
+ } = reconcile('input', {
120
+ name: 'a',
121
+ onInput: () => undefined,
122
+ onClick: () => undefined
123
+ }, '<input name="a">');
124
+ expect(attributes).to.deep.equal({
125
+ name: 'a'
126
+ });
127
+ expect(mismatches).to.deep.equal([]);
128
+ });
129
+ it('ignores props that are instructions to Tungsten', () => {
130
+ const {
131
+ attributes,
132
+ mismatches
133
+ } = reconcile('div', {
134
+ __opaque: true,
135
+ dangerouslySetInnerHTML: {
136
+ __html: '<b>x</b>'
137
+ },
138
+ id: 'd'
139
+ }, '<div id="d"></div>');
140
+ expect(attributes).to.deep.equal({
141
+ id: 'd'
142
+ });
143
+ expect(mismatches).to.deep.equal([]);
144
+ });
145
+ it('reports what changed, for a dev-mode warning', () => {
146
+ const {
147
+ mismatches
148
+ } = reconcile('a', {
149
+ href: '/new'
150
+ }, '<a href="/old" title="stale"></a>');
151
+ expect(mismatches).to.deep.include({
152
+ name: 'href',
153
+ expected: '/new',
154
+ actual: '/old'
155
+ });
156
+ expect(mismatches).to.deep.include({
157
+ name: 'title',
158
+ expected: null,
159
+ actual: 'stale'
160
+ });
161
+ });
162
+ it('clears the checked property when removing a checked attribute', () => {
163
+ const domNode = element('<input type="checkbox" checked>');
164
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
165
+ const checkbox = domNode;
166
+ expect(checkbox.checked).to.equal(true);
167
+ reconcileHydratedAttributes(node('input', {
168
+ type: 'checkbox'
169
+ }), domNode);
170
+ expect(checkbox.checked).to.equal(false);
171
+ expect(domNode.hasAttribute('checked')).to.equal(false);
172
+ });
173
+ });
@@ -16,7 +16,11 @@ export * from './components/bindable/BindableTextarea.jsx';
16
16
  export * from './components/GoToComponent.jsx';
17
17
  export * from './components/Lazy.js';
18
18
  export * from './components/Link.jsx';
19
+ export * from './components/RawHtml.jsx';
19
20
  export * from './components/RouteRedirectComponent.jsx';
21
+ export { parseHtml, type ParseHtmlOptions } from './rawHtml/parseHtml.js';
22
+ export { safeHtmlAllowList } from './rawHtml/safeHtmlAllowList.js';
23
+ export type { HtmlAllowList } from './rawHtml/HtmlAllowList.js';
20
24
  export * from './components/WebRedirectComponent.jsx';
21
25
  export * from './nbsp.js';
22
26
  export * from './browserStorage.js';
@@ -1 +1 @@
1
- {"version":3,"file":"indexInner.d.ts","sourceRoot":"","sources":["../src/indexInner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"indexInner.d.ts","sourceRoot":"","sources":["../src/indexInner.ts"],"names":[],"mappings":"AAEA,cAAc,qBAAqB,CAAA;AACnC,OAAO,EACH,uBAAuB,EAAE,uBAAuB,IAAI,eAAe,EAAE,mBAAmB;AACxF,yBAAyB,EAAE,yBAAyB,IAAI,iBAAiB,EAAE,qBAAqB;AAEhG,gCAAgC,EAAE,gCAAgC,IAAI,wBAAwB,EAC9F,kCAAkC,EAAE,kCAAkC,IAAI,0BAA0B,GACvG,MAAM,kBAAkB,CAAA;AAEzB,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,uBAAuB,CAAA;AACrC,cAAc,4CAA4C,CAAA;AAC1D,cAAc,yCAAyC,CAAA;AACvD,cAAc,8CAA8C,CAAA;AAC5D,cAAc,yCAAyC,CAAA;AACvD,cAAc,0CAA0C,CAAA;AACxD,cAAc,4CAA4C,CAAA;AAC1D,cAAc,gCAAgC,CAAA;AAC9C,cAAc,sBAAsB,CAAA;AACpC,cAAc,uBAAuB,CAAA;AACrC,cAAc,0BAA0B,CAAA;AACxC,cAAc,yCAAyC,CAAA;AACvD,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAClE,YAAY,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAC/D,cAAc,uCAAuC,CAAA;AACrD,cAAc,WAAW,CAAA;AACzB,cAAc,qBAAqB,CAAA;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,cAAc,gBAAgB,CAAA;AAG9B,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,cAAc,oBAAoB,CAAA;AAClC,cAAc,kBAAkB,CAAA;AAChC,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAE/B,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEhD,cAAc,qBAAqB,CAAA;AAEnC,OAAO,EAAE,kBAAkB,EAAE,KAAK,UAAU,EAAE,KAAK,kBAAkB,EAAE,MAAM,iCAAiC,CAAA;AAC9G,cAAc,4BAA4B,CAAA;AAC1C,cAAc,2BAA2B,CAAA;AACzC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,mCAAmC,CAAA;AACjD,cAAc,8BAA8B,CAAA;AAC5C,cAAc,0BAA0B,CAAA;AACxC,cAAc,WAAW,CAAA;AACzB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,qBAAqB,CAAA;AACnC,cAAc,0BAA0B,CAAA;AACxC,cAAc,6BAA6B,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,cAAc,gBAAgB,CAAA;AAI9B,YAAY,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC/C,YAAY,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAC5F,YAAY,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAA;AACnF,cAAc,kBAAkB,CAAA;AAEhC,YAAY,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAC5D,YAAY,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACtD,YAAY,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AACnC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACvD,YAAY,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AACjD,YAAY,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AACjE,YAAY,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,IAAI,EAAE,KAAK,IAAI,IAAI,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACxH,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAC/D,YAAY,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAChE,YAAY,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AACvD,YAAY,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAA;AAC5E,YAAY,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AAC3E,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3D,YAAY,EACR,0BAA0B,EAAE,0BAA0B,IAAI,SAAS,EACnE,gCAAgC,EAAE,gCAAgC,IAAI,eAAe,EACrF,cAAc,EACjB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAG7C,mBAAmB,mBAAmB,CAAA"}
@@ -21,7 +21,10 @@ export * from "./components/bindable/BindableTextarea.js";
21
21
  export * from "./components/GoToComponent.js";
22
22
  export * from "./components/Lazy.js";
23
23
  export * from "./components/Link.js";
24
+ export * from "./components/RawHtml.js";
24
25
  export * from "./components/RouteRedirectComponent.js";
26
+ export { parseHtml } from "./rawHtml/parseHtml.js";
27
+ export { safeHtmlAllowList } from "./rawHtml/safeHtmlAllowList.js";
25
28
  export * from "./components/WebRedirectComponent.js";
26
29
  export * from "./nbsp.js";
27
30
  export * from "./browserStorage.js";
@@ -0,0 +1,16 @@
1
+ import type { NodeProps } from './ContentNode.js';
2
+ /**
3
+ * Reads React's `dangerouslySetInnerHTML` prop, returning the raw markup, or
4
+ * `null` when the element does not use it.
5
+ *
6
+ * The markup is the element's entire inner content: the server writes it into
7
+ * its HTML verbatim, the client assigns it to `innerHTML`, and the nodes the
8
+ * parser produces from it are never reconciled. Tungsten holds one opaque
9
+ * string where the DOM holds a whole subtree, so reconciling them would
10
+ * corrupt the document.
11
+ *
12
+ * `hasChildren` is supplied by the caller because the content and rendering
13
+ * trees test their children differently.
14
+ */
15
+ export declare function innerHtmlFromProps(type: string, props: NodeProps, hasChildren: boolean): string | null;
16
+ //# sourceMappingURL=innerHtmlFromProps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"innerHtmlFromProps.d.ts","sourceRoot":"","sources":["../src/innerHtmlFromProps.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAGjD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAC9B,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,GACrD,MAAM,GAAG,IAAI,CA8Bf"}
@@ -0,0 +1,31 @@
1
+ import { throwError } from "./throwError.js";
2
+ /**
3
+ * Reads React's `dangerouslySetInnerHTML` prop, returning the raw markup, or
4
+ * `null` when the element does not use it.
5
+ *
6
+ * The markup is the element's entire inner content: the server writes it into
7
+ * its HTML verbatim, the client assigns it to `innerHTML`, and the nodes the
8
+ * parser produces from it are never reconciled. Tungsten holds one opaque
9
+ * string where the DOM holds a whole subtree, so reconciling them would
10
+ * corrupt the document.
11
+ *
12
+ * `hasChildren` is supplied by the caller because the content and rendering
13
+ * trees test their children differently.
14
+ */
15
+ export function innerHtmlFromProps(type, props, hasChildren) {
16
+ const prop = props.dangerouslySetInnerHTML;
17
+ if (prop === undefined || prop === null) return null;
18
+ if (typeof prop !== 'object' || !('__html' in prop)) {
19
+ throwError('innerHtmlFromProps', `<${type}> dangerouslySetInnerHTML must be an object of the form { __html: '...' }.`);
20
+ }
21
+ const html = prop.__html;
22
+ if (html === undefined || html === null) {
23
+ throwError('innerHtmlFromProps', `<${type}> dangerouslySetInnerHTML.__html is ${String(html)}.`);
24
+ }
25
+ if (hasChildren) {
26
+ throwError('innerHtmlFromProps', `<${type}> has both dangerouslySetInnerHTML and children.` + ` An element's content must come from one or the other.`);
27
+ }
28
+ // TrustedHTML stringifies to its markup, so this accepts both forms the
29
+ // JSX typings allow.
30
+ return String(html);
31
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=innerHtmlFromProps.sxyt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"innerHtmlFromProps.sxyt.d.ts","sourceRoot":"","sources":["../src/innerHtmlFromProps.sxyt.ts"],"names":[],"mappings":""}