From ac6d4a6e8e9ae7ca197bf98d933ae2f205be3635 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Thu, 29 Aug 2024 14:05:41 +0200 Subject: [PATCH] Spreading `tailwindcss/defaultTheme` exports keeps bare values (#14257) In #14221 we added a new export to the `tailwindcss` package: `tailwindcss/defaultTheme`. This is build on top of the full config from V3 and will allow plugins to keep being compatible. However, spreading in from this package has overwritten the bare value callback handler. This PR fixes it by sharing the bare value callbacks with the compat config. --- CHANGELOG.md | 1 + .../src/compat/config/create-compat-config.ts | 179 +-------- .../tailwindcss/src/compat/default-theme.ts | 380 +++++++++++++++++- packages/tailwindcss/src/functions.test.ts | 14 + packages/tailwindcss/src/plugin-api.test.ts | 224 ++++++++++- 5 files changed, 623 insertions(+), 175 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a30cd6914..19f107a6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Handle arrays in the CSS `theme()` function when using plugins ([#14262](https://github.com/tailwindlabs/tailwindcss/pull/14262)) - Fix fallback values when using the CSS `theme()` function ([#14262](https://github.com/tailwindlabs/tailwindcss/pull/14262)) - Fix support for declaration fallbacks in plugins ([#14265](https://github.com/tailwindlabs/tailwindcss/pull/14265)) +- Support bare values when using `tailwindcss/defaultTheme` ([#14257](https://github.com/tailwindlabs/tailwindcss/pull/14257)) ## [4.0.0-alpha.20] - 2024-08-23 diff --git a/packages/tailwindcss/src/compat/config/create-compat-config.ts b/packages/tailwindcss/src/compat/config/create-compat-config.ts index 540886f0d..785167db7 100644 --- a/packages/tailwindcss/src/compat/config/create-compat-config.ts +++ b/packages/tailwindcss/src/compat/config/create-compat-config.ts @@ -1,179 +1,26 @@ -import type { NamedUtilityValue } from '../../candidate' import type { Theme } from '../../theme' -import { segment } from '../../utils/segment' +import defaultTheme from '../default-theme' import type { UserConfig } from './types' -function bareValues(fn: (value: NamedUtilityValue) => string | undefined) { - return { - // Ideally this would be a Symbol but some of the ecosystem assumes object with - // string / number keys for example by using `Object.entries()` which means that - // the function that handles the bare value would be lost - __BARE_VALUE__: fn, - } -} - -let bareIntegers = bareValues((value) => { - if (!Number.isNaN(Number(value.value))) { - return value.value - } -}) - -let barePercentages = bareValues((value: NamedUtilityValue) => { - if (!Number.isNaN(Number(value.value))) { - return `${value.value}%` - } -}) - -let barePixels = bareValues((value: NamedUtilityValue) => { - if (!Number.isNaN(Number(value.value))) { - return `${value.value}px` - } -}) - -let bareMilliseconds = bareValues((value: NamedUtilityValue) => { - if (!Number.isNaN(Number(value.value))) { - return `${value.value}ms` - } -}) - -let bareDegrees = bareValues((value: NamedUtilityValue) => { - if (!Number.isNaN(Number(value.value))) { - return `${value.value}deg` - } -}) - -export function createCompatConfig(theme: Theme): UserConfig { +export function createCompatConfig(cssTheme: Theme): UserConfig { return { theme: { + ...defaultTheme, + + // In the defaultTheme config, the `colors` key is not a function but a + // shallow object. We don't want to define the color namespace unless it + // is in the CSS theme so here we explicitly overwrite the defaultTheme + // and only allow colors from the CSS theme. colors: ({ theme }) => theme('color', {}), - accentColor: ({ theme }) => theme('colors'), - aspectRatio: bareValues((value) => { - if (value.fraction === null) return - let [lhs, rhs] = segment(value.fraction, '/') - if (!Number.isInteger(Number(lhs)) || !Number.isInteger(Number(rhs))) return - return value.fraction - }), - backdropBlur: ({ theme }) => theme('blur'), - backdropBrightness: ({ theme }) => ({ - ...theme('brightness'), - ...barePercentages, - }), - backdropContrast: ({ theme }) => ({ - ...theme('contrast'), - ...barePercentages, - }), - backdropGrayscale: ({ theme }) => ({ - ...theme('grayscale'), - ...barePercentages, - }), - backdropHueRotate: ({ theme }) => ({ - ...theme('hueRotate'), - ...bareDegrees, - }), - backdropInvert: ({ theme }) => ({ - ...theme('invert'), - ...barePercentages, - }), - backdropOpacity: ({ theme }) => ({ - ...theme('opacity'), - ...barePercentages, - }), - backdropSaturate: ({ theme }) => ({ - ...theme('saturate'), - ...barePercentages, - }), - backdropSepia: ({ theme }) => ({ - ...theme('sepia'), - ...barePercentages, - }), - backgroundColor: ({ theme }) => theme('colors'), - backgroundOpacity: ({ theme }) => theme('opacity'), - border: barePixels, - borderColor: ({ theme }) => theme('colors'), - borderOpacity: ({ theme }) => theme('opacity'), - borderSpacing: ({ theme }) => theme('spacing'), - boxShadowColor: ({ theme }) => theme('colors'), - brightness: barePercentages, - caretColor: ({ theme }) => theme('colors'), - columns: bareIntegers, - contrast: barePercentages, - divideColor: ({ theme }) => theme('borderColor'), - divideOpacity: ({ theme }) => theme('borderOpacity'), - divideWidth: ({ theme }) => ({ - ...theme('borderWidth'), - ...barePixels, - }), - fill: ({ theme }) => theme('colors'), - flexBasis: ({ theme }) => theme('spacing'), - flexGrow: bareIntegers, - flexShrink: bareIntegers, - gap: ({ theme }) => theme('spacing'), - gradientColorStopPositions: barePercentages, - gradientColorStops: ({ theme }) => theme('colors'), - grayscale: barePercentages, - gridRowEnd: bareIntegers, - gridRowStart: bareIntegers, - gridTemplateColumns: bareValues((value) => { - if (!Number.isNaN(Number(value.value))) { - return `repeat(${value.value}, minmax(0, 1fr))` - } - }), - gridTemplateRows: bareValues((value) => { - if (!Number.isNaN(Number(value.value))) { - return `repeat(${value.value}, minmax(0, 1fr))` - } - }), - height: ({ theme }) => theme('spacing'), - hueRotate: bareDegrees, - inset: ({ theme }) => theme('spacing'), - invert: barePercentages, - lineClamp: bareIntegers, - margin: ({ theme }) => theme('spacing'), - maxHeight: ({ theme }) => theme('spacing'), - maxWidth: ({ theme }) => theme('spacing'), - minHeight: ({ theme }) => theme('spacing'), - minWidth: ({ theme }) => theme('spacing'), - opacity: barePercentages, - order: bareIntegers, - outlineColor: ({ theme }) => theme('colors'), - outlineOffset: barePixels, - outlineWidth: barePixels, - padding: ({ theme }) => theme('spacing'), - placeholderColor: ({ theme }) => theme('colors'), - placeholderOpacity: ({ theme }) => theme('opacity'), - ringColor: ({ theme }) => theme('colors'), - ringOffsetColor: ({ theme }) => theme('colors'), - ringOffsetWidth: barePixels, - ringOpacity: ({ theme }) => theme('opacity'), - ringWidth: barePixels, - rotate: bareDegrees, - saturate: barePercentages, - scale: barePercentages, - scrollMargin: ({ theme }) => theme('spacing'), - scrollPadding: ({ theme }) => theme('spacing'), - sepia: barePercentages, - size: ({ theme }) => theme('spacing'), - skew: bareDegrees, - space: ({ theme }) => theme('spacing'), - stroke: ({ theme }) => theme('colors'), - strokeWidth: barePixels, - textColor: ({ theme }) => theme('colors'), - textDecorationColor: ({ theme }) => theme('colors'), - textDecorationThickness: barePixels, - textIndent: ({ theme }) => theme('spacing'), - textOpacity: ({ theme }) => theme('opacity'), - textUnderlineOffset: barePixels, - transitionDelay: bareMilliseconds, + transitionDuration: { - DEFAULT: theme.get(['--default-transition-duration']) ?? null, - ...bareMilliseconds, + ...defaultTheme.transitionDuration, + DEFAULT: cssTheme.get(['--default-transition-duration']) ?? null, }, transitionTimingFunction: { - DEFAULT: theme.get(['--default-transition-timing-function']) ?? null, + ...defaultTheme.transitionTimingFunction, + DEFAULT: cssTheme.get(['--default-transition-timing-function']) ?? null, }, - translate: ({ theme }) => theme('spacing'), - width: ({ theme }) => theme('spacing'), - zIndex: bareIntegers, }, } } diff --git a/packages/tailwindcss/src/compat/default-theme.ts b/packages/tailwindcss/src/compat/default-theme.ts index c64b7136c..5b5c3d2ab 100644 --- a/packages/tailwindcss/src/compat/default-theme.ts +++ b/packages/tailwindcss/src/compat/default-theme.ts @@ -1,10 +1,62 @@ -import { Theme } from '../theme' -import { createCompatConfig } from './config/create-compat-config' +import type { NamedUtilityValue } from '../candidate' +import { segment } from '../utils/segment' +import colors from './colors' +import type { UserConfig } from './config/types' -let theme = new Theme() +function bareValues(fn: (value: NamedUtilityValue) => string | undefined) { + return { + // Ideally this would be a Symbol but some of the ecosystem assumes object with + // string / number keys for example by using `Object.entries()` which means that + // the function that handles the bare value would be lost + __BARE_VALUE__: fn, + } +} + +let bareIntegers = bareValues((value) => { + if (!Number.isNaN(Number(value.value))) { + return value.value + } +}) + +let barePercentages = bareValues((value: NamedUtilityValue) => { + if (!Number.isNaN(Number(value.value))) { + return `${value.value}%` + } +}) + +let barePixels = bareValues((value: NamedUtilityValue) => { + if (!Number.isNaN(Number(value.value))) { + return `${value.value}px` + } +}) + +let bareMilliseconds = bareValues((value: NamedUtilityValue) => { + if (!Number.isNaN(Number(value.value))) { + return `${value.value}ms` + } +}) + +let bareDegrees = bareValues((value: NamedUtilityValue) => { + if (!Number.isNaN(Number(value.value))) { + return `${value.value}deg` + } +}) + +let bareAspectRatio = bareValues((value) => { + if (value.fraction === null) return + let [lhs, rhs] = segment(value.fraction, '/') + if (!Number.isInteger(Number(lhs)) || !Number.isInteger(Number(rhs))) return + return value.fraction +}) + +let bareRepeatValues = bareValues((value) => { + if (!Number.isNaN(Number(value.value))) { + return `repeat(${value.value}, minmax(0, 1fr))` + } +}) export default { - ...createCompatConfig(theme).theme, + accentColor: ({ theme }) => theme('colors'), animation: { none: 'none', spin: 'spin 1s linear infinite', @@ -27,7 +79,42 @@ export default { auto: 'auto', square: '1 / 1', video: '16 / 9', + ...bareAspectRatio, }, + backdropBlur: ({ theme }) => theme('blur'), + backdropBrightness: ({ theme }) => ({ + ...theme('brightness'), + ...barePercentages, + }), + backdropContrast: ({ theme }) => ({ + ...theme('contrast'), + ...barePercentages, + }), + backdropGrayscale: ({ theme }) => ({ + ...theme('grayscale'), + ...barePercentages, + }), + backdropHueRotate: ({ theme }) => ({ + ...theme('hueRotate'), + ...bareDegrees, + }), + backdropInvert: ({ theme }) => ({ + ...theme('invert'), + ...barePercentages, + }), + backdropOpacity: ({ theme }) => ({ + ...theme('opacity'), + ...barePercentages, + }), + backdropSaturate: ({ theme }) => ({ + ...theme('saturate'), + ...barePercentages, + }), + backdropSepia: ({ theme }) => ({ + ...theme('sepia'), + ...barePercentages, + }), + backgroundColor: ({ theme }) => theme('colors'), backgroundImage: { none: 'none', 'gradient-to-t': 'linear-gradient(to top, var(--tw-gradient-stops))', @@ -39,6 +126,7 @@ export default { 'gradient-to-l': 'linear-gradient(to left, var(--tw-gradient-stops))', 'gradient-to-tl': 'linear-gradient(to top left, var(--tw-gradient-stops))', }, + backgroundOpacity: ({ theme }) => theme('opacity'), backgroundPosition: { bottom: 'bottom', center: 'center', @@ -66,6 +154,11 @@ export default { '2xl': '40px', '3xl': '64px', }, + borderColor: ({ theme }) => ({ + DEFAULT: 'currentColor', + ...theme('colors'), + }), + borderOpacity: ({ theme }) => theme('opacity'), borderRadius: { none: '0px', sm: '0.125rem', @@ -77,12 +170,14 @@ export default { '3xl': '1.5rem', full: '9999px', }, + borderSpacing: ({ theme }) => theme('spacing'), borderWidth: { DEFAULT: '1px', 0: '0px', 2: '2px', 4: '4px', 8: '8px', + ...barePixels, }, boxShadow: { sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)', @@ -94,6 +189,7 @@ export default { inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.05)', none: 'none', }, + boxShadowColor: ({ theme }) => theme('colors'), brightness: { 0: '0', 50: '.5', @@ -106,7 +202,10 @@ export default { 125: '1.25', 150: '1.5', 200: '2', + ...barePercentages, }, + caretColor: ({ theme }) => theme('colors'), + colors: () => ({ ...colors }), columns: { auto: 'auto', 1: '1', @@ -134,6 +233,7 @@ export default { '5xl': '64rem', '6xl': '72rem', '7xl': '80rem', + ...bareIntegers, }, container: {}, content: { @@ -147,6 +247,7 @@ export default { 125: '1.25', 150: '1.5', 200: '2', + ...barePercentages, }, cursor: { auto: 'auto', @@ -186,6 +287,12 @@ export default { 'zoom-in': 'zoom-in', 'zoom-out': 'zoom-out', }, + divideColor: ({ theme }) => theme('borderColor'), + divideOpacity: ({ theme }) => theme('borderOpacity'), + divideWidth: ({ theme }) => ({ + ...theme('borderWidth'), + ...barePixels, + }), dropShadow: { sm: '0 1px 1px rgb(0 0 0 / 0.05)', DEFAULT: ['0 1px 2px rgb(0 0 0 / 0.1)', '0 1px 1px rgb(0 0 0 / 0.06)'], @@ -195,19 +302,53 @@ export default { '2xl': '0 25px 25px rgb(0 0 0 / 0.15)', none: '0 0 #0000', }, + fill: ({ theme }) => theme('colors'), flex: { 1: '1 1 0%', auto: '1 1 auto', initial: '0 1 auto', none: 'none', }, + flexBasis: ({ theme }) => ({ + auto: 'auto', + '1/2': '50%', + '1/3': '33.333333%', + '2/3': '66.666667%', + '1/4': '25%', + '2/4': '50%', + '3/4': '75%', + '1/5': '20%', + '2/5': '40%', + '3/5': '60%', + '4/5': '80%', + '1/6': '16.666667%', + '2/6': '33.333333%', + '3/6': '50%', + '4/6': '66.666667%', + '5/6': '83.333333%', + '1/12': '8.333333%', + '2/12': '16.666667%', + '3/12': '25%', + '4/12': '33.333333%', + '5/12': '41.666667%', + '6/12': '50%', + '7/12': '58.333333%', + '8/12': '66.666667%', + '9/12': '75%', + '10/12': '83.333333%', + '11/12': '91.666667%', + full: '100%', + ...theme('spacing'), + }), flexGrow: { 0: '0', DEFAULT: '1', + ...bareIntegers, }, flexShrink: { 0: '0', DEFAULT: '1', + ...bareIntegers, }, fontFamily: { sans: [ @@ -257,6 +398,8 @@ export default { extrabold: '800', black: '900', }, + gap: ({ theme }) => theme('spacing'), + gradientColorStops: ({ theme }) => theme('colors'), gradientColorStopPositions: { '0%': '0%', '5%': '5%', @@ -279,10 +422,12 @@ export default { '90%': '90%', '95%': '95%', '100%': '100%', + ...barePercentages, }, grayscale: { 0: '0', DEFAULT: '100%', + ...barePercentages, }, gridAutoColumns: { auto: 'auto', @@ -327,6 +472,7 @@ export default { 11: '11', 12: '12', 13: '13', + ...bareIntegers, }, gridColumnStart: { auto: 'auto', @@ -343,6 +489,7 @@ export default { 11: '11', 12: '12', 13: '13', + ...bareIntegers, }, gridRow: { auto: 'auto', @@ -375,6 +522,7 @@ export default { 11: '11', 12: '12', 13: '13', + ...bareIntegers, }, gridRowStart: { auto: 'auto', @@ -391,6 +539,7 @@ export default { 11: '11', 12: '12', 13: '13', + ...bareIntegers, }, gridTemplateColumns: { none: 'none', @@ -407,6 +556,7 @@ export default { 10: 'repeat(10, minmax(0, 1fr))', 11: 'repeat(11, minmax(0, 1fr))', 12: 'repeat(12, minmax(0, 1fr))', + ...bareRepeatValues, }, gridTemplateRows: { none: 'none', @@ -423,7 +573,35 @@ export default { 10: 'repeat(10, minmax(0, 1fr))', 11: 'repeat(11, minmax(0, 1fr))', 12: 'repeat(12, minmax(0, 1fr))', + ...bareRepeatValues, }, + height: ({ theme }) => ({ + auto: 'auto', + '1/2': '50%', + '1/3': '33.333333%', + '2/3': '66.666667%', + '1/4': '25%', + '2/4': '50%', + '3/4': '75%', + '1/5': '20%', + '2/5': '40%', + '3/5': '60%', + '4/5': '80%', + '1/6': '16.666667%', + '2/6': '33.333333%', + '3/6': '50%', + '4/6': '66.666667%', + '5/6': '83.333333%', + full: '100%', + screen: '100vh', + svh: '100svh', + lvh: '100lvh', + dvh: '100dvh', + min: 'min-content', + max: 'max-content', + fit: 'fit-content', + ...theme('spacing'), + }), hueRotate: { 0: '0deg', 15: '15deg', @@ -431,10 +609,23 @@ export default { 60: '60deg', 90: '90deg', 180: '180deg', + ...bareDegrees, }, + inset: ({ theme }) => ({ + auto: 'auto', + '1/2': '50%', + '1/3': '33.333333%', + '2/3': '66.666667%', + '1/4': '25%', + '2/4': '50%', + '3/4': '75%', + full: '100%', + ...theme('spacing'), + }), invert: { 0: '0', DEFAULT: '100%', + ...barePercentages, }, keyframes: { spin: { @@ -496,6 +687,10 @@ export default { listStyleImage: { none: 'none', }, + margin: ({ theme }) => ({ + auto: 'auto', + ...theme('spacing'), + }), lineClamp: { 1: '1', 2: '2', @@ -503,7 +698,58 @@ export default { 4: '4', 5: '5', 6: '6', + ...bareIntegers, }, + maxHeight: ({ theme }) => ({ + none: 'none', + full: '100%', + screen: '100vh', + svh: '100svh', + lvh: '100lvh', + dvh: '100dvh', + min: 'min-content', + max: 'max-content', + fit: 'fit-content', + ...theme('spacing'), + }), + maxWidth: ({ theme }) => ({ + none: 'none', + xs: '20rem', + sm: '24rem', + md: '28rem', + lg: '32rem', + xl: '36rem', + '2xl': '42rem', + '3xl': '48rem', + '4xl': '56rem', + '5xl': '64rem', + '6xl': '72rem', + '7xl': '80rem', + full: '100%', + min: 'min-content', + max: 'max-content', + fit: 'fit-content', + prose: '65ch', + ...theme('spacing'), + }), + minHeight: ({ theme }) => ({ + full: '100%', + screen: '100vh', + svh: '100svh', + lvh: '100lvh', + dvh: '100dvh', + min: 'min-content', + max: 'max-content', + fit: 'fit-content', + ...theme('spacing'), + }), + minWidth: ({ theme }) => ({ + full: '100%', + min: 'min-content', + max: 'max-content', + fit: 'fit-content', + ...theme('spacing'), + }), objectPosition: { bottom: 'bottom', center: 'center', @@ -537,6 +783,7 @@ export default { 90: '0.9', 95: '0.95', 100: '1', + ...barePercentages, }, order: { first: '-9999', @@ -554,13 +801,16 @@ export default { 10: '10', 11: '11', 12: '12', + ...bareIntegers, }, + outlineColor: ({ theme }) => theme('colors'), outlineOffset: { 0: '0px', 1: '1px', 2: '2px', 4: '4px', 8: '8px', + ...barePixels, }, outlineWidth: { 0: '0px', @@ -568,14 +818,28 @@ export default { 2: '2px', 4: '4px', 8: '8px', + ...barePixels, }, + padding: ({ theme }) => theme('spacing'), + placeholderColor: ({ theme }) => theme('colors'), + placeholderOpacity: ({ theme }) => theme('opacity'), + ringColor: ({ theme }) => ({ + DEFAULT: 'currentColor', + ...theme('colors'), + }), + ringOffsetColor: ({ theme }) => theme('colors'), ringOffsetWidth: { 0: '0px', 1: '1px', 2: '2px', 4: '4px', 8: '8px', + ...barePixels, }, + ringOpacity: ({ theme }) => ({ + DEFAULT: '0.5', + ...theme('opacity'), + }), ringWidth: { DEFAULT: '3px', 0: '0px', @@ -583,6 +847,7 @@ export default { 2: '2px', 4: '4px', 8: '8px', + ...barePixels, }, rotate: { 0: '0deg', @@ -594,6 +859,7 @@ export default { 45: '45deg', 90: '90deg', 180: '180deg', + ...bareDegrees, }, saturate: { 0: '0', @@ -601,6 +867,7 @@ export default { 100: '1', 150: '1.5', 200: '2', + ...barePercentages, }, scale: { 0: '0', @@ -613,6 +880,7 @@ export default { 110: '1.1', 125: '1.25', 150: '1.5', + ...barePercentages, }, screens: { sm: '640px', @@ -621,9 +889,12 @@ export default { xl: '1280px', '2xl': '1536px', }, + scrollMargin: ({ theme }) => theme('spacing'), + scrollPadding: ({ theme }) => theme('spacing'), sepia: { 0: '0', DEFAULT: '100%', + ...barePercentages, }, skew: { 0: '0deg', @@ -632,7 +903,9 @@ export default { 3: '3deg', 6: '6deg', 12: '12deg', + ...bareDegrees, }, + space: ({ theme }) => theme('spacing'), spacing: { px: '1px', 0: '0px', @@ -670,13 +943,20 @@ export default { 80: '20rem', 96: '24rem', }, + stroke: ({ theme }) => ({ + none: 'none', + ...theme('colors'), + }), strokeWidth: { 0: '0', 1: '1', 2: '2', + ...bareIntegers, }, supports: {}, data: {}, + textColor: ({ theme }) => theme('colors'), + textDecorationColor: ({ theme }) => theme('colors'), textDecorationThickness: { auto: 'auto', 'from-font': 'from-font', @@ -685,7 +965,10 @@ export default { 2: '2px', 4: '4px', 8: '8px', + ...barePixels, }, + textIndent: ({ theme }) => theme('spacing'), + textOpacity: ({ theme }) => theme('opacity'), textUnderlineOffset: { auto: 'auto', 0: '0px', @@ -693,6 +976,7 @@ export default { 2: '2px', 4: '4px', 8: '8px', + ...barePixels, }, transformOrigin: { center: 'center', @@ -715,6 +999,7 @@ export default { 500: '500ms', 700: '700ms', 1000: '1000ms', + ...bareMilliseconds, }, transitionDuration: { DEFAULT: '150ms', @@ -727,6 +1012,7 @@ export default { 500: '500ms', 700: '700ms', 1000: '1000ms', + ...bareMilliseconds, }, transitionProperty: { none: 'none', @@ -745,6 +1031,89 @@ export default { out: 'cubic-bezier(0, 0, 0.2, 1)', 'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)', }, + translate: ({ theme }) => ({ + '1/2': '50%', + '1/3': '33.333333%', + '2/3': '66.666667%', + '1/4': '25%', + '2/4': '50%', + '3/4': '75%', + full: '100%', + ...theme('spacing'), + }), + size: ({ theme }) => ({ + auto: 'auto', + '1/2': '50%', + '1/3': '33.333333%', + '2/3': '66.666667%', + '1/4': '25%', + '2/4': '50%', + '3/4': '75%', + '1/5': '20%', + '2/5': '40%', + '3/5': '60%', + '4/5': '80%', + '1/6': '16.666667%', + '2/6': '33.333333%', + '3/6': '50%', + '4/6': '66.666667%', + '5/6': '83.333333%', + '1/12': '8.333333%', + '2/12': '16.666667%', + '3/12': '25%', + '4/12': '33.333333%', + '5/12': '41.666667%', + '6/12': '50%', + '7/12': '58.333333%', + '8/12': '66.666667%', + '9/12': '75%', + '10/12': '83.333333%', + '11/12': '91.666667%', + full: '100%', + min: 'min-content', + max: 'max-content', + fit: 'fit-content', + ...theme('spacing'), + }), + width: ({ theme }) => ({ + auto: 'auto', + + '1/2': '50%', + '1/3': '33.333333%', + '2/3': '66.666667%', + '1/4': '25%', + '2/4': '50%', + '3/4': '75%', + '1/5': '20%', + '2/5': '40%', + '3/5': '60%', + '4/5': '80%', + '1/6': '16.666667%', + '2/6': '33.333333%', + '3/6': '50%', + '4/6': '66.666667%', + '5/6': '83.333333%', + '1/12': '8.333333%', + '2/12': '16.666667%', + '3/12': '25%', + '4/12': '33.333333%', + '5/12': '41.666667%', + '6/12': '50%', + '7/12': '58.333333%', + '8/12': '66.666667%', + '9/12': '75%', + '10/12': '83.333333%', + '11/12': '91.666667%', + full: '100%', + screen: '100vw', + svw: '100svw', + lvw: '100lvw', + dvw: '100dvw', + min: 'min-content', + max: 'max-content', + fit: 'fit-content', + ...theme('spacing'), + }), willChange: { auto: 'auto', scroll: 'scroll-position', @@ -759,5 +1128,6 @@ export default { 30: '30', 40: '40', 50: '50', + ...bareIntegers, }, -} +} satisfies UserConfig['theme'] diff --git a/packages/tailwindcss/src/functions.test.ts b/packages/tailwindcss/src/functions.test.ts index 6232faba7..e6703227a 100644 --- a/packages/tailwindcss/src/functions.test.ts +++ b/packages/tailwindcss/src/functions.test.ts @@ -285,6 +285,20 @@ describe('theme function', () => { }" `) }) + + test('theme(fontFamily.sans)', async () => { + expect( + await compileCss(css` + .fam { + font-family: theme(fontFamily.sans); + } + `), + ).toMatchInlineSnapshot(` + ".fam { + font-family: ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; + }" + `) + }) }) test('theme(colors.unknown.500)', async () => diff --git a/packages/tailwindcss/src/plugin-api.test.ts b/packages/tailwindcss/src/plugin-api.test.ts index 7e74572b0..d911b09e5 100644 --- a/packages/tailwindcss/src/plugin-api.test.ts +++ b/packages/tailwindcss/src/plugin-api.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test, vi } from 'vitest' import { compile } from '.' +import defaultTheme from './compat/default-theme' import plugin from './plugin' import type { CssInJs, PluginAPI } from './plugin-api' import { optimizeCss } from './test-utils/run' @@ -510,7 +511,7 @@ describe('theme', async () => { utility('my-backdrop-opacity', 'backdropOpacity') utility('my-backdrop-saturate', 'backdropSaturate') utility('my-backdrop-sepia', 'backdropSepia') - utility('my-border', 'border') + utility('my-border-width', 'borderWidth') utility('my-brightness', 'brightness') utility('my-columns', 'columns') utility('my-contrast', 'contrast') @@ -557,7 +558,7 @@ describe('theme', async () => { 'my-backdrop-opacity-1', 'my-backdrop-saturate-1', 'my-backdrop-sepia-1', - 'my-border-1', + 'my-border-width-1', 'my-brightness-1', 'my-columns-1', 'my-contrast-1', @@ -620,7 +621,7 @@ describe('theme', async () => { .my-backdrop-sepia-1 { --value: 1%; } - .my-border-1 { + .my-border-width-1 { --value: 1px; } .my-brightness-1 { @@ -702,7 +703,7 @@ describe('theme', async () => { --value: 1deg; } .my-stroke-width-1 { - --value: 1px; + --value: 1; } .my-text-decoration-thickness-1 { --value: 1px; @@ -866,6 +867,221 @@ describe('theme', async () => { " `) }) + + test('spreading `tailwindcss/defaultTheme` exports keeps bare values', async () => { + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` + + let { build } = await compile(input, { + loadPlugin: async () => { + return plugin(function ({ matchUtilities }) { + function utility(name: string, themeKey: string) { + matchUtilities( + { [name]: (value) => ({ '--value': value }) }, + // @ts-ignore + { values: defaultTheme[themeKey] }, + ) + } + + utility('my-aspect', 'aspectRatio') + // The following keys deliberately doesn't work as these are exported + // as functions from the compat config. + // + // utility('my-backdrop-brightness', 'backdropBrightness') + // utility('my-backdrop-contrast', 'backdropContrast') + // utility('my-backdrop-grayscale', 'backdropGrayscale') + // utility('my-backdrop-hue-rotate', 'backdropHueRotate') + // utility('my-backdrop-invert', 'backdropInvert') + // utility('my-backdrop-opacity', 'backdropOpacity') + // utility('my-backdrop-saturate', 'backdropSaturate') + // utility('my-backdrop-sepia', 'backdropSepia') + // utility('my-divide-width', 'divideWidth') + utility('my-border-width', 'borderWidth') + utility('my-brightness', 'brightness') + utility('my-columns', 'columns') + utility('my-contrast', 'contrast') + utility('my-flex-grow', 'flexGrow') + utility('my-flex-shrink', 'flexShrink') + utility('my-gradient-color-stop-positions', 'gradientColorStopPositions') + utility('my-grayscale', 'grayscale') + utility('my-grid-row-end', 'gridRowEnd') + utility('my-grid-row-start', 'gridRowStart') + utility('my-grid-template-columns', 'gridTemplateColumns') + utility('my-grid-template-rows', 'gridTemplateRows') + utility('my-hue-rotate', 'hueRotate') + utility('my-invert', 'invert') + utility('my-line-clamp', 'lineClamp') + utility('my-opacity', 'opacity') + utility('my-order', 'order') + utility('my-outline-offset', 'outlineOffset') + utility('my-outline-width', 'outlineWidth') + utility('my-ring-offset-width', 'ringOffsetWidth') + utility('my-ring-width', 'ringWidth') + utility('my-rotate', 'rotate') + utility('my-saturate', 'saturate') + utility('my-scale', 'scale') + utility('my-sepia', 'sepia') + utility('my-skew', 'skew') + utility('my-stroke-width', 'strokeWidth') + utility('my-text-decoration-thickness', 'textDecorationThickness') + utility('my-text-underline-offset', 'textUnderlineOffset') + utility('my-transition-delay', 'transitionDelay') + utility('my-transition-duration', 'transitionDuration') + utility('my-z-index', 'zIndex') + }) + }, + }) + + let output = build([ + 'my-aspect-2/5', + // 'my-backdrop-brightness-1', + // 'my-backdrop-contrast-1', + // 'my-backdrop-grayscale-1', + // 'my-backdrop-hue-rotate-1', + // 'my-backdrop-invert-1', + // 'my-backdrop-opacity-1', + // 'my-backdrop-saturate-1', + // 'my-backdrop-sepia-1', + // 'my-divide-width-1', + 'my-border-width-1', + 'my-brightness-1', + 'my-columns-1', + 'my-contrast-1', + 'my-flex-grow-1', + 'my-flex-shrink-1', + 'my-gradient-color-stop-positions-1', + 'my-grayscale-1', + 'my-grid-row-end-1', + 'my-grid-row-start-1', + 'my-grid-template-columns-1', + 'my-grid-template-rows-1', + 'my-hue-rotate-1', + 'my-invert-1', + 'my-line-clamp-1', + 'my-opacity-1', + 'my-order-1', + 'my-outline-offset-1', + 'my-outline-width-1', + 'my-ring-offset-width-1', + 'my-ring-width-1', + 'my-rotate-1', + 'my-saturate-1', + 'my-scale-1', + 'my-sepia-1', + 'my-skew-1', + 'my-stroke-width-1', + 'my-text-decoration-thickness-1', + 'my-text-underline-offset-1', + 'my-transition-delay-1', + 'my-transition-duration-1', + 'my-z-index-1', + ]) + + expect(output).toMatchInlineSnapshot(` + ".my-aspect-2\\/5 { + --value: 2/5; + } + .my-border-width-1 { + --value: 1px; + } + .my-brightness-1 { + --value: 1%; + } + .my-columns-1 { + --value: 1; + } + .my-contrast-1 { + --value: 1%; + } + .my-flex-grow-1 { + --value: 1; + } + .my-flex-shrink-1 { + --value: 1; + } + .my-gradient-color-stop-positions-1 { + --value: 1%; + } + .my-grayscale-1 { + --value: 1%; + } + .my-grid-row-end-1 { + --value: 1; + } + .my-grid-row-start-1 { + --value: 1; + } + .my-grid-template-columns-1 { + --value: repeat(1, minmax(0, 1fr)); + } + .my-grid-template-rows-1 { + --value: repeat(1, minmax(0, 1fr)); + } + .my-hue-rotate-1 { + --value: 1deg; + } + .my-invert-1 { + --value: 1%; + } + .my-line-clamp-1 { + --value: 1; + } + .my-opacity-1 { + --value: 1%; + } + .my-order-1 { + --value: 1; + } + .my-outline-offset-1 { + --value: 1px; + } + .my-outline-width-1 { + --value: 1px; + } + .my-ring-offset-width-1 { + --value: 1px; + } + .my-ring-width-1 { + --value: 1px; + } + .my-rotate-1 { + --value: 1deg; + } + .my-saturate-1 { + --value: 1%; + } + .my-scale-1 { + --value: 1%; + } + .my-sepia-1 { + --value: 1%; + } + .my-skew-1 { + --value: 1deg; + } + .my-stroke-width-1 { + --value: 1; + } + .my-text-decoration-thickness-1 { + --value: 1px; + } + .my-text-underline-offset-1 { + --value: 1px; + } + .my-transition-delay-1 { + --value: 1ms; + } + .my-transition-duration-1 { + --value: 1ms; + } + .my-z-index-1 { + --value: 1; + } + " + `) + }) }) describe('addUtilities()', () => {