Improve percentage canonicalization (#19072)

This PR improves the canonicalization of percentage values such that
`[.1]`, `[.10]`, `[10%]` and `[10.0%]` are all treated as the same
value.

Right now we're only focusing on percentages. We can likely do this for
all numbers, but I'm a little afraid of places where you can have
multiple numbers separated by multiple dots (think SVGs).

## Test plan

1. Added more tests to cover the new cases.
2. Tested it in a local test project, where you can see the
normalization in action.

<img width="1383" height="117" alt="image"
src="https://github.com/user-attachments/assets/03d99e3a-4404-437b-b458-58f7e8ce60da"
/>
This commit is contained in:
Robin Malfait 2025-10-07 15:47:22 +02:00 committed by GitHub
parent efe084b7e7
commit 0c8d881f0e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 22 additions and 1 deletions

View File

@ -254,6 +254,10 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s',
['[color:var(--color-red-500)]/[25%]', 'text-red-500/25'],
['[color:var(--color-red-500)]/[100%]', 'text-red-500'],
['[color:var(--color-red-500)]/100', 'text-red-500'],
['[color:var(--color-red-500)]/[10%]', 'text-red-500/10'],
['[color:var(--color-red-500)]/[10.0%]', 'text-red-500/10'],
['[color:var(--color-red-500)]/[.1]', 'text-red-500/10'],
['[color:var(--color-red-500)]/[.10]', 'text-red-500/10'],
// No need for `/50` because that's already encoded in the `--color-primary`
// value
['[color:oklch(62.3%_0.214_259.815)]/50', 'text-primary'],

View File

@ -1469,7 +1469,11 @@ function optimizeModifier(designSystem: DesignSystem, candidate: Candidate): Can
{
let newModifier: NamedUtilityValue = {
kind: 'named',
value: modifier.value.endsWith('%') ? modifier.value.slice(0, -1) : modifier.value,
value: modifier.value.endsWith('%')
? modifier.value.includes('.')
? `${Number(modifier.value.slice(0, -1))}`
: modifier.value.slice(0, -1)
: modifier.value,
fraction: null,
}

View File

@ -9,6 +9,8 @@ import { dimensions } from './utils/dimensions'
import { isValidSpacingMultiplier } from './utils/infer-data-type'
import * as ValueParser from './value-parser'
const FLOATING_POINT_PERCENTAGE = /\d*\.\d+(?:[eE][+-]?\d+)?%/g
// Given a utility, compute a signature that represents the utility. The
// signature will be a normalised form of the generated CSS for the utility, or
// a unique symbol if the utility is not valid. The class in the selector will
@ -59,6 +61,17 @@ export const computeUtilitySignature = new DefaultMap<
if (node.value === undefined || node.property === '--tw-sort') {
replaceWith([])
}
// Normalize percentages by removing unnecessary dots and zeros.
//
// E.g.: `50.0%` → `50%`
else if (node.value.includes('%')) {
FLOATING_POINT_PERCENTAGE.lastIndex = 0
node.value = node.value.replaceAll(
FLOATING_POINT_PERCENTAGE,
(match) => `${Number(match.slice(0, -1))}%`,
)
}
}
// Replace special nodes with its children