Add generalized modifier support to matchUtilities (#9541)

* 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
This commit is contained in:
Jordan Pittman 2022-10-13 14:01:17 -04:00 committed by GitHub
parent b5651e88c3
commit 45d1a1b593
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 426 additions and 81 deletions

View File

@ -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

View File

@ -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) }
@ -232,7 +231,7 @@ export let variantPlugins = {
supportsVariants: ({ matchVariant, theme }) => {
matchVariant(
'supports',
({ value = '' }) => {
(value = '') => {
let check = normalize(value)
let isRaw = /^\w*\s*\(/.test(check)

View File

@ -14,6 +14,7 @@ let featureFlags = {
],
experimental: [
'optimizeUniversalDefaults',
'generalizedModifiers',
// 'variantGrouping',
],
}

View File

@ -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'
@ -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)
}
@ -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]
}
}
@ -137,6 +153,10 @@ function applyVariant(variant, matches, context) {
if (match) {
variant = match[1]
args.modifier = match[2]
if (!flagEnabled(context.tailwindConfig, 'generalizedModifiers')) {
return []
}
}
}
@ -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)

View File

@ -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()
@ -358,6 +359,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
let defaultOptions = {
respectPrefix: true,
respectImportant: true,
modifiers: false,
}
options = normalizeOptionTypes({ ...defaultOptions, ...options })
@ -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 []
@ -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,
@ -418,6 +439,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
let defaultOptions = {
respectPrefix: true,
respectImportant: false,
modifiers: false,
}
options = normalizeOptionTypes({ ...defaultOptions, ...options })
@ -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 []
@ -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,
@ -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 }
)
},

View File

@ -22,5 +22,9 @@ export function formatClass(classPrefix, key) {
return `-${classPrefix}${key}`
}
if (key.startsWith('/')) {
return `${classPrefix}${key}`
}
return `${classPrefix}-${key}`
}

View File

