Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
42 changes: 42 additions & 0 deletions packages/tailwindcss/src/utils/compare.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
Expand Down Expand Up @@ -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',
])
}
})
25 changes: 16 additions & 9 deletions packages/tailwindcss/src/utils/compare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
Expand Down