Skip to content

Commit 1046b4d

Browse files
thecrypticaceKrisBraun
authored andcommitted
Sort arbitrary properties alphabetically across multiple class lists (#12911)
* Sort arbitrary properties alphabetically across multiple files * Update test
1 parent 92fdc15 commit 1046b4d

File tree

4 files changed

+90
-8
lines changed

4 files changed

+90
-8
lines changed

src/lib/generateRules.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ function extractArbitraryProperty(classCandidate, context) {
507507
return null
508508
}
509509

510-
let sort = context.offsets.arbitraryProperty()
510+
let sort = context.offsets.arbitraryProperty(classCandidate)
511511

512512
return [
513513
[

src/lib/offsets.js

+60-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import { remapBitfield } from './remap-bitfield.js'
2424
* @property {bigint} variants Dynamic size. 1 bit per registered variant. 0n means no variants
2525
* @property {bigint} parallelIndex Rule index for the parallel variant. 0 if not applicable.
2626
* @property {bigint} index Index of the rule / utility in its given *parent* layer. Monotonically increasing.
27+
* @property {bigint} propertyOffset Offset for the arbitrary property. Only valid after sorting.
28+
* @property {string} property Name/Value of the arbitrary property.
2729
* @property {VariantOption[]} options Some information on how we can sort arbitrary variants
2830
*/
2931

@@ -88,17 +90,21 @@ export class Offsets {
8890
variants: 0n,
8991
parallelIndex: 0n,
9092
index: this.offsets[layer]++,
93+
propertyOffset: 0n,
94+
property: '',
9195
options: [],
9296
}
9397
}
9498

9599
/**
100+
* @param {string} name
96101
* @returns {RuleOffset}
97102
*/
98-
arbitraryProperty() {
103+
arbitraryProperty(name) {
99104
return {
100105
...this.create('utilities'),
101106
arbitrary: 1n,
107+
property: name,
102108
}
103109
}
104110

@@ -262,6 +268,11 @@ export class Offsets {
262268
return a.arbitrary - b.arbitrary
263269
}
264270

271+
// Always sort arbitrary properties alphabetically
272+
if (a.propertyOffset !== b.propertyOffset) {
273+
return a.propertyOffset - b.propertyOffset
274+
}
275+
265276
// Sort utilities, components, etc… in the order they were registered
266277
return a.index - b.index
267278
}
@@ -320,14 +331,62 @@ export class Offsets {
320331
})
321332
}
322333

334+
/**
335+
* @template T
336+
* @param {[RuleOffset, T][]} list
337+
* @returns {[RuleOffset, T][]}
338+
*/
339+
sortArbitraryProperties(list) {
340+
// Collect all known arbitrary properties
341+
let known = new Set()
342+
343+
for (let [offset] of list) {
344+
if (offset.arbitrary === 1n) {
345+
known.add(offset.property)
346+
}
347+
}
348+
349+
// No arbitrary properties? Nothing to do.
350+
if (known.size === 0) {
351+
return list
352+
}
353+
354+
// Sort the properties alphabetically
355+
let properties = Array.from(known).sort()
356+
357+
// Create a map from the property name to its offset
358+
let offsets = new Map()
359+
360+
let offset = 1n
361+
for (let property of properties) {
362+
offsets.set(property, offset++)
363+
}
364+
365+
// Apply the sorted offsets to the list
366+
return list.map((item) => {
367+
let [offset, rule] = item
368+
369+
offset = {
370+
...offset,
371+
propertyOffset: offsets.get(offset.property) ?? 0n,
372+
}
373+
374+
return [offset, rule]
375+
})
376+
}
377+
323378
/**
324379
* @template T
325380
* @param {[RuleOffset, T][]} list
326381
* @returns {[RuleOffset, T][]}
327382
*/
328383
sort(list) {
384+
// Sort arbitrary variants so they're in alphabetical order
329385
list = this.remapArbitraryVariantOffsets(list)
330386

387+
// Sort arbitrary properties so they're in alphabetical order
388+
list = this.sortArbitraryProperties(list)
389+
331390
return list.sort(([a], [b]) => bigSign(this.compare(a, b)))
332391
}
333392
}

tests/getSortOrder.test.js

+23
Original file line numberDiff line numberDiff line change
@@ -217,3 +217,26 @@ it('Sorting is unchanged when multiple candidates share the same rule / object',
217217
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
218218
}
219219
})
220+
221+
it('sorts arbitrary values across one or more class lists consistently', () => {
222+
let classes = [
223+
['[--fg:#fff]', '[--fg:#fff]'],
224+
['[--bg:#111] [--bg_hover:#000] [--fg:#fff]', '[--bg:#111] [--bg_hover:#000] [--fg:#fff]'],
225+
]
226+
227+
let config = {
228+
theme: {},
229+
}
230+
231+
// Same context, different class lists
232+
let context = createContext(resolveConfig(config))
233+
for (const [input, output] of classes) {
234+
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
235+
}
236+
237+
// Different context, different class lists
238+
for (const [input, output] of classes) {
239+
context = createContext(resolveConfig(config))
240+
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
241+
}
242+
})

tests/variants.test.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,18 @@ test('order matters and produces different behaviour', () => {
3737

3838
return run('@tailwind utilities', config).then((result) => {
3939
expect(result.css).toMatchFormattedCss(css`
40-
.file\:hover\:\[--value\:2\]:hover::-webkit-file-upload-button {
41-
--value: 2;
42-
}
43-
.file\:hover\:\[--value\:2\]:hover::file-selector-button {
44-
--value: 2;
45-
}
4640
.hover\:file\:\[--value\:1\]::-webkit-file-upload-button:hover {
4741
--value: 1;
4842
}
4943
.hover\:file\:\[--value\:1\]::file-selector-button:hover {
5044
--value: 1;
5145
}
46+
.file\:hover\:\[--value\:2\]:hover::-webkit-file-upload-button {
47+
--value: 2;
48+
}
49+
.file\:hover\:\[--value\:2\]:hover::file-selector-button {
50+
--value: 2;
51+
}
5252
`)
5353
})
5454
})

0 commit comments

Comments
 (0)