Fix sorting numeric utilities when they have different magnitudes (#16414)
When sorting utilities it was possible for a utility like `duration-700` to come before `duration-75` or `duration-1000` to come before `duration-150`. This PR fixes this behavior by reading the full "magnitude" of each numbers before we compare them.
This commit is contained in:
parent
272c6dfa98
commit
121cf6bd80
3 changed files with 59 additions and 9 deletions
|
|
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Fixed
|
||||
|
||||
- Export `tailwindcss/lib/util/flattenColorPalette.js` for backward compatibility ([#16411](https://github.com/tailwindlabs/tailwindcss/pull/16411))
|
||||
- Fix sorting numeric utilities when they have different magnitudes ([#16414](https://github.com/tailwindlabs/tailwindcss/pull/16414))
|
||||
|
||||
## [4.0.6] - 2025-02-10
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,12 @@ it.each([
|
|||
['2', '1', GREATER],
|
||||
['1', '10', LESS],
|
||||
['10', '1', GREATER],
|
||||
|
||||
// Numbers of different lengths
|
||||
['75', '700', LESS],
|
||||
['700', '75', GREATER],
|
||||
['75', '770', LESS],
|
||||
['770', '75', GREATER],
|
||||
])('should compare "%s" with "%s" as "%d"', (a, b, expected) => {
|
||||
expect(Math.sign(compare(a, b))).toBe(expected)
|
||||
})
|
||||
|
|
@ -124,3 +130,39 @@ it('should sort strings with multiple numbers consistently using the `compare` f
|
|||
]
|
||||
`)
|
||||
})
|
||||
|
||||
it('sort is stable', () => {
|
||||
// Heap's algorithm for permutations
|
||||
function* permutations<T>(input: T[]) {
|
||||
let pos = 1
|
||||
let stack = input.map(() => 0)
|
||||
|
||||
yield input.slice()
|
||||
|
||||
while (pos < input.length) {
|
||||
if (stack[pos] < pos) {
|
||||
let k = pos % 2 == 0 ? 0 : stack[pos]
|
||||
;[input[k], input[pos]] = [input[pos], input[k]]
|
||||
yield input.slice()
|
||||
++stack[pos]
|
||||
pos = 1
|
||||
} else {
|
||||
stack[pos] = 0
|
||||
++pos
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let classes = ['duration-initial', 'duration-75', 'duration-150', 'duration-700', 'duration-1000']
|
||||
|
||||
for (let permutation of permutations(classes)) {
|
||||
let sorted = [...permutation].sort(compare)
|
||||
expect(sorted).toEqual([
|
||||
'duration-75',
|
||||
'duration-150',
|
||||
'duration-700',
|
||||
'duration-1000',
|
||||
'duration-initial',
|
||||
])
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -14,9 +14,6 @@ export function compare(a: string, z: string) {
|
|||
let aCode = a.charCodeAt(i)
|
||||
let zCode = z.charCodeAt(i)
|
||||
|
||||
// Continue if the characters are the same
|
||||
if (aCode === zCode) continue
|
||||
|
||||
// If both are numbers, compare them as numbers instead of strings.
|
||||
if (aCode >= ZERO && aCode <= NINE && zCode >= ZERO && zCode <= NINE) {
|
||||
let aStart = i
|
||||
|
|
@ -35,14 +32,24 @@ export function compare(a: string, z: string) {
|
|||
let aNumber = a.slice(aStart, aEnd)
|
||||
let zNumber = z.slice(zStart, zEnd)
|
||||
|
||||
return (
|
||||
Number(aNumber) - Number(zNumber) ||
|
||||
// Fallback case if numbers are the same but the string representation
|
||||
// is not. Fallback to string sorting. E.g.: `0123` vs `123`
|
||||
(aNumber < zNumber ? -1 : 1)
|
||||
)
|
||||
let diff = Number(aNumber) - Number(zNumber)
|
||||
if (diff) return diff
|
||||
|
||||
// Fallback case if numbers are the same but the string representation
|
||||
// is not. Fallback to string sorting. E.g.: `0123` vs `123`
|
||||
if (aNumber < zNumber) return -1
|
||||
if (aNumber > zNumber) return 1
|
||||
|
||||
// Continue with the next character otherwise short strings will appear
|
||||
// after long ones when containing numbers. E.g.:
|
||||
// - bg-red-500/70
|
||||
// - bg-red-500
|
||||
continue
|
||||
}
|
||||
|
||||
// Continue if the characters are the same
|
||||
if (aCode === zCode) continue
|
||||
|
||||
// Otherwise, compare them as strings
|
||||
return aCode - zCode
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue