@ -1,7 +1,7 @@
/** @import { Visitors } from 'zimmerframe' */
/** @import { Visitors } from 'zimmerframe' */
/** @import * as Compiler from '#compiler' */
/** @import * as Compiler from '#compiler' */
import { walk } from 'zimmerframe' ;
import { walk } from 'zimmerframe' ;
import { get _p ossible_values } from './utils.js' ;
import { get _p arent_rules , get _p ossible_values , is _outer _global } from './utils.js' ;
import { regex _ends _with _whitespace , regex _starts _with _whitespace } from '../../patterns.js' ;
import { regex _ends _with _whitespace , regex _starts _with _whitespace } from '../../patterns.js' ;
import { get _attribute _chunks , is _text _attribute } from '../../../utils/ast.js' ;
import { get _attribute _chunks , is _text _attribute } from '../../../utils/ast.js' ;
@ -172,7 +172,7 @@ function get_relative_selectors(node) {
}
}
/ * *
/ * *
* Discard trailing ` :global(...) ` selectors without a ` :has/is/where/not(...) ` modifier , these are unused for scoping purposes
* Discard trailing ` :global(...) ` selectors , these are unused for scoping purposes
* @ param { Compiler . Css . ComplexSelector } node
* @ param { Compiler . Css . ComplexSelector } node
* /
* /
function truncate ( node ) {
function truncate ( node ) {
@ -182,21 +182,22 @@ function truncate(node) {
// not after a :global selector
// not after a :global selector
! metadata . is _global _like &&
! metadata . is _global _like &&
! ( first . type === 'PseudoClassSelector' && first . name === 'global' && first . args === null ) &&
! ( first . type === 'PseudoClassSelector' && first . name === 'global' && first . args === null ) &&
// not a :global(...) without a :has/is/where/not(...) modifier
// not a :global(...) without a :has/is/where(...) modifier that is scoped
( ! metadata . is _global ||
! metadata . is _global
selectors . some (
( selector ) =>
selector . type === 'PseudoClassSelector' &&
selector . args !== null &&
( selector . name === 'has' ||
selector . name === 'is' ||
selector . name === 'where' ||
selector . name === 'not' )
) )
) ;
) ;
} ) ;
} ) ;
return node . children . slice ( 0 , i + 1 ) ;
return node . children . slice ( 0 , i + 1 ) . map ( ( child ) => {
// In case of `:root.y:has(...)`, `y` is unscoped, but everything in `:has(...)` should be scoped (if not global).
// To properly accomplish that, we gotta filter out all selector types except `:has`.
const root = child . selectors . find ( ( s ) => s . type === 'PseudoClassSelector' && s . name === 'root' ) ;
if ( ! root || child . metadata . is _global _like ) return child ;
return {
... child ,
selectors : child . selectors . filter ( ( s ) => s . type === 'PseudoClassSelector' && s . name === 'has' )
} ;
} ) ;
}
}
/ * *
/ * *
@ -334,7 +335,9 @@ function apply_combinator(combinator, relative_selector, parent_selectors, rule,
* @ param { Compiler . AST . RegularElement | Compiler . AST . SvelteElement } element
* @ param { Compiler . AST . RegularElement | Compiler . AST . SvelteElement } element
* /
* /
function mark ( relative _selector , element ) {
function mark ( relative _selector , element ) {
relative _selector . metadata . scoped = true ;
if ( ! is _outer _global ( relative _selector ) ) {
relative _selector . metadata . scoped = true ;
}
element . metadata . scoped = true ;
element . metadata . scoped = true ;
}
}
@ -415,6 +418,21 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
/** @type {Array<Compiler.AST.RegularElement | Compiler.AST.SvelteElement>} */
/** @type {Array<Compiler.AST.RegularElement | Compiler.AST.SvelteElement>} */
let sibling _elements ; // do them lazy because it's rarely used and expensive to calculate
let sibling _elements ; // do them lazy because it's rarely used and expensive to calculate
// If this is a :has inside a global selector, we gotta include the element itself, too,
// because the global selector might be for an element that's outside the component (e.g. :root).
const rules = [ rule , ... get _parent _rules ( rule ) ] ;
const include _self =
rules . some ( ( r ) => r . prelude . children . some ( ( c ) => c . children . some ( ( s ) => is _global ( s , r ) ) ) ) ||
rules [ rules . length - 1 ] . prelude . children . some ( ( c ) =>
c . children . some ( ( r ) =>
r . selectors . some ( ( s ) => s . type === 'PseudoClassSelector' && s . name === 'root' )
)
) ;
if ( include _self ) {
child _elements . push ( element ) ;
descendant _elements . push ( element ) ;
}
walk (
walk (
/** @type {Compiler.SvelteNode} */ ( element . fragment ) ,
/** @type {Compiler.SvelteNode} */ ( element . fragment ) ,
{ is _child : true } ,
{ is _child : true } ,
@ -460,7 +478,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
const descendants =
const descendants =
left _most _combinator . name === '+' || left _most _combinator . name === '~'
left _most _combinator . name === '+' || left _most _combinator . name === '~'
? ( sibling _elements ? ? = get _following _sibling _elements ( element ))
? ( sibling _elements ? ? = get _following _sibling _elements ( element , include _self ))
: left _most _combinator . name === '>'
: left _most _combinator . name === '>'
? child _elements
? child _elements
: descendant _elements ;
: descendant _elements ;
@ -481,20 +499,6 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
}
}
if ( ! matched ) {
if ( ! matched ) {
if ( relative _selector . metadata . is _global && ! relative _selector . metadata . is _global _like ) {
// Edge case: `:global(.x):has(.y)` where `.x` is global but `.y` doesn't match.
// Since `used` is set to `true` for `:global(.x)` in css-analyze beforehand, and
// we have no way of knowing if it's safe to set it back to `false`, we'll mark
// the inner selector as used and scoped to prevent it from being pruned, which could
// result in a invalid CSS output (e.g. `.x:has(/* unused .y */)`). The result
// can't match a real element, so the only drawback is the missing prune.
// TODO clean this up some day
complex _selectors [ 0 ] . metadata . used = true ;
complex _selectors [ 0 ] . children . forEach ( ( selector ) => {
selector . metadata . scoped = true ;
} ) ;
}
return false ;
return false ;
}
}
}
}
@ -507,9 +511,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
switch ( selector . type ) {
switch ( selector . type ) {
case 'PseudoClassSelector' : {
case 'PseudoClassSelector' : {
if ( name === 'host' || name === 'root' ) {
if ( name === 'host' || name === 'root' ) return false ;
return false ;
}
if (
if (
name === 'global' &&
name === 'global' &&
@ -578,23 +580,6 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
}
}
if ( ! matched ) {
if ( ! matched ) {
if (
relative _selector . metadata . is _global &&
! relative _selector . metadata . is _global _like
) {
// Edge case: `:global(.x):is(.y)` where `.x` is global but `.y` doesn't match.
// Since `used` is set to `true` for `:global(.x)` in css-analyze beforehand, and
// we have no way of knowing if it's safe to set it back to `false`, we'll mark
// the inner selector as used and scoped to prevent it from being pruned, which could
// result in a invalid CSS output (e.g. `.x:is(/* unused .y */)`). The result
// can't match a real element, so the only drawback is the missing prune.
// TODO clean this up some day
selector . args . children [ 0 ] . metadata . used = true ;
selector . args . children [ 0 ] . children . forEach ( ( selector ) => {
selector . metadata . scoped = true ;
} ) ;
}
return false ;
return false ;
}
}
}
}
@ -662,7 +647,10 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
const parent = /** @type {Compiler.Css.Rule} */ ( rule . metadata . parent _rule ) ;
const parent = /** @type {Compiler.Css.Rule} */ ( rule . metadata . parent _rule ) ;
for ( const complex _selector of parent . prelude . children ) {
for ( const complex _selector of parent . prelude . children ) {
if ( apply _selector ( get _relative _selectors ( complex _selector ) , parent , element , state ) ) {
if (
apply _selector ( get _relative _selectors ( complex _selector ) , parent , element , state ) ||
complex _selector . children . every ( ( s ) => is _global ( s , parent ) )
) {
complex _selector . metadata . used = true ;
complex _selector . metadata . used = true ;
matched = true ;
matched = true ;
}
}
@ -681,8 +669,11 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
return true ;
return true ;
}
}
/** @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} element */
/ * *
function get _following _sibling _elements ( element ) {
* @ param { Compiler . AST . RegularElement | Compiler . AST . SvelteElement } element
* @ param { boolean } include _self
* /
function get _following _sibling _elements ( element , include _self ) {
/** @type {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.Root | null} */
/** @type {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.Root | null} */
let parent = get _element _parent ( element ) ;
let parent = get _element _parent ( element ) ;
@ -723,6 +714,10 @@ function get_following_sibling_elements(element) {
}
}
}
}
if ( include _self ) {
sibling _elements . push ( element ) ;
}
return sibling _elements ;
return sibling _elements ;
}
}