Skip to content

Commit 8ec9497

Browse files
authored
Add dynamic negative value opt-in (#5713)
* Add `supportsNegativeValues` plugin option * Update `getClassList` to support dynamic negative values * Add test for using a negative scale value with a plugin that does not support dynamic negative values
1 parent e2ad6bc commit 8ec9497

6 files changed

+140
-80
lines changed

src/corePlugins.js

+88-57
Original file line numberDiff line numberDiff line change
@@ -529,19 +529,23 @@ export let corePlugins = {
529529
})
530530
},
531531

532-
inset: createUtilityPlugin('inset', [
533-
['inset', ['top', 'right', 'bottom', 'left']],
532+
inset: createUtilityPlugin(
533+
'inset',
534534
[
535-
['inset-x', ['left', 'right']],
536-
['inset-y', ['top', 'bottom']],
537-
],
538-
[
539-
['top', ['top']],
540-
['right', ['right']],
541-
['bottom', ['bottom']],
542-
['left', ['left']],
535+
['inset', ['top', 'right', 'bottom', 'left']],
536+
[
537+
['inset-x', ['left', 'right']],
538+
['inset-y', ['top', 'bottom']],
539+
],
540+
[
541+
['top', ['top']],
542+
['right', ['right']],
543+
['bottom', ['bottom']],
544+
['left', ['left']],
545+
],
543546
],
544-
]),
547+
{ supportsNegativeValues: true }
548+
),
545549

546550
isolation: ({ addUtilities }) => {
547551
addUtilities({
@@ -550,8 +554,8 @@ export let corePlugins = {
550554
})
551555
},
552556

553-
zIndex: createUtilityPlugin('zIndex', [['z', ['zIndex']]]),
554-
order: createUtilityPlugin('order'),
557+
zIndex: createUtilityPlugin('zIndex', [['z', ['zIndex']]], { supportsNegativeValues: true }),
558+
order: createUtilityPlugin('order', undefined, { supportsNegativeValues: true }),
555559
gridColumn: createUtilityPlugin('gridColumn', [['col', ['gridColumn']]]),
556560
gridColumnStart: createUtilityPlugin('gridColumnStart', [['col-start', ['gridColumnStart']]]),
557561
gridColumnEnd: createUtilityPlugin('gridColumnEnd', [['col-end', ['gridColumnEnd']]]),
@@ -576,19 +580,23 @@ export let corePlugins = {
576580
})
577581
},
578582

579-
margin: createUtilityPlugin('margin', [
580-
['m', ['margin']],
581-
[
582-
['mx', ['margin-left', 'margin-right']],
583-
['my', ['margin-top', 'margin-bottom']],
584-
],
583+
margin: createUtilityPlugin(
584+
'margin',
585585
[
586-
['mt', ['margin-top']],
587-
['mr', ['margin-right']],
588-
['mb', ['margin-bottom']],
589-
['ml', ['margin-left']],
586+
['m', ['margin']],
587+
[
588+
['mx', ['margin-left', 'margin-right']],
589+
['my', ['margin-top', 'margin-bottom']],
590+
],
591+
[
592+
['mt', ['margin-top']],
593+
['mr', ['margin-right']],
594+
['mb', ['margin-bottom']],
595+
['ml', ['margin-left']],
596+
],
590597
],
591-
]),
598+
{ supportsNegativeValues: true }
599+
),
592600

