@@ -74,11 +74,92 @@ export function finalizeSelector(format, { selector, candidate, context }) {
74
74
return p
75
75
} )
76
76
77
+ // This will make sure to move pseudo's to the correct spot (the end for
78
+ // pseudo elements) because otherwise the selector will never work
79
+ // anyway.
80
+ //
81
+ // E.g.:
82
+ // - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before`
83
+ // - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before`
84
+ //
85
+ // `::before:hover` doesn't work, which means that we can make it work for you by flipping the order.
86
+ function collectPseudoElements ( selector ) {
87
+ let nodes = [ ]
88
+
89
+ for ( let node of selector . nodes ) {
90
+ if ( isPseudoElement ( node ) ) {
91
+ nodes . push ( node )
92
+ selector . removeChild ( node )
93
+ }
94
+
95
+ if ( node ?. nodes ) {
96
+ nodes . push ( ...collectPseudoElements ( node ) )
97
+ }
98
+ }
99
+
100
+ return nodes
101
+ }
102
+
103
+ let pseudoElements = collectPseudoElements ( selector )
104
+ if ( pseudoElements . length > 0 ) {
105
+ selector . nodes . push ( pseudoElements . sort ( sortSelector ) )
106
+ }
107
+
77
108
return selector
78
109
} )
79
110
} ) . processSync ( selector )
80
111
}
81
112
113
+ // Note: As a rule, double colons (::) should be used instead of a single colon
114
+ // (:). This distinguishes pseudo-classes from pseudo-elements. However, since
115
+ // this distinction was not present in older versions of the W3C spec, most
116
+ // browsers support both syntaxes for the original pseudo-elements.
117
+ let pseudoElementsBC = [ ':before' , ':after' , ':first-line' , ':first-letter' ]
118
+
119
+ // These pseudo-elements _can_ be combined with other pseudo selectors AND the order does matter.
120
+ let pseudoElementExceptions = [ '::file-selector-button' ]
121
+
122
+ // This will make sure to move pseudo's to the correct spot (the end for
123
+ // pseudo elements) because otherwise the selector will never work
124
+ // anyway.
125
+ //
126
+ // E.g.:
127
+ // - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before`
128
+ // - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before`
129
+ //
130
+ // `::before:hover` doesn't work, which means that we can make it work
131
+ // for you by flipping the order.
132
+ function sortSelector ( a , z ) {
133
+ // Both nodes are non-pseudo's so we can safely ignore them and keep
134
+ // them in the same order.
135
+ if ( a . type !== 'pseudo' && z . type !== 'pseudo' ) {
136
+ return 0
137
+ }
138
+
139
+ // If one of them is a combinator, we need to keep it in the same order
140
+ // because that means it will start a new "section" in the selector.
141
+ if ( ( a . type === 'combinator' ) ^ ( z . type === 'combinator' ) ) {
142
+ return 0
143
+ }
144
+
145
+ // One of the items is a pseudo and the other one isn't. Let's move
146
+ // the pseudo to the right.
147
+ if ( ( a . type === 'pseudo' ) ^ ( z . type === 'pseudo' ) ) {
148
+ return ( a . type === 'pseudo' ) - ( z . type === 'pseudo' )
149
+ }
150
+
151
+ // Both are pseudo's, move the pseudo elements (except for
152
+ // ::file-selector-button) to the right.
153
+ return isPseudoElement ( a ) - isPseudoElement ( z )
154
+ }
155
+
156
+ function isPseudoElement ( node ) {
157
+ if ( node . type !== 'pseudo' ) return false
158
+ if ( pseudoElementExceptions . includes ( node . value ) ) return false
159
+
160
+ return node . value . startsWith ( '::' ) || pseudoElementsBC . includes ( node . value )
161
+ }
162
+
82
163
function resolveFunctionArgument ( haystack , needle , arg ) {
83
164
let startIdx = haystack . indexOf ( arg ? `${ needle } (${ arg } )` : needle )
84
165
if ( startIdx === - 1 ) return null
0 commit comments