diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a497ac4a..f3fbec4ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support environment API in `@tailwindcss/vite` ([#18970](https://github.com/tailwindlabs/tailwindcss/pull/18970)) - Preserve case of theme keys from JS configs and plugins ([#19337](https://github.com/tailwindlabs/tailwindcss/pull/19337)) - Write source maps correctly on the CLI when using `--watch` ([#19373](https://github.com/tailwindlabs/tailwindcss/pull/19373)) +- Handle special defaults (like `ringColor.DEFAULT`) in JS configs ([#19348](https://github.com/tailwindlabs/tailwindcss/pull/19348)) - Upgrade: Handle `future` and `experimental` config keys ([#19344](https://github.com/tailwindlabs/tailwindcss/pull/19344)) - Try to canonicalize any arbitrary utility to a bare value ([#19379](https://github.com/tailwindlabs/tailwindcss/pull/19379)) diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index a9d71b3d5..029ed77a8 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -2347,8 +2347,7 @@ test( --shadow-*: initial; --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); - --ring-width-*: initial; - --ring-width: 4px; + --default-ring-width: 4px; --blur: var(--custom-default-blur); diff --git a/integrations/upgrade/js-config.test.ts b/integrations/upgrade/js-config.test.ts index eaa04df96..450d8254c 100644 --- a/integrations/upgrade/js-config.test.ts +++ b/integrations/upgrade/js-config.test.ts @@ -34,6 +34,9 @@ test( steel: 'rgb(70 130 180 / )', smoke: 'rgba(245, 245, 245, var(--smoke-alpha, ))', }, + ringColor: { + DEFAULT: '#c0ffee', + }, opacity: { superOpaque: '0.95', }, @@ -191,6 +194,8 @@ test( --color-steel: rgb(70 130 180); --color-smoke: rgba(245, 245, 245, var(--smoke-alpha, 1)); + --default-ring-color: #c0ffee; + --opacity-*: initial; --opacity-superOpaque: 95%; diff --git a/packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts b/packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts index 1b2b066ab..6a451333e 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts @@ -237,16 +237,21 @@ async function migrateTheme( prevSectionKey = sectionKey } - if (resetNamespaces.has(key[0]) && resetNamespaces.get(key[0]) === false) { - resetNamespaces.set(key[0], true) - let property = keyPathToCssProperty([key[0]]) - if (property !== null) { - themeSection.push(` ${escape(`--${property}`)}-*: initial;`) - } - } - let property = keyPathToCssProperty(key) + if (property !== null) { + if ( + !property.startsWith('default-') && + resetNamespaces.has(key[0]) && + resetNamespaces.get(key[0]) === false + ) { + resetNamespaces.set(key[0], true) + let ns = keyPathToCssProperty([key[0]]) + if (ns !== null) { + themeSection.push(` ${escape(`--${ns}`)}-*: initial;`) + } + } + themeSection.push(` ${escape(`--${property}`)}: ${value};`) } } diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts index 614b4017b..2b088aa2e 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts @@ -59,7 +59,7 @@ const THEME_KEYS = new Map([ ['backdrop-blur-sm', '--backdrop-blur-sm'], ['backdrop-blur-xs', '--backdrop-blur-xs'], - ['ring', '--ring-width'], + ['ring', '--default-ring-width'], ['ring-3', '--ring-width-3'], ]) diff --git a/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts b/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts index f36e5ab15..12f195e94 100644 --- a/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts +++ b/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts @@ -31,6 +31,16 @@ test('config values can be merged into the theme', () => { normal: '0 1px 3px black', }, + borderWidth: { + DEFAULT: '1.5px', + }, + outlineWidth: { + DEFAULT: '2.5px', + }, + ringWidth: { + DEFAULT: '3.5px', + }, + borderRadius: { sm: '0.33rem', }, @@ -57,6 +67,10 @@ test('config values can be merged into the theme', () => { '2xl': ['2rem'], }, + ringColor: { + DEFAULT: '#fff', + }, + letterSpacing: { superWide: '0.25em', }, @@ -77,8 +91,12 @@ test('config values can be merged into the theme', () => { }, transitionTimingFunction: { + DEFAULT: 'ease-in-out', fast: 'cubic-bezier(0, 0.55, 0.45, 1)', }, + transitionDuration: { + DEFAULT: '1234ms', + }, }, }, base: '/root', @@ -125,6 +143,23 @@ test('config values can be merged into the theme', () => { expect(theme.resolve('100%', ['--width'])).toEqual('100%') expect(theme.resolve('9xs', ['--container'])).toEqual('6rem') expect(theme.resolve('fast', ['--ease'])).toEqual('cubic-bezier(0, 0.55, 0.45, 1)') + + expect(theme.get(['--border'])).toEqual(null) + expect(theme.get(['--default-border-width'])).toEqual('1.5px') + + expect(theme.get(['--outline'])).toEqual(null) + expect(theme.get(['--default-outline-width'])).toEqual('2.5px') + + expect(theme.get(['--ring-color'])).toEqual(null) + expect(theme.get(['--default-ring-color'])).toEqual('#fff') + + expect(theme.get(['--ring-width'])).toEqual(null) + expect(theme.get(['--default-ring-width'])).toEqual('3.5px') + + expect(theme.get(['--default-transition-duration'])).toEqual('1234ms') + + expect(theme.get(['--ease'])).toEqual(null) + expect(theme.get(['--default-transition-timing-function'])).toEqual('ease-in-out') }) test('will reset default theme values with overwriting theme values', () => { diff --git a/packages/tailwindcss/src/compat/apply-config-to-theme.ts b/packages/tailwindcss/src/compat/apply-config-to-theme.ts index ce3958eb3..885d2d9ba 100644 --- a/packages/tailwindcss/src/compat/apply-config-to-theme.ts +++ b/packages/tailwindcss/src/compat/apply-config-to-theme.ts @@ -147,32 +147,57 @@ export function themeableValues(config: ResolvedConfig['theme']): [string[], unk return toAdd } +const SPECIAL_DEFAULT_KEYS: Record = { + borderWidth: 'border-width', + outlineWidth: 'outline-width', + ringColor: 'ring-color', + ringWidth: 'ring-width', + transitionDuration: 'transition-duration', + transitionTimingFunction: 'transition-timing-function', +} + +const OLD_TO_NEW_NAMESPACE: Record = { + animation: 'animate', + aspectRatio: 'aspect', + borderRadius: 'radius', + boxShadow: 'shadow', + colors: 'color', + containers: 'container', + fontFamily: 'font', + fontSize: 'text', + letterSpacing: 'tracking', + lineHeight: 'leading', + maxWidth: 'container', + screens: 'breakpoint', + transitionTimingFunction: 'ease', +} + const IS_VALID_KEY = /^[a-zA-Z0-9-_%/\.]+$/ export function keyPathToCssProperty(path: string[]) { + // In some special cases the `DEFAULT` key did not map to a "default" utility + // e.g. `ringColor.DEFAULT` wasn't *just* used for `ring`. It was used for + // all ring utilities as the color when one wasn't specified. + // + // We place these specialty values under the `--default-*` namespace to signal + // that they are defaults used by (potentially) multiple utilities. + let specialDefault = SPECIAL_DEFAULT_KEYS[path[0]] + if (specialDefault && path[1] === 'DEFAULT') return `default-${specialDefault}` + // The legacy container component config should not be included in the Theme if (path[0] === 'container') return null - path = path.slice() - - if (path[0] === 'animation') path[0] = 'animate' - if (path[0] === 'aspectRatio') path[0] = 'aspect' - if (path[0] === 'borderRadius') path[0] = 'radius' - if (path[0] === 'boxShadow') path[0] = 'shadow' - if (path[0] === 'colors') path[0] = 'color' - if (path[0] === 'containers') path[0] = 'container' - if (path[0] === 'fontFamily') path[0] = 'font' - if (path[0] === 'fontSize') path[0] = 'text' - if (path[0] === 'letterSpacing') path[0] = 'tracking' - if (path[0] === 'lineHeight') path[0] = 'leading' - if (path[0] === 'maxWidth') path[0] = 'container' - if (path[0] === 'screens') path[0] = 'breakpoint' - if (path[0] === 'transitionTimingFunction') path[0] = 'ease' - for (let part of path) { if (!IS_VALID_KEY.test(part)) return null } + // Map old v3 namespaces to new theme namespaces + let ns = OLD_TO_NEW_NAMESPACE[path[0]] + if (ns) { + path = path.slice() + path[0] = ns + } + return ( path // [1] should move into the nested object tuple. To create the CSS variable