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] :is() and :where() not allowed inside :not()? #9038

Closed
miragecraft opened this issue Jul 6, 2023 · 10 comments
Closed

[selectors-4] :is() and :where() not allowed inside :not()? #9038

miragecraft opened this issue Jul 6, 2023 · 10 comments
Labels
selectors-4 Current Work

Comments

@miragecraft
Copy link

miragecraft commented Jul 6, 2023

Just looking for clarification.

I find that selector such as

.select *:not(.select :is(.deselect, .deselect *))

gets parsed as

.select *:not(.select .deselect, .deselect *) instead of .select *:not(.select .deselect, .select .deselect *)

in both Chrome and Firefox (figured out using trial and error).

Is this because this usage is not supported?

Edit:

To clarify the problem, :not(.a :is(.b, .b *)) behaves like :not(.a .b, .b *) instead of :not(.a .b, .a .b *).

Where did the .a in .a .b * go?

@SelenIT
Copy link
Collaborator

SelenIT commented Jul 6, 2023

It seems to be a known pitfall of :is()/:where(). Their argument has global scope rather than is scoped to the selector before it. I.e., .a *:is(.b .c) actually searches for the .c elements that have both .a and .b elements in their ancestors chain in any order, so not only .a > .b > .c, but also .b > .a > .c and .a.b > .c would match it. There is a scoped styles proposal in [css-cascade-6] to work around this problem.

@miragecraft
Copy link
Author

miragecraft commented Jul 6, 2023

So this usage is not supported due to performance consideration?

But you can already stack a lot of :not(), just without :is() and :where().

Does the argument come down to "we won't make it easier to hang yourself"?

@SelenIT
Copy link
Collaborator

SelenIT commented Jul 6, 2023

If I understod the problem correctly, it's supported, it just doesn't do what many could expect from it 😕

.select *:is(.deselect *) selects elements that have both .select and .deselect classes somewhere in their ancestors chain — no matter in which order. Similarly, .select :not(.deselect *)searches for elements that have the .select ancestor but don't have any .deselect ancestor — be it inside or outside the .select element in the tree.

To select content of .select elements except .deselect descendants and their subtrees, the new @scope (.select) to (.deselect) rule can be used. Alternatively, instead of selecting these elements directly, you can style them through custom properties by seting them for one class and resetting for the other (example).

@miragecraft
Copy link
Author

The problem I'm referring to, is using :is() inside :not() to simplify multiple conditions.

Not using :is() and :not() separately.

I understand that the proposed/upcoming @scope at-rule can solve this issue, but I have no confidence it'll be implemented by all major browsers within a reasonable timeframe.

@SelenIT
Copy link
Collaborator

SelenIT commented Jul 6, 2023

It seems that your problem boils down to expecting .select :is(.deselect *) to be expanded as .select .deselect * exclusively. Unfortunately, it's not how :is() works :(

@miragecraft
Copy link
Author

miragecraft commented Jul 6, 2023

It seems that your problem boils down to expecting .select :is(.deselect *) to be expanded as .select .deselect * exclusively. Unfortunately, it's not how :is() works :(

That's not the case.

I think you're misunderstanding the problem.

.select *:is(.deselect *) selects elements that have both .select and .deselect classes somewhere in their ancestors chain — no matter in which order. Similarly, .select :not(.deselect *)searches for elements that have the .select ancestor but don't have any .deselect ancestor — be it inside or outside the .select element in the tree.

I have no confusion on what your example selector here is doing, it's simply not related to the problem I'm having.

Please refer back to my original example:

.select *:not(.select :is(.deselect, .deselect *))

I expect it to be the equivalent of

.select *:not(.select .deselect, .select .deselect *)

But it actually became

.select *:not(.select .deselect, .deselect *)

That's the central problem.

How did :not(.a :is(.b, .b *)) become :not(.a .b, .b *) instead of :not(.a .b, .a .b *)?

@SelenIT
Copy link
Collaborator

SelenIT commented Jul 6, 2023

.a :is(.b *) is equivalent to .a .b *, .a.b *, .b .a *

@miragecraft
Copy link
Author

Ah.... I think I see it now.

The problem is that .a :is(.b *) is not equivalent to .a .b * because it no longer ensures that .b is nested inside .a.

Sorry about not getting it until now, this is a bit of a brain twister for me.

@Loirooriol
Copy link
Contributor

BTW, it seems that what you actually want is a scoping limit. Something like

@scope (.select) to (.deselect) {
  * { /* ... */ }
}

@miragecraft
Copy link
Author

BTW, it seems that what you actually want is a scoping limit. Something like

@scope (.select) to (.deselect) {
  * { /* ... */ }
}

Yes that's exactly what I want, I'm trying to MacGyver a rudimentary version of @scope, by stacking a few of those selectors:

/* not nested */
.select *:not(.deselect, .deselect *),
/* nested x1 */
.deselect .select *:not(.deselect .deselect, .deselect .deselect *),
/* nested x 2 */
.deselect .deselect .select *:not(.deselect .deselect .deselect, .deselect .deselect .deselect *) {
    /* scoped styles */
}

Yes, it doesn't support infinite nesting, but realstically you won't be nesting more than one or two times anyways so it's enough for my needs.

Of course, actual @scope would be great, but I'm not expecting full browser support to be here anytime soon.

@fantasai fantasai added the selectors-4 Current Work label Jan 9, 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

4 participants