Handle group/peer variants with quoted strings (#10400)
* Handle group/peer variants with quoted strings * Fix CS * Use `splitAtTopLevelOnly` instead This solution isn’t that pretty but it is reusing existing machinery * inline return Co-authored-by: Robin Malfait <malfait.robin@gmail.com> * Fix return type Co-authored-by: Robin Malfait <malfait.robin@gmail.com> * Fixup * Update changelog Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This commit is contained in:
parent
667eac5e88
commit
f821c71b7e
5 changed files with 112 additions and 5 deletions
|
|
@ -40,6 +40,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Consider earlier variants before sorting functions ([#10288](https://github.com/tailwindlabs/tailwindcss/pull/10288))
|
||||
- Allow variants with slashes ([#10336](https://github.com/tailwindlabs/tailwindcss/pull/10336))
|
||||
- Ensure generated CSS is always sorted in the same order for a given set of templates ([#10382](https://github.com/tailwindlabs/tailwindcss/pull/10382))
|
||||
- Handle variants when the same class appears multiple times in a selector ([#10397](https://github.com/tailwindlabs/tailwindcss/pull/10397))
|
||||
- Handle group/peer variants with quoted strings ([#10400](https://github.com/tailwindlabs/tailwindcss/pull/10400))
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
|||
|
|
@ -168,7 +168,30 @@ export let variantPlugins = {
|
|||
if (!result.includes('&')) result = '&' + result
|
||||
|
||||
let [a, b] = fn('', extra)
|
||||
return result.replace(/&(\S+)?/g, (_, pseudo = '') => a + pseudo + b)
|
||||
|
||||
let start = null
|
||||
let end = null
|
||||
let quotes = 0
|
||||
|
||||
for (let i = 0; i < result.length; ++i) {
|
||||
let c = result[i]
|
||||
if (c === '&') {
|
||||
start = i
|
||||
} else if (c === "'" || c === '"') {
|
||||
quotes += 1
|
||||
} else if (start !== null && c === ' ' && !quotes) {
|
||||
end = i
|
||||
}
|
||||
}
|
||||
|
||||
if (start !== null && end === null) {
|
||||
end = result.length
|
||||
}
|
||||
|
||||
// Basically this but can handle quotes:
|
||||
// result.replace(/&(\S+)?/g, (_, pseudo = '') => a + pseudo + b)
|
||||
|
||||
return result.slice(0, start) + a + result.slice(start + 1, end) + b + result.slice(end)
|
||||
},
|
||||
{ values: Object.fromEntries(pseudoVariants) }
|
||||
)
|
||||
|
|
|
|||
|
|
@ -152,10 +152,18 @@ function applyVariant(variant, matches, context) {
|
|||
|
||||
// Retrieve "modifier"
|
||||
{
|
||||
let match = /(.*)\/(.*)$/g.exec(variant)
|
||||
if (match && !context.variantMap.has(variant)) {
|
||||
variant = match[1]
|
||||
args.modifier = match[2]
|
||||
let [baseVariant, ...modifiers] = splitAtTopLevelOnly(variant, '/')
|
||||
|
||||
// This is a hack to support variants with `/` in them, like `ar-1/10/20:text-red-500`
|
||||
// In this case 1/10 is a value but /20 is a modifier
|
||||
if (modifiers.length > 1) {
|
||||
baseVariant = baseVariant + '/' + modifiers.slice(0, -1).join('/')
|
||||
modifiers = modifiers.slice(-1)
|
||||
}
|
||||
|
||||
if (modifiers.length && !context.variantMap.has(variant)) {
|
||||
variant = baseVariant
|
||||
args.modifier = modifiers[0]
|
||||
|
||||
if (!flagEnabled(context.tailwindConfig, 'generalizedModifiers')) {
|
||||
return []
|
||||
|
|
|
|||
|
|
@ -949,4 +949,66 @@ crosscheck(({ stable, oxide }) => {
|
|||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('detects quoted arbitrary values containing a slash', async () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="group-[[href^='/']]:hidden"></div>`,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
let result = await run(input, config)
|
||||
|
||||
oxide.expect(result.css).toMatchFormattedCss(css`
|
||||
.group[href^='/'] .group-\[\[href\^\=\'\/\'\]\]\:hidden {
|
||||
display: none;
|
||||
}
|
||||
`)
|
||||
|
||||
stable.expect(result.css).toMatchFormattedCss(css`
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.group[href^='/'] .group-\[\[href\^\=\'\/\'\]\]\:hidden {
|
||||
display: none;
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('handled quoted arbitrary values containing escaped spaces', async () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="group-[[href^='_bar']]:hidden"></div>`,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
let result = await run(input, config)
|
||||
|
||||
oxide.expect(result.css).toMatchFormattedCss(css`
|
||||
.group[href^=' bar'] .group-\[\[href\^\=\'_bar\'\]\]\:hidden {
|
||||
display: none;
|
||||
}
|
||||
`)
|
||||
|
||||
stable.expect(result.css).toMatchFormattedCss(css`
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.group[href^=' bar'] .group-\[\[href\^\=\'_bar\'\]\]\:hidden {
|
||||
display: none;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -60,6 +60,18 @@ let nullProxy = new Proxy(
|
|||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @typedef {object} CrossCheck
|
||||
* @property {typeof import('@jest/globals')} oxide
|
||||
* @property {typeof import('@jest/globals')} stable
|
||||
* @property {object} engine
|
||||
* @property {boolean} engine.oxide
|
||||
* @property {boolean} engine.stable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {(data: CrossCheck) => void} fn
|
||||
*/
|
||||
export function crosscheck(fn) {
|
||||
let engines =
|
||||
env.ENGINE === 'oxide' ? [{ engine: 'Stable' }, { engine: 'Oxide' }] : [{ engine: 'Stable' }]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue