Skip to content

Add option explicitAmpersand on @emotion/cache to handle :is and :where, etc #3210

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/brown-points-push.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@emotion/cache': minor
---

Add option `explicitAmpersand` to opt-out of automatically prefixing pseudo classes with the parent class
34 changes: 34 additions & 0 deletions packages/cache/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,37 @@ A DOM node that emotion will insert all of its style tags into. This is useful f
`boolean`

A boolean representing whether to prepend rather than append style tags into the specified container DOM node.

### `explicitAmpersand`

`boolean`

Defaults to `false`. Opt-out of automatic pseudo class prefixing.

Consider the following component:

```jsx
<article
css={css`
:where([data-theme='dark']) & {
/* ... */
}
`}
/>
```

With `explicitAmpersand` set to `true`:

```css
:where([data-theme='dark']) .emotion-article {
/* ... */
}
```

With `explicitAmpersand` set to `false`:

```css
.emotion-article:where([data-theme='dark']) .emotion-article {
/* ... */
}
```
108 changes: 108 additions & 0 deletions packages/cache/__tests__/__snapshots__/index.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,112 @@ exports[`should accept insertionPoint option 1`] = `
</head>
`;

exports[`should not prefix pseudo-classes automatically when using explicitAmpersand option 1`] = `
:where([dir='rtl']) .emotion-0 {
color: red;
}

.emotion-0 :is(p, ul)+:is(p, ul) {
margin-top: 1em;
}

.emotion-0:before {
content: 'test';
}

<body>
<div
id="container"
>
<style
data-emotion="test-container"
data-s=""
>

.emotion-0{}
</style>
<style
data-emotion="test-container"
data-s=""
>

:where([dir='rtl']) .emotion-0{color:red;}
</style>
<style
data-emotion="test-container"
data-s=""
>

.emotion-0 :is(p, ul)+:is(p, ul){margin-top:1em;}
</style>
<style
data-emotion="test-container"
data-s=""
>

.emotion-0:before{content:'test';}
</style>
</div>
<div>
<div
class="emotion-0"
/>
</div>
</body>
`;

exports[`should prefix pseudo-classes automatically when not using explicitAmpersand option 1`] = `
.emotion-0:where([dir='rtl']) .emotion-0 {
color: red;
}

.emotion-0:is(p, ul)+:is(p, ul) {
margin-top: 1em;
}

.emotion-0:before {
content: 'test';
}

<body>
<div
id="container"
>
<style
data-emotion="test-container"
data-s=""
>

.emotion-0{}
</style>
<style
data-emotion="test-container"
data-s=""
>

.emotion-0:where([dir='rtl']) .emotion-0{color:red;}
</style>
<style
data-emotion="test-container"
data-s=""
>

.emotion-0:is(p, ul)+:is(p, ul){margin-top:1em;}
</style>
<style
data-emotion="test-container"
data-s=""
>

.emotion-0:before{content:'test';}
</style>
</div>
<div>
<div
class="emotion-0"
/>
</div>
</body>
`;

exports[`throws correct error with invalid key 1`] = `"Emotion key must only contain lower case alphabetical characters and - but "." was passed"`;
67 changes: 66 additions & 1 deletion packages/cache/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import 'test-utils/next-env'
import { safeQuerySelector } from 'test-utils'
import createCache from '@emotion/cache'
import { jsx, CacheProvider } from '@emotion/react'
import { jsx, CacheProvider, css } from '@emotion/react'
import { render } from '@testing-library/react'

test('throws correct error with invalid key', () => {
Expand Down Expand Up @@ -54,3 +54,68 @@ it('should accept container option', () => {

expect(document.body).toMatchSnapshot()
})

it('should not prefix pseudo-classes automatically when using explicitAmpersand option', () => {
const body = safeQuerySelector('body')
body.innerHTML = `<div id="container" />`

const cache = createCache({
key: 'test-container',
container: safeQuerySelector('#container'),
explicitAmpersand: true
})

render(
<CacheProvider value={cache}>
<div
css={css`
:where([dir='rtl']) & {
color: red;
}

:is(p, ul) + :is(p, ul) {
margin-top: 1em;
}

&:before {
content: 'test';
}
`}
/>
</CacheProvider>
)

expect(document.body).toMatchSnapshot()
})

it('should prefix pseudo-classes automatically when not using explicitAmpersand option', () => {
const body = safeQuerySelector('body')
body.innerHTML = `<div id="container" />`

const cache = createCache({
key: 'test-container',
container: safeQuerySelector('#container')
})

render(
<CacheProvider value={cache}>
<div
css={css`
:where([dir='rtl']) & {
color: red;
}

:is(p, ul) + :is(p, ul) {
margin-top: 1em;
}

&:before {
content: 'test';
}
`}
/>
</CacheProvider>
)

expect(document.body).toMatchSnapshot()
})
5 changes: 4 additions & 1 deletion packages/cache/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type Options = {
container?: HTMLElement,
speedy?: boolean,
prepend?: boolean,
explicitAmpersand?: boolean,
insertionPoint?: HTMLElement
}
*/
Expand Down Expand Up @@ -115,7 +116,9 @@ let createCache = (options /*: Options */) /*: EmotionCache */ => {
sheet: StyleSheet,
shouldCache: boolean
) => string | void */
const omnipresentPlugins = [compat, removeLabel]
const omnipresentPlugins = options.explicitAmpersand
? [removeLabel]
: [compat, removeLabel]

if (process.env.NODE_ENV !== 'production') {
omnipresentPlugins.push(
Expand Down
Loading