593601
boxSizing: ({ addUtilities }) => {
594602
addUtilities({
@@ -653,33 +661,48 @@ export let corePlugins = {
653661
},
654662

655663
transformOrigin: createUtilityPlugin('transformOrigin', [['origin', ['transformOrigin']]]),
656-
translate: createUtilityPlugin('translate', [
664+
translate: createUtilityPlugin(
665+
'translate',
657666
[
658667
[
659-
'translate-x',
660-
[['@defaults transform', {}], '--tw-translate-x', ['transform', 'var(--tw-transform)']],
661-
],
662-
[
663-
'translate-y',
664-
[['@defaults transform', {}], '--tw-translate-y', ['transform', 'var(--tw-transform)']],
668+
[
669+
'translate-x',
670+
[['@defaults transform', {}], '--tw-translate-x', ['transform', 'var(--tw-transform)']],
671+
],
672+
[
673+
'translate-y',
674+
[['@defaults transform', {}], '--tw-translate-y', ['transform', 'var(--tw-transform)']],
675+
],
665676
],
666677
],
667-
]),
668-
rotate: createUtilityPlugin('rotate', [
669-
['rotate', [['@defaults transform', {}], '--tw-rotate', ['transform', 'var(--tw-transform)']]],
670-
]),
671-
skew: createUtilityPlugin('skew', [
678+
{ supportsNegativeValues: true }
679+
),
680+
rotate: createUtilityPlugin(
681+
'rotate',
672682
[
673683
[
674-
'skew-x',
675-
[['@defaults transform', {}], '--tw-skew-x', ['transform', 'var(--tw-transform)']],
684+
'rotate',
685+
[['@defaults transform', {}], '--tw-rotate', ['transform', 'var(--tw-transform)']],
676686
],
687+
],
688+
{ supportsNegativeValues: true }
689+
),
690+
skew: createUtilityPlugin(
691+
'skew',
692+
[
677693
[
678-
'skew-y',
679-
[['@defaults transform', {}], '--tw-skew-y', ['transform', 'var(--tw-transform)']],
694+
[
695+
'skew-x',
696+
[['@defaults transform', {}], '--tw-skew-x', ['transform', 'var(--tw-transform)']],
697+
],
698+
[
699+
'skew-y',
700+
[['@defaults transform', {}], '--tw-skew-y', ['transform', 'var(--tw-transform)']],
701+
],
680702
],
681703
],
682-
]),
704+
{ supportsNegativeValues: true }
705+
),
683706
scale: createUtilityPlugin('scale', [
684707
[
685708
'scale',
@@ -859,19 +882,23 @@ export let corePlugins = {
859882
})
860883
},
861884

862-
scrollMargin: createUtilityPlugin('scrollMargin', [
863-
['scroll-m', ['scroll-margin']],
864-
[
865-
['scroll-mx', ['scroll-margin-left', 'scroll-margin-right']],
866-
['scroll-my', ['scroll-margin-top', 'scroll-margin-bottom']],
867-
],
885+
scrollMargin: createUtilityPlugin(
886+
'scrollMargin',
868887
[
869-
['scroll-mt', ['scroll-margin-top']],
870-
['scroll-mr', ['scroll-margin-right']],
871-
['scroll-mb', ['scroll-margin-bottom']],
872-
['scroll-ml', ['scroll-margin-left']],
888+
['scroll-m', ['scroll-margin']],
889+
[
890+
['scroll-mx', ['scroll-margin-left', 'scroll-margin-right']],
891+
['scroll-my', ['scroll-margin-top', 'scroll-margin-bottom']],
892+
],
893+
[
894+
['scroll-mt', ['scroll-margin-top']],
895+
['scroll-mr', ['scroll-margin-right']],
896+
['scroll-mb', ['scroll-margin-bottom']],
897+
['scroll-ml', ['scroll-margin-left']],
898+
],
873899
],
874-
]),
900+
{ supportsNegativeValues: true }
901+
),
875902

876903
scrollPadding: createUtilityPlugin('scrollPadding', [
877904
['scroll-p', ['scroll-padding']],
@@ -1069,7 +1096,7 @@ export let corePlugins = {
10691096
}
10701097
},
10711098
},
1072-
{ values: theme('space') }
1099+
{ values: theme('space'), supportsNegativeValues: true }
10731100
)
10741101

10751102
addUtilities({
@@ -1641,7 +1668,9 @@ export let corePlugins = {
16411668
})
16421669
},
16431670

1644-
textIndent: createUtilityPlugin('textIndent', [['indent', ['text-indent']]]),
1671+
textIndent: createUtilityPlugin('textIndent', [['indent', ['text-indent']]], {
1672+
supportsNegativeValues: true,
1673+
}),
16451674

16461675
verticalAlign: ({ addUtilities, matchUtilities }) => {
16471676
addUtilities({
@@ -1730,7 +1759,9 @@ export let corePlugins = {
17301759
},
17311760

17321761
lineHeight: createUtilityPlugin('lineHeight', [['leading', ['lineHeight']]]),
1733-
letterSpacing: createUtilityPlugin('letterSpacing', [['tracking', ['letterSpacing']]]),
1762+
letterSpacing: createUtilityPlugin('letterSpacing', [['tracking', ['letterSpacing']]], {
1763+
supportsNegativeValues: true,
1764+
}),
17341765

17351766
textColor: ({ matchUtilities, theme, corePlugins }) => {
17361767
matchUtilities(
@@ -2099,7 +2130,7 @@ export let corePlugins = {
20992130
}
21002131
},
21012132
},
2102-
{ values: theme('hueRotate') }
2133+
{ values: theme('hueRotate'), supportsNegativeValues: true }
21032134
)
21042135
},
21052136

@@ -2250,7 +2281,7 @@ export let corePlugins = {
22502281
}
22512282
},
22522283
},
2253-
{ values: theme('backdropHueRotate') }
2284+
{ values: theme('backdropHueRotate'), supportsNegativeValues: true }
22542285
)
22552286
},
22562287

src/lib/setupContextUtils.js

+11-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import * as sharedState from './sharedState'
1717
import { env } from './sharedState'
1818
import { toPath } from '../util/toPath'
1919
import log from '../util/log'
20+
import negateValue from '../util/negateValue'
2021

2122
function insertInto(list, value, { before = [] } = {}) {
2223
before = [].concat(before)
@@ -300,7 +301,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
300301
function wrapped(modifier, { isOnlyPlugin }) {
301302
let { type = 'any' } = options
302303
type = [].concat(type)
303-
let [value, coercedType] = coerceValue(type, modifier, options.values, tailwindConfig)
304+
let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig)
304305

305306
if (value === undefined) {
306307
return []
@@ -352,7 +353,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
352353
function wrapped(modifier, { isOnlyPlugin }) {
353354
let { type = 'any' } = options
354355
type = [].concat(type)
355-
let [value, coercedType] = coerceValue(type, modifier, options.values, tailwindConfig)
356+
let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig)
356357

357358
if (value === undefined) {
358359
return []
@@ -670,10 +671,16 @@ function registerPlugins(plugins, context) {
670671
for (let util of classList) {
671672
if (Array.isArray(util)) {
672673
let [utilName, options] = util
674+
let negativeClasses = []
673675

674-
for (let value of Object.keys(options?.values ?? {})) {
675-
output.push(formatClass(utilName, value))
676+
for (let [key, value] of Object.entries(options?.values ?? {})) {
677+
output.push(formatClass(utilName, key))
678+
if (options?.supportsNegativeValues && negateValue(value)) {
679+
negativeClasses.push(formatClass(utilName, `-${key}`))
680+
}
676681
}
682+
683+
output.push(...negativeClasses)
677684
} else {
678685
output.push(util)
679686
}

src/util/createUtilityPlugin.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import transformThemeValue from './transformThemeValue'
33
export default function createUtilityPlugin(
44
themeKey,
55
utilityVariations = [[themeKey, [themeKey]]],
6-
{ filterDefault = false, type = 'any' } = {}
6+
{ filterDefault = false, ...options } = {}
77
) {
88
let transformValue = transformThemeValue(themeKey)
99
return function ({ matchUtilities, theme }) {
@@ -24,12 +24,12 @@ export default function createUtilityPlugin(
2424
})
2525
}, {}),
2626
{
27+
...options,
2728
values: filterDefault
2829
? Object.fromEntries(
2930
Object.entries(theme(themeKey) ?? {}).filter(([modifier]) => modifier !== 'DEFAULT')
3031
)
3132
: theme(themeKey),
32-
type,
3333
}
3434
)
3535
}

src/util/pluginUtils.js

+17-17
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ function resolveArbitraryValue(modifier, validate) {
182182
return normalize(value)
183183
}
184184

185-
function asNegativeValue(modifier, lookup, validate) {
185+
function asNegativeValue(modifier, lookup = {}, validate) {
186186
let positiveValue = lookup[modifier]
187187

188188
if (positiveValue !== undefined) {
@@ -200,15 +200,15 @@ function asNegativeValue(modifier, lookup, validate) {
200200
}
201201
}
202202

203-
export function asValue(modifier, lookup = {}, { validate = () => true } = {}) {
204-
let value = lookup[modifier]
203+
export function asValue(modifier, options = {}, { validate = () => true } = {}) {
204+
let value = options.values?.[modifier]
205205

206206
if (value !== undefined) {
207207
return value
208208
}
209209

210-
if (modifier.startsWith('-')) {
211-
return asNegativeValue(modifier.slice(1), lookup, validate)
210+
if (options.supportsNegativeValues && modifier.startsWith('-')) {
211+
return asNegativeValue(modifier.slice(1), options.values, validate)
212212
}
213213

214214
return resolveArbitraryValue(modifier, validate)
@@ -228,16 +228,16 @@ function splitAlpha(modifier) {
228228
return [modifier.slice(0, slashIdx), modifier.slice(slashIdx + 1)]
229229
}
230230

231-
export function asColor(modifier, lookup = {}, tailwindConfig = {}) {
232-
if (lookup[modifier] !== undefined) {
233-
return lookup[modifier]
231+
export function asColor(modifier, options = {}, { tailwindConfig = {} } = {}) {
232+
if (options.values?.[modifier] !== undefined) {
233+
return options.values?.[modifier]
234234
}
235235

236236
let [color, alpha] = splitAlpha(modifier)
237237

238238
if (alpha !== undefined) {
239239
let normalizedColor =
240-
lookup[color] ?? (isArbitraryValue(color) ? color.slice(1, -1) : undefined)
240+
options.values?.[color] ?? (isArbitraryValue(color) ? color.slice(1, -1) : undefined)
241241

242242
if (normalizedColor === undefined) {
243243
return undefined
@@ -254,16 +254,16 @@ export function asColor(modifier, lookup = {}, tailwindConfig = {}) {
254254
return withAlphaValue(normalizedColor, tailwindConfig.theme.opacity[alpha])
255255
}
256256

257-
return asValue(modifier, lookup, { validate: validateColor })
257+
return asValue(modifier, options, { validate: validateColor })
258258
}
259259

260-
export function asLookupValue(modifier, lookup = {}) {
261-
return lookup[modifier]
260+
export function asLookupValue(modifier, options = {}) {
261+
return options.values?.[modifier]
262262
}
263263

264264
function guess(validate) {
265-
return (modifier, lookup) => {
266-
return asValue(modifier, lookup, { validate })
265+
return (modifier, options) => {
266+
return asValue(modifier, options, { validate })
267267
}
268268
}
269269

@@ -292,7 +292,7 @@ function splitAtFirst(input, delim) {
292292
return [input.slice(0, idx), input.slice(idx + 1)]
293293
}
294294

295-
export function coerceValue(types, modifier, values, tailwindConfig) {
295+
export function coerceValue(types, modifier, options, tailwindConfig) {
296296
if (isArbitraryValue(modifier)) {
297297
let [explicitType, value] = splitAtFirst(modifier.slice(1, -1), ':')
298298

@@ -301,13 +301,13 @@ export function coerceValue(types, modifier, values, tailwindConfig) {
301301
}
302302

303303
if (value.length > 0 && supportedTypes.includes(explicitType)) {
304-
return [asValue(`[${value}]`, values, tailwindConfig), explicitType]
304+
return [asValue(`[${value}]`, options), explicitType]
305305
}
306306
}
307307

308308
// Find first matching type
309309
for (let type of [].concat(types)) {
310-
let result = typeMap[type](modifier, values, tailwindConfig)
310+
let result = typeMap[type](modifier, options, { tailwindConfig })
311311
if (result) return [result, type]
312312
}
313313

0 commit comments

Comments
 (0)