@@ -74,11 +74,92 @@ export function finalizeSelector(format, { selector, candidate, context }) {
7474 return p
7575 } )
7676
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+
77108 return selector
78109 } )
79110 } ) . processSync ( selector )
80111}
81112
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+
82163function resolveFunctionArgument ( haystack , needle , arg ) {
83164 let startIdx = haystack . indexOf ( arg ? `${ needle } (${ arg } )` : needle )
84165 if ( startIdx === - 1 ) return null
0 commit comments