@ -19,6 +19,7 @@ import {
} from './dataTypes'
import negateValue from './negateValue'
import { backgroundSize } from './validateFormalSyntax'
import { flagEnabled } from '../featureFlags.js'
export function updateAllClasses(selectors, updateClass) {
let parser = selectorParser((selectors) => {
@ -86,11 +87,20 @@ function isArbitraryValue(input) {
return input.startsWith('[') && input.endsWith(']')
}
function splitAlpha(modifier) {
function splitUtilityModifier(modifier) {
let slashIdx = modifier.lastIndexOf('/')
if (slashIdx === -1 || slashIdx === modifier.length - 1) {
return [modifier]
return [modifier, undefined]
}
let arbitrary = isArbitraryValue(modifier)
// The modifier could be of the form `[foo]/[bar]`
// We want to handle this case properly
// without affecting `[foo/bar]`
if (arbitrary && !modifier.includes(']/[')) {
return [modifier, undefined]
}
return [modifier.slice(0, slashIdx), modifier.slice(slashIdx + 1)]
@ -106,12 +116,18 @@ export function parseColorFormat(value) {
return value
}
export function asColor(modifier, options = {}, { tailwindConfig = {} } = {}) {
if (options.values?.[modifier] !== undefined) {
return parseColorFormat(options.values?.[modifier])
export function asColor(
_,
options = {},
{ tailwindConfig = {}, utilityModifier, rawModifier } = {}
) {
if (options.values?.[rawModifier] !== undefined) {
return parseColorFormat(options.values?.[rawModifier])
}
let [color, alpha] = splitAlpha(modifier)
// TODO: Hoist this up to getMatchingTypes or something
// We do this here because we need the alpha value (if any)
let [color, alpha] = splitUtilityModifier(rawModifier)
if (alpha !== undefined) {
let normalizedColor =
@ -134,7 +150,7 @@ export function asColor(modifier, options = {}, { tailwindConfig = {} } = {}) {
return withAlphaValue(normalizedColor, tailwindConfig.theme.opacity[alpha])
}
return asValue(modifier, options, { validate: validateColor })
return asValue(rawModifier, options, { rawModifier, utilityModifier, validate: validateColor })
}
export function asLookupValue(modifier, options = {}) {
@ -142,8 +158,8 @@ export function asLookupValue(modifier, options = {}) {
}
function guess(validate) {
return (modifier, options) => {
return asValue(modifier, options, { validate })
return (modifier, options, extras) => {
return asValue(modifier, options, { ...extras, validate })
}
}
@ -192,15 +208,73 @@ export function coerceValue(types, modifier, options, tailwindConfig) {
}
if (value.length > 0 && supportedTypes.includes(explicitType)) {
return [asValue(`[${value}]`, options), explicitType]
return [asValue(`[${value}]`, options), explicitType, null]
}
}
let matches = getMatchingTypes(types, modifier, options, tailwindConfig)
// Find first matching type
for (let { type } of types) {
let result = typeMap[type](modifier, options, { tailwindConfig })
if (result !== undefined) return [result, type]
for (let match of matches) {
return match
}
return []
}
/**
*
* @param {{type: string}[]} types
* @param {string} rawModifier
* @param {any} options
* @param {any} tailwindConfig
* @returns {Iterator<[value: string, type: string, modifier: string | null]>}
*/
export function* getMatchingTypes(types, rawModifier, options, tailwindConfig) {
let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')
let canUseUtilityModifier =
modifiersEnabled &&
options.modifiers != null &&
(options.modifiers === 'any' || typeof options.modifiers === 'object')
let [modifier, utilityModifier] = canUseUtilityModifier
? splitUtilityModifier(rawModifier)
: [rawModifier, undefined]
if (utilityModifier !== undefined && modifier === '') {
modifier = 'DEFAULT'
}
// Check the full value first
// TODO: Move to asValue… somehow
if (utilityModifier !== undefined) {
if (typeof options.modifiers === 'object') {
let configValue = options.modifiers?.[utilityModifier] ?? null
if (configValue !== null) {
utilityModifier = configValue
} else if (isArbitraryValue(utilityModifier)) {
utilityModifier = utilityModifier.slice(1, -1)
}
}
let result = asValue(rawModifier, options, { rawModifier, utilityModifier, tailwindConfig })
if (result !== undefined) {
yield [result, 'any', null]
}
}
for (const { type } of types ?? []) {
let result = typeMap[type](modifier, options, {
rawModifier,
utilityModifier,
tailwindConfig,
})
if (result === undefined) {
continue
}
yield [result, type, utilityModifier ?? null]
}
}

View File

@ -709,6 +709,9 @@ it('should support supports', () => {
it('should be possible to use modifiers and arbitrary groups', () => {
let config = {
experimental: {
generalizedModifiers: true,
},
content: [
{
raw: html`
@ -810,6 +813,9 @@ it('should be possible to use modifiers and arbitrary groups', () => {
it('should be possible to use modifiers and arbitrary peers', () => {
let config = {
experimental: {
generalizedModifiers: true,
},
content: [
{
raw: html`

View File

@ -0,0 +1,177 @@
import { run, html, css } from './util/run'
test('match utilities with modifiers', async () => {
let config = {
experimental: {
generalizedModifiers: true,
},
content: [
{
raw: html`<div class="test test/foo test-1/foo test-2/foo test/[foo] test-1/[foo]"></div> `,
},
],
corePlugins: { preflight: false },
plugins: [
({ matchUtilities }) => {
matchUtilities(
{
test: (value, { modifier }) => ({
color: `${value}_${modifier}`,
}),
},
{
values: {
DEFAULT: 'default',
bar: 'bar',
'1': 'one',
'2': 'two',
'1/foo': 'onefoo',
},
modifiers: 'any',
}
)
},
],
}
let input = css`
@tailwind utilities;
`
let result = await run(input, config)
expect(result.css).toMatchFormattedCss(css`
.test {
color: default_null;
}
.test\/foo {
color: default_foo;
}
.test-1\/foo {
color: onefoo_null;
}
.test-2\/foo {
color: two_foo;
}
.test\/\[foo\] {
color: default_[foo];
}
.test-1\/\[foo\] {
color: one_[foo];
}
`)
})
test('match utilities with modifiers in the config', async () => {
let config = {
experimental: {
generalizedModifiers: true,
},
content: [
{
raw: html`<div class="test test/foo test-1/foo test/[bar] test-1/[bar]"></div> `,
},
],
corePlugins: { preflight: false },
plugins: [
({ matchUtilities }) => {
matchUtilities(
{
test: (value, { modifier }) => ({
color: `${value}_${modifier}`,
}),
},
{
values: {
DEFAULT: 'default',
bar: 'bar',
'1': 'one',
},
modifiers: {
foo: 'mewtwo',
},
}
)
},
],
}
let input = css`
@tailwind utilities;
`
let result = await run(input, config)
expect(result.css).toMatchFormattedCss(css`
.test {
color: default_null;
}
.test\/foo {
color: default_mewtwo;
}
.test-1\/foo {
color: one_mewtwo;
}
.test\/\[bar\] {
color: default_bar;
}
.test-1\/\[bar\] {
color: one_bar;
}
`)
})
test('match utilities can omit utilities by returning null', async () => {
let config = {
experimental: {
generalizedModifiers: true,
},
content: [
{
raw: html`<div class="test test/good test/bad"></div> `,
},
],
corePlugins: { preflight: false },
plugins: [
({ matchUtilities }) => {
matchUtilities(
{
test: (value, { modifier }) =>
modifier === 'bad'
? null
: {
color: `${value}_${modifier}`,
},
},
{
values: {
DEFAULT: 'default',
bar: 'bar',
'1': 'one',
},
modifiers: 'any',
}
)
},
],
}
let input = css`
@tailwind utilities;
`
let result = await run(input, config)
expect(result.css).toMatchFormattedCss(css`
.test {
color: default_null;
}
.test\/good {
color: default_good;
}
`)
})

View File

@ -10,7 +10,7 @@ test('partial arbitrary variants', () => {
corePlugins: { preflight: false },
plugins: [
({ matchVariant }) => {
matchVariant('potato', ({ value: flavor }) => `.potato-${flavor} &`)
matchVariant('potato', (flavor) => `.potato-${flavor} &`)
},
],
}
@ -43,7 +43,7 @@ test('partial arbitrary variants with at-rules', () => {
corePlugins: { preflight: false },
plugins: [
({ matchVariant }) => {
matchVariant('potato', ({ value: flavor }) => `@media (potato: ${flavor})`)
matchVariant('potato', (flavor) => `@media (potato: ${flavor})`)
},
],
}
@ -79,7 +79,7 @@ test('partial arbitrary variants with at-rules and placeholder', () => {
corePlugins: { preflight: false },
plugins: [
({ matchVariant }) => {
matchVariant('potato', ({ value: flavor }) => `@media (potato: ${flavor}) { &:potato }`)
matchVariant('potato', (flavor) => `@media (potato: ${flavor}) { &:potato }`)
},
],
}
@ -115,7 +115,7 @@ test('partial arbitrary variants with default values', () => {
corePlugins: { preflight: false },
plugins: [
({ matchVariant }) => {
matchVariant('tooltip', ({ value: side }) => `&${side}`, {
matchVariant('tooltip', (side) => `&${side}`, {
values: {
bottom: '[data-location="bottom"]',
top: '[data-location="top"]',
@ -154,7 +154,7 @@ test('matched variant values maintain the sort order they are registered in', ()
corePlugins: { preflight: false },
plugins: [
({ matchVariant }) => {
matchVariant('alphabet', ({ value: side }) => `&${side}`, {
matchVariant('alphabet', (side) => `&${side}`, {
values: {
a: '[data-value="a"]',
b: '[data-value="b"]',
@ -201,7 +201,7 @@ test('matchVariant can return an array of format strings from the function', ()
corePlugins: { preflight: false },
plugins: [
({ matchVariant }) => {
matchVariant('test', ({ value: selector }) =>
matchVariant('test', (selector) =>
selector.split(',').map((selector) => `&.${selector} > *`)
)
},
@ -243,7 +243,7 @@ it('should be possible to sort variants', () => {
corePlugins: { preflight: false },
plugins: [
({ matchVariant }) => {
matchVariant('min', ({ value }) => `@media (min-width: ${value})`, {
matchVariant('min', (value) => `@media (min-width: ${value})`, {
sort(a, z) {
return parseInt(a.value) - parseInt(z.value)
},
@ -287,7 +287,7 @@ it('should be possible to compare arbitrary variants and hardcoded variants', ()
corePlugins: { preflight: false },
plugins: [
({ matchVariant }) => {
matchVariant('min', ({ value }) => `@media (min-width: ${value})`, {
matchVariant('min', (value) => `@media (min-width: ${value})`, {
values: {
example: '600px',
},
@ -347,13 +347,13 @@ it('should be possible to sort stacked arbitrary variants correctly', () => {
corePlugins: { preflight: false },
plugins: [
({ matchVariant }) => {
matchVariant('min', ({ value }) => `@media (min-width: ${value})`, {
matchVariant('min', (value) => `@media (min-width: ${value})`, {
sort(a, z) {
return parseInt(a.value) - parseInt(z.value)
},
})
matchVariant('max', ({ value }) => `@media (max-width: ${value})`, {
matchVariant('max', (value) => `@media (max-width: ${value})`, {
sort(a, z) {
return parseInt(z.value) - parseInt(a.value)
},
@ -412,13 +412,13 @@ it('should maintain sort from other variants, if sort functions of arbitrary var
corePlugins: { preflight: false },
plugins: [
({ matchVariant }) => {
matchVariant('min', ({ value }) => `@media (min-width: ${value})`, {
matchVariant('min', (value) => `@media (min-width: ${value})`, {
sort(a, z) {
return parseInt(a.value) - parseInt(z.value)
},
})
matchVariant('max', ({ value }) => `@media (max-width: ${value})`, {
matchVariant('max', (value) => `@media (max-width: ${value})`, {
sort(a, z) {
return parseInt(z.value) - parseInt(a.value)
},
@ -464,12 +464,12 @@ it('should sort arbitrary variants left to right (1)', () => {
corePlugins: { preflight: false },
plugins: [
({ matchVariant }) => {
matchVariant('min', ({ value }) => `@media (min-width: ${value})`, {
matchVariant('min', (value) => `@media (min-width: ${value})`, {
sort(a, z) {
return parseInt(a.value) - parseInt(z.value)
},
})
matchVariant('max', ({ value }) => `@media (max-width: ${value})`, {
matchVariant('max', (value) => `@media (max-width: ${value})`, {
sort(a, z) {
return parseInt(z.value) - parseInt(a.value)
},
@ -532,12 +532,12 @@ it('should sort arbitrary variants left to right (2)', () => {
corePlugins: { preflight: false },
plugins: [
({ matchVariant }) => {
matchVariant('min', ({ value }) => `@media (min-width: ${value})`, {
matchVariant('min', (value) => `@media (min-width: ${value})`, {
sort(a, z) {
return parseInt(a.value) - parseInt(z.value)
},
})
matchVariant('max', ({ value }) => `@media (max-width: ${value})`, {
matchVariant('max', (value) => `@media (max-width: ${value})`, {
sort(a, z) {
return parseInt(z.value) - parseInt(a.value)
},
@ -598,7 +598,7 @@ it('should guarantee that we are not passing values from other variants to the w
corePlugins: { preflight: false },
plugins: [
({ matchVariant }) => {
matchVariant('min', ({ value }) => `@media (min-width: ${value})`, {
matchVariant('min', (value) => `@media (min-width: ${value})`, {
sort(a, z) {
let lookup = ['100px', '200px']
if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) {
@ -607,7 +607,7 @@ it('should guarantee that we are not passing values from other variants to the w
return lookup.indexOf(a.value) - lookup.indexOf(z.value)
},
})
matchVariant('max', ({ value }) => `@media (max-width: ${value})`, {
matchVariant('max', (value) => `@media (max-width: ${value})`, {
sort(a, z) {
let lookup = ['300px', '400px']
if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) {

41
types/config.d.ts vendored
View File

@ -12,7 +12,7 @@ interface RecursiveKeyValuePair<K extends keyof any = string, V = string> {
[key: string]: V | RecursiveKeyValuePair<K, V>
}
type ResolvableTo<T> = T | ((utils: PluginUtils) => T)
type CSSRuleObject = RecursiveKeyValuePair<string, string | string[]>
type CSSRuleObject = RecursiveKeyValuePair<string, null | string | string[]>
interface PluginUtils {
colors: DefaultColors
@ -263,13 +263,17 @@ export interface PluginAPI {
}>
): void
// for registering new dynamic utility styles
matchUtilities<T>(
utilities: KeyValuePair<string, (value: T) => CSSRuleObject>,
matchUtilities<T = string, U = string>(
utilities: KeyValuePair<
string,
(value: T | string, extra: { modifier: U | string | null }) => CSSRuleObject | null
>,
options?: Partial<{
respectPrefix: boolean
respectImportant: boolean
type: ValueType | ValueType[]
values: KeyValuePair<string, T>
modifiers: 'any' | KeyValuePair<string, U>
supportsNegativeValues: boolean
}>
): void
@ -282,13 +286,17 @@ export interface PluginAPI {
}>
): void
// for registering new dynamic component styles
matchComponents<T>(
components: KeyValuePair<string, (value: T) => CSSRuleObject>,
matchComponents<T = string, U = string>(
components: KeyValuePair<
string,
(value: T | string, extra: { modifier: U | string | null }) => CSSRuleObject | null
>,
options?: Partial<{
respectPrefix: boolean
respectImportant: boolean
type: ValueType | ValueType[]
values: KeyValuePair<string, T>
modifiers: 'any' | KeyValuePair<string, U>
supportsNegativeValues: boolean
}>
): void
@ -296,18 +304,14 @@ export interface PluginAPI {
addBase(base: CSSRuleObject | CSSRuleObject[]): void
// for registering custom variants
addVariant(name: string, definition: string | string[] | (() => string) | (() => string)[]): void
matchVariant(
matchVariant<T = string>(
name: string,
cb: (options: { value: string; modifier: string | null }) => string | string[]
): void
matchVariant<Values extends {}>(
name: string,
cb: (options: { value: string; modifier: string | null }) => string | string[],
options: {
values: Values
sort(
a: { value: keyof Values | string; modifier: string | null },
b: { value: keyof Values | string; modifier: string | null }
cb: (value: T | string, extra: { modifier: string | null }) => string | string[],
options?: {
values?: KeyValuePair<string, T>
sort?(
a: { value: T | string; modifier: string | null },
b: { value: T | string; modifier: string | null }
): number
}
): void
@ -327,7 +331,10 @@ export type PluginCreator = (api: PluginAPI) => void
export type PluginsConfig = (
| PluginCreator
| { handler: PluginCreator; config?: Partial<Config> }
| { (options: any): { handler: PluginCreator; config?: Partial<Config> }; __isOptionsFunction: true }
| {
(options: any): { handler: PluginCreator; config?: Partial<Config> }
__isOptionsFunction: true
}
)[]
// Top level config related