From 88c30a630f17f6641d7bc4e3646b1d620c13f918 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Thu, 7 Nov 2024 14:29:11 +0100 Subject: [PATCH 1/9] Convert spacing `theme()` calls to use `calc()` --- .../template/codemods/theme-to-var.test.ts | 103 +++++++++++------- .../src/template/codemods/theme-to-var.ts | 22 +++- 2 files changed, 80 insertions(+), 45 deletions(-) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts index 120b446a308c..29bede1442c2 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts @@ -9,14 +9,18 @@ test.each([ ['[color:red]', '[color:red]'], // Handle special cases around `.1` in the `theme(…)` - ['[--value:theme(spacing.1)]', '[--value:var(--spacing-1)]'], + ['[--value:theme(spacing.1)]', '[--value:calc(var(--spacing)*1)]'], ['[--value:theme(fontSize.xs.1.lineHeight)]', '[--value:var(--font-size-xs--line-height)]'], + ['[--value:theme(spacing[1.25])]', '[--value:calc(var(--spacing)*1.25)]'], + + // Should not convert invalid spacing values to calc + ['[--value:theme(spacing[1.1])]', '[--value:theme(spacing[1.1])]'], // Convert to `var(…)` if we can resolve the path ['[color:theme(colors.red.500)]', '[color:var(--color-red-500)]'], // Arbitrary property ['[color:theme(colors.red.500)]/50', '[color:var(--color-red-500)]/50'], // Arbitrary property + modifier ['bg-[theme(colors.red.500)]', 'bg-[var(--color-red-500)]'], // Arbitrary value - ['bg-[size:theme(spacing.4)]', 'bg-[size:var(--spacing-4)]'], // Arbitrary value + data type hint + ['bg-[size:theme(spacing.4)]', 'bg-[size:calc(var(--spacing)*4)]'], // Arbitrary value + data type hint // Convert to `var(…)` if we can resolve the path, but keep fallback values ['bg-[theme(colors.red.500,red)]', 'bg-[var(--color-red-500,red)]'], @@ -79,15 +83,18 @@ test.each([ // Variants, we can't use `var(…)` especially inside of `@media(…)`. We can // still upgrade the `theme(…)` to the modern syntax. - ['max-[theme(spacing.4)]:flex', 'max-[theme(--spacing-4)]:flex'], + ['max-[theme(spacing.4)]:flex', 'max-[theme(spacing.4)]:flex'], // This test in itself doesn't make much sense. But we need to make sure // that this doesn't end up as the modifier in the candidate itself. - ['max-[theme(spacing.4/50)]:flex', 'max-[theme(--spacing-4/50)]:flex'], + ['max-[theme(spacing.4/50)]:flex', 'max-[theme(spacing.4/50)]:flex'], // `theme(…)` calls in another CSS function is replaced correctly. // Additionally we remove unnecessary whitespace. - ['grid-cols-[min(50%_,_theme(spacing.80))_auto]', 'grid-cols-[min(50%,var(--spacing-80))_auto]'], + [ + 'grid-cols-[min(50%_,_theme(spacing.80))_auto]', + 'grid-cols-[min(50%,calc(var(--spacing)*80))_auto]', + ], // `theme(…)` calls valid in v3, but not in v4 should still be converted. ['[--foo:theme(transitionDuration.500)]', '[--foo:theme(transitionDuration.500)]'], @@ -110,51 +117,67 @@ test.each([ '[--foo:theme(colors.red.500/50/50)_var(--color-blue-200)]/50', ], ])('%s => %s', async (candidate, result) => { + let designSystem = await __unstable__loadDesignSystem( + css` + @import 'tailwindcss'; + `, + { + base: __dirname, + }, + ) + + expect(themeToVar(designSystem, {}, candidate)).toEqual(result) +}) + +test('extended space scale converts to var or calc', async () => { + let designSystem = await __unstable__loadDesignSystem( + css` + @import 'tailwindcss'; + @theme { + --spacing-2: 2px; + --spacing-miami: 0.875rem; + } + `, + { + base: __dirname, + }, + ) + expect(themeToVar(designSystem, {}, '[--value:theme(spacing.1)]')).toEqual( + '[--value:calc(var(--spacing)*1)]', + ) + expect(themeToVar(designSystem, {}, '[--value:theme(spacing.2)]')).toEqual( + '[--value:var(--spacing-2)]', + ) + expect(themeToVar(designSystem, {}, '[--value:theme(spacing.miami)]')).toEqual( + '[--value:var(--spacing-miami)]', + ) + // should error + expect(themeToVar(designSystem, {}, '[--value:theme(spacing.nyc)]')).toEqual( + '[--value:theme(spacing.nyc)]', + ) +}) + +test('custom space scale converts to var', async () => { let designSystem = await __unstable__loadDesignSystem( css` @import 'tailwindcss'; @theme { - --spacing-px: 1px; - --spacing-0: 0px; - --spacing-0_5: 0.125rem; + --spacing-*: initial; --spacing-1: 0.25rem; - --spacing-1_5: 0.375rem; --spacing-2: 0.5rem; - --spacing-2_5: 0.625rem; - --spacing-3: 0.75rem; - --spacing-3_5: 0.875rem; - --spacing-4: 1rem; - --spacing-5: 1.25rem; - --spacing-6: 1.5rem; - --spacing-7: 1.75rem; - --spacing-8: 2rem; - --spacing-9: 2.25rem; - --spacing-10: 2.5rem; - --spacing-11: 2.75rem; - --spacing-12: 3rem; - --spacing-14: 3.5rem; - --spacing-16: 4rem; - --spacing-20: 5rem; - --spacing-24: 6rem; - --spacing-28: 7rem; - --spacing-32: 8rem; - --spacing-36: 9rem; - --spacing-40: 10rem; - --spacing-44: 11rem; - --spacing-48: 12rem; - --spacing-52: 13rem; - --spacing-56: 14rem; - --spacing-60: 15rem; - --spacing-64: 16rem; - --spacing-72: 18rem; - --spacing-80: 20rem; - --spacing-96: 24rem; } `, { base: __dirname, }, ) - - expect(themeToVar(designSystem, {}, candidate)).toEqual(result) + expect(themeToVar(designSystem, {}, '[--value:theme(spacing.1)]')).toEqual( + '[--value:var(--spacing-1)]', + ) + expect(themeToVar(designSystem, {}, '[--value:theme(spacing.2)]')).toEqual( + '[--value:var(--spacing-2)]', + ) + expect(themeToVar(designSystem, {}, '[--value:theme(spacing.3)]')).toEqual( + '[--value:theme(spacing.3)]', + ) }) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts index 4934108e85d2..53da1d08a893 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts @@ -191,17 +191,29 @@ export function createConverter(designSystem: DesignSystem, { prettyPrint = fals } function pathToVariableName(path: string) { - let variable = `--${keyPathToCssProperty(toKeyPath(path))}` as const - if (!designSystem.theme.get([variable])) return null + let keyPath = toKeyPath(path) + let variable = `--${keyPathToCssProperty(keyPath)}` as const + if (designSystem.theme.get([variable])) return variable + if (keyPath[0] === 'spacing' && designSystem.theme.get(['--spacing'])) { + } - return variable + return null } function toVar(path: string, fallback?: string) { let variable = pathToVariableName(path) - if (!variable) return null + if (variable) return fallback ? `var(${variable}, ${fallback})` : `var(${variable})` + + let keyPath = toKeyPath(path) + if (keyPath[0] === 'spacing' && designSystem.theme.get(['--spacing'])) { + let multiplier = keyPath[1] + let num = Number(multiplier) + if (num < 0 || num % 0.25 !== 0 || String(num) !== multiplier) return null + + return 'calc(var(--spacing) * ' + multiplier + ')' + } - return fallback ? `var(${variable}, ${fallback})` : `var(${variable})` + return null } function toTheme(path: string, fallback?: string) { From bc722b1d4d2863da902f12e751020fc91e89628d Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Thu, 7 Nov 2024 15:20:53 +0100 Subject: [PATCH 2/9] Remove spacing values from JS theme that can be computed from the default scale --- integrations/upgrade/js-config.test.ts | 256 ++++++++++++++++++ .../src/migrate-js-config.ts | 45 ++- 2 files changed, 300 insertions(+), 1 deletion(-) diff --git a/integrations/upgrade/js-config.test.ts b/integrations/upgrade/js-config.test.ts index 398402139853..557b67478246 100644 --- a/integrations/upgrade/js-config.test.ts +++ b/integrations/upgrade/js-config.test.ts @@ -1415,4 +1415,260 @@ describe('border compatibility', () => { `) }, ) + + test( + 'migrates extended spacing keys', + { + fs: { + 'package.json': json` + { + "dependencies": { + "@tailwindcss/upgrade": "workspace:^" + } + } + `, + 'tailwind.config.ts': ts` + import { type Config } from 'tailwindcss' + + export default { + content: ['./src/**/*.html'], + theme: { + extend: { + spacing: { + 2: '0.5rem', + 4.5: '1.125rem', + 5.5: '1.375em', // Units are different from --spacing scale + 13: '3.25rem', + 100: '100px', + miami: '1337px', + }, + }, + }, + } satisfies Config + `, + 'src/input.css': css` + @tailwind base; + @tailwind components; + @tailwind utilities; + + .container { + width: theme(spacing.2); + width: theme(spacing[4.5]); + width: theme(spacing[5.5]); + width: theme(spacing[13]); + width: theme(spacing[100]); + width: theme(spacing.miami); + } + `, + 'src/index.html': html` +
+ `, + }, + }, + async ({ exec, fs }) => { + await exec('npx @tailwindcss/upgrade') + + expect(await fs.dumpFiles('src/**/*.{css,html}')).toMatchInlineSnapshot(` + " + --- src/index.html --- +
+ + --- src/input.css --- + @import 'tailwindcss'; + + @theme { + --spacing-100: 100px; + --spacing-5_5: 1.375em; + --spacing-miami: 1337px; + } + + /* + The default border color has changed to \`currentColor\` in Tailwind CSS v4, + so we've added these compatibility styles to make sure everything still + looks the same as it did with Tailwind CSS v3. + + If we ever want to remove these styles, we need to add an explicit border + color utility to any element that depends on these defaults. + */ + @layer base { + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + border-color: var(--color-gray-200, currentColor); + } + } + + /* + Form elements have a 1px border by default in Tailwind CSS v4, so we've + added these compatibility styles to make sure everything still looks the + same as it did with Tailwind CSS v3. + + If we ever want to remove these styles, we need to add \`border-0\` to + any form elements that shouldn't have a border. + */ + @layer base { + input:where(:not([type='button'], [type='reset'], [type='submit'])), + select, + textarea { + border-width: 0; + } + } + + .container { + width: calc(var(--spacing) * 2); + width: calc(var(--spacing) * 4.5); + width: var(--spacing-5_5); + width: calc(var(--spacing) * 13); + width: var(--spacing-100); + width: var(--spacing-miami); + } + " + `) + }, + ) + + test( + 'retains overwriting spacing scale', + { + fs: { + 'package.json': json` + { + "dependencies": { + "@tailwindcss/upgrade": "workspace:^" + } + } + `, + 'tailwind.config.ts': ts` + import { type Config } from 'tailwindcss' + + export default { + content: ['./src/**/*.html'], + theme: { + spacing: { + 2: '0.5rem', + 4.5: '1.125rem', + 5.5: '1.375em', + 13: '3.25rem', + 100: '100px', + miami: '1337px', + }, + }, + } satisfies Config + `, + 'src/input.css': css` + @tailwind base; + @tailwind components; + @tailwind utilities; + + .container { + width: theme(spacing.2); + width: theme(spacing[4.5]); + width: theme(spacing[5.5]); + width: theme(spacing[13]); + width: theme(spacing[100]); + width: theme(spacing.miami); + } + `, + 'src/index.html': html` +
+ `, + }, + }, + async ({ exec, fs }) => { + await exec('npx @tailwindcss/upgrade') + + expect(await fs.dumpFiles('src/**/*.{css,html}')).toMatchInlineSnapshot(` + " + --- src/index.html --- +
+ + --- src/input.css --- + @import 'tailwindcss'; + + @theme { + --spacing-*: initial; + --spacing-2: 0.5rem; + --spacing-13: 3.25rem; + --spacing-100: 100px; + --spacing-4_5: 1.125rem; + --spacing-5_5: 1.375em; + --spacing-miami: 1337px; + } + + /* + The default border color has changed to \`currentColor\` in Tailwind CSS v4, + so we've added these compatibility styles to make sure everything still + looks the same as it did with Tailwind CSS v3. + + If we ever want to remove these styles, we need to add an explicit border + color utility to any element that depends on these defaults. + */ + @layer base { + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + border-color: var(--color-gray-200, currentColor); + } + } + + /* + Form elements have a 1px border by default in Tailwind CSS v4, so we've + added these compatibility styles to make sure everything still looks the + same as it did with Tailwind CSS v3. + + If we ever want to remove these styles, we need to add \`border-0\` to + any form elements that shouldn't have a border. + */ + @layer base { + input:where(:not([type='button'], [type='reset'], [type='submit'])), + select, + textarea { + border-width: 0; + } + } + + .container { + width: var(--spacing-2); + width: var(--spacing-4_5); + width: var(--spacing-5_5); + width: var(--spacing-13); + width: var(--spacing-100); + width: var(--spacing-miami); + } + " + `) + }, + ) }) diff --git a/packages/@tailwindcss-upgrade/src/migrate-js-config.ts b/packages/@tailwindcss-upgrade/src/migrate-js-config.ts index be33b726bb4e..8d3d571cec1e 100644 --- a/packages/@tailwindcss-upgrade/src/migrate-js-config.ts +++ b/packages/@tailwindcss-upgrade/src/migrate-js-config.ts @@ -12,7 +12,7 @@ import { } from '../../tailwindcss/src/compat/apply-config-to-theme' import { keyframesToRules } from '../../tailwindcss/src/compat/apply-keyframes-to-theme' import { resolveConfig, type ConfigFile } from '../../tailwindcss/src/compat/config/resolve-config' -import type { ThemeConfig } from '../../tailwindcss/src/compat/config/types' +import type { ResolvedConfig, ThemeConfig } from '../../tailwindcss/src/compat/config/types' import { darkModePlugin } from '../../tailwindcss/src/compat/dark-mode' import type { DesignSystem } from '../../tailwindcss/src/design-system' import { escape } from '../../tailwindcss/src/utils/escape' @@ -101,6 +101,8 @@ async function migrateTheme( Array.from(replacedThemeKeys.entries()).map(([key]) => [key, false]), ) + removeUnnecessarySpacingKeys(designSystem, resolvedConfig, replacedThemeKeys) + let prevSectionKey = '' let css = '\n@tw-bucket theme {\n' css += `\n@theme {\n` @@ -317,3 +319,44 @@ function patternSourceFiles(source: { base: string; pattern: string }): string[] scanner.scan() return scanner.files } + +function removeUnnecessarySpacingKeys( + designSystem: DesignSystem, + resolvedConfig: ResolvedConfig, + replacedThemeKeys: Set, +) { + // We want to keep the spacing scale as-is if the user is overwriting + if (replacedThemeKeys.has('spacing')) return + + // Ensure we have a spacing multiplier + let spacingScale = designSystem.theme.get(['--spacing']) + if (!spacingScale) return + + let [spacingMultiplier, spacingUnit] = splitNumberAndUnit(spacingScale) + if (!spacingMultiplier || !spacingUnit) return + + if (spacingScale && !replacedThemeKeys.has('spacing')) { + for (let [key, value] of Object.entries(resolvedConfig.theme.spacing ?? {})) { + let [multiplier, unit] = splitNumberAndUnit(value as string) + if (multiplier === null) continue + + let num = Number(key) + if (num < 0 || num % 0.25 !== 0 || String(num) !== key) continue + + if (unit !== spacingUnit) continue + + if (parseFloat(multiplier) === num * parseFloat(spacingMultiplier)) { + delete resolvedConfig.theme.spacing[key] + designSystem.theme.clearNamespace(escape(`--spacing-${key.replaceAll('.', '_')}`), 0) + } + } + } +} + +function splitNumberAndUnit(value: string): [string, string] | [null, null] { + let match = value.match(/^([0-9.]+)(.*)$/) + if (!match) { + return [null, null] + } + return [match[1], match[2]] +} From b3d476a2e6781ae0b83483a1aa3004c2e07d3f8b Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Thu, 7 Nov 2024 15:30:03 +0100 Subject: [PATCH 3/9] Document behavior where we can't convert to calc --- .../src/template/codemods/theme-to-var.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts index 29bede1442c2..24bf2cd8d5d4 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts @@ -83,6 +83,8 @@ test.each([ // Variants, we can't use `var(…)` especially inside of `@media(…)`. We can // still upgrade the `theme(…)` to the modern syntax. + ['max-[theme(screens.lg)]:flex', 'max-[theme(--breakpoint-lg)]:flex'], + // There are no variables for `--spacing` multiples, so we can't convert this ['max-[theme(spacing.4)]:flex', 'max-[theme(spacing.4)]:flex'], // This test in itself doesn't make much sense. But we need to make sure From 0b58fe4780289e222c87b18d3b1b1eb6486cce5a Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Thu, 7 Nov 2024 15:40:34 +0100 Subject: [PATCH 4/9] Add change log --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 399e4c53d7c4..2e97ca973f76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - _Upgrade (experimental)_: Rename `drop-shadow` to `drop-shadow-sm` and `drop-shadow-sm` to `drop-shadow-xs` ([#14875](https://github.com/tailwindlabs/tailwindcss/pull/14875)) - _Upgrade (experimental)_: Rename `rounded` to `rounded-sm` and `rounded-sm` to `rounded-xs` ([#14875](https://github.com/tailwindlabs/tailwindcss/pull/14875)) - _Upgrade (experimental)_: Rename `blur` to `blur-sm` and `blur-sm` to `blur-xs` ([#14875](https://github.com/tailwindlabs/tailwindcss/pull/14875)) +- _Upgrade (experimental)_: Migrate `theme()` calls of `spacing` multiplies to use `calc()` where possible ([#14905](https://github.com/tailwindlabs/tailwindcss/pull/14905)) +- _Upgrade (experimental)_: Remove extending `spacing` theme keys that can be compute by the new `--spacing` multiplier ([#14905](https://github.com/tailwindlabs/tailwindcss/pull/14905)) ### Fixed From 499ab13b7551e42ce84b8f9c3d83ba01494ecbaf Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Thu, 7 Nov 2024 15:58:52 +0100 Subject: [PATCH 5/9] Remove comment --- .../src/template/codemods/theme-to-var.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts index 24bf2cd8d5d4..44dca3973603 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts @@ -153,7 +153,6 @@ test('extended space scale converts to var or calc', async () => { expect(themeToVar(designSystem, {}, '[--value:theme(spacing.miami)]')).toEqual( '[--value:var(--spacing-miami)]', ) - // should error expect(themeToVar(designSystem, {}, '[--value:theme(spacing.nyc)]')).toEqual( '[--value:theme(spacing.nyc)]', ) From 9c630e97f28d5a7b1a8d4dedf1bf05c5ef7e6d3d Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Thu, 7 Nov 2024 16:13:31 +0100 Subject: [PATCH 6/9] Fixes --- .../@tailwindcss-upgrade/src/migrate-js-config.ts | 7 +++---- .../src/template/codemods/theme-to-var.ts | 13 +++++-------- packages/tailwindcss/src/utilities.ts | 6 ++---- packages/tailwindcss/src/utils/infer-data-type.ts | 8 ++++++++ 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/@tailwindcss-upgrade/src/migrate-js-config.ts b/packages/@tailwindcss-upgrade/src/migrate-js-config.ts index 8d3d571cec1e..a5ed0e52a636 100644 --- a/packages/@tailwindcss-upgrade/src/migrate-js-config.ts +++ b/packages/@tailwindcss-upgrade/src/migrate-js-config.ts @@ -16,6 +16,7 @@ import type { ResolvedConfig, ThemeConfig } from '../../tailwindcss/src/compat/c import { darkModePlugin } from '../../tailwindcss/src/compat/dark-mode' import type { DesignSystem } from '../../tailwindcss/src/design-system' import { escape } from '../../tailwindcss/src/utils/escape' +import { isValidSpacingMultiplier } from '../../tailwindcss/src/utils/infer-data-type' import { findStaticPlugins, type StaticPluginOptions } from './utils/extract-static-plugins' import { info } from './utils/renderer' @@ -340,12 +341,10 @@ function removeUnnecessarySpacingKeys( let [multiplier, unit] = splitNumberAndUnit(value as string) if (multiplier === null) continue - let num = Number(key) - if (num < 0 || num % 0.25 !== 0 || String(num) !== key) continue - + if (!isValidSpacingMultiplier(multiplier)) continue if (unit !== spacingUnit) continue - if (parseFloat(multiplier) === num * parseFloat(spacingMultiplier)) { + if (parseFloat(multiplier) === parseFloat(multiplier) * parseFloat(spacingMultiplier)) { delete resolvedConfig.theme.spacing[key] designSystem.theme.clearNamespace(escape(`--spacing-${key.replaceAll('.', '_')}`), 0) } diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts index 53da1d08a893..21ddf7dcd1c7 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts @@ -7,6 +7,7 @@ import { } from '../../../../tailwindcss/src/candidate' import { keyPathToCssProperty } from '../../../../tailwindcss/src/compat/apply-config-to-theme' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { isValidSpacingMultiplier } from '../../../../tailwindcss/src/utils/infer-data-type' import { segment } from '../../../../tailwindcss/src/utils/segment' import { toKeyPath } from '../../../../tailwindcss/src/utils/to-key-path' import * as ValueParser from '../../../../tailwindcss/src/value-parser' @@ -191,13 +192,10 @@ export function createConverter(designSystem: DesignSystem, { prettyPrint = fals } function pathToVariableName(path: string) { - let keyPath = toKeyPath(path) - let variable = `--${keyPathToCssProperty(keyPath)}` as const - if (designSystem.theme.get([variable])) return variable - if (keyPath[0] === 'spacing' && designSystem.theme.get(['--spacing'])) { - } + let variable = `--${keyPathToCssProperty(toKeyPath(path))}` as const + if (!designSystem.theme.get([variable])) return null - return null + return variable } function toVar(path: string, fallback?: string) { @@ -207,8 +205,7 @@ export function createConverter(designSystem: DesignSystem, { prettyPrint = fals let keyPath = toKeyPath(path) if (keyPath[0] === 'spacing' && designSystem.theme.get(['--spacing'])) { let multiplier = keyPath[1] - let num = Number(multiplier) - if (num < 0 || num % 0.25 !== 0 || String(num) !== multiplier) return null + if (!isValidSpacingMultiplier(multiplier)) return null return 'calc(var(--spacing) * ' + multiplier + ')' } diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts index f6b018fa0a41..b557f47daeb6 100644 --- a/packages/tailwindcss/src/utilities.ts +++ b/packages/tailwindcss/src/utilities.ts @@ -2,7 +2,7 @@ import { atRoot, atRule, decl, styleRule, type AstNode } from './ast' import type { Candidate, CandidateModifier, NamedUtilityValue } from './candidate' import type { Theme, ThemeKey } from './theme' import { DefaultMap } from './utils/default-map' -import { inferDataType, isPositiveInteger } from './utils/infer-data-type' +import { inferDataType, isPositiveInteger, isValidSpacingMultiplier } from './utils/infer-data-type' import { replaceShadowColors } from './utils/replace-shadow-colors' import { segment } from './utils/segment' @@ -397,9 +397,7 @@ export function createUtilities(theme: Theme) { handleBareValue: ({ value }) => { let multiplier = theme.resolve(null, ['--spacing']) if (!multiplier) return null - - let num = Number(value) - if (num < 0 || num % 0.25 !== 0 || String(num) !== value) return null + if (!isValidSpacingMultiplier(value)) return null return `calc(${multiplier} * ${value})` }, diff --git a/packages/tailwindcss/src/utils/infer-data-type.ts b/packages/tailwindcss/src/utils/infer-data-type.ts index 53abed449c5d..6aa34fbe1d7e 100644 --- a/packages/tailwindcss/src/utils/infer-data-type.ts +++ b/packages/tailwindcss/src/utils/infer-data-type.ts @@ -328,3 +328,11 @@ export function isPositiveInteger(value: any) { let num = Number(value) return Number.isInteger(num) && num >= 0 && String(num) === String(value) } + +/** + * Returns true if the value is either a positive whole number or a multiple of 0.25. + */ +export function isValidSpacingMultiplier(value: any) { + let num = Number(value) + return num >= 0 && num % 0.25 === 0 && String(num) === String(value) +} From cb3f28c0097c3b5d35a9cebe86549b1cccfe84f6 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Thu, 7 Nov 2024 16:25:59 +0100 Subject: [PATCH 7/9] Fixes --- packages/@tailwindcss-upgrade/src/migrate-js-config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@tailwindcss-upgrade/src/migrate-js-config.ts b/packages/@tailwindcss-upgrade/src/migrate-js-config.ts index a5ed0e52a636..63fc094db8fb 100644 --- a/packages/@tailwindcss-upgrade/src/migrate-js-config.ts +++ b/packages/@tailwindcss-upgrade/src/migrate-js-config.ts @@ -341,10 +341,10 @@ function removeUnnecessarySpacingKeys( let [multiplier, unit] = splitNumberAndUnit(value as string) if (multiplier === null) continue - if (!isValidSpacingMultiplier(multiplier)) continue + if (!isValidSpacingMultiplier(key)) continue if (unit !== spacingUnit) continue - if (parseFloat(multiplier) === parseFloat(multiplier) * parseFloat(spacingMultiplier)) { + if (parseFloat(multiplier) === Number(key) * parseFloat(spacingMultiplier)) { delete resolvedConfig.theme.spacing[key] designSystem.theme.clearNamespace(escape(`--spacing-${key.replaceAll('.', '_')}`), 0) } From a18b3b52d53ca4c99eea989e4b024f2dbf7d23d7 Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Thu, 7 Nov 2024 14:04:37 -0500 Subject: [PATCH 8/9] Update CHANGELOG.md --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e97ca973f76..f16bb05b96ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,8 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - _Upgrade (experimental)_: Rename `drop-shadow` to `drop-shadow-sm` and `drop-shadow-sm` to `drop-shadow-xs` ([#14875](https://github.com/tailwindlabs/tailwindcss/pull/14875)) - _Upgrade (experimental)_: Rename `rounded` to `rounded-sm` and `rounded-sm` to `rounded-xs` ([#14875](https://github.com/tailwindlabs/tailwindcss/pull/14875)) - _Upgrade (experimental)_: Rename `blur` to `blur-sm` and `blur-sm` to `blur-xs` ([#14875](https://github.com/tailwindlabs/tailwindcss/pull/14875)) -- _Upgrade (experimental)_: Migrate `theme()` calls of `spacing` multiplies to use `calc()` where possible ([#14905](https://github.com/tailwindlabs/tailwindcss/pull/14905)) -- _Upgrade (experimental)_: Remove extending `spacing` theme keys that can be compute by the new `--spacing` multiplier ([#14905](https://github.com/tailwindlabs/tailwindcss/pull/14905)) +- _Upgrade (experimental)_: Migrate `theme()` usage and JS config files to use the new `--spacing` multiplier where possible ([#14905](https://github.com/tailwindlabs/tailwindcss/pull/14905)) ### Fixed From 3853a7c7fe66854b24786b2f07ae6c54d19e7e94 Mon Sep 17 00:00:00 2001 From: Adam Wathan <4323180+adamwathan@users.noreply.github.com> Date: Thu, 7 Nov 2024 14:13:53 -0500 Subject: [PATCH 9/9] Fix test --- .../src/template/codemods/theme-to-var.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts index 1832aa184ae5..0e6850672586 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts @@ -10,7 +10,7 @@ test.each([ // Handle special cases around `.1` in the `theme(…)` ['[--value:theme(spacing.1)]', '[--value:calc(var(--spacing)*1)]'], - ['[--value:theme(fontSize.xs.1.lineHeight)]', '[--value:var(--text-size-xs--line-height)]'], + ['[--value:theme(fontSize.xs.1.lineHeight)]', '[--value:var(--text-xs--line-height)]'], ['[--value:theme(spacing[1.25])]', '[--value:calc(var(--spacing)*1.25)]'], // Should not convert invalid spacing values to calc