diff --git a/.changeset/cool-actors-tan.md b/.changeset/cool-actors-tan.md new file mode 100644 index 0000000000..2e7bd90ce6 --- /dev/null +++ b/.changeset/cool-actors-tan.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +feat: MathML support diff --git a/packages/svelte/src/compiler/phases/1-parse/read/options.js b/packages/svelte/src/compiler/phases/1-parse/read/options.js index 9879d774ef..cae13c271e 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/options.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/options.js @@ -1,4 +1,4 @@ -import { namespace_svg } from '../../../../constants.js'; +import { namespace_mathml, namespace_svg } from '../../../../constants.js'; import * as e from '../../../errors.js'; const regex_valid_tag_name = /^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/; @@ -155,10 +155,20 @@ export default function read_options(node) { if (value === namespace_svg) { component_options.namespace = 'svg'; - } else if (value === 'html' || value === 'svg' || value === 'foreign') { + } else if (value === namespace_mathml) { + component_options.namespace = 'mathml'; + } else if ( + value === 'html' || + value === 'mathml' || + value === 'svg' || + value === 'foreign' + ) { component_options.namespace = value; } else { - e.svelte_options_invalid_attribute_value(attribute, `"html", "svg" or "foreign"`); + e.svelte_options_invalid_attribute_value( + attribute, + `"html", "mathml", "svg" or "foreign"` + ); } break; diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 562065ec61..7a55c575ff 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -11,14 +11,14 @@ import { object } from '../../utils/ast.js'; import * as b from '../../utils/builders.js'; -import { ReservedKeywords, Runes, SVGElements } from '../constants.js'; +import { MathMLElements, ReservedKeywords, Runes, SVGElements } from '../constants.js'; import { Scope, ScopeRoot, create_scopes, get_rune, set_scope } from '../scope.js'; import { merge } from '../visitors.js'; import { validation_legacy, validation_runes, validation_runes_js } from './validation.js'; import check_graph_for_cycles from './utils/check_graph_for_cycles.js'; import { regex_starts_with_newline } from '../patterns.js'; import { create_attribute, is_element_node } from '../nodes.js'; -import { DelegatedEvents, namespace_svg } from '../../../constants.js'; +import { DelegatedEvents, namespace_mathml, namespace_svg } from '../../../constants.js'; import { should_proxy_or_freeze } from '../3-transform/client/utils.js'; import { analyze_css } from './css/css-analyze.js'; import { prune } from './css/css-prune.js'; @@ -1379,8 +1379,9 @@ const common_visitors = { FunctionExpression: function_visitor, FunctionDeclaration: function_visitor, RegularElement(node, context) { - if (context.state.options.namespace !== 'foreign' && SVGElements.includes(node.name)) { - node.metadata.svg = true; + if (context.state.options.namespace !== 'foreign') { + if (SVGElements.includes(node.name)) node.metadata.svg = true; + else if (MathMLElements.includes(node.name)) node.metadata.mathml = true; } determine_element_spread(node); @@ -1438,20 +1439,29 @@ const common_visitors = { SvelteElement(node, context) { context.state.analysis.elements.push(node); + // TODO why are we handling the `` case? there is no + // reason for someone to use a static value with `` if ( context.state.options.namespace !== 'foreign' && node.tag.type === 'Literal' && - typeof node.tag.value === 'string' && - SVGElements.includes(node.tag.value) + typeof node.tag.value === 'string' ) { - node.metadata.svg = true; - return; + if (SVGElements.includes(node.tag.value)) { + node.metadata.svg = true; + return; + } + + if (MathMLElements.includes(node.tag.value)) { + node.metadata.mathml = true; + return; + } } for (const attribute of node.attributes) { if (attribute.type === 'Attribute') { if (attribute.name === 'xmlns' && is_text_attribute(attribute)) { node.metadata.svg = attribute.value[0].data === namespace_svg; + node.metadata.mathml = attribute.value[0].data === namespace_mathml; return; } } @@ -1467,6 +1477,7 @@ const common_visitors = { ) { // Inside a slot or a snippet -> this resets the namespace, so assume the component namespace node.metadata.svg = context.state.options.namespace === 'svg'; + node.metadata.mathml = context.state.options.namespace === 'mathml'; return; } if (ancestor.type === 'SvelteElement' || ancestor.type === 'RegularElement') { @@ -1474,6 +1485,10 @@ const common_visitors = { ancestor.type === 'RegularElement' && ancestor.name === 'foreignObject' ? false : ancestor.metadata.svg; + node.metadata.mathml = + ancestor.type === 'RegularElement' && ancestor.name === 'foreignObject' + ? false + : ancestor.metadata.mathml; return; } } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index 3a7e52ac0c..c508414f72 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -50,7 +50,11 @@ import { walk } from 'zimmerframe'; */ function get_attribute_name(element, attribute, context) { let name = attribute.name; - if (!element.metadata.svg && context.state.metadata.namespace !== 'foreign') { + if ( + !element.metadata.svg && + !element.metadata.mathml && + context.state.metadata.namespace !== 'foreign' + ) { name = name.toLowerCase(); if (name in AttributeAliases) { name = AttributeAliases[name]; @@ -292,7 +296,9 @@ function serialize_element_spread_attributes( } const lowercase_attributes = - element.metadata.svg || is_custom_element_node(element) ? b.false : b.true; + element.metadata.svg || element.metadata.mathml || is_custom_element_node(element) + ? b.false + : b.true; const id = context.state.scope.generate('attributes'); const update = b.stmt( @@ -465,6 +471,7 @@ function serialize_element_attribute_update_assignment(element, node_id, attribu const state = context.state; const name = get_attribute_name(element, attribute, context); const is_svg = context.state.metadata.namespace === 'svg'; + const is_mathml = context.state.metadata.namespace === 'mathml'; let [contains_call_expression, value] = serialize_attribute_value(attribute.value, context); // The foreign namespace doesn't have any special handling, everything goes through the attr function @@ -490,7 +497,13 @@ function serialize_element_attribute_update_assignment(element, node_id, attribu let update; if (name === 'class') { - update = b.stmt(b.call(is_svg ? '$.set_svg_class' : '$.set_class', node_id, value)); + update = b.stmt( + b.call( + is_svg ? '$.set_svg_class' : is_mathml ? '$.set_mathml_class' : '$.set_class', + node_id, + value + ) + ); } else if (DOMProperties.includes(name)) { update = b.stmt(b.assignment('=', b.member(node_id, b.id(name)), value)); } else { @@ -872,7 +885,9 @@ function serialize_inline_component(node, component_name, context) { node_id, // TODO would be great to do this at runtime instead. Svelte 4 also can't handle cases today // where it's not statically determinable whether the component is used in a svg or html context - context.state.metadata.namespace === 'svg' ? b.false : b.true, + context.state.metadata.namespace === 'svg' || context.state.metadata.namespace === 'mathml' + ? b.false + : b.true, b.thunk(b.object(custom_css_props)), b.arrow([b.id('$$node')], prev(b.id('$$node'))) ); @@ -1138,9 +1153,11 @@ function get_template_function(namespace, state) { ? contains_script_tag ? '$.svg_template_with_script' : '$.svg_template' - : contains_script_tag - ? '$.template_with_script' - : '$.template'; + : namespace === 'mathml' + ? '$.mathml_template' + : contains_script_tag + ? '$.template_with_script' + : '$.template'; } /** @@ -1635,7 +1652,8 @@ export const template_visitors = { '$.html', context.state.node, b.thunk(/** @type {import('estree').Expression} */ (context.visit(node.expression))), - b.literal(context.state.metadata.namespace === 'svg') + b.literal(context.state.metadata.namespace === 'svg'), + b.literal(context.state.metadata.namespace === 'mathml') ) ) ); @@ -2125,7 +2143,11 @@ export const template_visitors = { }) ); - const args = [context.state.node, get_tag, node.metadata.svg ? b.true : b.false]; + const args = [ + context.state.node, + get_tag, + node.metadata.svg || node.metadata.mathml ? b.true : b.false + ]; if (inner.length > 0) { args.push(b.arrow([element_id, b.id('$$anchor')], b.block(inner))); } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index 2f5ed1d4aa..0a96310bac 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -484,7 +484,11 @@ function serialize_set_binding(node, context, fallback) { */ function get_attribute_name(element, attribute, context) { let name = attribute.name; - if (!element.metadata.svg && context.state.metadata.namespace !== 'foreign') { + if ( + !element.metadata.svg && + !element.metadata.mathml && + context.state.metadata.namespace !== 'foreign' + ) { name = name.toLowerCase(); // don't lookup boolean aliases here, the server runtime function does only // check for the lowercase variants of boolean attributes @@ -899,15 +903,19 @@ function serialize_element_spread_attributes( } const lowercase_attributes = - element.metadata.svg || (element.type === 'RegularElement' && is_custom_element_node(element)) + element.metadata.svg || + element.metadata.mathml || + (element.type === 'RegularElement' && is_custom_element_node(element)) ? b.false : b.true; - const is_svg = element.metadata.svg ? b.true : b.false; + + const is_html = element.metadata.svg || element.metadata.mathml ? b.false : b.true; + /** @type {import('estree').Expression[]} */ const args = [ b.array(values), lowercase_attributes, - is_svg, + is_html, b.literal(context.state.analysis.css.hash) ]; diff --git a/packages/svelte/src/compiler/phases/3-transform/utils.js b/packages/svelte/src/compiler/phases/3-transform/utils.js index de163612a9..f7c26eabc9 100644 --- a/packages/svelte/src/compiler/phases/3-transform/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/utils.js @@ -219,7 +219,10 @@ export function infer_namespace(namespace, parent, nodes, path) { } if (parent_node?.type === 'RegularElement' || parent_node?.type === 'SvelteElement') { - return parent_node.metadata.svg ? 'svg' : 'html'; + if (parent_node.metadata.svg) { + return 'svg'; + } + return parent_node.metadata.mathml ? 'mathml' : 'html'; } // Re-evaluate the namespace inside slot nodes that reset the namespace @@ -254,11 +257,11 @@ function check_nodes_for_namespace(nodes, namespace) { * @param {{stop: () => void}} context */ const RegularElement = (node, { stop }) => { - if (!node.metadata.svg) { + if (!node.metadata.svg && !node.metadata.mathml) { namespace = 'html'; stop(); } else if (namespace === 'keep') { - namespace = 'svg'; + namespace = node.metadata.svg ? 'svg' : 'mathml'; } }; @@ -312,7 +315,11 @@ export function determine_namespace_for_children(node, namespace) { return 'html'; } - return node.metadata.svg ? 'svg' : 'html'; + if (node.metadata.svg) { + return 'svg'; + } + + return node.metadata.mathml ? 'mathml' : 'html'; } /** diff --git a/packages/svelte/src/compiler/phases/constants.js b/packages/svelte/src/compiler/phases/constants.js index 0fec2e8948..54f99995c6 100644 --- a/packages/svelte/src/compiler/phases/constants.js +++ b/packages/svelte/src/compiler/phases/constants.js @@ -149,6 +149,39 @@ export const SVGElements = [ 'vkern' ]; +export const MathMLElements = [ + 'annotation', + 'annotation-xml', + 'maction', + 'math', + 'merror', + 'mfrac', + 'mi', + 'mmultiscripts', + 'mn', + 'mo', + 'mover', + 'mpadded', + 'mphantom', + 'mprescripts', + 'mroot', + 'mrow', + 'ms', + 'mspace', + 'msqrt', + 'mstyle', + 'msub', + 'msubsup', + 'msup', + 'mtable', + 'mtd', + 'mtext', + 'mtr', + 'munder', + 'munderover', + 'semantics' +]; + export const EventModifiers = [ 'preventDefault', 'stopPropagation', diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index e8f20c9b33..05ac91bda2 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -39,11 +39,12 @@ export interface Fragment { /** * - `html` — the default, for e.g. `
` or `` * - `svg` — for e.g. `` or `` + * - `mathml` — for e.g. `` or `` * - `foreign` — for other compilation targets than the web, e.g. Svelte Native. * Disallows bindings other than bind:this, disables a11y checks, disables any special attribute handling * (also see https://github.com/sveltejs/svelte/pull/5652) */ -export type Namespace = 'html' | 'svg' | 'foreign'; +export type Namespace = 'html' | 'svg' | 'mathml' | 'foreign'; export interface Root extends BaseNode { type: 'Root'; @@ -287,6 +288,8 @@ export interface RegularElement extends BaseElement { metadata: { /** `true` if this is an svg element */ svg: boolean; + /** `true` if this is a mathml element */ + mathml: boolean; /** `true` if contains a SpreadAttribute */ has_spread: boolean; scoped: boolean; @@ -319,6 +322,11 @@ export interface SvelteElement extends BaseElement { * the tag is dynamic, but we do our best to infer it from the template. */ svg: boolean; + /** + * `true` if this is a mathml element. The boolean may not be accurate because + * the tag is dynamic, but we do our best to infer it from the template. + */ + mathml: boolean; scoped: boolean; }; } diff --git a/packages/svelte/src/constants.js b/packages/svelte/src/constants.js index 376809fb76..d9fc86d600 100644 --- a/packages/svelte/src/constants.js +++ b/packages/svelte/src/constants.js @@ -104,6 +104,7 @@ export const DOMBooleanAttributes = [ ]; export const namespace_svg = 'http://www.w3.org/2000/svg'; +export const namespace_mathml = 'http://www.w3.org/1998/Math/MathML'; // while `input` is also an interactive element, it is never moved by the browser, so we don't need to check for it export const interactive_elements = new Set([ diff --git a/packages/svelte/src/internal/client/dom/blocks/html.js b/packages/svelte/src/internal/client/dom/blocks/html.js index ea8e011443..6873adf70c 100644 --- a/packages/svelte/src/internal/client/dom/blocks/html.js +++ b/packages/svelte/src/internal/client/dom/blocks/html.js @@ -30,14 +30,15 @@ function remove_from_parent_effect(effect, to_remove) { * @param {Element | Text | Comment} anchor * @param {() => string} get_value * @param {boolean} svg + * @param {boolean} mathml * @returns {void} */ -export function html(anchor, get_value, svg) { +export function html(anchor, get_value, svg, mathml) { const parent_effect = anchor.parentNode !== current_effect?.dom ? current_effect : null; let value = derived(get_value); render_effect(() => { - var dom = html_to_dom(anchor, parent_effect, get(value), svg); + var dom = html_to_dom(anchor, parent_effect, get(value), svg, mathml); if (dom) { return () => { @@ -58,20 +59,22 @@ export function html(anchor, get_value, svg) { * @param {import('#client').Effect | null} effect * @param {V} value * @param {boolean} svg + * @param {boolean} mathml * @returns {Element | Comment | (Element | Comment | Text)[]} */ -function html_to_dom(target, effect, value, svg) { +function html_to_dom(target, effect, value, svg, mathml) { if (hydrating) return hydrate_nodes; var html = value + ''; if (svg) html = `${html}`; + else if (mathml) html = `${html}`; // Don't use create_fragment_with_script_from_html here because that would mean script tags are executed. // @html is basically `.innerHTML = ...` and that doesn't execute scripts either due to security reasons. /** @type {DocumentFragment | Element} */ var node = create_fragment_from_html(html); - if (svg) { + if (svg || mathml) { node = /** @type {Element} */ (node.firstChild); } @@ -86,7 +89,7 @@ function html_to_dom(target, effect, value, svg) { var nodes = /** @type {Array} */ ([...node.childNodes]); - if (svg) { + if (svg || mathml) { while (node.firstChild) { target.before(node.firstChild); } diff --git a/packages/svelte/src/internal/client/dom/elements/class.js b/packages/svelte/src/internal/client/dom/elements/class.js index 074152d901..238f62a602 100644 --- a/packages/svelte/src/internal/client/dom/elements/class.js +++ b/packages/svelte/src/internal/client/dom/elements/class.js @@ -30,6 +30,35 @@ export function set_svg_class(dom, value) { } } +/** + * @param {MathMLElement} dom + * @param {string} value + * @returns {void} + */ +export function set_mathml_class(dom, value) { + // @ts-expect-error need to add __className to patched prototype + var prev_class_name = dom.__className; + var next_class_name = to_class(value); + + if (hydrating && dom.getAttribute('class') === next_class_name) { + // In case of hydration don't reset the class as it's already correct. + // @ts-expect-error need to add __className to patched prototype + dom.__className = next_class_name; + } else if ( + prev_class_name !== next_class_name || + (hydrating && dom.getAttribute('class') !== next_class_name) + ) { + if (next_class_name === '') { + dom.removeAttribute('class'); + } else { + dom.setAttribute('class', next_class_name); + } + + // @ts-expect-error need to add __className to patched prototype + dom.__className = next_class_name; + } +} + /** * @param {HTMLElement} dom * @param {string} value diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index 82732eaa1e..061351bdc8 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -158,6 +158,49 @@ export function svg_template_with_script(content, flags) { }; } +/** + * @param {string} content + * @param {number} flags + * @returns {() => Node | Node[]} + */ +/*#__NO_SIDE_EFFECTS__*/ +export function mathml_template(content, flags) { + var is_fragment = (flags & TEMPLATE_FRAGMENT) !== 0; + var fn = template(`${content}`, 0); // we don't need to worry about using importNode for MathML + + /** @type {Element | DocumentFragment} */ + var node; + + return () => { + if (hydrating) { + return push_template_node(is_fragment ? hydrate_nodes : hydrate_nodes[0]); + } + + if (!node) { + var math = /** @type {Element} */ (fn()); + + if ((flags & TEMPLATE_FRAGMENT) === 0) { + node = /** @type {Element} */ (math.firstChild); + } else { + node = document.createDocumentFragment(); + while (math.firstChild) { + node.appendChild(math.firstChild); + } + } + } + + var clone = clone_node(node, true); + + push_template_node( + is_fragment + ? /** @type {import('#client').TemplateNode[]} */ ([...clone.childNodes]) + : /** @type {import('#client').TemplateNode} */ (clone) + ); + + return clone; + }; +} + /** * Creating a document fragment from HTML that contains script tags will not execute * the scripts. We need to replace the script tags with new ones so that they are executed. diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 5f58555929..992eaa0c00 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -20,7 +20,7 @@ export { set_dynamic_element_attributes, set_xlink_attribute } from './dom/elements/attributes.js'; -export { set_class, set_svg_class, toggle_class } from './dom/elements/class.js'; +export { set_class, set_svg_class, set_mathml_class, toggle_class } from './dom/elements/class.js'; export { event, delegate } from './dom/elements/events.js'; export { autofocus, remove_textarea_child } from './dom/elements/misc.js'; export { set_style } from './dom/elements/style.js'; @@ -70,6 +70,7 @@ export { comment, svg_template, svg_template_with_script, + mathml_template, template, template_with_script, text diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 56c68a9ae8..f6e68acb1c 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -289,12 +289,12 @@ export function css_props(payload, is_html, props, component) { /** * @param {Record[]} attrs * @param {boolean} lowercase_attributes - * @param {boolean} is_svg + * @param {boolean} is_html * @param {string} class_hash * @param {{ styles: Record | null; classes: string }} [additional] * @returns {string} */ -export function spread_attributes(attrs, lowercase_attributes, is_svg, class_hash, additional) { +export function spread_attributes(attrs, lowercase_attributes, is_html, class_hash, additional) { /** @type {Record} */ const merged_attrs = {}; let key; @@ -344,7 +344,7 @@ export function spread_attributes(attrs, lowercase_attributes, is_svg, class_has if (lowercase_attributes) { name = name.toLowerCase(); } - const is_boolean = !is_svg && DOMBooleanAttributes.includes(name); + const is_boolean = is_html && DOMBooleanAttributes.includes(name); attr_str += attr(name, merged_attrs[name], is_boolean); } diff --git a/packages/svelte/tests/runtime-legacy/samples/namespace-html/_config.js b/packages/svelte/tests/runtime-legacy/samples/namespace-html/_config.js index 2196084202..3be9f0e925 100644 --- a/packages/svelte/tests/runtime-legacy/samples/namespace-html/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/namespace-html/_config.js @@ -7,6 +7,10 @@ export default test({ hellooooo + + + +
hi
`, @@ -15,6 +19,10 @@ export default test({ ok(svg); assert.equal(svg.namespaceURI, 'http://www.w3.org/2000/svg'); + const math = target.querySelector('math'); + ok(math); + assert.equal(math.namespaceURI, 'http://www.w3.org/1998/Math/MathML'); + const div = target.querySelector('div'); ok(div); assert.equal(div.namespaceURI, 'http://www.w3.org/1999/xhtml'); diff --git a/packages/svelte/tests/runtime-legacy/samples/namespace-html/main.svelte b/packages/svelte/tests/runtime-legacy/samples/namespace-html/main.svelte index e85a736eb4..a020c61533 100644 --- a/packages/svelte/tests/runtime-legacy/samples/namespace-html/main.svelte +++ b/packages/svelte/tests/runtime-legacy/samples/namespace-html/main.svelte @@ -2,4 +2,8 @@ hellooooo + + + +
hi
diff --git a/packages/svelte/tests/runtime-runes/samples/mathml-namespace-infer/Wrapper.svelte b/packages/svelte/tests/runtime-runes/samples/mathml-namespace-infer/Wrapper.svelte new file mode 100644 index 0000000000..60b4f3723e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/mathml-namespace-infer/Wrapper.svelte @@ -0,0 +1,19 @@ + + +{#if true} + +{/if} + +{#each Array(2).fill(0) as item, idx} + +{/each} + +{@html ''} + +{@render test()} + +{#snippet test(text)} + +{/snippet} + + diff --git a/packages/svelte/tests/runtime-runes/samples/mathml-namespace-infer/_config.js b/packages/svelte/tests/runtime-runes/samples/mathml-namespace-infer/_config.js new file mode 100644 index 0000000000..80398675e3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/mathml-namespace-infer/_config.js @@ -0,0 +1,27 @@ +import { test, ok } from '../../test'; + +export default test({ + html: ` + + + + + + + + +`, + test({ assert, target }) { + const math = target.querySelector('math'); + ok(math); + + assert.equal(math.namespaceURI, 'http://www.w3.org/1998/Math/MathML'); + + const mrow_elements = target.querySelectorAll('mrow'); + + assert.equal(mrow_elements.length, 6); + + for (const { namespaceURI } of mrow_elements) + assert.equal(namespaceURI, 'http://www.w3.org/1998/Math/MathML'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/mathml-namespace-infer/main.svelte b/packages/svelte/tests/runtime-runes/samples/mathml-namespace-infer/main.svelte new file mode 100644 index 0000000000..2abcb903e1 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/mathml-namespace-infer/main.svelte @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/svelte/tests/validator/samples/namespace-invalid/errors.json b/packages/svelte/tests/validator/samples/namespace-invalid/errors.json index 566725e1f3..ae98ee7e25 100644 --- a/packages/svelte/tests/validator/samples/namespace-invalid/errors.json +++ b/packages/svelte/tests/validator/samples/namespace-invalid/errors.json @@ -1,7 +1,7 @@ [ { "code": "svelte_options_invalid_attribute_value", - "message": "Valid values are \"html\", \"svg\" or \"foreign\"", + "message": "Valid values are \"html\", \"mathml\", \"svg\" or \"foreign\"", "start": { "line": 1, "column": 16 diff --git a/packages/svelte/tests/validator/samples/namespace-non-literal/errors.json b/packages/svelte/tests/validator/samples/namespace-non-literal/errors.json index 6d6ffec16c..439dbd032b 100644 --- a/packages/svelte/tests/validator/samples/namespace-non-literal/errors.json +++ b/packages/svelte/tests/validator/samples/namespace-non-literal/errors.json @@ -1,7 +1,7 @@ [ { "code": "svelte_options_invalid_attribute_value", - "message": "Valid values are \"html\", \"svg\" or \"foreign\"", + "message": "Valid values are \"html\", \"mathml\", \"svg\" or \"foreign\"", "start": { "line": 1, "column": 16 diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 6b7dc0b8b5..07e5c1104e 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -1321,11 +1321,12 @@ declare module 'svelte/compiler' { /** * - `html` — the default, for e.g. `
` or `` * - `svg` — for e.g. `` or `` + * - `mathml` — for e.g. `` or `` * - `foreign` — for other compilation targets than the web, e.g. Svelte Native. * Disallows bindings other than bind:this, disables a11y checks, disables any special attribute handling * (also see https://github.com/sveltejs/svelte/pull/5652) */ - type Namespace = 'html' | 'svg' | 'foreign'; + type Namespace = 'html' | 'svg' | 'mathml' | 'foreign'; interface Root extends BaseNode { type: 'Root'; @@ -1569,6 +1570,8 @@ declare module 'svelte/compiler' { metadata: { /** `true` if this is an svg element */ svg: boolean; + /** `true` if this is a mathml element */ + mathml: boolean; /** `true` if contains a SpreadAttribute */ has_spread: boolean; scoped: boolean; @@ -1601,6 +1604,11 @@ declare module 'svelte/compiler' { * the tag is dynamic, but we do our best to infer it from the template. */ svg: boolean; + /** + * `true` if this is a mathml element. The boolean may not be accurate because + * the tag is dynamic, but we do our best to infer it from the template. + */ + mathml: boolean; scoped: boolean; }; } @@ -2562,11 +2570,12 @@ declare module 'svelte/types/compiler/interfaces' { /** * - `html` — the default, for e.g. `
` or `` * - `svg` — for e.g. `` or `` + * - `mathml` — for e.g. `` or `` * - `foreign` — for other compilation targets than the web, e.g. Svelte Native. * Disallows bindings other than bind:this, disables a11y checks, disables any special attribute handling * (also see https://github.com/sveltejs/svelte/pull/5652) */ - type Namespace = 'html' | 'svg' | 'foreign'; + type Namespace = 'html' | 'svg' | 'mathml' | 'foreign'; }declare module '*.svelte' { export { SvelteComponent as default } from 'svelte'; } diff --git a/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md b/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md index c5b35e2423..9cb885555b 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md +++ b/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md @@ -164,7 +164,7 @@ Various error and warning codes have been renamed slightly. ### Reduced number of namespaces -The number of valid namespaces you can pass to the compiler option `namespace` has been reduced to `html` (the default), `svg` and `foreign`. +The number of valid namespaces you can pass to the compiler option `namespace` has been reduced to `html` (the default), `mathml`, `svg` and `foreign`. ### beforeUpdate/afterUpdate changes