Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[selectors-4] Should :has() be a complex selector? #10756

Closed
byung-woo opened this issue Aug 19, 2024 · 8 comments
Closed

[selectors-4] Should :has() be a complex selector? #10756

byung-woo opened this issue Aug 19, 2024 · 8 comments
Labels
selectors-4 Current Work

Comments

@byung-woo
Copy link
Member

This is a follow-up question about the validity of :host(<selector>) argument (csswg-drafts/issues/7953) and the wpt pull request that adds tests for :host(:has(section)) case (web-platform-tests/wpt/pull/47441).

Given the fact that :has() represents a relationship between multiple elements, it seems reasonable to treat a selector containing:has() as a complex selector even if the selector is a form of compound selector. (e.g. .a:has(.b))

The spec describes that compound selector represents conditions on a single element and complex selector represents relationships between multiple elements:

If it makes sense, I think it might be helpful to add a comment to the complex (or compound) selector description that says that even if :has() describes a 'has-descendant' relationship (e.g., :has(.a)) which doesn't have an explicit combinator, it is a complex selector because of the implicit descendant relationship between the anchor element and the subject of the argument.

@byung-woo
Copy link
Member Author

Or, given that the :has() grammar definition accepts a list of relative selectors as argument, I think we could ask this a little differently: "Should a relative selector be a complex selector?"

Then it seems that this edit proposal in #5093 (comment) may cover the :host(:has(section)) case.

The [=logical combination pseudo-classes=]
are allowed anywhere that any other [=pseudo-classes=] are allowed,
but pass any restrictions to their arguments.
(For example, if only [=compound selectors=] are allowed,
then only [=compound selectors=] are valid within an '':is()''.)

Based on the assumption that the above statement will be applied to the :has() as well, I think we can say that:host(:has(section)) will be invalid because the relative selector section in the :has() is not a compound selector but a complex selector.

@JoshTumath JoshTumath added the selectors-4 Current Work label Aug 21, 2024
@sorvell
Copy link

sorvell commented Aug 26, 2024

I'm not suggesting the analysis here is incorrect (based on #7953), but from a developer perspective, yes, it should be complex and it should definitely match inside :host(...).

The :has selector has been an incredibly useful addition to css.

We need less, not more styling restrictions for Shadow DOM styling.

Right now :host(:has(...)) has different behavior in each major engine (see example):

  • Chrome 128: does not match at all
  • Firefox 129: matches compound selector, e.g. :host(:has(div))
  • Safari 17.5: matches complex selector, e.g. :host(:has(div div))

What developers who actually use Shadow DOM beg for is Safari's behavior. Please clarify the spec to clearly support this. And Safari's implementation seems like an existence proof that the implementation is doable.

@michaelwarren1106
Copy link

michaelwarren1106 commented Aug 26, 2024

echo @sorvell here. I definitely think that ironing out the behavior of :has with the :host selector so that a) it’s consistent, b) powerful even in shadow root styles, and c) super flexible as well are all good things.

right now all we really have for styling light dom elements from inside shadow roots is ::slotted() and there are definitely problems with that already. if we could add :has alongside that would definitely help alleviate some of the problems of ::slotted

@byung-woo
Copy link
Member Author

byung-woo commented Aug 26, 2024

@sorvell: What I tried to clarify in this issue is not about the :has() usage in :host() but about the different behavior what you pointed:

  • Chrome 128: does not match at all
  • Firefox 129: matches compound selector, e.g. :host(:has(div))
  • Safari 17.5: matches complex selector, e.g. :host(:has(div div))

Chrome and Firefox follow the agreement in #5093 (comment) that passes restrictions to the argument of logical combinations. So :host(:has(div div)) is invalid in both browsers due to the compound-selector restriction on :host() argument.

But it seems that there is a conflict between Firefox and Chrome on :host(:has(div)) case.

Firefox allows :host(:has(div)) case because it seems to treat the div inside :has() as a compound selector. If the div is a compound selector, we can say that Firefox follows the compound-selector restriction in this case.

Chrome doesn't allow :host(:has(div)) case because it treats the div inside :has() as a non-compound selector. If the div is not a compound selector, we can say that Chrome follows the compound-selector restriction in this case.

