Fix use of :where(.btn) when matching !btn (#10601)
* Cleanup code This makes it more explicit that we’re parsing a string selector, modifying it, and turning it back into a string * Fix important modifier when :where is involved * Only parse selector list once when handling the important modifier * Fix import * Fix lint errors
This commit is contained in:
parent
7f81849a43
commit
903eacb1ee
4 changed files with 74 additions and 41 deletions
|
|
@ -3,10 +3,14 @@ import selectorParser from 'postcss-selector-parser'
|
|||
import parseObjectStyles from '../util/parseObjectStyles'
|
||||
import isPlainObject from '../util/isPlainObject'
|
||||
import prefixSelector from '../util/prefixSelector'
|
||||
import { updateAllClasses, filterSelectorsForClass, getMatchingTypes } from '../util/pluginUtils'
|
||||
import { updateAllClasses, getMatchingTypes } from '../util/pluginUtils'
|
||||
import log from '../util/log'
|
||||
import * as sharedState from './sharedState'
|
||||
import { formatVariantSelector, finalizeSelector } from '../util/formatVariantSelector'
|
||||
import {
|
||||
formatVariantSelector,
|
||||
finalizeSelector,
|
||||
eliminateIrrelevantSelectors,
|
||||
} from '../util/formatVariantSelector'
|
||||
import { asClass } from '../util/nameClass'
|
||||
import { normalize } from '../util/dataTypes'
|
||||
import { isValidVariantFormatString, parseVariant } from './setupContextUtils'
|
||||
|
|
@ -111,22 +115,28 @@ function applyImportant(matches, classCandidate) {
|
|||
if (matches.length === 0) {
|
||||
return matches
|
||||
}
|
||||
|
||||
let result = []
|
||||
|
||||
for (let [meta, rule] of matches) {
|
||||
let container = postcss.root({ nodes: [rule.clone()] })
|
||||
|
||||
container.walkRules((r) => {
|
||||
r.selector = updateAllClasses(
|
||||
filterSelectorsForClass(r.selector, classCandidate),
|
||||
(className) => {
|
||||
if (className === classCandidate) {
|
||||
return `!${className}`
|
||||
}
|
||||
return className
|
||||
}
|
||||
let ast = selectorParser().astSync(r.selector)
|
||||
|
||||
// Remove extraneous selectors that do not include the base candidate
|
||||
ast.each((sel) => eliminateIrrelevantSelectors(sel, classCandidate))
|
||||
|
||||
// Update all instances of the base candidate to include the important marker
|
||||
updateAllClasses(ast, (className) =>
|
||||
className === classCandidate ? `!${className}` : className
|
||||
)
|
||||
|
||||
r.selector = ast.toString()
|
||||
|
||||
r.walkDecls((d) => (d.important = true))
|
||||
})
|
||||
|
||||
result.push([{ ...meta, important: true }, container.nodes[0]])
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ function resortSelector(sel) {
|
|||
* @param {Selector} ast
|
||||
* @param {string} base
|
||||
*/
|
||||
function eliminateIrrelevantSelectors(sel, base) {
|
||||
export function eliminateIrrelevantSelectors(sel, base) {
|
||||
let hasClassesMatchingCandidate = false
|
||||
|
||||
sel.walk((child) => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import selectorParser from 'postcss-selector-parser'
|
||||
import escapeCommas from './escapeCommas'
|
||||
import { withAlphaValue } from './withAlphaVariable'
|
||||
import {
|
||||
|
|
@ -21,37 +20,19 @@ import negateValue from './negateValue'
|
|||
import { backgroundSize } from './validateFormalSyntax'
|
||||
import { flagEnabled } from '../featureFlags.js'
|
||||
|
||||
/**
|
||||
* @param {import('postcss-selector-parser').Container} selectors
|
||||
* @param {(className: string) => string} updateClass
|
||||
* @returns {string}
|
||||
*/
|
||||
export function updateAllClasses(selectors, updateClass) {
|
||||
let parser = selectorParser((selectors) => {
|
||||
selectors.walkClasses((sel) => {
|
||||
let updatedClass = updateClass(sel.value)
|
||||
sel.value = updatedClass
|
||||
if (sel.raws && sel.raws.value) {
|
||||
sel.raws.value = escapeCommas(sel.raws.value)
|
||||
}
|
||||
})
|
||||
selectors.walkClasses((sel) => {
|
||||
sel.value = updateClass(sel.value)
|
||||
|
||||
if (sel.raws && sel.raws.value) {
|
||||
sel.raws.value = escapeCommas(sel.raws.value)
|
||||
}
|
||||
})
|
||||
|
||||
let result = parser.processSync(selectors)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function filterSelectorsForClass(selectors, classCandidate) {
|
||||
let parser = selectorParser((selectors) => {
|
||||
selectors.each((sel) => {
|
||||
const containsClass = sel.nodes.some(
|
||||
(node) => node.type === 'class' && node.value === classCandidate
|
||||
)
|
||||
if (!containsClass) {
|
||||
sel.remove()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
let result = parser.processSync(selectors)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function resolveArbitraryValue(modifier, validate) {
|
||||
|
|
|
|||
|
|
@ -108,4 +108,46 @@ crosscheck(() => {
|
|||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('the important modifier works on utilities using :where()', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html` <div class="btn hover:btn !btn hover:focus:disabled:!btn"></div> `,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [
|
||||
function ({ addComponents }) {
|
||||
addComponents({
|
||||
':where(.btn)': {
|
||||
backgroundColor: '#00f',
|
||||
},
|
||||
})
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
:where(.\!btn) {
|
||||
background-color: #00f !important;
|
||||
}
|
||||
:where(.btn) {
|
||||
background-color: #00f;
|
||||
}
|
||||
:where(.hover\:btn:hover) {
|
||||
background-color: #00f;
|
||||
}
|
||||
:where(.hover\:focus\:disabled\:\!btn:disabled:focus:hover) {
|
||||
background-color: #00f !important;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue