mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Make negative values a first-class feature, rather than theme-driven (#5709)
* WIP * Add failing test for negating default values * 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 * Support dynamic negation of `DEFAULT` values (#5718) * Add test case Co-authored-by: Brad Cornes <bradlc41@gmail.com>
This commit is contained in:
parent
b661614265
commit
97062c398b
@ -529,19 +529,23 @@ export let corePlugins = {
|
||||
})
|
||||
},
|
||||
|
||||
inset: createUtilityPlugin('inset', [
|
||||
['inset', ['top', 'right', 'bottom', 'left']],
|
||||
inset: createUtilityPlugin(
|
||||
'inset',
|
||||
[
|
||||
['inset-x', ['left', 'right']],
|
||||
['inset-y', ['top', 'bottom']],
|
||||
['inset', ['top', 'right', 'bottom', 'left']],
|
||||
[
|
||||
['inset-x', ['left', 'right']],
|
||||
['inset-y', ['top', 'bottom']],
|
||||
],
|
||||
[
|
||||
['top', ['top']],
|
||||
['right', ['right']],
|
||||
['bottom', ['bottom']],
|
||||
['left', ['left']],
|
||||
],
|
||||
],
|
||||
[
|
||||
['top', ['top']],
|
||||
['right', ['right']],
|
||||
['bottom', ['bottom']],
|
||||
['left', ['left']],
|
||||
],
|
||||
]),
|
||||
{ supportsNegativeValues: true }
|
||||
),
|
||||
|
||||
isolation: ({ addUtilities }) => {
|
||||
addUtilities({
|
||||
@ -550,8 +554,8 @@ export let corePlugins = {
|
||||
})
|
||||
},
|
||||
|
||||
zIndex: createUtilityPlugin('zIndex', [['z', ['zIndex']]]),
|
||||
order: createUtilityPlugin('order'),
|
||||
zIndex: createUtilityPlugin('zIndex', [['z', ['zIndex']]], { supportsNegativeValues: true }),
|
||||
order: createUtilityPlugin('order', undefined, { supportsNegativeValues: true }),
|
||||
gridColumn: createUtilityPlugin('gridColumn', [['col', ['gridColumn']]]),
|
||||
gridColumnStart: createUtilityPlugin('gridColumnStart', [['col-start', ['gridColumnStart']]]),
|
||||
gridColumnEnd: createUtilityPlugin('gridColumnEnd', [['col-end', ['gridColumnEnd']]]),
|
||||
@ -576,19 +580,23 @@ export let corePlugins = {
|
||||
})
|
||||
},
|
||||
|
||||
margin: createUtilityPlugin('margin', [
|
||||
['m', ['margin']],
|
||||
margin: createUtilityPlugin(
|
||||
'margin',
|
||||
[
|
||||
['mx', ['margin-left', 'margin-right']],
|
||||
['my', ['margin-top', 'margin-bottom']],
|
||||
['m', ['margin']],
|
||||
[
|
||||
['mx', ['margin-left', 'margin-right']],
|
||||
['my', ['margin-top', 'margin-bottom']],
|
||||
],
|
||||
[
|
||||
['mt', ['margin-top']],
|
||||
['mr', ['margin-right']],
|
||||
['mb', ['margin-bottom']],
|
||||
['ml', ['margin-left']],
|
||||
],
|
||||
],
|
||||
[
|
||||
['mt', ['margin-top']],
|
||||
['mr', ['margin-right']],
|
||||
['mb', ['margin-bottom']],
|
||||
['ml', ['margin-left']],
|
||||
],
|
||||
]),
|
||||
{ supportsNegativeValues: true }
|
||||
),
|
||||
|
||||
boxSizing: ({ addUtilities }) => {
|
||||
addUtilities({
|
||||
@ -653,33 +661,48 @@ export let corePlugins = {
|
||||
},
|
||||
|
||||
transformOrigin: createUtilityPlugin('transformOrigin', [['origin', ['transformOrigin']]]),
|
||||
translate: createUtilityPlugin('translate', [
|
||||
translate: createUtilityPlugin(
|
||||
'translate',
|
||||
[
|
||||
[
|
||||
'translate-x',
|
||||
[['@defaults transform', {}], '--tw-translate-x', ['transform', 'var(--tw-transform)']],
|
||||
],
|
||||
[
|
||||
'translate-y',
|
||||
[['@defaults transform', {}], '--tw-translate-y', ['transform', 'var(--tw-transform)']],
|
||||
[
|
||||
'translate-x',
|
||||
[['@defaults transform', {}], '--tw-translate-x', ['transform', 'var(--tw-transform)']],
|
||||
],
|
||||
[
|
||||
'translate-y',
|
||||
[['@defaults transform', {}], '--tw-translate-y', ['transform', 'var(--tw-transform)']],
|
||||
],
|
||||
],
|
||||
],
|
||||
]),
|
||||
rotate: createUtilityPlugin('rotate', [
|
||||
['rotate', [['@defaults transform', {}], '--tw-rotate', ['transform', 'var(--tw-transform)']]],
|
||||
]),
|
||||
skew: createUtilityPlugin('skew', [
|
||||
{ supportsNegativeValues: true }
|
||||
),
|
||||
rotate: createUtilityPlugin(
|
||||
'rotate',
|
||||
[
|
||||
[
|
||||
'skew-x',
|
||||
[['@defaults transform', {}], '--tw-skew-x', ['transform', 'var(--tw-transform)']],
|
||||
],
|
||||
[
|
||||
'skew-y',
|
||||
[['@defaults transform', {}], '--tw-skew-y', ['transform', 'var(--tw-transform)']],
|
||||
'rotate',
|
||||
[['@defaults transform', {}], '--tw-rotate', ['transform', 'var(--tw-transform)']],
|
||||
],
|
||||
],
|
||||
]),
|
||||
{ supportsNegativeValues: true }
|
||||
),
|
||||
skew: createUtilityPlugin(
|
||||
'skew',
|
||||
[
|
||||
[
|
||||
[
|
||||
'skew-x',
|
||||
[['@defaults transform', {}], '--tw-skew-x', ['transform', 'var(--tw-transform)']],
|
||||
],
|
||||
[
|
||||
'skew-y',
|
||||
[['@defaults transform', {}], '--tw-skew-y', ['transform', 'var(--tw-transform)']],
|
||||
],
|
||||
],
|
||||
],
|
||||
{ supportsNegativeValues: true }
|
||||
),
|
||||
scale: createUtilityPlugin('scale', [
|
||||
[
|
||||
'scale',
|
||||
@ -859,19 +882,23 @@ export let corePlugins = {
|
||||
})
|
||||
},
|
||||
|
||||
scrollMargin: createUtilityPlugin('scrollMargin', [
|
||||
['scroll-m', ['scroll-margin']],
|
||||
scrollMargin: createUtilityPlugin(
|
||||
'scrollMargin',
|
||||
[
|
||||
['scroll-mx', ['scroll-margin-left', 'scroll-margin-right']],
|
||||
['scroll-my', ['scroll-margin-top', 'scroll-margin-bottom']],
|
||||
['scroll-m', ['scroll-margin']],
|
||||
[
|
||||
['scroll-mx', ['scroll-margin-left', 'scroll-margin-right']],
|
||||
['scroll-my', ['scroll-margin-top', 'scroll-margin-bottom']],
|
||||
],
|
||||
[
|
||||
['scroll-mt', ['scroll-margin-top']],
|
||||
['scroll-mr', ['scroll-margin-right']],
|
||||
['scroll-mb', ['scroll-margin-bottom']],
|
||||
['scroll-ml', ['scroll-margin-left']],
|
||||
],
|
||||
],
|
||||
[
|
||||
['scroll-mt', ['scroll-margin-top']],
|
||||
['scroll-mr', ['scroll-margin-right']],
|
||||
['scroll-mb', ['scroll-margin-bottom']],
|
||||
['scroll-ml', ['scroll-margin-left']],
|
||||
],
|
||||
]),
|
||||
{ supportsNegativeValues: true }
|
||||
),
|
||||
|
||||
scrollPadding: createUtilityPlugin('scrollPadding', [
|
||||
['scroll-p', ['scroll-padding']],
|
||||
@ -1069,7 +1096,7 @@ export let corePlugins = {
|
||||
}
|
||||
},
|
||||
},
|
||||
{ values: theme('space') }
|
||||
{ values: theme('space'), supportsNegativeValues: true }
|
||||
)
|
||||
|
||||
addUtilities({
|
||||
@ -1641,7 +1668,9 @@ export let corePlugins = {
|
||||
})
|
||||
},
|
||||
|
||||
textIndent: createUtilityPlugin('textIndent', [['indent', ['text-indent']]]),
|
||||
textIndent: createUtilityPlugin('textIndent', [['indent', ['text-indent']]], {
|
||||
supportsNegativeValues: true,
|
||||
}),
|
||||
|
||||
verticalAlign: ({ addUtilities, matchUtilities }) => {
|
||||
addUtilities({
|
||||
@ -1730,7 +1759,9 @@ export let corePlugins = {
|
||||
},
|
||||
|
||||
lineHeight: createUtilityPlugin('lineHeight', [['leading', ['lineHeight']]]),
|
||||
letterSpacing: createUtilityPlugin('letterSpacing', [['tracking', ['letterSpacing']]]),
|
||||
letterSpacing: createUtilityPlugin('letterSpacing', [['tracking', ['letterSpacing']]], {
|
||||
supportsNegativeValues: true,
|
||||
}),
|
||||
|
||||
textColor: ({ matchUtilities, theme, corePlugins }) => {
|
||||
matchUtilities(
|
||||
@ -2099,7 +2130,7 @@ export let corePlugins = {
|
||||
}
|
||||
},
|
||||
},
|
||||
{ values: theme('hueRotate') }
|
||||
{ values: theme('hueRotate'), supportsNegativeValues: true }
|
||||
)
|
||||
},
|
||||
|
||||
@ -2250,7 +2281,7 @@ export let corePlugins = {
|
||||
}
|
||||
},
|
||||
},
|
||||
{ values: theme('backdropHueRotate') }
|
||||
{ values: theme('backdropHueRotate'), supportsNegativeValues: true }
|
||||
)
|
||||
},
|
||||
|
||||
|
||||
@ -185,6 +185,10 @@ function* resolveMatchedPlugins(classCandidate, context) {
|
||||
candidatePrefix = twConfigPrefix + candidatePrefix.slice(twConfigPrefixLen + 1)
|
||||
}
|
||||
|
||||
if (negative && context.candidateRuleMap.has(candidatePrefix)) {
|
||||
yield [context.candidateRuleMap.get(candidatePrefix), '-DEFAULT']
|
||||
}
|
||||
|
||||
for (let [prefix, modifier] of candidatePermutations(candidatePrefix)) {
|
||||
if (context.candidateRuleMap.has(prefix)) {
|
||||
yield [context.candidateRuleMap.get(prefix), negative ? `-${modifier}` : modifier]
|
||||
@ -238,7 +242,7 @@ function* resolveMatches(candidate, context) {
|
||||
}
|
||||
}
|
||||
// Only process static plugins on exact matches
|
||||
else if (modifier === 'DEFAULT') {
|
||||
else if (modifier === 'DEFAULT' || modifier === '-DEFAULT') {
|
||||
let ruleSet = plugin
|
||||
let [rules, options] = parseRules(ruleSet, context.postCssNodeCache)
|
||||
for (let rule of rules) {
|
||||
|
||||
@ -17,6 +17,7 @@ import * as sharedState from './sharedState'
|
||||
import { env } from './sharedState'
|
||||
import { toPath } from '../util/toPath'
|
||||
import log from '../util/log'
|
||||
import negateValue from '../util/negateValue'
|
||||
|
||||
function insertInto(list, value, { before = [] } = {}) {
|
||||
before = [].concat(before)
|
||||
@ -300,7 +301,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
|
||||
function wrapped(modifier, { isOnlyPlugin }) {
|
||||
let { type = 'any' } = options
|
||||
type = [].concat(type)
|
||||
let [value, coercedType] = coerceValue(type, modifier, options.values, tailwindConfig)
|
||||
let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig)
|
||||
|
||||
if (value === undefined) {
|
||||
return []
|
||||
@ -352,7 +353,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
|
||||
function wrapped(modifier, { isOnlyPlugin }) {
|
||||
let { type = 'any' } = options
|
||||
type = [].concat(type)
|
||||
let [value, coercedType] = coerceValue(type, modifier, options.values, tailwindConfig)
|
||||
let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig)
|
||||
|
||||
if (value === undefined) {
|
||||
return []
|
||||
@ -670,10 +671,16 @@ function registerPlugins(plugins, context) {
|
||||
for (let util of classList) {
|
||||
if (Array.isArray(util)) {
|
||||
let [utilName, options] = util
|
||||
let negativeClasses = []
|
||||
|
||||
for (let value of Object.keys(options?.values ?? {})) {
|
||||
output.push(formatClass(utilName, value))
|
||||
for (let [key, value] of Object.entries(options?.values ?? {})) {
|
||||
output.push(formatClass(utilName, key))
|
||||
if (options?.supportsNegativeValues && negateValue(value)) {
|
||||
negativeClasses.push(formatClass(utilName, `-${key}`))
|
||||
}
|
||||
}
|
||||
|
||||
output.push(...negativeClasses)
|
||||
} else {
|
||||
output.push(util)
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import transformThemeValue from './transformThemeValue'
|
||||
export default function createUtilityPlugin(
|
||||
themeKey,
|
||||
utilityVariations = [[themeKey, [themeKey]]],
|
||||
{ filterDefault = false, type = 'any' } = {}
|
||||
{ filterDefault = false, ...options } = {}
|
||||
) {
|
||||
let transformValue = transformThemeValue(themeKey)
|
||||
return function ({ matchUtilities, theme }) {
|
||||
@ -24,12 +24,12 @@ export default function createUtilityPlugin(
|
||||
})
|
||||
}, {}),
|
||||
{
|
||||
...options,
|
||||
values: filterDefault
|
||||
? Object.fromEntries(
|
||||
Object.entries(theme(themeKey) ?? {}).filter(([modifier]) => modifier !== 'DEFAULT')
|
||||
)
|
||||
: theme(themeKey),
|
||||
type,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ export function formatClass(classPrefix, key) {
|
||||
return classPrefix
|
||||
}
|
||||
|
||||
if (key === '-') {
|
||||
if (key === '-' || key === '-DEFAULT') {
|
||||
return `-${classPrefix}`
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
export default function (value) {
|
||||
value = `${value}`
|
||||
|
||||
if (value === '0') {
|
||||
return '0'
|
||||
}
|
||||
|
||||
// Flip sign of numbers
|
||||
if (/^[+-]?(\d+|\d*\.\d+)(e[+-]?\d+)?(%|\w+)?$/.test(value)) {
|
||||
return value.replace(/^[+-]?/, (sign) => (sign === '-' ? '' : '-'))
|
||||
@ -9,6 +13,4 @@ export default function (value) {
|
||||
if (value.includes('var(') || value.includes('calc(')) {
|
||||
return `calc(${value} * -1)`
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
position,
|
||||
lineWidth,
|
||||
} from './dataTypes'
|
||||
import negateValue from './negateValue'
|
||||
|
||||
export function applyStateToMarker(selector, marker, state, join) {
|
||||
let markerIdx = selector.search(new RegExp(`${marker}[:[]`))
|
||||
@ -167,18 +168,12 @@ export function transformLastClasses(transformClass, { wrap, withRule } = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
export function asValue(modifier, lookup = {}, { validate = () => true } = {}) {
|
||||
let value = lookup[modifier]
|
||||
|
||||
if (value !== undefined) {
|
||||
return value
|
||||
}
|
||||
|
||||
function resolveArbitraryValue(modifier, validate) {
|
||||
if (!isArbitraryValue(modifier)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
value = modifier.slice(1, -1)
|
||||
let value = modifier.slice(1, -1)
|
||||
|
||||
if (!validate(value)) {
|
||||
return undefined
|
||||
@ -187,6 +182,38 @@ export function asValue(modifier, lookup = {}, { validate = () => true } = {}) {
|
||||
return normalize(value)
|
||||
}
|
||||
|
||||
function asNegativeValue(modifier, lookup = {}, validate) {
|
||||
let positiveValue = lookup[modifier]
|
||||
|
||||
if (positiveValue !== undefined) {
|
||||
return negateValue(positiveValue)
|
||||
}
|
||||
|
||||
if (isArbitraryValue(modifier)) {
|
||||
let resolved = resolveArbitraryValue(modifier, validate)
|
||||
|
||||
if (resolved === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return negateValue(resolved)
|
||||
}
|
||||
}
|
||||
|
||||
export function asValue(modifier, options = {}, { validate = () => true } = {}) {
|
||||
let value = options.values?.[modifier]
|
||||
|
||||
if (value !== undefined) {
|
||||
return value
|
||||
}
|
||||
|
||||
if (options.supportsNegativeValues && modifier.startsWith('-')) {
|
||||
return asNegativeValue(modifier.slice(1), options.values, validate)
|
||||
}
|
||||
|
||||
return resolveArbitraryValue(modifier, validate)
|
||||
}
|
||||
|
||||
function isArbitraryValue(input) {
|
||||
return input.startsWith('[') && input.endsWith(']')
|
||||
}
|
||||
@ -201,16 +228,16 @@ function splitAlpha(modifier) {
|
||||
return [modifier.slice(0, slashIdx), modifier.slice(slashIdx + 1)]
|
||||
}
|
||||
|
||||
export function asColor(modifier, lookup = {}, tailwindConfig = {}) {
|
||||
if (lookup[modifier] !== undefined) {
|
||||
return lookup[modifier]
|
||||
export function asColor(modifier, options = {}, { tailwindConfig = {} } = {}) {
|
||||
if (options.values?.[modifier] !== undefined) {
|
||||
return options.values?.[modifier]
|
||||
}
|
||||
|
||||
let [color, alpha] = splitAlpha(modifier)
|
||||
|
||||
if (alpha !== undefined) {
|
||||
let normalizedColor =
|
||||
lookup[color] ?? (isArbitraryValue(color) ? color.slice(1, -1) : undefined)
|
||||
options.values?.[color] ?? (isArbitraryValue(color) ? color.slice(1, -1) : undefined)
|
||||
|
||||
if (normalizedColor === undefined) {
|
||||
return undefined
|
||||
@ -227,16 +254,16 @@ export function asColor(modifier, lookup = {}, tailwindConfig = {}) {
|
||||
return withAlphaValue(normalizedColor, tailwindConfig.theme.opacity[alpha])
|
||||
}
|
||||
|
||||
return asValue(modifier, lookup, { validate: validateColor })
|
||||
return asValue(modifier, options, { validate: validateColor })
|
||||
}
|
||||
|
||||
export function asLookupValue(modifier, lookup = {}) {
|
||||
return lookup[modifier]
|
||||
export function asLookupValue(modifier, options = {}) {
|
||||
return options.values?.[modifier]
|
||||
}
|
||||
|
||||
function guess(validate) {
|
||||
return (modifier, lookup) => {
|
||||
return asValue(modifier, lookup, { validate })
|
||||
return (modifier, options) => {
|
||||
return asValue(modifier, options, { validate })
|
||||
}
|
||||
}
|
||||
|
||||
@ -265,7 +292,7 @@ function splitAtFirst(input, delim) {
|
||||
return [input.slice(0, idx), input.slice(idx + 1)]
|
||||
}
|
||||
|
||||
export function coerceValue(types, modifier, values, tailwindConfig) {
|
||||
export function coerceValue(types, modifier, options, tailwindConfig) {
|
||||
if (isArbitraryValue(modifier)) {
|
||||
let [explicitType, value] = splitAtFirst(modifier.slice(1, -1), ':')
|
||||
|
||||
@ -274,13 +301,13 @@ export function coerceValue(types, modifier, values, tailwindConfig) {
|
||||
}
|
||||
|
||||
if (value.length > 0 && supportedTypes.includes(explicitType)) {
|
||||
return [asValue(`[${value}]`, values, tailwindConfig), explicitType]
|
||||
return [asValue(`[${value}]`, options), explicitType]
|
||||
}
|
||||
}
|
||||
|
||||
// Find first matching type
|
||||
for (let type of [].concat(types)) {
|
||||
let result = typeMap[type](modifier, values, tailwindConfig)
|
||||
let result = typeMap[type](modifier, options, { tailwindConfig })
|
||||
if (result) return [result, type]
|
||||
}
|
||||
|
||||
|
||||
@ -40,10 +40,16 @@ function mergeWith(target, ...sources) {
|
||||
const configUtils = {
|
||||
colors,
|
||||
negative(scale) {
|
||||
// TODO: Log that this function isn't really needed anymore?
|
||||
return Object.keys(scale)
|
||||
.filter((key) => scale[key] !== '0')
|
||||
.reduce((negativeScale, key) => {
|
||||
negativeScale[`-${key}`] = negateValue(scale[key])
|
||||
let negativeValue = negateValue(scale[key])
|
||||
|
||||
if (negativeValue !== undefined) {
|
||||
negativeScale[`-${key}`] = negativeValue
|
||||
}
|
||||
|
||||
return negativeScale
|
||||
}, {})
|
||||
},
|
||||
|
||||
@ -257,11 +257,6 @@ module.exports = {
|
||||
DEFAULT: '100%',
|
||||
},
|
||||
hueRotate: {
|
||||
'-180': '-180deg',
|
||||
'-90': '-90deg',
|
||||
'-60': '-60deg',
|
||||
'-30': '-30deg',
|
||||
'-15': '-15deg',
|
||||
0: '0deg',
|
||||
15: '15deg',
|
||||
30: '30deg',
|
||||
@ -510,10 +505,9 @@ module.exports = {
|
||||
full: '100%',
|
||||
screen: '100vh',
|
||||
}),
|
||||
inset: ({ theme, negative }) => ({
|
||||
inset: ({ theme }) => ({
|
||||
auto: 'auto',
|
||||
...theme('spacing'),
|
||||
...negative(theme('spacing')),
|
||||
'1/2': '50%',
|
||||
'1/3': '33.333333%',
|
||||
'2/3': '66.666667%',
|
||||
@ -521,13 +515,6 @@ module.exports = {
|
||||
'2/4': '50%',
|
||||
'3/4': '75%',
|
||||
full: '100%',
|
||||
'-1/2': '-50%',
|
||||
'-1/3': '-33.333333%',
|
||||
'-2/3': '-66.666667%',
|
||||
'-1/4': '-25%',
|
||||
'-2/4': '-50%',
|
||||
'-3/4': '-75%',
|
||||
'-full': '-100%',
|
||||
}),
|
||||
keyframes: {
|
||||
spin: {
|
||||
@ -586,10 +573,9 @@ module.exports = {
|
||||
disc: 'disc',
|
||||
decimal: 'decimal',
|
||||
},
|
||||
margin: ({ theme, negative }) => ({
|
||||
margin: ({ theme }) => ({
|
||||
auto: 'auto',
|
||||
...theme('spacing'),
|
||||
...negative(theme('spacing')),
|
||||
}),
|
||||
maxHeight: ({ theme }) => ({
|
||||
...theme('spacing'),
|
||||
@ -705,14 +691,6 @@ module.exports = {
|
||||
8: '8px',
|
||||
},
|
||||
rotate: {
|
||||
'-180': '-180deg',
|
||||
'-90': '-90deg',
|
||||
'-45': '-45deg',
|
||||
'-12': '-12deg',
|
||||
'-6': '-6deg',
|
||||
'-3': '-3deg',
|
||||
'-2': '-2deg',
|
||||
'-1': '-1deg',
|
||||
0: '0deg',
|
||||
1: '1deg',
|
||||
2: '2deg',
|
||||
@ -742,9 +720,8 @@ module.exports = {
|
||||
125: '1.25',
|
||||
150: '1.5',
|
||||
},
|
||||
scrollMargin: ({ theme, negative }) => ({
|
||||
scrollMargin: ({ theme }) => ({
|
||||
...theme('spacing'),
|
||||
...negative(theme('spacing')),
|
||||
}),
|
||||
scrollPadding: ({ theme }) => theme('spacing'),
|
||||
sepia: {
|
||||
@ -752,11 +729,6 @@ module.exports = {
|
||||
DEFAULT: '100%',
|
||||
},
|
||||
skew: {
|
||||
'-12': '-12deg',
|
||||
'-6': '-6deg',
|
||||
'-3': '-3deg',
|
||||
'-2': '-2deg',
|
||||
'-1': '-1deg',
|
||||
0: '0deg',
|
||||
1: '1deg',
|
||||
2: '2deg',
|
||||
@ -764,9 +736,8 @@ module.exports = {
|
||||
6: '6deg',
|
||||
12: '12deg',
|
||||
},
|
||||
space: ({ theme, negative }) => ({
|
||||
space: ({ theme }) => ({
|
||||
...theme('spacing'),
|
||||
...negative(theme('spacing')),
|
||||
}),
|
||||
stroke: {
|
||||
current: 'currentColor',
|
||||
@ -777,9 +748,8 @@ module.exports = {
|
||||
2: '2',
|
||||
},
|
||||
textColor: ({ theme }) => theme('colors'),
|
||||
textIndent: ({ theme, negative }) => ({
|
||||
textIndent: ({ theme }) => ({
|
||||
...theme('spacing'),
|
||||
...negative(theme('spacing')),
|
||||
}),
|
||||
textOpacity: ({ theme }) => theme('opacity'),
|
||||
transformOrigin: {
|
||||
@ -831,9 +801,8 @@ module.exports = {
|
||||
out: 'cubic-bezier(0, 0, 0.2, 1)',
|
||||
'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
},
|
||||
translate: ({ theme, negative }) => ({
|
||||
translate: ({ theme }) => ({
|
||||
...theme('spacing'),
|
||||
...negative(theme('spacing')),
|
||||
'1/2': '50%',
|
||||
'1/3': '33.333333%',
|
||||
'2/3': '66.666667%',
|
||||
@ -841,13 +810,6 @@ module.exports = {
|
||||
'2/4': '50%',
|
||||
'3/4': '75%',
|
||||
full: '100%',
|
||||
'-1/2': '-50%',
|
||||
'-1/3': '-33.333333%',
|
||||
'-2/3': '-66.666667%',
|
||||
'-1/4': '-25%',
|
||||
'-2/4': '-50%',
|
||||
'-3/4': '-75%',
|
||||
'-full': '-100%',
|
||||
}),
|
||||
width: ({ theme }) => ({
|
||||
auto: 'auto',
|
||||
|
||||
@ -15,4 +15,12 @@ it('should generate every possible class, without variants', () => {
|
||||
|
||||
// Verify we handle negative values correctly
|
||||
expect(context.getClassList()).toContain('-inset-1/4')
|
||||
expect(context.getClassList()).toContain('-m-0')
|
||||
expect(context.getClassList()).not.toContain('-uppercase')
|
||||
expect(context.getClassList()).not.toContain('-opacity-50')
|
||||
expect(
|
||||
createContext(
|
||||
resolveConfig({ theme: { extend: { margin: { DEFAULT: '5px' } } } })
|
||||
).getClassList()
|
||||
).not.toContain('-m-DEFAULT')
|
||||
})
|
||||
|
||||
@ -8,7 +8,7 @@ test('it negates numeric CSS values', () => {
|
||||
expect(negateValue('-7ch')).toEqual('7ch')
|
||||
})
|
||||
|
||||
test('it leaves keywords untouched', () => {
|
||||
expect(negateValue('auto')).toEqual('auto')
|
||||
expect(negateValue('cover')).toEqual('cover')
|
||||
test('values that cannot be negated become undefined', () => {
|
||||
expect(negateValue('auto')).toBeUndefined()
|
||||
expect(negateValue('cover')).toBeUndefined()
|
||||
})
|
||||
|
||||
308
tests/negative-prefix.test.js
Normal file
308
tests/negative-prefix.test.js
Normal file
@ -0,0 +1,308 @@
|
||||
import { run, html, css } from './util/run'
|
||||
|
||||
test('using a negative prefix with a negative scale value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mt-2 -mt-2"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
2: '8px',
|
||||
'-2': '-4px',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.mt-2 {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.-mt-2 {
|
||||
margin-top: -4px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('using a negative scale value with a plugin that does not support dynamic negative values', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-opacity-50"></div>` }],
|
||||
theme: {
|
||||
opacity: {
|
||||
'-50': '0.5',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-opacity-50 {
|
||||
opacity: 0.5;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('using a negative prefix without a negative scale value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mt-5 -mt-5"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
5: '20px',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.mt-5 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.-mt-5 {
|
||||
margin-top: -20px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('being an asshole', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-mt-[10px]"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
'-[10px]': '55px',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-\\[10px\\] {
|
||||
margin-top: 55px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('being a real asshole', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-mt-[10px]"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
'[10px]': '55px',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-\\[10px\\] {
|
||||
margin-top: -55px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('a value that includes a variable', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mt-5 -mt-5"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
5: 'var(--sizing-5)',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.mt-5 {
|
||||
margin-top: var(--sizing-5);
|
||||
}
|
||||
.-mt-5 {
|
||||
margin-top: calc(var(--sizing-5) * -1);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('a value that includes a calc', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mt-5 -mt-5"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
5: 'calc(52px * -3)',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.mt-5 {
|
||||
margin-top: calc(52px * -3);
|
||||
}
|
||||
.-mt-5 {
|
||||
margin-top: calc(calc(52px * -3) * -1);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('a keyword value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mt-auto -mt-auto"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
auto: 'auto',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.mt-auto {
|
||||
margin-top: auto;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('a zero value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mt-0 -mt-0"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
0: '0',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.mt-0 {
|
||||
margin-top: 0;
|
||||
}
|
||||
.-mt-0 {
|
||||
margin-top: 0;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('a color', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-bg-red"></div>` }],
|
||||
theme: {
|
||||
colors: {
|
||||
red: 'red',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css``)
|
||||
})
|
||||
})
|
||||
|
||||
test('arbitrary values', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-mt-[10px]"></div>` }],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-\\[10px\\] {
|
||||
margin-top: -10px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('negating a negative scale value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-mt-weird"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
weird: '-15px',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-weird {
|
||||
margin-top: 15px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('negating a default value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-mt"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
DEFAULT: '15px',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt {
|
||||
margin-top: -15px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('using a negative prefix with a negative default scale value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mt -mt"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
DEFAULT: '8px',
|
||||
'-DEFAULT': '-4px',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.mt {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.-mt {
|
||||
margin-top: -4px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('negating a default value with a configured prefix', () => {
|
||||
let config = {
|
||||
prefix: 'tw-',
|
||||
content: [{ raw: html`<div class="tw--mt"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
DEFAULT: '15px',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.tw--mt {
|
||||
margin-top: -15px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('arbitrary value keywords should be ignored', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-mt-[auto]"></div>` }],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css``)
|
||||
})
|
||||
})
|
||||
@ -1107,12 +1107,11 @@ test('custom properties are multiplied by -1 for negative values', () => {
|
||||
bar: 'var(--bar, 500px)',
|
||||
baz: 'calc(50% - 10px)',
|
||||
qux: '10poops',
|
||||
'-0': '-0',
|
||||
'-0': '0',
|
||||
'-1': '-1px',
|
||||
'-2': '-2px',
|
||||
'-3': '-3px',
|
||||
'-4': '-4px',
|
||||
'-auto': 'auto',
|
||||
'-foo': 'calc(var(--foo) * -1)',
|
||||
'-bar': 'calc(var(--bar, 500px) * -1)',
|
||||
'-baz': 'calc(calc(50% - 10px) * -1)',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user