From c9d9e7dca201f49d2c0d34f97ea85dd129f792fc Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Thu, 2 May 2024 15:03:31 -0700 Subject: [PATCH 01/15] [lit-html] Add MathML support with the math template tag --- .changeset/beige-lamps-camp.md | 7 + package-lock.json | 144 +++++++++++++----- packages/lit-html/src/directive-helpers.ts | 1 + packages/lit-html/src/lit-html.ts | 51 +++++-- .../src/test/directive-helpers_test.ts | 20 +++ packages/lit-html/src/test/lit-html_test.ts | 23 +++ packages/lit-starter-js/package.json | 2 +- packages/lit-starter-ts/package.json | 2 +- 8 files changed, 202 insertions(+), 48 deletions(-) create mode 100644 .changeset/beige-lamps-camp.md diff --git a/.changeset/beige-lamps-camp.md b/.changeset/beige-lamps-camp.md new file mode 100644 index 0000000000..84184171f3 --- /dev/null +++ b/.changeset/beige-lamps-camp.md @@ -0,0 +1,7 @@ +--- +'lit-html': minor +'lit': minor +'lit-element': minor +--- + +Add MathML support with the `math` template tag diff --git a/package-lock.json b/package-lock.json index 830fdbfee6..d47efdd40c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,9 +58,9 @@ }, "examples/nextjs-v13": { "name": "@lit-examples/nextjs-v13", - "version": "0.1.1", + "version": "0.1.2", "dependencies": { - "@lit-labs/nextjs": "^0.1.2", + "@lit-labs/nextjs": "^0.2.0", "@lit/react": "^1.0.0", "@types/node": "^18.11.11", "@types/react": "^18.0.26", @@ -84,9 +84,9 @@ }, "examples/nextjs-v14": { "name": "@lit-examples/nextjs-v14", - "version": "0.0.0", + "version": "0.0.1", "dependencies": { - "@lit-labs/nextjs": "^0.1.2", + "@lit-labs/nextjs": "^0.2.0", "@lit/react": "^1.0.0", "@types/node": "^18.11.11", "@types/react": "^18.0.26", @@ -102,9 +102,9 @@ }, "examples/nextjs-v14-app": { "name": "@lit-examples/nextjs-v14-app", - "version": "0.0.0", + "version": "0.0.1", "dependencies": { - "@lit-labs/nextjs": "^0.1.2", + "@lit-labs/nextjs": "^0.2.0", "@lit/react": "^1.0.0", "@types/node": "^18.11.11", "@types/react": "^18.0.26", @@ -27879,7 +27879,7 @@ }, "packages/context": { "name": "@lit/context", - "version": "1.1.0", + "version": "1.1.1", "license": "BSD-3-Clause", "dependencies": { "@lit/reactive-element": "^1.6.2 || ^2.0.0" @@ -27920,7 +27920,7 @@ }, "packages/labs/analyzer": { "name": "@lit-labs/analyzer", - "version": "0.11.1", + "version": "0.12.0", "license": "BSD-3-Clause", "dependencies": { "package-json-type": "^1.0.3", @@ -27933,10 +27933,10 @@ }, "packages/labs/cli": { "name": "@lit-labs/cli", - "version": "0.6.2", + "version": "0.6.4", "license": "BSD-3-Clause", "dependencies": { - "@lit-labs/analyzer": "^0.11.0", + "@lit-labs/analyzer": "^0.12.0", "@lit-labs/gen-utils": "^0.3.0", "@lit/localize-tools": "^0.7.0", "chalk": "^5.0.1", @@ -27985,10 +27985,10 @@ }, "packages/labs/compiler": { "name": "@lit-labs/compiler", - "version": "1.0.2", + "version": "1.0.3", "license": "BSD-3-Clause", "dependencies": { - "@lit-labs/analyzer": "^0.11.0", + "@lit-labs/analyzer": "^0.12.0", "@parse5/tools": "^0.3.0", "lit-html": "^3.1.2", "parse5": "^7.1.2", @@ -28073,10 +28073,10 @@ }, "packages/labs/eslint-plugin": { "name": "eslint-plugin-lit", - "version": "0.0.0", + "version": "0.0.1", "license": "BSD-3-Clause", "dependencies": { - "@lit-labs/analyzer": "^0.11.1", + "@lit-labs/analyzer": "^0.12.0", "@typescript-eslint/utils": "^6.19.0", "typescript": "~5.3.3" }, @@ -28098,10 +28098,10 @@ }, "packages/labs/gen-manifest": { "name": "@lit-labs/gen-manifest", - "version": "0.3.1", + "version": "0.3.2", "license": "BSD-3-Clause", "dependencies": { - "@lit-labs/analyzer": "^0.11.0", + "@lit-labs/analyzer": "^0.12.0", "@lit-labs/gen-utils": "^0.3.0", "custom-elements-manifest": "^2.0.0" }, @@ -28122,10 +28122,10 @@ }, "packages/labs/gen-utils": { "name": "@lit-labs/gen-utils", - "version": "0.3.1", + "version": "0.3.2", "license": "BSD-3-Clause", "dependencies": { - "@lit-labs/analyzer": "^0.11.0" + "@lit-labs/analyzer": "^0.12.0" }, "devDependencies": { "@lit-internal/tests": "^0.0.1" @@ -28136,10 +28136,10 @@ }, "packages/labs/gen-wrapper-angular": { "name": "@lit-labs/gen-wrapper-angular", - "version": "0.1.2", + "version": "0.1.3", "license": "BSD-3-Clause", "dependencies": { - "@lit-labs/analyzer": "^0.11.0", + "@lit-labs/analyzer": "^0.12.0", "@lit-labs/gen-utils": "^0.3.0" }, "devDependencies": { @@ -28154,10 +28154,10 @@ }, "packages/labs/gen-wrapper-react": { "name": "@lit-labs/gen-wrapper-react", - "version": "0.3.1", + "version": "0.3.2", "license": "BSD-3-Clause", "dependencies": { - "@lit-labs/analyzer": "^0.11.0", + "@lit-labs/analyzer": "^0.12.0", "@lit-labs/gen-utils": "^0.3.0" }, "devDependencies": { @@ -28169,10 +28169,10 @@ }, "packages/labs/gen-wrapper-vue": { "name": "@lit-labs/gen-wrapper-vue", - "version": "0.3.2", + "version": "0.3.3", "license": "BSD-3-Clause", "dependencies": { - "@lit-labs/analyzer": "^0.11.0", + "@lit-labs/analyzer": "^0.12.0", "@lit-labs/gen-utils": "^0.3.0", "@lit-labs/vue-utils": "^0.1.1" }, @@ -28197,10 +28197,10 @@ }, "packages/labs/nextjs": { "name": "@lit-labs/nextjs", - "version": "0.1.4", + "version": "0.2.0", "license": "BSD-3-Clause", "dependencies": { - "@lit-labs/ssr-react": "^0.2.1", + "@lit-labs/ssr-react": "^0.3.0", "@webcomponents/template-shadowroot": "^0.2.1", "imports-loader": "^4.0.1" }, @@ -29002,14 +29002,14 @@ }, "packages/labs/ssr-react": { "name": "@lit-labs/ssr-react", - "version": "0.2.3", + "version": "0.3.0", "license": "BSD-3-Clause", "dependencies": { "@lit-labs/ssr": "^3.2.2", "@lit-labs/ssr-client": "^1.1.7" }, "devDependencies": { - "@lit/react": "1.0.3", + "@lit/react": "1.0.5", "@types/node": "^20.11.25", "@types/react": "^18.0.27", "@types/react-dom": "^18.0.10", @@ -29067,10 +29067,10 @@ }, "packages/labs/test-projects/test-elements-react": { "name": "@lit-internal/test-elements-react", - "version": "1.0.4", + "version": "1.0.6", "dependencies": { "@lit-internal/test-element-a": "1.0.1", - "@lit/react": "1.0.3" + "@lit/react": "1.0.5" }, "peerDependencies": { "@types/react": "^17 || ^18", @@ -29083,7 +29083,7 @@ }, "packages/labs/testing": { "name": "@lit-labs/testing", - "version": "0.2.3", + "version": "0.2.4", "license": "BSD-3-Clause", "dependencies": { "@lit-labs/ssr": "^3.1.8", @@ -29131,7 +29131,7 @@ } }, "packages/lit": { - "version": "3.1.2", + "version": "3.1.3", "license": "BSD-3-Clause", "dependencies": { "@lit/reactive-element": "^2.0.4", @@ -29147,7 +29147,7 @@ } }, "packages/lit-element": { - "version": "4.0.4", + "version": "4.0.5", "license": "BSD-3-Clause", "dependencies": { "@lit-labs/ssr-dom-shim": "^1.2.0", @@ -29164,7 +29164,7 @@ } }, "packages/lit-html": { - "version": "3.1.2", + "version": "3.1.3", "license": "BSD-3-Clause", "dependencies": { "@types/trusted-types": "^2.0.2" @@ -29189,7 +29189,7 @@ "@11ty/eleventy-plugin-syntaxhighlight": "^4.0.0", "@babel/eslint-parser": "^7.17.0", "@custom-elements-manifest/analyzer": "^0.6.3", - "@open-wc/testing": "^3.1.5", + "@open-wc/testing": "^4.0.0", "@rollup/plugin-node-resolve": "^13.3.0", "@rollup/plugin-replace": "^5.0.2", "@web/dev-server": "^0.1.31", @@ -29206,6 +29206,41 @@ "rollup-plugin-terser": "^7.0.2" } }, + "packages/lit-starter-js/node_modules/@open-wc/scoped-elements": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@open-wc/scoped-elements/-/scoped-elements-3.0.5.tgz", + "integrity": "sha512-q4U+hFTQQRyorJILOpmBm6PY2hgjCnQe214nXJNjbJMQ9EvT55oyZ7C8BY5aFYJkytUyBoawlMpZt4F2xjdzHw==", + "dev": true, + "dependencies": { + "@open-wc/dedupe-mixin": "^1.4.0", + "lit": "^3.0.0" + } + }, + "packages/lit-starter-js/node_modules/@open-wc/testing": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@open-wc/testing/-/testing-4.0.0.tgz", + "integrity": "sha512-KI70O0CJEpBWs3jrTju4BFCy7V/d4tFfYWkg8pMzncsDhD7TYNHLw5cy+s1FHXIgVFetnMDhPpwlKIPvtTQW7w==", + "dev": true, + "dependencies": { + "@esm-bundle/chai": "^4.3.4-fix.0", + "@open-wc/semantic-dom-diff": "^0.20.0", + "@open-wc/testing-helpers": "^3.0.0", + "@types/chai-dom": "^1.11.0", + "@types/sinon-chai": "^3.2.3", + "chai-a11y-axe": "^1.5.0" + } + }, + "packages/lit-starter-js/node_modules/@open-wc/testing-helpers": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@open-wc/testing-helpers/-/testing-helpers-3.0.1.tgz", + "integrity": "sha512-hyNysSatbgT2FNxHJsS3rGKcLEo6+HwDFu1UQL6jcSQUabp/tj3PyX7UnXL3H5YGv0lJArdYLSnvjLnjn3O2fw==", + "dev": true, + "dependencies": { + "@open-wc/scoped-elements": "^3.0.2", + "lit": "^2.0.0 || ^3.0.0", + "lit-html": "^2.0.0 || ^3.0.0" + } + }, "packages/lit-starter-js/node_modules/@rollup/plugin-node-resolve": { "version": "13.3.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz", @@ -29357,7 +29392,7 @@ "@11ty/eleventy": "^1.0.1", "@11ty/eleventy-plugin-syntaxhighlight": "^4.0.0", "@custom-elements-manifest/analyzer": "^0.6.3", - "@open-wc/testing": "^3.1.5", + "@open-wc/testing": "^4.0.0", "@rollup/plugin-node-resolve": "^13.3.0", "@rollup/plugin-replace": "^5.0.2", "@typescript-eslint/eslint-plugin": "^5.25.0", @@ -29377,6 +29412,41 @@ "typescript": "~5.3.3" } }, + "packages/lit-starter-ts/node_modules/@open-wc/scoped-elements": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@open-wc/scoped-elements/-/scoped-elements-3.0.5.tgz", + "integrity": "sha512-q4U+hFTQQRyorJILOpmBm6PY2hgjCnQe214nXJNjbJMQ9EvT55oyZ7C8BY5aFYJkytUyBoawlMpZt4F2xjdzHw==", + "dev": true, + "dependencies": { + "@open-wc/dedupe-mixin": "^1.4.0", + "lit": "^3.0.0" + } + }, + "packages/lit-starter-ts/node_modules/@open-wc/testing": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@open-wc/testing/-/testing-4.0.0.tgz", + "integrity": "sha512-KI70O0CJEpBWs3jrTju4BFCy7V/d4tFfYWkg8pMzncsDhD7TYNHLw5cy+s1FHXIgVFetnMDhPpwlKIPvtTQW7w==", + "dev": true, + "dependencies": { + "@esm-bundle/chai": "^4.3.4-fix.0", + "@open-wc/semantic-dom-diff": "^0.20.0", + "@open-wc/testing-helpers": "^3.0.0", + "@types/chai-dom": "^1.11.0", + "@types/sinon-chai": "^3.2.3", + "chai-a11y-axe": "^1.5.0" + } + }, + "packages/lit-starter-ts/node_modules/@open-wc/testing-helpers": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@open-wc/testing-helpers/-/testing-helpers-3.0.1.tgz", + "integrity": "sha512-hyNysSatbgT2FNxHJsS3rGKcLEo6+HwDFu1UQL6jcSQUabp/tj3PyX7UnXL3H5YGv0lJArdYLSnvjLnjn3O2fw==", + "dev": true, + "dependencies": { + "@open-wc/scoped-elements": "^3.0.2", + "lit": "^2.0.0 || ^3.0.0", + "lit-html": "^2.0.0 || ^3.0.0" + } + }, "packages/lit-starter-ts/node_modules/@rollup/plugin-node-resolve": { "version": "13.3.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz", @@ -30012,7 +30082,7 @@ }, "packages/react": { "name": "@lit/react", - "version": "1.0.3", + "version": "1.0.5", "license": "BSD-3-Clause", "devDependencies": { "@lit-internal/scripts": "^1.0.1", diff --git a/packages/lit-html/src/directive-helpers.ts b/packages/lit-html/src/directive-helpers.ts index 0c6f427c0c..79bea04384 100644 --- a/packages/lit-html/src/directive-helpers.ts +++ b/packages/lit-html/src/directive-helpers.ts @@ -44,6 +44,7 @@ export const isPrimitive = (value: unknown): value is Primitive => export const TemplateResultType = { HTML: 1, SVG: 2, + MATHML: 3, } as const; export type TemplateResultType = diff --git a/packages/lit-html/src/lit-html.ts b/packages/lit-html/src/lit-html.ts index 738b30e6eb..1ed230fcc7 100644 --- a/packages/lit-html/src/lit-html.ts +++ b/packages/lit-html/src/lit-html.ts @@ -440,8 +440,9 @@ const rawTextElement = /^(?:script|style|textarea|title)$/i; /** TemplateResult types */ const HTML_RESULT = 1; const SVG_RESULT = 2; +const MATHML_RESULT = 3; -type ResultType = typeof HTML_RESULT | typeof SVG_RESULT; +type ResultType = typeof HTML_RESULT | typeof SVG_RESULT | typeof MATHML_RESULT; // TemplatePart types // IMPORTANT: these must match the values in PartType @@ -511,6 +512,8 @@ export type HTMLTemplateResult = TemplateResult; export type SVGTemplateResult = TemplateResult; +export type MathMLTemplateResult = TemplateResult; + /** * A TemplateResult that has been compiled by @lit-labs/compiler, skipping the * prepare step. @@ -587,8 +590,8 @@ const tag = export const html = tag(HTML_RESULT); /** - * Interprets a template literal as an SVG fragment that can efficiently - * render to and update a container. + * Interprets a template literal as an SVG fragment that can efficiently render + * to and update a container. * * ```ts * const rect = svg``; @@ -607,10 +610,37 @@ export const html = tag(HTML_RESULT); * * In LitElement usage, it's invalid to return an SVG fragment from the * `render()` method, as the SVG fragment will be contained within the element's - * shadow root and thus cannot be used within an `` HTML element. + * shadow root and thus not be properly contained within an `` HTML + * element. */ export const svg = tag(SVG_RESULT); +/** + * Interprets a template literal as MathML fragment that can efficiently render + * to and update a container. + * + * ```ts + * const num = math`1`; + * + * const eq = html` + * + * ${num} + * `; + * ``` + * + * The `math` *tag function* should only be used for MathML fragments, or + * elements that would be contained **inside** an `` HTML element. A + * common error is placing an `` *element* in a template tagged with the + * `math` tag function. The `` element is an HTML element and should be + * used within a template tagged with the {@linkcode html} tag function. + * + * In LitElement usage, it's invalid to return an MathML fragment from the + * `render()` method, as the MathML fragment will be contained within the + * element's shadow root and thus not be properly contained within an `` + * HTML element. + */ +export const math = tag(MATHML_RESULT); + /** * A sentinel value that signals that a value was handled by a directive and * should not be written to the DOM. @@ -765,7 +795,8 @@ const getTemplateHtml = ( // parts. ElementParts are also reflected in this array as undefined // rather than a string, to disambiguate from attribute bindings. const attrNames: Array = []; - let html = type === SVG_RESULT ? '' : ''; + let html = + type === SVG_RESULT ? '' : type === MATHML_RESULT ? '' : ''; // When we're inside a raw text tag (not it's text content), the regex // will still be tagRegex so we can find attributes, but will switch to @@ -898,7 +929,9 @@ const getTemplateHtml = ( } const htmlResult: string | TrustedHTML = - html + (strings[l] || '') + (type === SVG_RESULT ? '' : ''); + html + + (strings[l] || '') + + (type === SVG_RESULT ? '' : type === MATHML_RESULT ? '' : ''); // Returned as an array for terseness return [trustFromTemplateString(strings, htmlResult), attrNames]; @@ -929,9 +962,9 @@ class Template { walker.currentNode = this.el.content; // Re-parent SVG nodes into template root - if (type === SVG_RESULT) { - const svgElement = this.el.content.firstChild!; - svgElement.replaceWith(...svgElement.childNodes); + if (type === SVG_RESULT || type === MATHML_RESULT) { + const wrapper = this.el.content.firstChild!; + wrapper.replaceWith(...wrapper.childNodes); } // Walk the template to find binding markers and create TemplateParts diff --git a/packages/lit-html/src/test/directive-helpers_test.ts b/packages/lit-html/src/test/directive-helpers_test.ts index c98463921a..c5f9b85455 100644 --- a/packages/lit-html/src/test/directive-helpers_test.ts +++ b/packages/lit-html/src/test/directive-helpers_test.ts @@ -12,6 +12,7 @@ import { CompiledTemplateResult, CompiledTemplate, UncompiledTemplateResult, + math, } from 'lit-html'; import {assert} from '@esm-bundle/chai'; import {stripExpressionComments} from '@lit-labs/testing'; @@ -73,6 +74,7 @@ suite('directive-helpers', () => { test('isTemplateResult', () => { assert.isTrue(isTemplateResult(html``)); assert.isTrue(isTemplateResult(svg``)); + assert.isTrue(isTemplateResult(math``)); if (isTestFileNotCompiled) { assert.isTrue(isTemplateResult(html``, TemplateResultType.HTML)); } else { @@ -81,12 +83,15 @@ suite('directive-helpers', () => { assert.isFalse(isTemplateResult(html``, TemplateResultType.HTML)); } assert.isTrue(isTemplateResult(svg``, TemplateResultType.SVG)); + assert.isTrue(isTemplateResult(math``, TemplateResultType.MATHML)); assert.isFalse(isTemplateResult(null)); assert.isFalse(isTemplateResult(undefined)); assert.isFalse(isTemplateResult({})); assert.isFalse(isTemplateResult(html``, TemplateResultType.SVG)); + assert.isFalse(isTemplateResult(html``, TemplateResultType.MATHML)); assert.isFalse(isTemplateResult(svg``, TemplateResultType.HTML)); + assert.isFalse(isTemplateResult(svg``, TemplateResultType.MATHML)); assert.isFalse(isTemplateResult(null, TemplateResultType.HTML)); assert.isFalse(isTemplateResult(undefined, TemplateResultType.HTML)); assert.isFalse(isTemplateResult({}, TemplateResultType.HTML)); @@ -131,6 +136,9 @@ suite('directive-helpers', () => { function acceptTemplateResultSvg( _v: TemplateResult, ) {} + function acceptTemplateResultMathMl( + _v: TemplateResult, + ) {} const v = html`` as TemplateResult | CompiledTemplateResult; if (isTemplateResult(v)) { @@ -144,12 +152,24 @@ suite('directive-helpers', () => { acceptTemplateResultHtml(v); // @ts-expect-error v is an html template result acceptTemplateResultSvg(v); + // @ts-expect-error v is an html template result + acceptTemplateResultMathMl(v); } if (isTemplateResult(v, TemplateResultType.SVG)) { acceptUncompiledTemplateResult(v); acceptTemplateResultSvg(v); // @ts-expect-error v is an svg template result acceptTemplateResultHtml(v); + // @ts-expect-error v is an svg template result + acceptTemplateResultMathMl(v); + } + if (isTemplateResult(v, TemplateResultType.MATHML)) { + acceptUncompiledTemplateResult(v); + acceptTemplateResultMathMl(v); + // @ts-expect-error v is a MathML template result + acceptTemplateResultSvg(v); + // @ts-expect-error v is a MathML template result + acceptTemplateResultHtml(v); } }); diff --git a/packages/lit-html/src/test/lit-html_test.ts b/packages/lit-html/src/test/lit-html_test.ts index 1c786e68de..9942b98e85 100644 --- a/packages/lit-html/src/test/lit-html_test.ts +++ b/packages/lit-html/src/test/lit-html_test.ts @@ -7,6 +7,7 @@ import { ChildPart, CompiledTemplateResult, html, + math, noChange, nothing, render, @@ -17,6 +18,7 @@ import { SanitizerFactory, Part, CompiledTemplate, + MathMLTemplateResult, } from 'lit-html'; import { @@ -794,6 +796,27 @@ suite('lit-html', () => { }); }); + suite('MathML', () => { + test('renders MathML', () => { + const container = document.createElement('math'); + const t = math`x`; + render(t, container); + const mi = container.firstElementChild!; + assert.equal(mi.tagName, 'mi'); + assert.equal(mi.namespaceURI, 'http://www.w3.org/1998/Math/MathML'); + }); + + const staticAssertExtends = (_?: [T, U]) => {}; + + test('`MathMLTemplateResult` is a subtype of `TemplateResult`', () => { + staticAssertExtends(); + }); + + test('`math` returns a `MathMLTemplateResult`', () => { + staticAssertExtends>(); + }); + }); + suite('attributes', () => { test('renders to a quoted attribute', () => { render(html`
`, container); diff --git a/packages/lit-starter-js/package.json b/packages/lit-starter-js/package.json index 5abf5f907c..c20e096306 100644 --- a/packages/lit-starter-js/package.json +++ b/packages/lit-starter-js/package.json @@ -46,7 +46,7 @@ "@11ty/eleventy-plugin-syntaxhighlight": "^4.0.0", "@babel/eslint-parser": "^7.17.0", "@custom-elements-manifest/analyzer": "^0.6.3", - "@open-wc/testing": "^3.1.5", + "@open-wc/testing": "^4.0.0", "@rollup/plugin-node-resolve": "^13.3.0", "@rollup/plugin-replace": "^5.0.2", "@web/dev-server": "^0.1.31", diff --git a/packages/lit-starter-ts/package.json b/packages/lit-starter-ts/package.json index e0bef933ba..119d4a523f 100644 --- a/packages/lit-starter-ts/package.json +++ b/packages/lit-starter-ts/package.json @@ -47,7 +47,7 @@ "@11ty/eleventy": "^1.0.1", "@11ty/eleventy-plugin-syntaxhighlight": "^4.0.0", "@custom-elements-manifest/analyzer": "^0.6.3", - "@open-wc/testing": "^3.1.5", + "@open-wc/testing": "^4.0.0", "@rollup/plugin-node-resolve": "^13.3.0", "@rollup/plugin-replace": "^5.0.2", "@typescript-eslint/eslint-plugin": "^5.25.0", From 87cdafcbf126071991bc39cb6a554a536623ffa5 Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Thu, 2 May 2024 15:15:44 -0700 Subject: [PATCH 02/15] Update size checks --- scripts/check-size.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/check-size.js b/scripts/check-size.js index 7135418765..4ea3aa6331 100644 --- a/scripts/check-size.js +++ b/scripts/check-size.js @@ -9,8 +9,8 @@ import * as fs from 'fs'; // it's likely that we'll ask you to investigate ways to reduce the size. // // In either case, update the size here and push a new commit to your PR. -const expectedLitCoreSize = 15443; -const expectedLitHtmlSize = 7258; +const expectedLitCoreSize = 15503; +const expectedLitHtmlSize = 7313; const litCoreSrc = fs.readFileSync('packages/lit/lit-core.min.js', 'utf8'); const litCoreSize = fs.readFileSync('packages/lit/lit-core.min.js').byteLength; From 8b1137dcc446e43d999b654f89d6e6c751fb26d1 Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Thu, 2 May 2024 15:32:03 -0700 Subject: [PATCH 03/15] Update packages/lit-html/src/lit-html.ts Co-authored-by: Augustine Kim --- packages/lit-html/src/lit-html.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lit-html/src/lit-html.ts b/packages/lit-html/src/lit-html.ts index 1ed230fcc7..981e554b0b 100644 --- a/packages/lit-html/src/lit-html.ts +++ b/packages/lit-html/src/lit-html.ts @@ -961,7 +961,7 @@ class Template { this.el = Template.createElement(html, options); walker.currentNode = this.el.content; - // Re-parent SVG nodes into template root + // Re-parent SVG or MathML nodes into template root if (type === SVG_RESULT || type === MATHML_RESULT) { const wrapper = this.el.content.firstChild!; wrapper.replaceWith(...wrapper.childNodes); From 89e0f143e2158084aa6c4c9ccf86d7c14bc08ca1 Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Thu, 2 May 2024 15:58:06 -0700 Subject: [PATCH 04/15] Add unsafeMath directive --- packages/lit-html/package.json | 13 ++ packages/lit-html/rollup.config.js | 1 + .../lit-html/src/directives/unsafe-math.ts | 33 +++++ .../src/test/directives/unsafe-math_test.ts | 137 ++++++++++++++++++ packages/lit/package.json | 4 + packages/lit/rollup.config.js | 1 + packages/lit/src/directives/unsafe-math.ts | 7 + 7 files changed, 196 insertions(+) create mode 100644 packages/lit-html/src/directives/unsafe-math.ts create mode 100644 packages/lit-html/src/test/directives/unsafe-math_test.ts create mode 100644 packages/lit/src/directives/unsafe-math.ts diff --git a/packages/lit-html/package.json b/packages/lit-html/package.json index bcfa7091f3..ee86d5ef5c 100644 --- a/packages/lit-html/package.json +++ b/packages/lit-html/package.json @@ -286,6 +286,19 @@ "development": "./development/directives/unsafe-html.js", "default": "./directives/unsafe-html.js" }, + "./directives/unsafe-math.js": { + "types": "./development/directives/unsafe-math.d.ts", + "browser": { + "development": "./development/directives/unsafe-math.js", + "default": "./directives/unsafe-math.js" + }, + "node": { + "development": "./node/development/directives/unsafe-math.js", + "default": "./node/directives/unsafe-math.js" + }, + "development": "./development/directives/unsafe-math.js", + "default": "./directives/unsafe-math.js" + }, "./directives/unsafe-svg.js": { "types": "./development/directives/unsafe-svg.d.ts", "browser": { diff --git a/packages/lit-html/rollup.config.js b/packages/lit-html/rollup.config.js index 36fb024283..75b0c433cf 100644 --- a/packages/lit-html/rollup.config.js +++ b/packages/lit-html/rollup.config.js @@ -28,6 +28,7 @@ export const defaultConfig = (options = {}) => 'directives/style-map', 'directives/template-content', 'directives/unsafe-html', + 'directives/unsafe-math', 'directives/unsafe-svg', 'directives/until', 'directives/when', diff --git a/packages/lit-html/src/directives/unsafe-math.ts b/packages/lit-html/src/directives/unsafe-math.ts new file mode 100644 index 0000000000..68a4510c2c --- /dev/null +++ b/packages/lit-html/src/directives/unsafe-math.ts @@ -0,0 +1,33 @@ +/** + * @license + * Copyright 2024 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ + +import {directive} from '../directive.js'; +import {UnsafeHTMLDirective} from './unsafe-html.js'; + +const MATHML_RESULT = 3; + +class UnsafeMathDirective extends UnsafeHTMLDirective { + static override directiveName = 'unsafeMath'; + static override resultType = MATHML_RESULT; +} + +/** + * Renders the result as MathML, rather than text. + * + * The values `undefined`, `null`, and `nothing`, will all result in no content + * (empty string) being rendered. + * + * Note, this is unsafe to use with any user-provided input that hasn't been + * sanitized or escaped, as it may lead to cross-site-scripting + * vulnerabilities. + */ +export const unsafeMath = directive(UnsafeMathDirective); + +/** + * The type of the class that powers this directive. Necessary for naming the + * directive's return type. + */ +export type {UnsafeMathDirective}; diff --git a/packages/lit-html/src/test/directives/unsafe-math_test.ts b/packages/lit-html/src/test/directives/unsafe-math_test.ts new file mode 100644 index 0000000000..617d7a20d2 --- /dev/null +++ b/packages/lit-html/src/test/directives/unsafe-math_test.ts @@ -0,0 +1,137 @@ +/** + * @license + * Copyright 2020 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ + +import {unsafeMath} from 'lit-html/directives/unsafe-math.js'; +import {render, html, nothing, noChange} from 'lit-html'; +import {stripExpressionMarkers} from '@lit-labs/testing'; +import {assert} from '@esm-bundle/chai'; + +suite('unsafeMath', () => { + let container: HTMLElement; + + setup(() => { + container = document.createElement('div'); + }); + + test('renders MathML', () => { + render( + // prettier-ignore + html`${unsafeMath( + 'x' + )}`, + container, + ); + assert.oneOf(stripExpressionMarkers(container.innerHTML), [ + 'x', + 'x', + ]); + const miElement = container.querySelector('mi')!; + assert.equal(miElement.namespaceURI, 'http://www.w3.org/1998/Math/MathML'); + }); + + test('rendering `nothing` renders empty string to content', () => { + render(html`before${unsafeMath(nothing)}after`, container); + assert.equal( + stripExpressionMarkers(container.innerHTML), + 'beforeafter', + ); + }); + + test('rendering `noChange` does not change the previous content', () => { + const template = (v: string | typeof noChange) => + html`before${unsafeMath(v)}after`; + render(template('Hi'), container); + assert.equal( + stripExpressionMarkers(container.innerHTML), + 'beforeHiafter', + ); + render(template(noChange), container); + assert.equal( + stripExpressionMarkers(container.innerHTML), + 'beforeHiafter', + ); + }); + + test('rendering `undefined` renders empty string to content', () => { + render(html`before${unsafeMath(undefined)}after`, container); + assert.equal( + stripExpressionMarkers(container.innerHTML), + 'beforeafter', + ); + }); + + test('rendering `null` renders empty string to content', () => { + render(html`before${unsafeMath(null)}after`, container); + assert.equal( + stripExpressionMarkers(container.innerHTML), + 'beforeafter', + ); + }); + + test('dirty checks primitive values', () => { + const value = 'aaa'; + const t = () => html`${unsafeMath(value)}`; + + // Initial render + render(t(), container); + assert.oneOf(stripExpressionMarkers(container.innerHTML), [ + 'aaa', + 'aaa', + ]); + + // Modify instance directly. Since lit-html doesn't dirty check against + // actual DOM, but against previous part values, this modification should + // persist through the next render if dirty checking works. + const text = container.querySelector('math')!.childNodes[1] as Text; + text.textContent = 'bbb'; + assert.oneOf(stripExpressionMarkers(container.innerHTML), [ + 'bbb', + 'bbb', + ]); + + // Re-render with the same value + render(t(), container); + assert.oneOf(stripExpressionMarkers(container.innerHTML), [ + 'bbb', + 'bbb', + ]); + const text2 = container.querySelector('math')!.childNodes[1] as Text; + assert.strictEqual(text, text2); + }); + + test('throws on non-string values', () => { + const value = ['aaa']; + const t = () => html`
${unsafeMath(value as any)}
`; + assert.throws(() => render(t(), container)); + }); + + test('renders after other values', () => { + const value = 'x'; + const primitive = 'aaa'; + const t = (content: any) => html`${content}`; + + // Initial unsafeMath render + render(t(unsafeMath(value)), container); + assert.oneOf(stripExpressionMarkers(container.innerHTML), [ + 'x', + 'x', + ]); + + // Re-render with a non-unsafemath value + render(t(primitive), container); + assert.oneOf(stripExpressionMarkers(container.innerHTML), [ + 'aaa', + 'aaa', + ]); + + // Re-render with unsafemath again + render(t(unsafeMath(value)), container); + assert.oneOf(stripExpressionMarkers(container.innerHTML), [ + '', + '', + ]); + }); +}); diff --git a/packages/lit/package.json b/packages/lit/package.json index 150ede0c3c..b428fd71ea 100644 --- a/packages/lit/package.json +++ b/packages/lit/package.json @@ -141,6 +141,10 @@ "types": "./development/directives/unsafe-html.d.ts", "default": "./directives/unsafe-html.js" }, + "./directives/unsafe-math.js": { + "types": "./development/directives/unsafe-math.d.ts", + "default": "./directives/unsafe-math.js" + }, "./directives/unsafe-svg.js": { "types": "./development/directives/unsafe-svg.d.ts", "default": "./directives/unsafe-svg.js" diff --git a/packages/lit/rollup.config.js b/packages/lit/rollup.config.js index b3c64151f5..b9f3ad7380 100644 --- a/packages/lit/rollup.config.js +++ b/packages/lit/rollup.config.js @@ -62,6 +62,7 @@ export default litProdConfig({ 'directives/style-map', 'directives/template-content', 'directives/unsafe-html', + 'directives/unsafe-math', 'directives/unsafe-svg', 'directives/until', 'directives/when', diff --git a/packages/lit/src/directives/unsafe-math.ts b/packages/lit/src/directives/unsafe-math.ts new file mode 100644 index 0000000000..2824573a4c --- /dev/null +++ b/packages/lit/src/directives/unsafe-math.ts @@ -0,0 +1,7 @@ +/** + * @license + * Copyright 2024 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ + +export * from 'lit-html/directives/unsafe-math.js'; From 4fc728cb2463313c2da5aa42990b38204849fe0a Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Thu, 2 May 2024 16:00:12 -0700 Subject: [PATCH 05/15] Fix grammar --- packages/lit-html/src/lit-html.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/lit-html/src/lit-html.ts b/packages/lit-html/src/lit-html.ts index 981e554b0b..4c8a4f2ed5 100644 --- a/packages/lit-html/src/lit-html.ts +++ b/packages/lit-html/src/lit-html.ts @@ -629,14 +629,14 @@ export const svg = tag(SVG_RESULT); * ``` * * The `math` *tag function* should only be used for MathML fragments, or - * elements that would be contained **inside** an `` HTML element. A - * common error is placing an `` *element* in a template tagged with the - * `math` tag function. The `` element is an HTML element and should be - * used within a template tagged with the {@linkcode html} tag function. + * elements that would be contained **inside** a `` HTML element. A common + * error is placing a `` *element* in a template tagged with the `math` + * tag function. The `` element is an HTML element and should be used + * within a template tagged with the {@linkcode html} tag function. * * In LitElement usage, it's invalid to return an MathML fragment from the * `render()` method, as the MathML fragment will be contained within the - * element's shadow root and thus not be properly contained within an `` + * element's shadow root and thus not be properly contained within a `` * HTML element. */ export const math = tag(MATHML_RESULT); From 9ecde061857ef09bc4aaf8eec8467ccdb05b0071 Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Thu, 2 May 2024 16:18:04 -0700 Subject: [PATCH 06/15] Fix test --- packages/lit-html/src/test/directives/unsafe-math_test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/lit-html/src/test/directives/unsafe-math_test.ts b/packages/lit-html/src/test/directives/unsafe-math_test.ts index 617d7a20d2..517c4a6e74 100644 --- a/packages/lit-html/src/test/directives/unsafe-math_test.ts +++ b/packages/lit-html/src/test/directives/unsafe-math_test.ts @@ -120,18 +120,18 @@ suite('unsafeMath', () => { 'x', ]); - // Re-render with a non-unsafemath value + // Re-render with a non-unsafeMath value render(t(primitive), container); assert.oneOf(stripExpressionMarkers(container.innerHTML), [ 'aaa', 'aaa', ]); - // Re-render with unsafemath again + // Re-render with unsafeMath again render(t(unsafeMath(value)), container); assert.oneOf(stripExpressionMarkers(container.innerHTML), [ - '', - '', + 'x', + 'x', ]); }); }); From e0c26dfc5dfabb17bbe1ecda7796db56cc6e1bad Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Thu, 2 May 2024 16:20:49 -0700 Subject: [PATCH 07/15] Add SSR tests --- .../ssr/src/test/integration/tests/basic.ts | 40 ++++++++++++++++++- .../labs/ssr/src/test/lib/render-lit_test.ts | 18 +++++++++ .../src/test/test-files/render-test-module.ts | 6 ++- 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/packages/labs/ssr/src/test/integration/tests/basic.ts b/packages/labs/ssr/src/test/integration/tests/basic.ts index 316989ea3e..4b92ac16be 100644 --- a/packages/labs/ssr/src/test/integration/tests/basic.ts +++ b/packages/labs/ssr/src/test/integration/tests/basic.ts @@ -6,7 +6,7 @@ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'; -import {html, svg, noChange, nothing, Part} from 'lit'; +import {html, math, svg, noChange, nothing, Part} from 'lit'; import {html as staticHtml, literal} from 'lit/static-html.js'; import { directive, @@ -30,6 +30,7 @@ import {until} from 'lit/directives/until.js'; import {ifDefined} from 'lit/directives/if-defined.js'; import {live} from 'lit/directives/live.js'; import {unsafeHTML} from 'lit/directives/unsafe-html.js'; +import {unsafeMath} from 'lit/directives/unsafe-math.js'; import {unsafeSVG} from 'lit/directives/unsafe-svg.js'; import {createRef, ref} from 'lit/directives/ref.js'; @@ -320,6 +321,24 @@ export const tests: {[name: string]: SSRTest} = { stableSelectors: ['svg', 'circle'], }, + 'ChildPart accepts TemplateResult with MATHML type': { + render(x: unknown) { + return html` ${math`${x}`} `; + }, + expectations: [ + { + args: ['a'], + html: 'a', + check(assert: Chai.Assert, dom: HTMLElement) { + const mathElements = dom.querySelectorAll('math'); + // Expect only a single math element to have been rendered. + assert.lengthOf(mathElements, 1); + }, + }, + ], + stableSelectors: ['math', 'mi'], + }, + 'multiple ChildParts, adjacent primitive values': { render(x: unknown, y: unknown) { return html`
${x}${y}
`; @@ -952,7 +971,24 @@ export const tests: {[name: string]: SSRTest} = { html: '', }, ], - stableSelectors: ['div'], + stableSelectors: ['svg'], + }, + + 'ChildPart accepts directive: unsafeMath': { + render(v) { + return html` ${unsafeMath(v)} `; + }, + expectations: [ + { + args: ['a'], + html: 'a', + }, + { + args: ['1'], + html: '1', + }, + ], + stableSelectors: ['math'], }, /****************************************************** diff --git a/packages/labs/ssr/src/test/lib/render-lit_test.ts b/packages/labs/ssr/src/test/lib/render-lit_test.ts index 613312b1c5..0d6f6567b4 100644 --- a/packages/labs/ssr/src/test/lib/render-lit_test.ts +++ b/packages/labs/ssr/src/test/lib/render-lit_test.ts @@ -407,6 +407,24 @@ for (const global of [emptyVmGlobal, shimmedVmGlobal]) { ); }); + test('math fragment template', async () => { + const {render, mathTemplate} = await setup(); + const result = await render(mathTemplate(0)); + assert.is( + result, + `0` + ); + }); + + test('html template type with math template type ChildPart', async () => { + const {render, templateWithMathTemplate} = await setup(); + const result = await render(templateWithMathTemplate(0)); + assert.is( + result, + `0` + ); + }); + test('element with reflected properties', async () => { const {render, elementWithReflectedProperties} = await setup(); const result = await render(elementWithReflectedProperties); diff --git a/packages/labs/ssr/src/test/test-files/render-test-module.ts b/packages/labs/ssr/src/test/test-files/render-test-module.ts index 484425a64f..4c159104f8 100644 --- a/packages/labs/ssr/src/test/test-files/render-test-module.ts +++ b/packages/labs/ssr/src/test/test-files/render-test-module.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-3-Clause */ -import {html, svg, nothing} from 'lit'; +import {html, math, svg, nothing} from 'lit'; import {repeat} from 'lit/directives/repeat.js'; import {classMap} from 'lit/directives/class-map.js'; import {ref, createRef} from 'lit/directives/ref.js'; @@ -58,6 +58,10 @@ export const templateWithMixedCaseAttrs = (str: string) => html` svg``; // prettier-ignore export const templateWithSvgTemplate = (x: number, y: number, r: number) => html`${svgTemplate(x, y, r)}`; +// prettier-ignore +export const mathTemplate = (x: number) => math`${x}`; +// prettier-ignore +export const templateWithMathTemplate = (x: number) => html`${mathTemplate(x)}`; /* Reflected Property Expressions */ From 9aca70c9ea0ad12d34190b2ca3894ae05a13c07d Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Sat, 11 May 2024 07:55:33 -0700 Subject: [PATCH 08/15] Rename unsafeMath to unsafeMathML --- .../ssr/src/test/integration/tests/basic.ts | 4 ++-- packages/lit-html/package.json | 16 +++++++------- packages/lit-html/rollup.config.js | 2 +- .../{unsafe-math.ts => unsafe-mathml.ts} | 6 ++--- ...afe-math_test.ts => unsafe-mathml_test.ts} | 22 +++++++++---------- packages/lit/package.json | 6 ++--- packages/lit/rollup.config.js | 2 +- .../{unsafe-math.ts => unsafe-mathml.ts} | 2 +- 8 files changed, 30 insertions(+), 30 deletions(-) rename packages/lit-html/src/directives/{unsafe-math.ts => unsafe-mathml.ts} (80%) rename packages/lit-html/src/test/directives/{unsafe-math_test.ts => unsafe-mathml_test.ts} (86%) rename packages/lit/src/directives/{unsafe-math.ts => unsafe-mathml.ts} (62%) diff --git a/packages/labs/ssr/src/test/integration/tests/basic.ts b/packages/labs/ssr/src/test/integration/tests/basic.ts index 4b92ac16be..0206c755d7 100644 --- a/packages/labs/ssr/src/test/integration/tests/basic.ts +++ b/packages/labs/ssr/src/test/integration/tests/basic.ts @@ -30,7 +30,7 @@ import {until} from 'lit/directives/until.js'; import {ifDefined} from 'lit/directives/if-defined.js'; import {live} from 'lit/directives/live.js'; import {unsafeHTML} from 'lit/directives/unsafe-html.js'; -import {unsafeMath} from 'lit/directives/unsafe-math.js'; +import {unsafeMathML} from 'lit/directives/unsafe-mathml.js'; import {unsafeSVG} from 'lit/directives/unsafe-svg.js'; import {createRef, ref} from 'lit/directives/ref.js'; @@ -976,7 +976,7 @@ export const tests: {[name: string]: SSRTest} = { 'ChildPart accepts directive: unsafeMath': { render(v) { - return html` ${unsafeMath(v)} `; + return html` ${unsafeMathML(v)} `; }, expectations: [ { diff --git a/packages/lit-html/package.json b/packages/lit-html/package.json index ee86d5ef5c..0259c54213 100644 --- a/packages/lit-html/package.json +++ b/packages/lit-html/package.json @@ -286,18 +286,18 @@ "development": "./development/directives/unsafe-html.js", "default": "./directives/unsafe-html.js" }, - "./directives/unsafe-math.js": { - "types": "./development/directives/unsafe-math.d.ts", + "./directives/unsafe-mathml.js": { + "types": "./development/directives/unsafe-mathml.d.ts", "browser": { - "development": "./development/directives/unsafe-math.js", - "default": "./directives/unsafe-math.js" + "development": "./development/directives/unsafe-mathml.js", + "default": "./directives/unsafe-mathml.js" }, "node": { - "development": "./node/development/directives/unsafe-math.js", - "default": "./node/directives/unsafe-math.js" + "development": "./node/development/directives/unsafe-mathml.js", + "default": "./node/directives/unsafe-mathml.js" }, - "development": "./development/directives/unsafe-math.js", - "default": "./directives/unsafe-math.js" + "development": "./development/directives/unsafe-mathml.js", + "default": "./directives/unsafe-mathml.js" }, "./directives/unsafe-svg.js": { "types": "./development/directives/unsafe-svg.d.ts", diff --git a/packages/lit-html/rollup.config.js b/packages/lit-html/rollup.config.js index 75b0c433cf..469e3006f4 100644 --- a/packages/lit-html/rollup.config.js +++ b/packages/lit-html/rollup.config.js @@ -28,7 +28,7 @@ export const defaultConfig = (options = {}) => 'directives/style-map', 'directives/template-content', 'directives/unsafe-html', - 'directives/unsafe-math', + 'directives/unsafe-mathml', 'directives/unsafe-svg', 'directives/until', 'directives/when', diff --git a/packages/lit-html/src/directives/unsafe-math.ts b/packages/lit-html/src/directives/unsafe-mathml.ts similarity index 80% rename from packages/lit-html/src/directives/unsafe-math.ts rename to packages/lit-html/src/directives/unsafe-mathml.ts index 68a4510c2c..9f599daaa9 100644 --- a/packages/lit-html/src/directives/unsafe-math.ts +++ b/packages/lit-html/src/directives/unsafe-mathml.ts @@ -9,7 +9,7 @@ import {UnsafeHTMLDirective} from './unsafe-html.js'; const MATHML_RESULT = 3; -class UnsafeMathDirective extends UnsafeHTMLDirective { +class UnsafeMathMLDirective extends UnsafeHTMLDirective { static override directiveName = 'unsafeMath'; static override resultType = MATHML_RESULT; } @@ -24,10 +24,10 @@ class UnsafeMathDirective extends UnsafeHTMLDirective { * sanitized or escaped, as it may lead to cross-site-scripting * vulnerabilities. */ -export const unsafeMath = directive(UnsafeMathDirective); +export const unsafeMathML = directive(UnsafeMathMLDirective); /** * The type of the class that powers this directive. Necessary for naming the * directive's return type. */ -export type {UnsafeMathDirective}; +export type {UnsafeMathMLDirective as UnsafeMathDirective}; diff --git a/packages/lit-html/src/test/directives/unsafe-math_test.ts b/packages/lit-html/src/test/directives/unsafe-mathml_test.ts similarity index 86% rename from packages/lit-html/src/test/directives/unsafe-math_test.ts rename to packages/lit-html/src/test/directives/unsafe-mathml_test.ts index 517c4a6e74..c2b04d6273 100644 --- a/packages/lit-html/src/test/directives/unsafe-math_test.ts +++ b/packages/lit-html/src/test/directives/unsafe-mathml_test.ts @@ -4,12 +4,12 @@ * SPDX-License-Identifier: BSD-3-Clause */ -import {unsafeMath} from 'lit-html/directives/unsafe-math.js'; +import {unsafeMathML} from 'lit-html/directives/unsafe-mathml.js'; import {render, html, nothing, noChange} from 'lit-html'; import {stripExpressionMarkers} from '@lit-labs/testing'; import {assert} from '@esm-bundle/chai'; -suite('unsafeMath', () => { +suite('unsafeMathML', () => { let container: HTMLElement; setup(() => { @@ -19,7 +19,7 @@ suite('unsafeMath', () => { test('renders MathML', () => { render( // prettier-ignore - html`${unsafeMath( + html`${unsafeMathML( 'x' )}`, container, @@ -33,7 +33,7 @@ suite('unsafeMath', () => { }); test('rendering `nothing` renders empty string to content', () => { - render(html`before${unsafeMath(nothing)}after`, container); + render(html`before${unsafeMathML(nothing)}after`, container); assert.equal( stripExpressionMarkers(container.innerHTML), 'beforeafter', @@ -42,7 +42,7 @@ suite('unsafeMath', () => { test('rendering `noChange` does not change the previous content', () => { const template = (v: string | typeof noChange) => - html`before${unsafeMath(v)}after`; + html`before${unsafeMathML(v)}after`; render(template('Hi'), container); assert.equal( stripExpressionMarkers(container.innerHTML), @@ -56,7 +56,7 @@ suite('unsafeMath', () => { }); test('rendering `undefined` renders empty string to content', () => { - render(html`before${unsafeMath(undefined)}after`, container); + render(html`before${unsafeMathML(undefined)}after`, container); assert.equal( stripExpressionMarkers(container.innerHTML), 'beforeafter', @@ -64,7 +64,7 @@ suite('unsafeMath', () => { }); test('rendering `null` renders empty string to content', () => { - render(html`before${unsafeMath(null)}after`, container); + render(html`before${unsafeMathML(null)}after`, container); assert.equal( stripExpressionMarkers(container.innerHTML), 'beforeafter', @@ -73,7 +73,7 @@ suite('unsafeMath', () => { test('dirty checks primitive values', () => { const value = 'aaa'; - const t = () => html`${unsafeMath(value)}`; + const t = () => html`${unsafeMathML(value)}`; // Initial render render(t(), container); @@ -104,7 +104,7 @@ suite('unsafeMath', () => { test('throws on non-string values', () => { const value = ['aaa']; - const t = () => html`
${unsafeMath(value as any)}
`; + const t = () => html`
${unsafeMathML(value as any)}
`; assert.throws(() => render(t(), container)); }); @@ -114,7 +114,7 @@ suite('unsafeMath', () => { const t = (content: any) => html`${content}`; // Initial unsafeMath render - render(t(unsafeMath(value)), container); + render(t(unsafeMathML(value)), container); assert.oneOf(stripExpressionMarkers(container.innerHTML), [ 'x', 'x', @@ -128,7 +128,7 @@ suite('unsafeMath', () => { ]); // Re-render with unsafeMath again - render(t(unsafeMath(value)), container); + render(t(unsafeMathML(value)), container); assert.oneOf(stripExpressionMarkers(container.innerHTML), [ 'x', 'x', diff --git a/packages/lit/package.json b/packages/lit/package.json index b428fd71ea..dcb3c47430 100644 --- a/packages/lit/package.json +++ b/packages/lit/package.json @@ -141,9 +141,9 @@ "types": "./development/directives/unsafe-html.d.ts", "default": "./directives/unsafe-html.js" }, - "./directives/unsafe-math.js": { - "types": "./development/directives/unsafe-math.d.ts", - "default": "./directives/unsafe-math.js" + "./directives/unsafe-mathml.js": { + "types": "./development/directives/unsafe-mathml.d.ts", + "default": "./directives/unsafe-mathml.js" }, "./directives/unsafe-svg.js": { "types": "./development/directives/unsafe-svg.d.ts", diff --git a/packages/lit/rollup.config.js b/packages/lit/rollup.config.js index b9f3ad7380..84b87e4057 100644 --- a/packages/lit/rollup.config.js +++ b/packages/lit/rollup.config.js @@ -62,7 +62,7 @@ export default litProdConfig({ 'directives/style-map', 'directives/template-content', 'directives/unsafe-html', - 'directives/unsafe-math', + 'directives/unsafe-mathml', 'directives/unsafe-svg', 'directives/until', 'directives/when', diff --git a/packages/lit/src/directives/unsafe-math.ts b/packages/lit/src/directives/unsafe-mathml.ts similarity index 62% rename from packages/lit/src/directives/unsafe-math.ts rename to packages/lit/src/directives/unsafe-mathml.ts index 2824573a4c..916abee232 100644 --- a/packages/lit/src/directives/unsafe-math.ts +++ b/packages/lit/src/directives/unsafe-mathml.ts @@ -4,4 +4,4 @@ * SPDX-License-Identifier: BSD-3-Clause */ -export * from 'lit-html/directives/unsafe-math.js'; +export * from 'lit-html/directives/unsafe-mathml.js'; From c219b9a64d49ba70fc0a639c50ce470f8abe4ec5 Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Sat, 11 May 2024 07:56:57 -0700 Subject: [PATCH 09/15] Rename math template tag to mathml --- packages/labs/ssr/src/test/integration/tests/basic.ts | 4 ++-- .../labs/ssr/src/test/test-files/render-test-module.ts | 4 ++-- packages/lit-html/src/lit-html.ts | 8 ++++---- packages/lit-html/src/test/directive-helpers_test.ts | 6 +++--- packages/lit-html/src/test/lit-html_test.ts | 8 ++++---- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/labs/ssr/src/test/integration/tests/basic.ts b/packages/labs/ssr/src/test/integration/tests/basic.ts index 0206c755d7..9f6d37363f 100644 --- a/packages/labs/ssr/src/test/integration/tests/basic.ts +++ b/packages/labs/ssr/src/test/integration/tests/basic.ts @@ -6,7 +6,7 @@ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'; -import {html, math, svg, noChange, nothing, Part} from 'lit'; +import {html, mathml, svg, noChange, nothing, Part} from 'lit'; import {html as staticHtml, literal} from 'lit/static-html.js'; import { directive, @@ -323,7 +323,7 @@ export const tests: {[name: string]: SSRTest} = { 'ChildPart accepts TemplateResult with MATHML type': { render(x: unknown) { - return html` ${math`${x}`} `; + return html` ${mathml`${x}`} `; }, expectations: [ { diff --git a/packages/labs/ssr/src/test/test-files/render-test-module.ts b/packages/labs/ssr/src/test/test-files/render-test-module.ts index 4c159104f8..31cd6037ad 100644 --- a/packages/labs/ssr/src/test/test-files/render-test-module.ts +++ b/packages/labs/ssr/src/test/test-files/render-test-module.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-3-Clause */ -import {html, math, svg, nothing} from 'lit'; +import {html, mathml, svg, nothing} from 'lit'; import {repeat} from 'lit/directives/repeat.js'; import {classMap} from 'lit/directives/class-map.js'; import {ref, createRef} from 'lit/directives/ref.js'; @@ -59,7 +59,7 @@ export const svgTemplate = (x: number, y: number, r: number) => svg` Date: Tue, 2 Jul 2024 18:43:27 -0700 Subject: [PATCH 12/15] Add static mathml tag --- packages/lit-html/src/static.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/lit-html/src/static.ts b/packages/lit-html/src/static.ts index f11103c9dd..94f9cdd421 100644 --- a/packages/lit-html/src/static.ts +++ b/packages/lit-html/src/static.ts @@ -7,7 +7,12 @@ // Any new exports need to be added to the export statement in // `packages/lit/src/index.all.ts`. -import {html as coreHtml, svg as coreSvg, TemplateResult} from './lit-html.js'; +import { + html as coreHtml, + svg as coreSvg, + mathml as coreMathml, + TemplateResult, +} from './lit-html.js'; export interface StaticValue { /** The value to interpolate as-is into the template. */ @@ -107,7 +112,7 @@ const stringsCache = new Map(); * Wraps a lit-html template tag (`html` or `svg`) to add static value support. */ export const withStatic = - (coreTag: typeof coreHtml | typeof coreSvg) => + (coreTag: typeof coreHtml | typeof coreSvg | typeof coreMathml) => (strings: TemplateStringsArray, ...values: unknown[]): TemplateResult => { const l = values.length; let staticValue: string | undefined; @@ -178,3 +183,11 @@ export const html = withStatic(coreHtml); * Includes static value support from `lit-html/static.js`. */ export const svg = withStatic(coreSvg); + +/** + * Interprets a template literal as MathML fragment that can efficiently render + * to and update a container. + * + * Includes static value support from `lit-html/static.js`. + */ +export const mathml = withStatic(coreMathml); From fabb7bb458e42f6beeb3019de1c773a3558d97f9 Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Tue, 2 Jul 2024 18:57:20 -0700 Subject: [PATCH 13/15] Update package-lock --- package-lock.json | 76 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/package-lock.json b/package-lock.json index 111549b19a..66dc0cc5fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30729,6 +30729,44 @@ "rollup-plugin-summary": "^2.0.1" } }, + "packages/lit-starter-js/node_modules/@open-wc/scoped-elements": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@open-wc/scoped-elements/-/scoped-elements-3.0.5.tgz", + "integrity": "sha512-q4U+hFTQQRyorJILOpmBm6PY2hgjCnQe214nXJNjbJMQ9EvT55oyZ7C8BY5aFYJkytUyBoawlMpZt4F2xjdzHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.4.0", + "lit": "^3.0.0" + } + }, + "packages/lit-starter-js/node_modules/@open-wc/testing": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@open-wc/testing/-/testing-4.0.0.tgz", + "integrity": "sha512-KI70O0CJEpBWs3jrTju4BFCy7V/d4tFfYWkg8pMzncsDhD7TYNHLw5cy+s1FHXIgVFetnMDhPpwlKIPvtTQW7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@esm-bundle/chai": "^4.3.4-fix.0", + "@open-wc/semantic-dom-diff": "^0.20.0", + "@open-wc/testing-helpers": "^3.0.0", + "@types/chai-dom": "^1.11.0", + "@types/sinon-chai": "^3.2.3", + "chai-a11y-axe": "^1.5.0" + } + }, + "packages/lit-starter-js/node_modules/@open-wc/testing-helpers": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@open-wc/testing-helpers/-/testing-helpers-3.0.1.tgz", + "integrity": "sha512-hyNysSatbgT2FNxHJsS3rGKcLEo6+HwDFu1UQL6jcSQUabp/tj3PyX7UnXL3H5YGv0lJArdYLSnvjLnjn3O2fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-wc/scoped-elements": "^3.0.2", + "lit": "^2.0.0 || ^3.0.0", + "lit-html": "^2.0.0 || ^3.0.0" + } + }, "packages/lit-starter-js/node_modules/prettier": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", @@ -30775,6 +30813,44 @@ "typescript": "~5.5.0" } }, + "packages/lit-starter-ts/node_modules/@open-wc/scoped-elements": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@open-wc/scoped-elements/-/scoped-elements-3.0.5.tgz", + "integrity": "sha512-q4U+hFTQQRyorJILOpmBm6PY2hgjCnQe214nXJNjbJMQ9EvT55oyZ7C8BY5aFYJkytUyBoawlMpZt4F2xjdzHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.4.0", + "lit": "^3.0.0" + } + }, + "packages/lit-starter-ts/node_modules/@open-wc/testing": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@open-wc/testing/-/testing-4.0.0.tgz", + "integrity": "sha512-KI70O0CJEpBWs3jrTju4BFCy7V/d4tFfYWkg8pMzncsDhD7TYNHLw5cy+s1FHXIgVFetnMDhPpwlKIPvtTQW7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@esm-bundle/chai": "^4.3.4-fix.0", + "@open-wc/semantic-dom-diff": "^0.20.0", + "@open-wc/testing-helpers": "^3.0.0", + "@types/chai-dom": "^1.11.0", + "@types/sinon-chai": "^3.2.3", + "chai-a11y-axe": "^1.5.0" + } + }, + "packages/lit-starter-ts/node_modules/@open-wc/testing-helpers": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@open-wc/testing-helpers/-/testing-helpers-3.0.1.tgz", + "integrity": "sha512-hyNysSatbgT2FNxHJsS3rGKcLEo6+HwDFu1UQL6jcSQUabp/tj3PyX7UnXL3H5YGv0lJArdYLSnvjLnjn3O2fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-wc/scoped-elements": "^3.0.2", + "lit": "^2.0.0 || ^3.0.0", + "lit-html": "^2.0.0 || ^3.0.0" + } + }, "packages/lit-starter-ts/node_modules/@types/semver": { "version": "7.5.6", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", From f1058fb987ed917f87dffbe94f73424ea7e177f6 Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Tue, 2 Jul 2024 18:57:30 -0700 Subject: [PATCH 14/15] Fix chai import --- packages/lit-html/src/test/directives/unsafe-mathml_test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lit-html/src/test/directives/unsafe-mathml_test.ts b/packages/lit-html/src/test/directives/unsafe-mathml_test.ts index c2b04d6273..6c8ca319b2 100644 --- a/packages/lit-html/src/test/directives/unsafe-mathml_test.ts +++ b/packages/lit-html/src/test/directives/unsafe-mathml_test.ts @@ -7,7 +7,7 @@ import {unsafeMathML} from 'lit-html/directives/unsafe-mathml.js'; import {render, html, nothing, noChange} from 'lit-html'; import {stripExpressionMarkers} from '@lit-labs/testing'; -import {assert} from '@esm-bundle/chai'; +import {assert} from 'chai'; suite('unsafeMathML', () => { let container: HTMLElement; From 2b78f7ee0476c886882fec2f01b2445a07365981 Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Tue, 2 Jul 2024 18:58:20 -0700 Subject: [PATCH 15/15] Update check-size script --- scripts/check-size.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/check-size.js b/scripts/check-size.js index 7564388c1d..7c3fa30aed 100644 --- a/scripts/check-size.js +++ b/scripts/check-size.js @@ -9,8 +9,8 @@ import * as fs from 'fs'; // it's likely that we'll ask you to investigate ways to reduce the size. // // In either case, update the size here and push a new commit to your PR. -const expectedLitCoreSize = 15660; -const expectedLitHtmlSize = 7263; +const expectedLitCoreSize = 15720; +const expectedLitHtmlSize = 7322; const litCoreSrc = fs.readFileSync('packages/lit/lit-core.min.js', 'utf8'); const litCoreSize = fs.readFileSync('packages/lit/lit-core.min.js').byteLength;