diff --git a/.changeset/swift-falcons-fetch.md b/.changeset/swift-falcons-fetch.md new file mode 100644 index 000000000000..02ea10991959 --- /dev/null +++ b/.changeset/swift-falcons-fetch.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: applying :global for +,~ sibling combinator when slots are present diff --git a/packages/svelte/src/compiler/compile/css/Selector.js b/packages/svelte/src/compiler/compile/css/Selector.js index 7f26989bcb27..a1c58515d726 100644 --- a/packages/svelte/src/compiler/compile/css/Selector.js +++ b/packages/svelte/src/compiler/compile/css/Selector.js @@ -291,14 +291,17 @@ function apply_selector(blocks, node, to_encapsulate) { } return false; } else if (block.combinator.name === '+' || block.combinator.name === '~') { - const siblings = get_possible_element_siblings(node, block.combinator.name === '+'); + const [siblings, has_slot_sibling] = get_possible_element_siblings( + node, + block.combinator.name === '+' + ); let has_match = false; // NOTE: if we have :global(), we couldn't figure out what is selected within `:global` due to the // css-tree limitation that does not parse the inner selector of :global // so unless we are sure there will be no sibling to match, we will consider it as matched const has_global = blocks.some((block) => block.global); if (has_global) { - if (siblings.size === 0 && get_element_parent(node) !== null) { + if (siblings.size === 0 && get_element_parent(node) !== null && !has_slot_sibling) { return false; } to_encapsulate.push({ node, block }); @@ -542,13 +545,15 @@ function get_element_parent(node) { *

Heading 1

*

Heading 2

* @param {import('../nodes/interfaces.js').INode} node - * @returns {import('../nodes/interfaces.js').INode} + * @returns {[import('../nodes/interfaces.js').INode, boolean]} */ function find_previous_sibling(node) { /** @type {import('../nodes/interfaces.js').INode} */ let current_node = node; + let has_slot_sibling = false; do { if (current_node.type === 'Slot') { + has_slot_sibling = true; const slot_children = current_node.children; if (slot_children.length > 0) { current_node = slot_children.slice(-1)[0]; // go to its last child first @@ -560,13 +565,13 @@ function find_previous_sibling(node) { } current_node = current_node.prev; } while (current_node && current_node.type === 'Slot'); - return current_node; + return [current_node, has_slot_sibling]; } /** * @param {import('../nodes/interfaces.js').INode} node * @param {boolean} adjacent_only - * @returns {Map} + * @returns {[Map, boolean]} */ function get_possible_element_siblings(node, adjacent_only) { /** @type {Map} */ @@ -574,7 +579,10 @@ function get_possible_element_siblings(node, adjacent_only) { /** @type {import('../nodes/interfaces.js').INode} */ let prev = node; - while ((prev = find_previous_sibling(prev))) { + let has_slot_sibling = false; + let slot_sibling_found = false; + while (([prev, slot_sibling_found] = find_previous_sibling(prev)) && prev) { + has_slot_sibling = has_slot_sibling || slot_sibling_found; if (prev.type === 'Element') { if ( !prev.attributes.find( @@ -590,7 +598,7 @@ function get_possible_element_siblings(node, adjacent_only) { const possible_last_child = get_possible_last_child(prev, adjacent_only); add_to_map(possible_last_child, result); if (adjacent_only && has_definite_elements(possible_last_child)) { - return result; + return [result, has_slot_sibling]; } } } @@ -605,7 +613,11 @@ function get_possible_element_siblings(node, adjacent_only) { parent.type === 'ElseBlock' || parent.type === 'AwaitBlock') ) { - const possible_siblings = get_possible_element_siblings(parent, adjacent_only); + const [possible_siblings, slot_sibling_found] = get_possible_element_siblings( + parent, + adjacent_only + ); + has_slot_sibling = has_slot_sibling || slot_sibling_found; add_to_map(possible_siblings, result); if (parent.type === 'EachBlock') { // first child of each block can select the last child of each block as previous sibling @@ -623,7 +635,7 @@ function get_possible_element_siblings(node, adjacent_only) { } } } - return result; + return [result, has_slot_sibling]; } /** diff --git a/packages/svelte/test/css/samples/siblings-combinator-global-slots/_config.js b/packages/svelte/test/css/samples/siblings-combinator-global-slots/_config.js new file mode 100644 index 000000000000..6744b032874e --- /dev/null +++ b/packages/svelte/test/css/samples/siblings-combinator-global-slots/_config.js @@ -0,0 +1,25 @@ +export default { + warnings: [ + { + code: 'css-unused-selector', + message: 'Unused CSS selector ":global(input) ~ p"', + pos: 160, + start: { + character: 160, + column: 1, + line: 11 + }, + end: { + character: 178, + column: 19, + line: 11 + }, + frame: ` 9: :global(input) ~ span { color: red; } + 10: /* no match */ + 11: :global(input) ~ p { color: red; } + ^ + 12: + ` + } + ] +}; diff --git a/packages/svelte/test/css/samples/siblings-combinator-global-slots/expected.css b/packages/svelte/test/css/samples/siblings-combinator-global-slots/expected.css new file mode 100644 index 000000000000..9b583715eda1 --- /dev/null +++ b/packages/svelte/test/css/samples/siblings-combinator-global-slots/expected.css @@ -0,0 +1 @@ +input+span.svelte-xyz{color:red}input~span.svelte-xyz{color:red} \ No newline at end of file diff --git a/packages/svelte/test/css/samples/siblings-combinator-global-slots/input.svelte b/packages/svelte/test/css/samples/siblings-combinator-global-slots/input.svelte new file mode 100644 index 000000000000..35edb26e58d4 --- /dev/null +++ b/packages/svelte/test/css/samples/siblings-combinator-global-slots/input.svelte @@ -0,0 +1,12 @@ +
+ + Hello +
+ +