I think that, clarifying this conflict would be helpful for the general discussion of :has() usage inside :host() because we can narrow down the case as supporting complex selector inside :host(). (For me, :host(.a:has(div)), :host(.a:has(> div)), :host(:is(div .a), :host(:is(div > .a)) looks have similar problem space caused by checking parent/child or ancestor/descendant relationship inside :host())

@Westbrook
Copy link

I would say that the compound-selector restriction on :host() should be removed all together, rather than attempting to add extra nuance to it in these cases (e.g. :host(:has(div)), :host(.a:has(div)), :host(.a:has(> div)), :host(:is(div .a), :host(:is(div > .a), et al). These are are important selecting contexts that shouldn't be removed from a developers tool box just because they chose to leverage shadow DOM as their encapsulation method rather than something like @scope.

I would go further to day that this restriction and the same on ::slotted() are based on performance data from more than 10 years ago when no browsers was shipping :has() or container queries (both, also, for their performance constrains). That's multiple epochs in frontend development, and 100+ major versions in various browsing engines. It would be a really interesting addition to this conversation to be able to see updated numbers on whether the costs here continue to warrant the need for these restrictions in the more modern architectures by which browsers power selector parsing today.

@byung-woo
Copy link
Member Author

I can fully understand that there are requirements of using :has() inside :host(). And the discussion about the case may lead to the discussion of the compound-selector restriction on :host(). But I thought that this is a different issue of a confusion that can be discussed separately based on the current agreements. (I think that it is worth to clarifying this, regardless of how we agreed on the compound-selector restriction on :host() or :host(:has()) cases)

In the perspective of spec reader, I thought that there might be a grey area on applying the compound-selector restriction to :host(:has(div)) case, and it seems to be related to how to read :has() argument. So I thought that clarifying the grey area would be helpful for further discussion about :has() related cases including the :host(:has()) cases.

This is how I understood the confusion so far:

  1. The argument of :has() is a list of relative-selector which implies the anchor element at leftmost:

    Relative selectors begin with a combinator, with a selector representing the anchor element implied at the start of the selector. (If no combinator is present, the descendant combinator is implied.)

  2. Based on the relative selector definition, we should read the argument of :has(div) as <has-anchor-element> <descendant-combinator> div, not just div. And if this makes sense, I think that we should treat a relative-selector as a complex-selector because it always have a combinator.

  3. If the above steps make sense, given the recent agreement of passing restrictions to the argument of logical combinations, the :host(:has(div)) seems to violate the compound-selector restriction on :host() because the :has() argument div is a complex selector that has a descendant-combinator.

Among the above steps, I wanted to clarify the step 2 (how to read :has() argument) aside from the :host(:has()) cases because I thought that it might be helpful for minimizing some confusions related to the differences between compound-selector and complex-selector.

I thought about adding a short note in the complex-selector description so that it helps reducing the confusion : "relative-selector is a complex-selector as it always has a explicit combinator or an implicit descendant combinator. (e.g. the argument div in :has(div) is a complex-selector as it has implicit descendant combinator)"

And I thought that this will not affect further discussions about :host(:has()) case or :host(<complex-selector>) case because this just clarifies how to apply the compound restriction to a specific case.

@cdoublev
Copy link
Collaborator

cdoublev commented Aug 27, 2024

I thought about adding a short note in the complex-selector description so that it helps reducing the confusion

I cannot say if/how this can be improved but the WD live version of the spec has this note that the current REC published version does not:

Since :has() takes a <relative-selector-list>, its arguments are inherently complex selectors (because they start, perhaps implicitly, with a combinator). This means :has() cannot be used in contexts that don’t allow complex selectors; its arguments will be guaranteed to be invalid.

@byung-woo
Copy link
Member Author

Oh right. It's already very clearly stated in the live version! Thanks @cdoublev for sharing!

I'm closing this issue as there seem to be no arguable point on how to read the ':has()' argument.

@byung-woo byung-woo closed this as not planned Won't fix, can't repro, duplicate, stale Aug 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
selectors-4 Current Work
Projects
None yet
Development

No branches or pull requests

6 participants