Skip to content

Commit

Permalink
Pull pseudo elements outside of :is and :has when using @apply
Browse files Browse the repository at this point in the history
  • Loading branch information
thecrypticace committed Mar 29, 2023
1 parent a9149e4 commit 6466817
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 9 deletions.
12 changes: 12 additions & 0 deletions src/lib/expandApplyAtRules.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import parser from 'postcss-selector-parser'
import { resolveMatches } from './generateRules'
import escapeClassName from '../util/escapeClassName'
import { applyImportantSelector } from '../util/applyImportantSelector'
import { collectPseudoElements, sortSelector } from '../util/formatVariantSelector.js'

/** @typedef {Map<string, [any, import('postcss').Rule[]]>} ApplyCache */

Expand Down Expand Up @@ -562,6 +563,17 @@ function processApply(root, context, localCache) {
rule.walkDecls((d) => {
d.important = meta.important || important
})

// Move pseudo elements to the end of the selector (if necessary)
let selector = parser().astSync(rule.selector)
selector.each((sel) => {
let [pseudoElements] = collectPseudoElements(sel, true, [':is', ':has'])
if (pseudoElements.length > 0) {
sel.nodes.push(...pseudoElements.sort(sortSelector))
}
})

rule.selector = selector.toString()
})
}

Expand Down
53 changes: 44 additions & 9 deletions src/util/formatVariantSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ export function finalizeSelector(current, formats, { context, candidate, base })

// Move pseudo elements to the end of the selector (if necessary)
selector.each((sel) => {
let pseudoElements = collectPseudoElements(sel)
let [pseudoElements] = collectPseudoElements(sel)
if (pseudoElements.length > 0) {
sel.nodes.push(pseudoElements.sort(sortSelector))
}
Expand Down Expand Up @@ -339,6 +339,17 @@ let pseudoElementExceptions = [
'::-webkit-resizer',
]

export function containsNode(selector, values) {
let found = false
selector.walk((node) => {
if (values.includes(node.value)) {
found = true
return false
}
})
return found
}

/**
* This will make sure to move pseudo's to the correct spot (the end for
* pseudo elements) because otherwise the selector will never work
Expand All @@ -351,23 +362,43 @@ let pseudoElementExceptions = [
* `::before:hover` doesn't work, which means that we can make it work for you by flipping the order.
*
* @param {Selector} selector
* @param {boolean} force
* @param {string[]|null} safelist
**/
function collectPseudoElements(selector) {
export function collectPseudoElements(selector, force = false, safelist = null) {
/** @type {Node[]} */
let nodes = []
let seenPseudoElement = null

if (safelist !== null && !containsNode(selector, safelist)) {
return [[], seenPseudoElement]
}

for (let node of selector.nodes) {
if (isPseudoElement(node)) {
for (let node of [...selector.nodes]) {
if (isPseudoElement(node, force)) {
nodes.push(node)
selector.removeChild(node)
seenPseudoElement = node.value
} else if (seenPseudoElement !== null) {
if (pseudoElementExceptions.includes(seenPseudoElement) && isPseudoClass(node, force)) {
nodes.push(node)
selector.removeChild(node)
} else {
seenPseudoElement = null
}
}

if (node?.nodes) {
nodes.push(...collectPseudoElements(node))
let [collected, seenPseudoElementInSelector] = collectPseudoElements(node, force)
if (seenPseudoElementInSelector) {
seenPseudoElement = seenPseudoElementInSelector
}

nodes.push(...collected)
}
}

return nodes
return [nodes, seenPseudoElement]
}

// This will make sure to move pseudo's to the correct spot (the end for
Expand All @@ -380,7 +411,7 @@ function collectPseudoElements(selector) {
//
// `::before:hover` doesn't work, which means that we can make it work
// for you by flipping the order.
function sortSelector(a, z) {
export function sortSelector(a, z) {
// Both nodes are non-pseudo's so we can safely ignore them and keep
// them in the same order.
if (a.type !== 'pseudo' && z.type !== 'pseudo') {
Expand All @@ -404,9 +435,13 @@ function sortSelector(a, z) {
return isPseudoElement(a) - isPseudoElement(z)
}

function isPseudoElement(node) {
function isPseudoElement(node, force = false) {
if (node.type !== 'pseudo') return false
if (pseudoElementExceptions.includes(node.value)) return false
if (pseudoElementExceptions.includes(node.value) && !force) return false

return node.value.startsWith('::') || pseudoElementsBC.includes(node.value)
}

function isPseudoClass(node, force) {
return node.type === 'pseudo' && !isPseudoElement(node, force)
}
70 changes: 70 additions & 0 deletions tests/apply.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2357,4 +2357,74 @@ crosscheck(({ stable, oxide }) => {
`)
})
})

it('pseudo elements inside apply are moved outside of :is() or :has()', () => {
let config = {
darkMode: 'class',
content: [
{
raw: html` <div class="foo bar baz qux steve bob"></div> `,
},
],
}

let input = css`
.foo::before {
@apply dark:bg-black/100;
}
.bar::before {
@apply rtl:dark:bg-black/100;
}
.baz::before {
@apply rtl:dark:hover:bg-black/100;
}
.qux::file-selector-button {
@apply rtl:dark:hover:bg-black/100;
}
.steve::before {
@apply rtl:hover:dark:bg-black/100;
}
.bob::file-selector-button {
@apply rtl:hover:dark:bg-black/100;
}
.foo::before {
@apply [:has([dir="rtl"]_&)]:hover:bg-black/100;
}
.bar::file-selector-button {
@apply [:has([dir="rtl"]_&)]:hover:bg-black/100;
}
`

return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
:is(.dark .foo)::before,
:is([dir='rtl'] :is(.dark .bar))::before,
:is([dir='rtl'] :is(.dark .baz:hover))::before {
background-color: #000;
}
:is([dir='rtl'] :is(.dark .qux))::file-selector-button:hover {
background-color: #000;
}
:is([dir='rtl'] :is(.dark .steve):hover):before {
background-color: #000;
}
:is([dir='rtl'] :is(.dark .bob))::file-selector-button:hover {
background-color: #000;
}
:has([dir='rtl'] .foo:hover):before {
background-color: #000;
}
:has([dir='rtl'] .bar)::file-selector-button:hover {
background-color: #000;
}
`)
})
})
})

0 comments on commit 6466817

Please sign in to comment.