Skip to content

Commit

Permalink
Add generalized modifier support to matchUtilities (#9541)
Browse files Browse the repository at this point in the history
* Change `matchVariant` API to use positional arguments

* Fix CS

wip

* Change match variant wrap modifier in an object

Needed for compat w/ some group and peer plugins

* Add modifier support to matchUtilities

* refactor

* Hoist utility modifier splitting

* Rename fn

* refactor

* Add support for generic utility modifiers

* Fix CS

* wip

* update types

* Warn when using modifiers without the option

* Allow modifiers to be a config object

* Make sure we can return null from matchUtilities to omit rules

* Feature flag generalized modifiers

We’re putting a flag for modifiers in front of matchVariant and matchUtilities

* cleanup

* Update changelog

* Properly flag variants using modifiers

* Fix test
  • Loading branch information
thecrypticace committed Oct 13, 2022
1 parent b5651e8 commit 45d1a1b
Show file tree
Hide file tree
Showing 11 changed files with 426 additions and 81 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -26,6 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added 'place-items-baseline' utility ([#9507](https://github.com/tailwindlabs/tailwindcss/pull/9507))
- Added 'content-baseline' utility ([#9507](https://github.com/tailwindlabs/tailwindcss/pull/9507))
- Prepare for container queries setup ([#9526](https://github.com/tailwindlabs/tailwindcss/pull/9526))
- Add support for modifiers to `matchUtilities` ([#9541](https://github.com/tailwindlabs/tailwindcss/pull/9541))
- Switch to positional argument + object for modifiers ([#9541](https://github.com/tailwindlabs/tailwindcss/pull/9541))

### Fixed

Expand Down
15 changes: 7 additions & 8 deletions src/corePlugins.js
Expand Up @@ -144,28 +144,27 @@ export let variantPlugins = {
}

let variants = {
group: ({ modifier }) =>
group: (_, { modifier }) =>
modifier ? [`:merge(.group\\/${modifier})`, ' &'] : [`:merge(.group)`, ' &'],
peer: ({ modifier }) =>
peer: (_, { modifier }) =>
modifier ? [`:merge(.peer\\/${modifier})`, ' ~ &'] : [`:merge(.peer)`, ' ~ &'],
}

for (let [name, fn] of Object.entries(variants)) {
matchVariant(
name,
(ctx = {}) => {
let { modifier, value = '' } = ctx
if (modifier) {
(value = '', extra) => {
if (extra.modifier) {
log.warn(`modifier-${name}-experimental`, [
`The ${name} variant modifier feature in Tailwind CSS is currently in preview.`,
'Preview features are not covered by semver, and may be improved in breaking ways at any time.',
])
}

let result = normalize(typeof value === 'function' ? value(ctx) : value)
let result = normalize(typeof value === 'function' ? value(extra) : value)
if (!result.includes('&')) result = '&' + result

let [a, b] = fn({ modifier })
let [a, b] = fn('', extra)
return result.replace(/&(\S+)?/g, (_, pseudo = '') => a + pseudo + b)
},
{ values: Object.fromEntries(pseudoVariants) }
Expand Down Expand Up @@ -232,7 +231,7 @@ export let variantPlugins = {
supportsVariants: ({ matchVariant, theme }) => {
matchVariant(
'supports',
({ value = '' }) => {
(value = '') => {
let check = normalize(value)
let isRaw = /^\w*\s*\(/.test(check)

Expand Down
1 change: 1 addition & 0 deletions src/featureFlags.js
Expand Up @@ -14,6 +14,7 @@ let featureFlags = {
],
experimental: [
'optimizeUniversalDefaults',
'generalizedModifiers',
// 'variantGrouping',
],
}
Expand Down
48 changes: 33 additions & 15 deletions src/lib/generateRules.js
Expand Up @@ -3,7 +3,7 @@ import selectorParser from 'postcss-selector-parser'
import parseObjectStyles from '../util/parseObjectStyles'
import isPlainObject from '../util/isPlainObject'
import prefixSelector from '../util/prefixSelector'
import { updateAllClasses, typeMap } 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'
Expand Down Expand Up @@ -34,13 +34,24 @@ function* candidatePermutations(candidate) {

while (lastIndex >= 0) {
let dashIdx
let wasSlash = false

if (lastIndex === Infinity && candidate.endsWith(']')) {
let bracketIdx = candidate.indexOf('[')

// If character before `[` isn't a dash or a slash, this isn't a dynamic class
// eg. string[]
dashIdx = ['-', '/'].includes(candidate[bracketIdx - 1]) ? bracketIdx - 1 : -1
if (candidate[bracketIdx - 1] === '-') {
dashIdx = bracketIdx - 1
} else if (candidate[bracketIdx - 1] === '/') {
dashIdx = bracketIdx - 1
wasSlash = true
} else {
dashIdx = -1
}
} else if (lastIndex === Infinity && candidate.includes('/')) {
dashIdx = candidate.lastIndexOf('/')
wasSlash = true
} else {
dashIdx = candidate.lastIndexOf('-', lastIndex)
}
Expand All @@ -50,11 +61,16 @@ function* candidatePermutations(candidate) {
}

let prefix = candidate.slice(0, dashIdx)
let modifier = candidate.slice(dashIdx + 1)

yield [prefix, modifier]
let modifier = candidate.slice(wasSlash ? dashIdx : dashIdx + 1)

lastIndex = dashIdx - 1

// TODO: This feels a bit hacky
if (prefix === '' || modifier === '/') {
continue
}

yield [prefix, modifier]
}
}

Expand Down Expand Up @@ -137,6 +153,10 @@ function applyVariant(variant, matches, context) {
if (match) {
variant = match[1]
args.modifier = match[2]

if (!flagEnabled(context.tailwindConfig, 'generalizedModifiers')) {
return []
}
}
}

Expand Down Expand Up @@ -552,16 +572,14 @@ function* resolveMatches(candidate, context, original = candidate) {
}

if (matchesPerPlugin.length > 0) {
let matchingTypes = (sort.options?.types ?? [])
.map(({ type }) => type)
// Only track the types for this plugin that resulted in some result
.filter((type) => {
return Boolean(
typeMap[type](modifier, sort.options, {
tailwindConfig: context.tailwindConfig,
})
)
})
let matchingTypes = Array.from(
getMatchingTypes(
sort.options?.types ?? [],
modifier,
sort.options ?? {},
context.tailwindConfig
)
).map(([_, type]) => type)

if (matchingTypes.length > 0) {
typesByMatches.set(matchesPerPlugin, matchingTypes)
Expand Down
77 changes: 67 additions & 10 deletions src/lib/setupContextUtils.js
Expand Up @@ -21,6 +21,7 @@ import isValidArbitraryValue from '../util/isValidArbitraryValue'
import { generateRules } from './generateRules'
import { hasContentChanged } from './cacheInvalidation.js'
import { Offsets } from './offsets.js'
import { flagEnabled } from '../featureFlags.js'

let MATCH_VARIANT = Symbol()

Expand Down Expand Up @@ -358,6 +359,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
let defaultOptions = {
respectPrefix: true,
respectImportant: true,
modifiers: false,
}

options = normalizeOptionTypes({ ...defaultOptions, ...options })
Expand All @@ -371,7 +373,12 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
classList.add([prefixedIdentifier, options])

function wrapped(modifier, { isOnlyPlugin }) {
let [value, coercedType] = coerceValue(options.types, modifier, options, tailwindConfig)
let [value, coercedType, utilityModifier] = coerceValue(
options.types,
modifier,
options,
tailwindConfig
)

if (value === undefined) {
return []
Expand All @@ -395,8 +402,22 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
return []
}

let extras = {
get modifier() {
if (!options.modifiers) {
log.warn(`modifier-used-without-options-for-${identifier}`, [
'Your plugin must set `modifiers: true` in its options to support modifiers.',
])
}

return utilityModifier
},
}

let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')

let ruleSets = []
.concat(rule(value))
.concat(modifiersEnabled ? rule(value, extras) : rule(value))
.filter(Boolean)
.map((declaration) => ({
[nameClass(identifier, modifier)]: declaration,
Expand All @@ -418,6 +439,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
let defaultOptions = {
respectPrefix: true,
respectImportant: false,
modifiers: false,
}

options = normalizeOptionTypes({ ...defaultOptions, ...options })
Expand All @@ -431,7 +453,12 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
classList.add([prefixedIdentifier, options])

function wrapped(modifier, { isOnlyPlugin }) {
let [value, coercedType] = coerceValue(options.types, modifier, options, tailwindConfig)
let [value, coercedType, utilityModifier] = coerceValue(
options.types,
modifier,
options,
tailwindConfig
)

if (value === undefined) {
return []
Expand All @@ -455,8 +482,22 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
return []
}

let extras = {
get modifier() {
if (!options.modifiers) {
log.warn(`modifier-used-without-options-for-${identifier}`, [
'Your plugin must set `modifiers: true` in its options to support modifiers.',
])
}

return utilityModifier
},
}

let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')

let ruleSets = []
.concat(rule(value))
.concat(modifiersEnabled ? rule(value, extras) : rule(value))
.filter(Boolean)
.map((declaration) => ({
[nameClass(identifier, modifier)]: declaration,
Expand Down Expand Up @@ -522,21 +563,37 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
let id = ++variantIdentifier // A unique identifier that "groups" these variables together.
let isSpecial = variant === '@'

let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')

for (let [key, value] of Object.entries(options?.values ?? {})) {
api.addVariant(
isSpecial ? `${variant}${key}` : `${variant}-${key}`,
Object.assign(({ args, container }) => variantFn({ ...args, container, value }), {
[MATCH_VARIANT]: true,
}),
Object.assign(
({ args, container }) =>
variantFn(
value,
modifiersEnabled ? { modifier: args.modifier, container } : { container }
),
{
[MATCH_VARIANT]: true,
}
),
{ ...options, value, id }
)
}

api.addVariant(
variant,
Object.assign(({ args, container }) => variantFn({ ...args, container }), {
[MATCH_VARIANT]: true,
}),
Object.assign(
({ args, container }) =>
variantFn(
args.value,
modifiersEnabled ? { modifier: args.modifier, container } : { container }
),
{
[MATCH_VARIANT]: true,
}
),
{ ...options, id }
)
},
Expand Down
4 changes: 4 additions & 0 deletions src/util/nameClass.js
Expand Up @@ -22,5 +22,9 @@ export function formatClass(classPrefix, key) {
return `-${classPrefix}${key}`
}

if (key.startsWith('/')) {
return `${classPrefix}${key}`
}

return `${classPrefix}-${key}`
}

0 comments on commit 45d1a1b

Please sign in to comment.