diff --git a/CHANGELOG.md b/CHANGELOG.md index 72065f316..e2d1e5c20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ensure transitions between `inset-shadow-none` and other inset shadows work correctly ([#20208](https://github.com/tailwindlabs/tailwindcss/pull/20208)) - Ensure explicitly referenced `@source` directories are scanned even when ignored by git ([#20214](https://github.com/tailwindlabs/tailwindcss/pull/20214)) - Ensure `@source` globs ending in `**/*` preserve dynamic path segments to avoid scanning too many files ([#20217](https://github.com/tailwindlabs/tailwindcss/pull/20217)) +- Canonicalization: don't fold `calc(…)` divisions when the result would require high precision (e.g. `w-[calc(100%/3.5)]` → `w-[calc(100%/3.5)]`, not `w-[28.571428571428573%]`) ([#20221](https://github.com/tailwindlabs/tailwindcss/pull/20221)) ### Changed diff --git a/packages/tailwindcss/src/canonicalize-candidates.test.ts b/packages/tailwindcss/src/canonicalize-candidates.test.ts index c78d40e76..c01ee78e1 100644 --- a/packages/tailwindcss/src/canonicalize-candidates.test.ts +++ b/packages/tailwindcss/src/canonicalize-candidates.test.ts @@ -1244,6 +1244,17 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s', await expectCanonicalization(input, candidate, expected) }) + + // https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1591 + test.each([ + ['w-[calc(100%/3.5)]', 'w-[calc(100%/3.5)]'], // Not w-[28.571428571428573%] + ])(testName, { timeout }, async (candidate, expected) => { + let input = css` + @import 'tailwindcss'; + ` + + await expectCanonicalization(input, candidate, expected) + }) }) describe('theme to var', () => { diff --git a/packages/tailwindcss/src/constant-fold-declaration.test.ts b/packages/tailwindcss/src/constant-fold-declaration.test.ts index c48b64b34..5a0c867b6 100644 --- a/packages/tailwindcss/src/constant-fold-declaration.test.ts +++ b/packages/tailwindcss/src/constant-fold-declaration.test.ts @@ -127,3 +127,11 @@ it.each([ it('should not constant fold when dividing by `0`', () => { expect(constantFoldDeclaration('calc(123rem / 0)')).toBe('calc(123rem / 0)') }) + +it('should not constant fold when a computation has a high-precision result', () => { + expect(constantFoldDeclaration('calc(100% / 3.5)')).toBe('calc(100% / 3.5)') +}) + +it('should constant fold division results with floating-point error after scaling', () => { + expect(constantFoldDeclaration('calc(29% / 100)')).toBe('0.29%') +}) diff --git a/packages/tailwindcss/src/constant-fold-declaration.ts b/packages/tailwindcss/src/constant-fold-declaration.ts index 7c54ae414..34a746d8e 100644 --- a/packages/tailwindcss/src/constant-fold-declaration.ts +++ b/packages/tailwindcss/src/constant-fold-declaration.ts @@ -286,8 +286,19 @@ export function constantFoldDeclarationAst( ((lhs[1] === null && rhs[1] === null) || // Unitless / Unitless, e.g.: `8 / 2` (lhs[1] !== null && rhs[1] === null)) // Unit / Unitless, e.g.: `1rem / 2` ) { + let computed = lhs[0] / rhs[0] + + // Only fold with .xx precision, anything beyond that might be a bit too much. + // + // E.g. 100% / 3.5 = 28.571428571428573%, which is correct but not + // as user friendly. Especially when going from + // `w-[calc(100%/3.5)]` → `w-[28.571428571428573%]` + if (Math.round(computed * 100) / 100 !== computed) { + break + } + folded = true - return WalkAction.ReplaceSkip(ValueParser.word(`${lhs[0] / rhs[0]}${lhs[1] ?? ''}`)) + return WalkAction.ReplaceSkip(ValueParser.word(`${computed}${lhs[1] ?? ''}`)) } break }