fix: correctly infer `<a>` tag namespace (#14134)

`<a>` tags are valid in both the SVG and HTML namespace.  If there's no parent, we therefore have to look downwards to see if it's the parent of a SVG or HTML element.

fixes #7807
fixes #13793
pull/13429/merge
Simon H 2 days ago committed by GitHub
parent 8de5605b6a
commit 551284ca22
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: correctly infer `<a>` tag namespace

@ -9,6 +9,10 @@ export interface AnalysisState {
analysis: ComponentAnalysis;
options: ValidatedCompileOptions;
ast_type: 'instance' | 'template' | 'module';
/**
* Tag name of the parent element. `null` if the parent is `svelte:element`, `#snippet`, a component or the root.
* Parent doesn't necessarily mean direct path predecessor because there could be `#each`, `#if` etc in-between.
*/
parent_element: string | null;
has_props_rune: boolean;
/** Which slots the current parent component has */

@ -174,4 +174,17 @@ export function RegularElement(node, context) {
}
context.next({ ...context.state, parent_element: node.name });
// Special case: <a> tags are valid in both the SVG and HTML namespace.
// If there's no parent, look downwards to see if it's the parent of a SVG or HTML element.
if (node.name === 'a' && !context.state.parent_element) {
for (const child of node.fragment.nodes) {
if (child.type === 'RegularElement') {
if (child.metadata.svg && child.name !== 'svg') {
node.metadata.svg = true;
break;
}
}
}
}
}

@ -1,5 +1,5 @@
/** @import { AST } from '#compiler' */
/** @import { Context } from '../../types' */
/** @import { AnalysisState, Context } from '../../types' */
import * as e from '../../../../errors.js';
import { get_attribute_expression, is_expression_attribute } from '../../../../utils/ast.js';
import { determine_slot } from '../../../../utils/slot.js';
@ -96,6 +96,7 @@ export function visit_component(node, context) {
const component_slots = new Set();
for (const slot_name in nodes) {
/** @type {AnalysisState} */
const state = {
...context.state,
scope: node.metadata.scopes[slot_name],

@ -0,0 +1,26 @@
import { test } from '../../test';
export default test({
html: `
<div><a><span>Hello</span></a></div>
<div><a><span>Hello</span></a></div>
<div><a><span>Hello</span></a></div>
`,
test({ assert, target }) {
const div = target.querySelectorAll('div');
const a = target.querySelectorAll('a');
const span = target.querySelectorAll('span');
for (const element of div) {
assert.equal(element.namespaceURI, 'http://www.w3.org/1999/xhtml');
}
for (const element of a) {
assert.equal(element.namespaceURI, 'http://www.w3.org/1999/xhtml');
}
for (const element of span) {
assert.equal(element.namespaceURI, 'http://www.w3.org/1999/xhtml');
}
}
});

@ -0,0 +1,7 @@
<script>
let { children } = $props();
</script>
<div>
{@render children()}
</div>

@ -0,0 +1,24 @@
<script>
import Div from './div.svelte';
</script>
<div>
<a>
<span>Hello</span>
</a>
</div>
<div>
{#snippet test()}
<a>
<span>Hello</span>
</a>
{/snippet}
{@render test()}
</div>
<Div>
<a>
<span>Hello</span>
</a>
</Div>

@ -0,0 +1,26 @@
import { test } from '../../test';
export default test({
html: `
<svg><a><text>Hello</text></a></svg>
<svg><a><text>Hello</text></a></svg>
<svg><a><text>Hello</text></a></svg>
`,
test({ assert, target }) {
const svg = target.querySelectorAll('svg');
const a = target.querySelectorAll('a');
const text = target.querySelectorAll('text');
for (const element of svg) {
assert.equal(element.namespaceURI, 'http://www.w3.org/2000/svg');
}
for (const element of a) {
assert.equal(element.namespaceURI, 'http://www.w3.org/2000/svg');
}
for (const element of text) {
assert.equal(element.namespaceURI, 'http://www.w3.org/2000/svg');
}
}
});

@ -0,0 +1,24 @@
<script>
import Svg from './svg.svelte';
</script>
<svg>
<a>
<text>Hello</text>
</a>
</svg>
<svg>
{#snippet test()}
<a>
<text>Hello</text>
</a>
{/snippet}
{@render test()}
</svg>
<Svg>
<a>
<text>Hello</text>
</a>
</Svg>

@ -0,0 +1,7 @@
<script>
let { children } = $props();
</script>
<svg>
{@render children()}
</svg>
Loading…
Cancel
Save