@@ -46,6 +46,14 @@ class Constants:
4646
4747 combinator = ' combinator'
4848
49+ # Selectors Level 4 pseudo-classes that accept a full selector list
50+ # as their argument (rather than a simple expression like an+b).
51+ selector_pseudos = frozenset ([':has(' , ':is(' , ':where(' , ':matches(' , ':any(' ])
52+
53+ # CSS4 pseudo-elements whose argument is a selector (not an expression).
54+ # These push a 'pseudo-element' context and accept simple_selector_sequence.
55+ selector_pseudo_elements = frozenset (['::slotted(' , '::cue(' , '::cue-region(' ])
56+
4957
5058@dataclasses .dataclass
5159class New (cssutils .util ._BaseClass ):
@@ -154,7 +162,12 @@ def _COMMENT(self, expected, seq, token, tokenizer=None):
154162 def _S (self , expected , seq , token , tokenizer = None ):
155163 # S
156164 context = self .context [- 1 ]
157- if context .startswith ('pseudo-' ):
165+ if context == 'pseudo-class-has' and 'combinator' in expected :
166+ # space is a descendant combinator inside :has(), :is(), etc.
167+ self .append (seq , Constants .S , 'descendant' , token = token )
168+ return Constants .simple_selector_sequence + Constants .combinator
169+
170+ elif context .startswith ('pseudo-' ):
158171 if seq and seq [- 1 ].value not in '+-' :
159172 # e.g. x:func(a + b)
160173 self .append (seq , Constants .S , 'S' , token = token )
@@ -226,8 +239,19 @@ def _pseudo(self, expected, seq, token, tokenizer=None):
226239 if val .endswith ('(' ):
227240 # function
228241 # "pseudo-" "class" or "element"
229- self .context .append (typ )
230- return Constants .expressionstart
242+ if val .lower () in Constants .selector_pseudos :
243+ ctx = 'pseudo-class-has'
244+ elif val .lower () in Constants .selector_pseudo_elements :
245+ # CSS4 pseudo-elements accepting a full selector argument
246+ # (e.g. ::slotted(), ::cue()).
247+ ctx = 'pseudo-element'
248+ else :
249+ self .context .append (typ )
250+ return Constants .expressionstart
251+ # Selectors Level 4: both pseudo-class and pseudo-element
252+ # forms that accept a full selector list as argument
253+ self .context .append (ctx )
254+ return Constants .simple_selector_sequence
231255 elif 'negation' == context :
232256 return Constants .negationend
233257 elif 'pseudo-element' == typ :
@@ -312,6 +336,11 @@ def _ident(self, expected, seq, token, tokenizer=None):
312336 self .append (seq , val , 'negation-type-selector' , token = token )
313337 return Constants .negationend
314338
339+ # context: pseudo-class-has (selector-accepting pseudo like :has())
340+ elif context == 'pseudo-class-has' :
341+ self .append (seq , val , 'type-selector' , token = token )
342+ return Constants .simple_selector_sequence2 + Constants .combinator
343+
315344 # context: pseudo
316345 elif context .startswith ('pseudo-' ):
317346 # :func(...)
@@ -391,6 +420,18 @@ def _char(self, expected, seq, token, tokenizer=None): # noqa: C901
391420 context = self .context [- 1 ]
392421 return Constants .simple_selector_sequence + Constants .combinator
393422
423+ # context: pseudo-class-has (:has(), :is(), :where(), etc.)
424+ if ')' == val and context == 'pseudo-class-has' :
425+ # :has(selector) end
426+ self .append (seq , val , 'function-end' , token = token )
427+ self .context .pop () # pseudo-class-has is done
428+ context = self .context [- 1 ]
429+ if 'pseudo-element' == context :
430+ # inside ::slotted(:is(...)) — outer pseudo-element still open
431+ return Constants .expression
432+ else :
433+ return Constants .simple_selector_sequence + Constants .combinator
434+
394435 # context: pseudo (at least one expression)
395436 if val in '+-' and context .startswith ('pseudo-' ):
396437 # :func(+ -)"
@@ -404,11 +445,16 @@ def _char(self, expected, seq, token, tokenizer=None): # noqa: C901
404445 if (
405446 ')' == val
406447 and context .startswith ('pseudo-' )
407- and Constants .expression == expected
448+ and (
449+ Constants .expression == expected
450+ or (context == 'pseudo-element' and 'combinator' in expected )
451+ )
408452 ):
409- # :func(expression)"
453+ # :func(expression) or ::slotted(selector) end
410454 self .append (seq , val , 'function-end' , token = token )
411455 self .context .pop () # pseudo is done
456+ # context still holds the pre-pop value: check what *type* of pseudo
457+ # we just closed to decide what may follow.
412458 if 'pseudo-element' == context :
413459 return Constants .combinator
414460 else :
0 commit comments