Add canonicalizations for tracking-* utilities (#19827)
This PR adds support for canonicalizations for `tracking-*` utilities. This one is a bit of a funny one, if you take a look at the linked issue, there is a beautiful table: | Utility Name | Value | Arbitrary Value | Throws Suggestion | | - | -: | - | - | | tracking-tighter | -0.05em | tracking-[-0.05em] | ✗ | | tracking-tight | -0.025em | tracking-[-0.025em] | ✗ | | tracking-normal | 0em | tracking-[0em] | ✗ | | tracking-wide | 0.025em | tracking-[0.025em] | ✗ | | tracking-wider | 0.05em | tracking-[0.05em] | ✗ | | tracking-widest | 0.1em | tracking-[0.1em] | ✓ | It doesn't really make sense to _why_ only the `tracking-widest` one is properly suggested here. Until you look a little bit closer. Turns out that `-tracking-tighter` is equivalent to `tracking-wider`, `-tracking-tight` is equivalent to `tracking-wide` and so on. The way the canonicalization works internally is by generating a signature for a given utility class. If two utilities have the exact same signature, we can consider them the same. In this case `tracking-widest` and `tracking-[0.1em]` have the same signature. One of the rules we have internally is that if we find more than one replacement utility then we don't really know what to do, so we bail. Because if you get `foo` or `bar`, which one do you pick? If we refer to this above table again, the moment we want to canonicalize the `tracking-[-0.05em]` we get two suggestions: `tracking-tighter` and `-tracking-wider`, since we don't know what to do, we bail and we don't suggest anything. So the reason that `tracking-widest` _was_ suggested is just because we don't have a `-tracking-tightest`. How do we fix this? Well, since we have `tracking-*` and `-tracking-*` utilities, I wanted to deprecate the `-tracking-*` ones for named utilities (where the values come from your theme) because that doesn't really make sense. However, we have this exact pattern documented here: https://tailwindcss.com/docs/letter-spacing#using-negative-values Which means that I can't just deprecate those utilities. <img width="723" height="511" alt="image" src="https://github.com/user-attachments/assets/164b659b-abe9-4f6e-a176-701dd7ea505a" /> Instead, I added a different rule which says that if you get multiple possible replacements, then we prefer the "positive" one, the one without the `-`. Also added some additional checks to make sure that if you get `foo`, `-bar`, `baz`, that we also bail because we know that we should prefer `foo` or `baz` over `-bar`, but we don't know if we should pick `foo` or `baz`... This additional rule does solve the original issue, and we already prefer possible values over negative values in other places (related to bare values). Fixes: https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1558 ## Test plan 1. Existing tests pass 2. Added regression tests to make sure that the table from above _does_ get canonicalized correctly into the expected values.
This commit is contained in:
parent
d596b0c43d
commit
7482d47a54
3 changed files with 84 additions and 10 deletions
|
|
@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- _Experimental_: Add `@container-size` utility ([#18901](https://github.com/tailwindlabs/tailwindcss/pull/18901))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Improve canonicalizations for `tracking-*` utilities ([#19827](https://github.com/tailwindlabs/tailwindcss/pull/19827))
|
||||
|
||||
## [4.2.2] - 2026-03-18
|
||||
|
||||
### Fixed
|
||||
|
|
|
|||
|
|
@ -1120,6 +1120,40 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s',
|
|||
await expectCombinedCanonicalization(input, candidates.trim(), expected)
|
||||
})
|
||||
})
|
||||
|
||||
// https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1558
|
||||
test.each([
|
||||
['tracking-[-0.05em]', 'tracking-tighter'],
|
||||
['tracking-[-0.025em]', 'tracking-tight'],
|
||||
['tracking-[0em]', 'tracking-normal'],
|
||||
['tracking-[0.025em]', 'tracking-wide'],
|
||||
['tracking-[0.05em]', 'tracking-wider'],
|
||||
['tracking-[0.1em]', 'tracking-widest'],
|
||||
|
||||
// Negative values that don't make sense
|
||||
// See: https://tailwindcss.com/docs/letter-spacing#using-negative-values
|
||||
['-tracking-tighter', 'tracking-wider'],
|
||||
['-tracking-tight', 'tracking-wide'],
|
||||
['-tracking-normal', 'tracking-normal'],
|
||||
['-tracking-wide', 'tracking-tight'],
|
||||
['-tracking-wider', 'tracking-tighter'],
|
||||
])(testName, { timeout }, async (candidate, expected) => {
|
||||
await expectCanonicalization(
|
||||
css`
|
||||
@import 'tailwindcss';
|
||||
@theme {
|
||||
--tracking-tighter: -0.05em;
|
||||
--tracking-tight: -0.025em;
|
||||
--tracking-normal: 0em;
|
||||
--tracking-wide: 0.025em;
|
||||
--tracking-wider: 0.05em;
|
||||
--tracking-widest: 0.1em;
|
||||
}
|
||||
`,
|
||||
candidate,
|
||||
expected,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('theme to var', () => {
|
||||
|
|
|
|||
|
|
@ -1128,11 +1128,29 @@ function arbitraryUtilities(candidate: Candidate, options: InternalCanonicalizeO
|
|||
// Find a corresponding utility for the same signature
|
||||
let replacements = utilities.get(targetSignature)
|
||||
|
||||
// Multiple utilities can map to the same signature. Not sure how to migrate
|
||||
// this one so let's just skip it for now.
|
||||
//
|
||||
// TODO: Do we just migrate to the first one?
|
||||
if (replacements.length > 1) return
|
||||
// Multiple utilities can map to the same signature.
|
||||
if (replacements.length > 1) {
|
||||
// Prefer positive values over negative values
|
||||
let maybeReplacement: string | undefined = undefined
|
||||
for (let replacement of replacements) {
|
||||
if (replacement[0] === '-') continue // Skip negative values
|
||||
|
||||
// If multiple non-negative replacements exists then we are unsure
|
||||
// what to do, so let's bail.
|
||||
if (maybeReplacement) return
|
||||
|
||||
// Consider this replacement
|
||||
maybeReplacement = replacement
|
||||
}
|
||||
|
||||
if (maybeReplacement) {
|
||||
for (let replacementCandidate of parseCandidate(designSystem, maybeReplacement)) {
|
||||
yield replacementCandidate
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If we didn't find any replacement utilities, let's try to strip the
|
||||
// modifier and find a replacement then. If we do, we can try to re-add the
|
||||
|
|
@ -1353,11 +1371,29 @@ function bareValueUtilities(candidate: Candidate, options: InternalCanonicalizeO
|
|||
// Find a corresponding utility for the same signature
|
||||
let replacements = utilities.get(targetSignature)
|
||||
|
||||
// Multiple utilities can map to the same signature. Not sure how to migrate
|
||||
// this one so let's just skip it for now.
|
||||
//
|
||||
// TODO: Do we just migrate to the first one?
|
||||
if (replacements.length > 1) return
|
||||
// Multiple utilities can map to the same signature.
|
||||
if (replacements.length > 1) {
|
||||
// Prefer positive values over negative values
|
||||
let maybeReplacement: string | undefined = undefined
|
||||
for (let replacement of replacements) {
|
||||
if (replacement[0] === '-') continue // Skip negative values
|
||||
|
||||
// If multiple non-negative replacements exists then we are unsure
|
||||
// what to do, so let's bail.
|
||||
if (maybeReplacement) return
|
||||
|
||||
// Consider this replacement
|
||||
maybeReplacement = replacement
|
||||
}
|
||||
|
||||
if (maybeReplacement) {
|
||||
for (let replacementCandidate of parseCandidate(designSystem, maybeReplacement)) {
|
||||
yield replacementCandidate
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If we didn't find any replacement utilities, let's try to strip the
|
||||
// modifier and find a replacement then. If we do, we can try to re-add the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue