From 240a0addd30e5807960247e5c919c36a26b84b61 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Tue, 13 Feb 2024 14:08:43 -0500 Subject: [PATCH] Sort arbitrary properties alphabetically across multiple class lists (#12911) * Sort arbitrary properties alphabetically across multiple files * Update test --- src/lib/generateRules.js | 2 +- src/lib/offsets.js | 63 ++++++++++++++++++++++++++++++++++++-- tests/getSortOrder.test.js | 23 ++++++++++++++ tests/variants.test.js | 34 ++++++++++++-------- 4 files changed, 107 insertions(+), 15 deletions(-) diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index 05bdd784e95e..e8e5574b5233 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -512,7 +512,7 @@ function extractArbitraryProperty(classCandidate, context) { return null } - let sort = context.offsets.arbitraryProperty() + let sort = context.offsets.arbitraryProperty(classCandidate) return [ [ diff --git a/src/lib/offsets.js b/src/lib/offsets.js index a43ebe4f36b8..3e9f194b1254 100644 --- a/src/lib/offsets.js +++ b/src/lib/offsets.js @@ -23,7 +23,9 @@ import { remapBitfield } from './remap-bitfield.js' * @property {bigint} arbitrary 0n if false, 1n if true * @property {bigint} variants Dynamic size. 1 bit per registered variant. 0n means no variants * @property {bigint} parallelIndex Rule index for the parallel variant. 0 if not applicable. - * @property {bigint} index Index of the rule / utility in it's given *parent* layer. Monotonically increasing. + * @property {bigint} index Index of the rule / utility in its given *parent* layer. Monotonically increasing. + * @property {bigint} propertyOffset Offset for the arbitrary property. Only valid after sorting. + * @property {string} property Name/Value of the arbitrary property. * @property {VariantOption[]} options Some information on how we can sort arbitrary variants */ @@ -88,17 +90,21 @@ export class Offsets { variants: 0n, parallelIndex: 0n, index: this.offsets[layer]++, + propertyOffset: 0n, + property: '', options: [], } } /** + * @param {string} name * @returns {RuleOffset} */ - arbitraryProperty() { + arbitraryProperty(name) { return { ...this.create('utilities'), arbitrary: 1n, + property: name, } } @@ -262,6 +268,11 @@ export class Offsets { return a.arbitrary - b.arbitrary } + // Always sort arbitrary properties alphabetically + if (a.propertyOffset !== b.propertyOffset) { + return a.propertyOffset - b.propertyOffset + } + // Sort utilities, components, etc… in the order they were registered return a.index - b.index } @@ -320,14 +331,62 @@ export class Offsets { }) } + /** + * @template T + * @param {[RuleOffset, T][]} list + * @returns {[RuleOffset, T][]} + */ + sortArbitraryProperties(list) { + // Collect all known arbitrary properties + let known = new Set() + + for (let [offset] of list) { + if (offset.arbitrary === 1n) { + known.add(offset.property) + } + } + + // No arbitrary properties? Nothing to do. + if (known.size === 0) { + return list + } + + // Sort the properties alphabetically + let properties = Array.from(known).sort() + + // Create a map from the property name to its offset + let offsets = new Map() + + let offset = 1n + for (let property of properties) { + offsets.set(property, offset++) + } + + // Apply the sorted offsets to the list + return list.map((item) => { + let [offset, rule] = item + + offset = { + ...offset, + propertyOffset: offsets.get(offset.property) ?? 0n, + } + + return [offset, rule] + }) + } + /** * @template T * @param {[RuleOffset, T][]} list * @returns {[RuleOffset, T][]} */ sort(list) { + // Sort arbitrary variants so they're in alphabetical order list = this.remapArbitraryVariantOffsets(list) + // Sort arbitrary properties so they're in alphabetical order + list = this.sortArbitraryProperties(list) + return list.sort(([a], [b]) => bigSign(this.compare(a, b))) } } diff --git a/tests/getSortOrder.test.js b/tests/getSortOrder.test.js index 3cfc03e6773e..2af2bcfc2efb 100644 --- a/tests/getSortOrder.test.js +++ b/tests/getSortOrder.test.js @@ -220,3 +220,26 @@ it('Sorting is unchanged when multiple candidates share the same rule / object', expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output) } }) + +it('sorts arbitrary values across one or more class lists consistently', () => { + let classes = [ + ['[--fg:#fff]', '[--fg:#fff]'], + ['[--bg:#111] [--bg_hover:#000] [--fg:#fff]', '[--bg:#111] [--bg_hover:#000] [--fg:#fff]'], + ] + + let config = { + theme: {}, + } + + // 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) + } +}) diff --git a/tests/variants.test.js b/tests/variants.test.js index 6cf246476acf..bdfb8a5d7beb 100644 --- a/tests/variants.test.js +++ b/tests/variants.test.js @@ -36,8 +36,8 @@ crosscheck(({ stable, oxide }) => { content: [ { raw: html` -
-
+
+
`, }, ], @@ -45,21 +45,31 @@ crosscheck(({ stable, oxide }) => { return run('@tailwind utilities', config).then((result) => { stable.expect(result.css).toMatchFormattedCss(css` - .file\:hover\:bg-pink-600:hover::file-selector-button { - --tw-bg-opacity: 1; - background-color: rgb(219 39 119 / var(--tw-bg-opacity)); + .hover\:file\:\[--value\:1\]::-webkit-file-upload-button:hover { + --value: 1; } - .hover\:file\:bg-pink-600::file-selector-button:hover { - --tw-bg-opacity: 1; - background-color: rgb(219 39 119 / var(--tw-bg-opacity)); + .hover\:file\:\[--value\:1\]::file-selector-button:hover { + --value: 1; + } + .file\:hover\:\[--value\:2\]:hover::-webkit-file-upload-button { + --value: 2; + } + .file\:hover\:\[--value\:2\]:hover::file-selector-button { + --value: 2; } `) oxide.expect(result.css).toMatchFormattedCss(css` - .file\:hover\:bg-pink-600:hover::file-selector-button { - background-color: #db2777; + .hover\:file\:\[--value\:1\]::-webkit-file-upload-button:hover { + --value: 1; + } + .hover\:file\:\[--value\:1\]::file-selector-button:hover { + --value: 1; + } + .file\:hover\:\[--value\:2\]:hover::-webkit-file-upload-button { + --value: 2; } - .hover\:file\:bg-pink-600::file-selector-button:hover { - background-color: #db2777; + .file\:hover\:\[--value\:2\]:hover::file-selector-button { + --value: 2; } `) })