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

Add generalized modifier support to matchUtilities #9541

Merged
merged 20 commits into from Oct 13, 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 @@ -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}`
}