mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Add matchVariant API (#8310)
* update regex extractor * implement `matchVariant` API * add `matchVariant` test * add `values` option to the `matchVariant` API * move `matchVariant` tests to own file * update changelog
This commit is contained in:
parent
6c63f67d20
commit
fc25299aa6
@ -43,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Add `grid-flow-dense` utility ([#8193](https://github.com/tailwindlabs/tailwindcss/pull/8193))
|
||||
- Add `mix-blend-plus-lighter` utility ([#8288](https://github.com/tailwindlabs/tailwindcss/pull/8288))
|
||||
- Add arbitrary variants ([#8299](https://github.com/tailwindlabs/tailwindcss/pull/8299))
|
||||
- Add `matchVariant` API ([#8310](https://github.com/tailwindlabs/tailwindcss/pull/8310))
|
||||
|
||||
## [3.0.24] - 2022-04-12
|
||||
|
||||
|
||||
@ -25,7 +25,10 @@ function* buildRegExps(context) {
|
||||
// Variants
|
||||
'((?=((',
|
||||
regex.any(
|
||||
[regex.pattern([/\[[^\s"'\\]+\]/, separator]), regex.pattern([/[^\s"'\[\\]+/, separator])],
|
||||
[
|
||||
regex.pattern([/([^\s"'\[\\]+-)?\[[^\s"'\\]+\]/, separator]),
|
||||
regex.pattern([/[^\s"'\[\\]+/, separator]),
|
||||
],
|
||||
true
|
||||
),
|
||||
')+))\\2)?',
|
||||
|
||||
@ -127,6 +127,14 @@ function applyVariant(variant, matches, context) {
|
||||
return matches
|
||||
}
|
||||
|
||||
let args
|
||||
|
||||
// Find partial arbitrary variants
|
||||
if (variant.endsWith(']') && !variant.startsWith('[')) {
|
||||
args = variant.slice(variant.lastIndexOf('[') + 1, -1)
|
||||
variant = variant.slice(0, variant.indexOf(args) - 1 /* - */ - 1 /* [ */)
|
||||
}
|
||||
|
||||
// Register arbitrary variants
|
||||
if (isArbitraryValue(variant) && !context.variantMap.has(variant)) {
|
||||
let selector = normalize(variant.slice(1, -1))
|
||||
@ -204,6 +212,7 @@ function applyVariant(variant, matches, context) {
|
||||
format(selectorFormat) {
|
||||
collectedFormats.push(selectorFormat)
|
||||
},
|
||||
args,
|
||||
})
|
||||
|
||||
if (typeof ruleWithVariant === 'string') {
|
||||
|
||||
@ -22,6 +22,8 @@ import isValidArbitraryValue from '../util/isValidArbitraryValue'
|
||||
import { generateRules } from './generateRules'
|
||||
import { hasContentChanged } from './cacheInvalidation.js'
|
||||
|
||||
let MATCH_VARIANT = Symbol()
|
||||
|
||||
function prefix(context, selector) {
|
||||
let prefix = context.tailwindConfig.prefix
|
||||
return typeof prefix === 'function' ? prefix(selector) : prefix + selector
|
||||
@ -219,13 +221,18 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
|
||||
return context.tailwindConfig.prefix + identifier
|
||||
}
|
||||
|
||||
return {
|
||||
let api = {
|
||||
addVariant(variantName, variantFunctions, options = {}) {
|
||||
variantFunctions = [].concat(variantFunctions).map((variantFunction) => {
|
||||
if (typeof variantFunction !== 'string') {
|
||||
// Safelist public API functions
|
||||
return ({ modifySelectors, container, separator }) => {
|
||||
let result = variantFunction({ modifySelectors, container, separator })
|
||||
return ({ args, modifySelectors, container, separator, wrap, format }) => {
|
||||
let result = variantFunction(
|
||||
Object.assign(
|
||||
{ modifySelectors, container, separator },
|
||||
variantFunction[MATCH_VARIANT] && { args, wrap, format }
|
||||
)
|
||||
)
|
||||
|
||||
if (typeof result === 'string' && !isValidVariantFormatString(result)) {
|
||||
throw new Error(
|
||||
@ -462,7 +469,35 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
|
||||
context.candidateRuleMap.get(prefixedIdentifier).push(withOffsets)
|
||||
}
|
||||
},
|
||||
matchVariant: function (variants, options) {
|
||||
for (let variant in variants) {
|
||||
for (let [k, v] of Object.entries(options?.values ?? {})) {
|
||||
api.addVariant(`${variant}-${k}`, variants[variant](v))
|
||||
}
|
||||
|
||||
api.addVariant(
|
||||
variant,
|
||||
Object.assign(
|
||||
({ args, wrap }) => {
|
||||
let formatString = variants[variant](args)
|
||||
if (!formatString) return null
|
||||
|
||||
if (!formatString.startsWith('@')) {
|
||||
return formatString
|
||||
}
|
||||
|
||||
let [, name, params] = /@(.*?)( .+|[({].*)/g.exec(formatString)
|
||||
return wrap(postcss.atRule({ name, params: params.trim() }))
|
||||
},
|
||||
{ [MATCH_VARIANT]: true }
|
||||
),
|
||||
options
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return api
|
||||
}
|
||||
|
||||
let fileModifiedMapCache = new WeakMap()
|
||||
|
||||
132
tests/match-variants.test.js
Normal file
132
tests/match-variants.test.js
Normal file
@ -0,0 +1,132 @@
|
||||
import { run, html, css } from './util/run'
|
||||
|
||||
test('partial arbitrary variants', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="potato-[yellow]:bg-yellow-200 potato-[baked]:w-3"></div> `,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [
|
||||
({ matchVariant }) => {
|
||||
matchVariant({
|
||||
potato: (flavor) => `.potato-${flavor} &`,
|
||||
})
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.potato-baked .potato-\[baked\]\:w-3 {
|
||||
width: 0.75rem;
|
||||
}
|
||||
|
||||
.potato-yellow .potato-\[yellow\]\:bg-yellow-200 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(254 240 138 / var(--tw-bg-opacity));
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('partial arbitrary variants with default values', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="tooltip-bottom:mt-2 tooltip-top:mb-2"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [
|
||||
({ matchVariant }) => {
|
||||
matchVariant(
|
||||
{
|
||||
tooltip: (side) => `&${side}`,
|
||||
},
|
||||
{
|
||||
values: {
|
||||
bottom: '[data-location="bottom"]',
|
||||
top: '[data-location="top"]',
|
||||
},
|
||||
}
|
||||
)
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.tooltip-bottom\:mt-2[data-location='bottom'] {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.tooltip-top\:mb-2[data-location='top'] {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('matched variant values maintain the sort order they are registered in', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div
|
||||
class="alphabet-c:underline alphabet-a:underline alphabet-d:underline alphabet-b:underline"
|
||||
></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [
|
||||
({ matchVariant }) => {
|
||||
matchVariant(
|
||||
{
|
||||
alphabet: (side) => `&${side}`,
|
||||
},
|
||||
{
|
||||
values: {
|
||||
a: '[data-value="a"]',
|
||||
b: '[data-value="b"]',
|
||||
c: '[data-value="c"]',
|
||||
d: '[data-value="d"]',
|
||||
},
|
||||
}
|
||||
)
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.alphabet-a\:underline[data-value='a'] {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.alphabet-b\:underline[data-value='b'] {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.alphabet-c\:underline[data-value='c'] {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.alphabet-d\:underline[data-value='d'] {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user