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

Remove text opacity CSS variables from ::marker #8622

Merged
merged 4 commits into from Jun 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix missing spaces around arithmetic operators ([#8615](https://github.com/tailwindlabs/tailwindcss/pull/8615))
- Detect alpha value in CSS `theme()` function when using quotes ([#8625](https://github.com/tailwindlabs/tailwindcss/pull/8625))
- Fix "Maximum call stack size exceeded" bug ([#8636](https://github.com/tailwindlabs/tailwindcss/pull/8636))
- Allow functions returning parallel variants to mutate the container ([#8622](https://github.com/tailwindlabs/tailwindcss/pull/8622))
- Remove text opacity CSS variables from `::marker` ([#8622](https://github.com/tailwindlabs/tailwindcss/pull/8622))

## [3.1.2] - 2022-06-10

Expand Down
35 changes: 19 additions & 16 deletions src/corePlugins.js
Expand Up @@ -14,14 +14,27 @@ import { version as tailwindVersion } from '../package.json'
import log from './util/log'
import { normalizeScreens } from './util/normalizeScreens'
import { formatBoxShadowValue, parseBoxShadowValue } from './util/parseBoxShadowValue'
import { removeAlphaVariables } from './util/removeAlphaVariables'
import { flagEnabled } from './featureFlags'

export let variantPlugins = {
pseudoElementVariants: ({ addVariant }) => {
addVariant('first-letter', '&::first-letter')
addVariant('first-line', '&::first-line')

addVariant('marker', ['& *::marker', '&::marker'])
addVariant('marker', [
({ container }) => {
removeAlphaVariables(container, ['--tw-text-opacity'])

return '& *::marker'
},
({ container }) => {
removeAlphaVariables(container, ['--tw-text-opacity'])

return '&::marker'
},
])

addVariant('selection', ['& *::selection', '&::selection'])

addVariant('file', '&::file-selector-button')
Expand Down Expand Up @@ -77,21 +90,11 @@ export let variantPlugins = {
[
'visited',
({ container }) => {
let toRemove = ['--tw-text-opacity', '--tw-border-opacity', '--tw-bg-opacity']

container.walkDecls((decl) => {
if (toRemove.includes(decl.prop)) {
decl.remove()

return
}

for (const varName of toRemove) {
if (decl.value.includes(`/ var(${varName})`)) {
decl.value = decl.value.replace(`/ var(${varName})`, '')
}
}
})
removeAlphaVariables(container, [
'--tw-text-opacity',
'--tw-border-opacity',
'--tw-bg-opacity',
])

return '&:visited'
},
Expand Down
28 changes: 18 additions & 10 deletions src/lib/generateRules.js
Expand Up @@ -163,15 +163,17 @@ function applyVariant(variant, matches, context) {

let container = postcss.root({ nodes: [rule.clone()] })

for (let [variantSort, variantFunction] of variantFunctionTuples) {
let clone = container.clone()
for (let [variantSort, variantFunction, containerFromArray] of variantFunctionTuples) {
let clone = containerFromArray ?? container.clone()
let collectedFormats = []

let originals = new Map()

function prepareBackup() {
if (originals.size > 0) return // Already prepared, chicken out
clone.walkRules((rule) => originals.set(rule, rule.selector))
// Already prepared, chicken out
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was like lol "chicken out" and then I realised this was already there...
image

if (clone.raws.neededBackup) {
return
}
clone.raws.neededBackup = true
clone.walkRules((rule) => (rule.raws.originalSelector = rule.selector))
}

function modifySelectors(modifierFunction) {
Expand Down Expand Up @@ -231,6 +233,10 @@ function applyVariant(variant, matches, context) {
// reserving additional X places for these 'unknown' variants in between.
variantSort | BigInt(idx << ruleWithVariant.length),
variantFunction,

// If the clone has been modified we have to pass that back
// though so each rule can use the modified container
clone.clone(),
])
}
continue
Expand All @@ -244,13 +250,15 @@ function applyVariant(variant, matches, context) {
continue
}

// We filled the `originals`, therefore we assume that somebody touched
// We had to backup selectors, therefore we assume that somebody touched
// `container` or `modifySelectors`. Let's see if they did, so that we
// can restore the selectors, and collect the format strings.
if (originals.size > 0) {
if (clone.raws.neededBackup) {
delete clone.raws.neededBackup
clone.walkRules((rule) => {
if (!originals.has(rule)) return
let before = originals.get(rule)
let before = rule.raws.originalSelector
if (!before) return
delete rule.raws.originalSelector
if (before === rule.selector) return // No mutation happened

let modified = rule.selector
Expand Down
24 changes: 24 additions & 0 deletions src/util/removeAlphaVariables.js
@@ -0,0 +1,24 @@
/**
* This function removes any uses of CSS variables used as an alpha channel
*
* This is required for selectors like `:visited` which do not allow
* changes in opacity or external control using CSS variables.
*
* @param {import('postcss').Container} container
* @param {string[]} toRemove
*/
export function removeAlphaVariables(container, toRemove) {
container.walkDecls((decl) => {
if (toRemove.includes(decl.prop)) {
decl.remove()

return
}

for (let varName of toRemove) {
if (decl.value.includes(`/ var(${varName})`)) {
decl.value = decl.value.replace(`/ var(${varName})`, '')
}
}
})
}
49 changes: 49 additions & 0 deletions tests/parallel-variants.test.js
Expand Up @@ -85,3 +85,52 @@ test('parallel variants can be generated using a function that returns parallel
`)
})
})

test('a function that returns parallel variants can modify the container', async () => {
let config = {
content: [
{
raw: html`<div
class="hover:test:font-black test:font-bold test:font-medium font-normal"
></div>`,
},
],
plugins: [
function test({ addVariant }) {
addVariant('test', ({ container }) => {
container.walkDecls((decl) => {
decl.value = `calc(0 + ${decl.value})`
})

return ['& *::test', '&::test']
})
},
],
}

return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-normal {
font-weight: 400;
}
.test\:font-bold *::test {
font-weight: calc(0 + 700);
}
.test\:font-medium *::test {
font-weight: calc(0 + 500);
}
.test\:font-bold::test {
font-weight: calc(0 + 700);
}
.test\:font-medium::test {
font-weight: calc(0 + 500);
}
.hover\:test\:font-black *:hover::test {
font-weight: calc(0 + 900);
}
.hover\:test\:font-black:hover::test {
font-weight: calc(0 + 900);
}
`)
})
})
6 changes: 2 additions & 4 deletions tests/variants.test.css
Expand Up @@ -112,16 +112,14 @@
line-height: 1.75rem;
}
.marker\:text-red-500 *::marker {
--tw-text-opacity: 1;
color: rgb(239 68 68 / var(--tw-text-opacity));
color: rgb(239 68 68);
}
.marker\:text-lg::marker {
font-size: 1.125rem;
line-height: 1.75rem;
}
.marker\:text-red-500::marker {
--tw-text-opacity: 1;
color: rgb(239 68 68 / var(--tw-text-opacity));
color: rgb(239 68 68);
}
.selection\:bg-blue-500 *::selection {
--tw-bg-opacity: 1;
Expand Down
3 changes: 1 addition & 2 deletions tests/variants.test.js
Expand Up @@ -485,8 +485,7 @@ test('returning non-strings and non-selectors in addVariant', () => {

addVariant('peer-aria-expanded-2', ({ modifySelectors, separator }) => {
let nodes = modifySelectors(
({ className }) =>
`.peer[aria-expanded="false"] ~ .${e(`peer-aria-expanded${separator}${className}`)}`
({ className }) => `.${e(`peer-aria-expanded-2${separator}${className}`)}`
)

return [
Expand Down