Skip to content

Commit

Permalink
feat(preset-mini): pseudo-class function for custom arguments (#2694)
Browse files Browse the repository at this point in the history
  • Loading branch information
enpitsuLin committed Jun 4, 2023
1 parent 3d0c60f commit db339c3
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 7 deletions.
34 changes: 27 additions & 7 deletions packages/preset-mini/src/_variants/pseudo.ts
@@ -1,7 +1,7 @@
import type { VariantObject } from '@unocss/core'
import { escapeRegExp, escapeSelector, warnOnce } from '@unocss/core'
import type { PresetMiniOptions } from '..'
import { handler as h, variantGetBracket } from '../_utils'
import { getBracket, handler as h, variantGetBracket } from '../_utils'

/**
* Note: the order of following pseudo classes will affect the order of generated css.
Expand Down Expand Up @@ -95,8 +95,9 @@ function taggedPseudoClassMatcher(tag: string, parent: string, combinator: strin
let splitRE: RegExp
let pseudoRE: RegExp
let pseudoColonRE: RegExp
let pseudoVarRE: RegExp

const matchBracket = (input: string) => {
const matchBracket = (input: string): [label: string, rest: string, prefix: string] | undefined => {
const body = variantGetBracket(`${tag}-`, input, [])
if (!body)
return
Expand All @@ -115,7 +116,7 @@ function taggedPseudoClassMatcher(tag: string, parent: string, combinator: strin
]
}

const matchPseudo = (input: string) => {
const matchPseudo = (input: string): [label: string, rest: string, prefix: string, pseudoKey: string] | undefined => {
const match = input.match(pseudoRE) || input.match(pseudoColonRE)
if (!match)
return
Expand All @@ -134,23 +135,39 @@ function taggedPseudoClassMatcher(tag: string, parent: string, combinator: strin
]
}

const matchPseudoVar = (input: string): [label: string, rest: string, prefix: string] | undefined => {
const match = input.match(pseudoVarRE)
if (!match)
return
const [original, fn, pseudoValue] = match
const label = match[3] ?? ''
const pseudo = `:${fn}(${pseudoValue})`

return [
label,
input.slice(original.length),
`${parent}${escapeSelector(label)}${pseudo}`,
]
}

return {
name: `pseudo:${tag}`,
match(input, ctx) {
if (!(splitRE && pseudoRE && pseudoColonRE)) {
splitRE = new RegExp(`(?:${ctx.generator.config.separators.join('|')})`)
pseudoRE = new RegExp(`^${tag}-(?:(?:(${PseudoClassFunctionsStr})-)?(${PseudoClassesStr}))(?:(/\\w+))?(?:${ctx.generator.config.separators.join('|')})`)
pseudoColonRE = new RegExp(`^${tag}-(?:(?:(${PseudoClassFunctionsStr})-)?(${PseudoClassesColonStr}))(?:(/\\w+))?(?:${ctx.generator.config.separators.filter(x => x !== '-').join('|')})`)
pseudoVarRE = new RegExp(`^${tag}-(?:(${PseudoClassFunctionsStr})-)?\\[(.+)\\](?:(/\\w+))?(?:${ctx.generator.config.separators.filter(x => x !== '-').join('|')})`)
}

if (!input.startsWith(tag))
return

const result = matchBracket(input) || matchPseudo(input)
const result = matchBracket(input) || matchPseudo(input) || matchPseudoVar(input)
if (!result)
return

const [label, matcher, prefix, pseudoName = ''] = result as [string, string, string, string | undefined]
const [label, matcher, prefix, pseudoName = ''] = result
if (label !== '')
warnOnce('The labeled variant is experimental and may not follow semver.')

Expand Down Expand Up @@ -231,17 +248,20 @@ export function variantPseudoClassesAndElements(): VariantObject {
export function variantPseudoClassFunctions(): VariantObject {
let PseudoClassFunctionsRE: RegExp
let PseudoClassColonFunctionsRE: RegExp
let PseudoClassVarFunctionRE: RegExp
return {
match(input, ctx) {
if (!(PseudoClassFunctionsRE && PseudoClassColonFunctionsRE)) {
PseudoClassFunctionsRE = new RegExp(`^(${PseudoClassFunctionsStr})-(${PseudoClassesStr})(?:${ctx.generator.config.separators.join('|')})`)
PseudoClassColonFunctionsRE = new RegExp(`^(${PseudoClassFunctionsStr})-(${PseudoClassesColonStr})(?:${ctx.generator.config.separators.filter(x => x !== '-').join('|')})`)
PseudoClassVarFunctionRE = new RegExp(`^(${PseudoClassFunctionsStr})-(\\[.+\\])(?:${ctx.generator.config.separators.filter(x => x !== '-').join('|')})`)
}

const match = input.match(PseudoClassFunctionsRE) || input.match(PseudoClassColonFunctionsRE)
const match = input.match(PseudoClassFunctionsRE) || input.match(PseudoClassColonFunctionsRE) || input.match(PseudoClassVarFunctionRE)
if (match) {
const fn = match[1]
const pseudo = PseudoClasses[match[2]] || PseudoClassesColon[match[2]] || `:${match[2]}`
const fnVal = getBracket(match[2], '[', ']')
const pseudo = fnVal ? h.bracket(match[2]) : (PseudoClasses[match[2]] || PseudoClassesColon[match[2]] || `:${match[2]}`)
return {
matcher: input.slice(match[0].length),
selector: s => `${s}:${fn}(${pseudo})`,
Expand Down
5 changes: 5 additions & 0 deletions test/assets/output/preset-mini-targets.css
Expand Up @@ -77,6 +77,11 @@
.children\:m-auto>*,
.m-auto{margin:auto;}
.all\:m1\/1 *{margin:100%;}
.group:not([data-potato]) .group-not-\[\[data-potato\]\]\:m-1,
.has-\[\:hover\]\:m-1:has(:hover),
.parent:not(#someId)>.parent-not-\[\#someId\]\:m-1,
.peer:where(.child)~.peer-where-\[\.child\]\:m-1,
.previous:is(div)+.previous-is-\[div\]\:m-1{margin:0.25rem;}
.m-\[3em\]{margin:3em;}
.m-0,
.m-none{margin:0;}
Expand Down
7 changes: 7 additions & 0 deletions test/assets/preset-mini-targets.ts
Expand Up @@ -1039,6 +1039,13 @@ export const presetMiniTargets: string[] = [
'group-has-placeholder-shown:text-4xl',
'focus-within:has-first:checked:bg-gray/20',

// variants - pseudo function with custom value
'has-[:hover]:m-1',
'group-not-[[data-potato]]:m-1',
'previous-is-[div]:m-1',
'peer-where-[.child]:m-1',
'parent-not-[#someId]:m-1',

// variants scope
'scope-[.scope\\_class]:translate-0',
'scope-[unocss]:block',
Expand Down

0 comments on commit db339c3

Please sign in to comment.