Skip to content

Commit da86594

Browse files
committed
Sort classes using position of first matching rule (#11504)
* Refactor * Sort based on first occurence of a candidate This primarily affects components and utilities which contain multiple matched classes * Simplify * Update changelog * Update
1 parent 1dfd113 commit da86594

File tree

3 files changed

+44
-1
lines changed

3 files changed

+44
-1
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
- Ensure `repeating-conic-gradient` is detected as an image ([#11180](https://github.com/tailwindlabs/tailwindcss/pull/11180))
1616
- Move unknown pseudo-elements outside of `:is` by default ([#11345](https://github.com/tailwindlabs/tailwindcss/pull/11345))
1717
- Escape animation names when prefixes contain special characters ([#11470](https://github.com/tailwindlabs/tailwindcss/pull/11470))
18+
- Sort classes using position of first matching rule ([#11504](https://github.com/tailwindlabs/tailwindcss/pull/11504))
1819

1920
### Added
2021

src/lib/setupContextUtils.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -949,7 +949,11 @@ function registerPlugins(plugins, context) {
949949
let idx = BigInt(parasiteUtilities.length)
950950

951951
for (const [, rule] of rules) {
952-
sortedClassNames.set(rule.raws.tailwind.candidate, idx++)
952+
let candidate = rule.raws.tailwind.candidate
953+
954+
// When multiple rules match a candidate
955+
// always take the position of the first one
956+
sortedClassNames.set(candidate, sortedClassNames.get(candidate) ?? idx++)
953957
}
954958

955959
return classes.map((className) => {

tests/getSortOrder.test.js

+38
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,41 @@ crosscheck(() => {
143143
}
144144
})
145145
})
146+
147+
it('sorts based on first occurence of a candidate / rule', () => {
148+
let classes = [
149+
['foo-1 foo', 'foo foo-1'],
150+
['bar', 'bar'],
151+
['foo-1 foo', 'foo foo-1'],
152+
]
153+
154+
let config = {
155+
theme: {},
156+
plugins: [
157+
function ({ addComponents }) {
158+
addComponents({
159+
'.foo': { display: 'block' },
160+
'.foo-1': { display: 'block' },
161+
'.bar': { display: 'block' },
162+
163+
// This rule matches both the candidate `foo` and `bar`
164+
// But when sorting `foo` — we've already got a
165+
// position for `foo` so we should use it
166+
'.bar .foo': { display: 'block' },
167+
})
168+
},
169+
],
170+
}
171+
172+
// Same context, different class lists
173+
let context = createContext(resolveConfig(config))
174+
for (const [input, output] of classes) {
175+
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
176+
}
177+
178+
// Different context, different class lists
179+
for (const [input, output] of classes) {
180+
context = createContext(resolveConfig(config))
181+
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
182+
}
183+
})

0 commit comments

Comments
 (0)