diff --git a/.changeset/eleven-cycles-applaud.md b/.changeset/eleven-cycles-applaud.md new file mode 100644 index 000000000000..3e73a89252e9 --- /dev/null +++ b/.changeset/eleven-cycles-applaud.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +breaking: preserve slots inside templates with a shadowrootmode attribute diff --git a/packages/svelte/src/compiler/phases/1-parse/state/element.js b/packages/svelte/src/compiler/phases/1-parse/state/element.js index f40bccd44421..16960e9721e6 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/element.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js @@ -51,6 +51,23 @@ function parent_is_head(stack) { return false; } +/** @param {import('#compiler').TemplateNode[]} stack */ +function parent_is_shadowroot_template(stack) { + // https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#building_a_declarative_shadow_root + let i = stack.length; + while (i--) { + if ( + stack[i].type === 'RegularElement' && + /** @type {import('#compiler').RegularElement} */ (stack[i]).attributes.some( + (a) => a.type === 'Attribute' && a.name === 'shadowrootmode' + ) + ) { + return true; + } + } + return false; +} + const regex_closing_textarea_tag = /^<\/textarea(\s[^>]*)?>/i; const regex_closing_comment = /-->/; const regex_capital_letter = /[A-Z]/; @@ -112,7 +129,8 @@ export default function tag(parser) { ? 'Component' : name === 'title' && parent_is_head(parser.stack) ? 'TitleElement' - : name === 'slot' + : // TODO Svelte 6/7: once slots are removed in favor of snippets, always keep slot as a regular element + name === 'slot' && !parent_is_shadowroot_template(parser.stack) ? 'SlotElement' : 'RegularElement'; diff --git a/packages/svelte/tests/parser-modern/samples/template-shadowroot/input.svelte b/packages/svelte/tests/parser-modern/samples/template-shadowroot/input.svelte new file mode 100644 index 000000000000..bcac79a03c51 --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/template-shadowroot/input.svelte @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/packages/svelte/tests/parser-modern/samples/template-shadowroot/output.json b/packages/svelte/tests/parser-modern/samples/template-shadowroot/output.json new file mode 100644 index 000000000000..eb9ffe51de07 --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/template-shadowroot/output.json @@ -0,0 +1,160 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 125, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "RegularElement", + "start": 0, + "end": 66, + "name": "template", + "attributes": [ + { + "type": "Attribute", + "start": 10, + "end": 31, + "name": "shadowrootmode", + "value": [ + { + "start": 26, + "end": 30, + "type": "Text", + "raw": "open", + "data": "open" + } + ] + } + ], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 32, + "end": 34, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "RegularElement", + "start": 34, + "end": 54, + "name": "p", + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "RegularElement", + "start": 37, + "end": 50, + "name": "slot", + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [], + "transparent": true + } + } + ], + "transparent": true + } + }, + { + "type": "Text", + "start": 54, + "end": 55, + "raw": "\n", + "data": "\n" + } + ], + "transparent": true + } + }, + { + "type": "Text", + "start": 66, + "end": 67, + "raw": "\n", + "data": "\n" + }, + { + "type": "RegularElement", + "start": 67, + "end": 111, + "name": "template", + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 77, + "end": 79, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "RegularElement", + "start": 79, + "end": 99, + "name": "p", + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "SlotElement", + "start": 82, + "end": 95, + "name": "slot", + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [], + "transparent": true + } + } + ], + "transparent": true + } + }, + { + "type": "Text", + "start": 99, + "end": 100, + "raw": "\n", + "data": "\n" + } + ], + "transparent": true + } + }, + { + "type": "Text", + "start": 111, + "end": 112, + "raw": "\n", + "data": "\n" + }, + { + "type": "SlotElement", + "start": 112, + "end": 125, + "name": "slot", + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [], + "transparent": true + } + } + ], + "transparent": false + }, + "options": null +}