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

Don't add classname for :is() and :where() selectors #2836

Open
siriwatknp opened this issue Jul 24, 2022 · 13 comments
Open

Don't add classname for :is() and :where() selectors #2836

siriwatknp opened this issue Jul 24, 2022 · 13 comments

Comments

@siriwatknp
Copy link

siriwatknp commented Jul 24, 2022

The problem

I want to leverage :where selector (it is good for theming because it has 0 specificity) to style a component based on the parent attribute.

However, emotion prefix the style with the generated class name in front of :where which does not make the style works:

const Component = style('div')`
  color: black
  :where([data-color-scheme="dark"]) & {
    color: white
  }
`

turned into

<style>
// to make this style work, the `.css-9qpay1` at the beginning should be removed
.css-9qpay1:where([data-color-scheme="dark"]) .css-9qpay1{color:white;}
</style>

Here is the reproduction: https://codesandbox.io/s/emotion-forked-e9fyes?file=/index.js

Proposed solution

Should :is and :where be the exceptional case to not implicitly append to the class name?

const Component = style('div')`
  :where([data-color-scheme="dark"]) & {
    color: white
  }
  // result as ':where([data-color-scheme="dark"]) .css { color: white };
`

const Component = style('div')`
  // explicitly declare the '&'
  &:where([data-color-scheme="dark"]) {
    color: white
  }
  // result as '.css:where([data-color-scheme="dark"]) .css { color: white };
`

Alternative solutions

Is there any workaround that I might have missed?

Additional context

Hi, I am from the MUI core team. We are working on the theming API to let developers theme components based on color schemes without having to read the mode from the theme.

Our API added an attribute to the html when the color scheme changes:

// light mode
<html data-color-scheme="light">

// dark mode
<html data-color-scheme="dark">
@Andarist
Copy link
Member

This is a byproduct of our compat plugin - however, this matches the behavior of Emotion 10 for all pseudo-selectors. I'd like to remove this compat plugin in the next major version of Emotion but I'm worried that changing the current behavior, even for a subset of pseudo-selectors, would be a breaking change that we can't do (we can continue discussing this though, those 2 are fairly new additions and it somewhat makes sense to treat them differently).

Right now you sort of can achieve this by combining namespace plugin and :global(): https://codesandbox.io/s/emotion-forked-rc5rpb?file=/index.js . Note that the generated rule has increased specificity because it uses the class name twice - maybe this could be fixed

@siriwatknp
Copy link
Author

@Andarist Thanks a lot for the info.

Note that the generated rule has increased specificity because it uses the class name twice - maybe this could be fixed

Can you point me to where to fix this? I can submit a PR.

Note, from what I check styled-components does not work as well.

@Andarist
Copy link
Member

My guess is that the problem is in this compat plugin:

export let compat = element => {

But I didn't yet analyze this in full.

One alternative solution that I was thinking about (since adding the namespace plugin in MUI might be problematic in the long run) is to provide a way to "escape" a pseudo selector somehow so we could ignore it in the compat plugin (since it's the one that automatically prepends implicit & to all rules starting with :). Any ideas on what kind of a token we could use there?

@siriwatknp
Copy link
Author

siriwatknp commented Jul 25, 2022

I can think of:

  • ~: it is the root dir in the terminal which could refer to global (this might not work, it refers to general sibling selectors)
  • *: this looks like global (this might not work, it refers to the universal selector in CSS)
  • !: or with the negate symbol to opt-out of implicit &

The usage would look like this, right?

const Component = style('div')`
  color: black
  ~:where([data-color-scheme="dark"]) & {
    color: white
  }
`

@siriwatknp
Copy link
Author

I found a workaround! https://codesandbox.io/s/emotion-forked-6eco3g?file=/index.js

@Andarist
Copy link
Member

Andarist commented Aug 8, 2022

Note that this might be prone to some edge cases but it probably shouldn't matter for real-life cases in which you are gonna use this.

@Andarist
Copy link
Member

After thinking about this a little bit - I think that a comment annotation would be the easiest solution for this. Something like:

const Component = style('div')`
  color: black
  :where([data-color-scheme="dark"]) & {
    /* emotion-no-compat */
    color: white
  }
`

I think I'm gonna add a dev-only warning for all instances of the "compat" behavior kicking in so people can start fixing their selectors in v11 but I can't change this default right now.

Note that whatever we could do about this in Emotion right now would require a new v11 minor version - and, from what I understand, you need this in MUI and you don't control which version of Emotion people have installed there as its a peer dep. So I'm unsure if any solution from our side would actually solve your issue - unless you expect people to update Emotion frequently.

@siriwatknp
Copy link
Author

I think that a comment annotation would be the easiest solution for this.

We have a util function that produces :where([data-color-scheme="dark"]) &. Does it mean that developers have to specify the comment all the time?:

styled('div')(({ theme }) => ({
  // theme.getColorSchemeSelector('dark') => ':where([data-color-scheme="dark"]) &'
  [theme.getColorSchemeSelector('dark')]: {
    /* emotion-no-compat */
    // ...styles
  }
}))

@ByronDWall
Copy link

It may not work for all use-cases, but in case anyone else comes looking, I've had success with using the * universal selector before the :where() pseudoclass along with the & class selector to apply the style to the desired class like so:

* :where() & {...

const  texStyle = css`
  color: red;
  * :where([data-your-theme-value="example"]) & {
    color: blue;
   }
`

() => (
  <>
    <div data-your-theme-value='example'>
      <span css={textStyle}> Blue Text Here </span>
    </div>
    <span css={textStyle}>Red Text Here</span>
  </>
)

See the codesandbox PoC here

@siriwatknp
Copy link
Author

It may not work for all use-cases, but in case anyone else comes looking, I've had success with using the * universal selector before the :where() pseudoclass along with the & class selector to apply the style to the desired class like so:

* :where() & {...

const  texStyle = css`

  color: red;

  * :where([data-your-theme-value="example"]) & {

    color: blue;

   }

`



() => (

  <>

    <div data-your-theme-value='example'>

      <span css={textStyle}> Blue Text Here </span>

    </div>

    <span css={textStyle}>Red Text Here</span>

  </>

)

See the codesandbox PoC here

Oh, great. Thanks for letting me know.

@ivanjonas
Copy link

ivanjonas commented Jun 27, 2023

After thinking about this a little bit - I think that a comment annotation would be the easiest solution for this. Something like:

const Component = style('div')`
  color: black
  :where([data-color-scheme="dark"]) & {
    /* emotion-no-compat */
    color: white
  }
`

I think I'm gonna add a dev-only warning for all instances of the "compat" behavior kicking in so people can start fixing their selectors in v11 but I can't change this default right now.

Unfortunately, this solution breaks down completely when using Emotion object notation.

<div
  css={{
    color: 'black',
    ':where([data-color-scheme="dark"]) &': {
      /* emotion-no-compat */              //  <== this is just a JS comment; will not be interpreted by Emotion.
      color: 'white',
    }
  }}
>

@thomaslindstrom
Copy link

Was this ever resolved?

@thomaslindstrom
Copy link

Solved in #3210

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants