diff --git a/CHANGELOG.md b/CHANGELOG.md index 41088563e195..fe2c078472db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ensure `repeating-conic-gradient` is detected as an image ([#11180](https://github.com/tailwindlabs/tailwindcss/pull/11180)) - Move unknown pseudo-elements outside of `:is` by default ([#11345](https://github.com/tailwindlabs/tailwindcss/pull/11345)) - Escape animation names when prefixes contain special characters ([#11470](https://github.com/tailwindlabs/tailwindcss/pull/11470)) +- Sort classes using position of first matching rule ([#11504](https://github.com/tailwindlabs/tailwindcss/pull/11504)) ### Added diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index 0749da68dbdc..6564eb6b881a 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -949,7 +949,11 @@ function registerPlugins(plugins, context) { let idx = BigInt(parasiteUtilities.length) for (const [, rule] of rules) { - sortedClassNames.set(rule.raws.tailwind.candidate, idx++) + let candidate = rule.raws.tailwind.candidate + + // When multiple rules match a candidate + // always take the position of the first one + sortedClassNames.set(candidate, sortedClassNames.get(candidate) ?? idx++) } return classes.map((className) => { diff --git a/tests/getSortOrder.test.js b/tests/getSortOrder.test.js index 4526c4874f69..1916e265f72a 100644 --- a/tests/getSortOrder.test.js +++ b/tests/getSortOrder.test.js @@ -143,3 +143,41 @@ crosscheck(() => { } }) }) + +it('sorts based on first occurence of a candidate / rule', () => { + let classes = [ + ['foo-1 foo', 'foo foo-1'], + ['bar', 'bar'], + ['foo-1 foo', 'foo foo-1'], + ] + + let config = { + theme: {}, + plugins: [ + function ({ addComponents }) { + addComponents({ + '.foo': { display: 'block' }, + '.foo-1': { display: 'block' }, + '.bar': { display: 'block' }, + + // This rule matches both the candidate `foo` and `bar` + // But when sorting `foo` — we've already got a + // position for `foo` so we should use it + '.bar .foo': { display: 'block' }, + }) + }, + ], + } + + // Same context, different class lists + let context = createContext(resolveConfig(config)) + for (const [input, output] of classes) { + expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output) + } + + // Different context, different class lists + for (const [input, output] of classes) { + context = createContext(resolveConfig(config)) + expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output) + } +})