diff --git a/src/corePlugins.js b/src/corePlugins.js index 92d6ce2bdd1c..527dabc15068 100644 --- a/src/corePlugins.js +++ b/src/corePlugins.js @@ -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']], - ], - [ - ['top', ['top']], - ['right', ['right']], - ['bottom', ['bottom']], - ['left', ['left']], + ['inset', ['top', 'right', 'bottom', 'left']], + [ + ['inset-x', ['left', 'right']], + ['inset-y', ['top', 'bottom']], + ], + [ + ['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']], - [ - ['mx', ['margin-left', 'margin-right']], - ['my', ['margin-top', 'margin-bottom']], - ], + margin: createUtilityPlugin( + 'margin', [ - ['mt', ['margin-top']], - ['mr', ['margin-right']], - ['mb', ['margin-bottom']], - ['ml', ['margin-left']], + ['m', ['margin']], + [ + ['mx', ['margin-left', 'margin-right']], + ['my', ['margin-top', 'margin-bottom']], + ], + [ + ['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)']], + 'rotate', + [['@defaults transform', {}], '--tw-rotate', ['transform', 'var(--tw-transform)']], ], + ], + { supportsNegativeValues: true } + ), + skew: createUtilityPlugin( + 'skew', + [ [ - 'skew-y', - [['@defaults transform', {}], '--tw-skew-y', ['transform', 'var(--tw-transform)']], + [ + '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']], - [ - ['scroll-mx', ['scroll-margin-left', 'scroll-margin-right']], - ['scroll-my', ['scroll-margin-top', 'scroll-margin-bottom']], - ], + scrollMargin: createUtilityPlugin( + 'scrollMargin', [ - ['scroll-mt', ['scroll-margin-top']], - ['scroll-mr', ['scroll-margin-right']], - ['scroll-mb', ['scroll-margin-bottom']], - ['scroll-ml', ['scroll-margin-left']], + ['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']], + ], ], - ]), + { 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 } ) }, diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index dd58b6734340..a6763c5e45e3 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -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) { diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index 8e124bb8a30c..53a9851ce07a 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -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) } diff --git a/src/util/createUtilityPlugin.js b/src/util/createUtilityPlugin.js index 1760a05cdce7..32c2c8ae3f1e 100644 --- a/src/util/createUtilityPlugin.js +++ b/src/util/createUtilityPlugin.js @@ -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, } ) } diff --git a/src/util/nameClass.js b/src/util/nameClass.js index f67b3fd581a8..e3a40f8eeae3 100644 --- a/src/util/nameClass.js +++ b/src/util/nameClass.js @@ -14,7 +14,7 @@ export function formatClass(classPrefix, key) { return classPrefix } - if (key === '-') { + if (key === '-' || key === '-DEFAULT') { return `-${classPrefix}` } diff --git a/src/util/negateValue.js b/src/util/negateValue.js index da7a5cf2c592..105915e03fbc 100644 --- a/src/util/negateValue.js +++ b/src/util/negateValue.js @@ -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 } diff --git a/src/util/pluginUtils.js b/src/util/pluginUtils.js index 4fa1bdf3ffe1..ef1cf2aaee0a 100644 --- a/src/util/pluginUtils.js +++ b/src/util/pluginUtils.js @@ -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] } diff --git a/src/util/resolveConfig.js b/src/util/resolveConfig.js index e631a973f521..3703f56fc46e 100644 --- a/src/util/resolveConfig.js +++ b/src/util/resolveConfig.js @@ -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 }, {}) }, diff --git a/stubs/defaultConfig.stub.js b/stubs/defaultConfig.stub.js index 3916362619e6..f6ac1bfca07e 100644 --- a/stubs/defaultConfig.stub.js +++ b/stubs/defaultConfig.stub.js @@ -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', diff --git a/tests/getClassList.test.js b/tests/getClassList.test.js index f8f4ab5d20cf..9289ed98c066 100644 --- a/tests/getClassList.test.js +++ b/tests/getClassList.test.js @@ -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') }) diff --git a/tests/negateValue.test.js b/tests/negateValue.test.js index d382db6a43e0..c31741d48876 100644 --- a/tests/negateValue.test.js +++ b/tests/negateValue.test.js @@ -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() }) diff --git a/tests/negative-prefix.test.js b/tests/negative-prefix.test.js new file mode 100644 index 000000000000..9f36cbf98a0f --- /dev/null +++ b/tests/negative-prefix.test.js @@ -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`
` }], + 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`
` }], + 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`
` }], + 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`
` }], + 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`
` }], + 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`
` }], + 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`
` }], + 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`
` }], + 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`
` }], + 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`
` }], + 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`
` }], + } + + 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`
` }], + 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`
` }], + 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`
` }], + 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`
` }], + 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`
` }], + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchCss(css``) + }) +}) diff --git a/tests/resolveConfig.test.js b/tests/resolveConfig.test.js index d8d0d8c3d363..2c435e5a6eb3 100644 --- a/tests/resolveConfig.test.js +++ b/tests/resolveConfig.test.js @@ -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)',