Skip to content

Commit

Permalink
feat: use getComponent for variant parameter (unocss/unocss#1704)
Browse files Browse the repository at this point in the history
  • Loading branch information
MellowCo committed Oct 28, 2022
1 parent 641e287 commit a4994ee
Show file tree
Hide file tree
Showing 12 changed files with 352 additions and 152 deletions.
66 changes: 54 additions & 12 deletions src/utils/utilities.ts
@@ -1,5 +1,5 @@
import type { CSSEntries, CSSObject, DynamicMatcher, ParsedColorValue, Rule, RuleContext, VariantContext } from '@unocss/core'
import { toArray } from '@unocss/core'
import { isString, toArray } from '@unocss/core'
import { restoreSelector } from 'unplugin-transform-class/utils'
import type { Theme } from '../theme'
import { colorOpacityToString, colorToString, parseCssColor } from './colors'
Expand Down Expand Up @@ -230,32 +230,76 @@ export function makeGlobalStaticRules(prefix: string, property?: string) {
return globalKeywords.map(keyword => [`${prefix}-${keyword}`, { [property ?? prefix]: keyword }] as Rule)
}

export function getComponent(str: string, open: string, close: string, separator: string) {
export function getBracket(str: string, open: string, close: string) {
if (str === '')
return

const l = str.length
let parenthesis = 0
let opened = false
let openAt = 0
for (let i = 0; i < l; i++) {
switch (str[i]) {
case open:
if (!opened) {
opened = true
openAt = i
}
parenthesis++
break

case close:
if (--parenthesis < 0)
--parenthesis
if (parenthesis < 0)
return
break

case separator:
if (parenthesis === 0) {
if (i === 0 || i === l - 1)
return
return [
str.slice(0, i),
str.slice(openAt, i + 1),
str.slice(i + 1),
str.slice(0, openAt),
]
}
break
}
}
}

export function getComponent(str: string, open: string, close: string, separators: string | string[]) {
if (str === '')
return

if (isString(separators))
separators = [separators]

if (separators.length === 0)
return

const l = str.length
let parenthesis = 0
for (let i = 0; i < l; i++) {
switch (str[i]) {
case open:
parenthesis++
break

case close:
if (--parenthesis < 0)
return
break

default:
for (const separator of separators) {
const separatorLength = separator.length
if (separatorLength && separator === str.slice(i, i + separatorLength) && parenthesis === 0) {
if (i === 0 || i === l - separatorLength)
return
return [
str.slice(0, i),
str.slice(i + separatorLength),
]
}
}
}
}

Expand All @@ -265,16 +309,14 @@ export function getComponent(str: string, open: string, close: string, separator
]
}

export function getComponents(str: string, separator: string, limit?: number) {
if (separator.length !== 1)
return
export function getComponents(str: string, separators: string | string[], limit?: number) {
limit = limit ?? 10
const components = []
let i = 0
while (str !== '') {
if (++i > limit)
return
const componentPair = getComponent(str, '(', ')', separator)
const componentPair = getComponent(str, '(', ')', separators)
if (!componentPair)
return
const [component, rest] = componentPair
Expand Down
33 changes: 30 additions & 3 deletions src/utils/variants.ts
@@ -1,11 +1,12 @@
import type { VariantHandler, VariantHandlerContext, VariantObject } from '@unocss/core'
import type { VariantHandlerContext, VariantObject } from '@unocss/core'
import { escapeRegExp } from '@unocss/core'
import { getBracket } from '../utils'

export const variantMatcher = (name: string, handler: (input: VariantHandlerContext) => Record<string, any>): VariantObject => {
const re = new RegExp(`^${escapeRegExp(name)}[:-]`)
return {
name,
match: (input: string): VariantHandler | undefined => {
match(input) {
const match = input.match(re)
if (match) {
return {
Expand All @@ -25,7 +26,7 @@ export const variantParentMatcher = (name: string, parent: string): VariantObjec
const re = new RegExp(`^${escapeRegExp(name)}[:-]`)
return {
name,
match: (input: string): VariantHandler | undefined => {
match(input) {
const match = input.match(re)
if (match) {
return {
Expand All @@ -40,3 +41,29 @@ export const variantParentMatcher = (name: string, parent: string): VariantObjec
autocomplete: `${name}:`,
}
}

export const variantGetBracket = (name: string, matcher: string, separators: string[]): string[] | undefined => {
if (matcher.startsWith(`${name}-[`)) {
const [match, rest] = getBracket(matcher.slice(name.length + 1), '[', ']') ?? []
if (match && rest) {
for (const separator of separators) {
if (rest.startsWith(separator))
return [match, rest.slice(separator.length), separator]
}
return [match, rest, '']
}
}
}

export const variantGetParameter = (name: string, matcher: string, separators: string[]): string[] | undefined => {
if (matcher.startsWith(`${name}-`)) {
const body = variantGetBracket(name, matcher, separators)
if (body)
return body
for (const separator of separators) {
const pos = matcher.indexOf(separator, name.length + 1)
if (pos !== -1)
return [matcher.slice(name.length + 1, pos), matcher.slice(pos + separator.length)]
}
}
}
58 changes: 33 additions & 25 deletions src/variants/combinators.ts
@@ -1,32 +1,40 @@
import type { Variant, VariantObject } from '@unocss/core'
import { handler as h, variantGetBracket } from '../utils'

const scopeMatcher = (strict: boolean, name: string, template: string): VariantObject => {
const re = strict
? new RegExp(`^${name}(?:-\\[(.+?)\\])[:-]`)
: new RegExp(`^${name}(?:-\\[(.+?)\\])?[:-]`)
return {
name: `combinator:${name}`,
match: (matcher: string) => {
const match = matcher.match(re)
if (match) {
return {
matcher: matcher.slice(match[0].length),
selector: s => template.replace('&&-s', s).replace('&&-c', match[1] ?? '*'),
const scopeMatcher = (name: string, combinator: string): VariantObject => ({
name: `combinator:${name}`,
match(matcher) {
if (!matcher.startsWith(name))
return

let body = variantGetBracket(name, matcher, [':', '-'])
if (!body) {
for (const separator of [':', '-']) {
if (matcher.startsWith(`${name}${separator}`)) {
body = ['', matcher.slice(name.length + separator.length)]
break
}
}
},
multiPass: true,
}
}
if (!body)
return
}

let bracketValue = h.bracket(body[0]) ?? ''
if (bracketValue === '')
bracketValue = '*'

return {
matcher: body[1],
selector: s => `${s}${combinator}${bracketValue}`,
}
},
multiPass: true,
})

export const variantCombinators: Variant[] = [
scopeMatcher(false, 'all', '&&-s &&-c'),
scopeMatcher(false, 'children', '&&-s>&&-c'),
scopeMatcher(false, 'next', '&&-s+&&-c'),
scopeMatcher(false, 'sibling', '&&-s+&&-c'),
scopeMatcher(false, 'siblings', '&&-s~&&-c'),
scopeMatcher(true, 'group', '&&-c &&-s'),
scopeMatcher(true, 'parent', '&&-c>&&-s'),
scopeMatcher(true, 'previous', '&&-c+&&-s'),
scopeMatcher(true, 'peer', '&&-c~&&-s'),
scopeMatcher('all', ' '),
scopeMatcher('children', '>'),
scopeMatcher('next', '+'),
scopeMatcher('sibling', '+'),
scopeMatcher('siblings', '~'),
]
3 changes: 2 additions & 1 deletion src/variants/default.ts
Expand Up @@ -13,7 +13,6 @@ import { variantSupports } from './supports'
import { partClasses, variantPseudoClassFunctions, variantPseudoClassesAndElements, variantTaggedPseudoClasses } from './pseudo'

export const variants = (options: PresetWeappOptions): Variant<Theme>[] => [
variantVariables,
variantCssLayer,

variantSelector,
Expand All @@ -34,4 +33,6 @@ export const variants = (options: PresetWeappOptions): Variant<Theme>[] => [
...variantColorsMediaOrClass(options),
...variantLanguageDirections,
variantScope,

variantVariables,
]
27 changes: 17 additions & 10 deletions src/variants/media.ts
@@ -1,21 +1,28 @@
import type { Variant, VariantContext, VariantObject } from '@unocss/core'
import type { Theme } from '../theme'
import { variantParentMatcher } from '../utils'
import { handler as h, variantGetParameter, variantParentMatcher } from '../utils'

export const variantPrint: Variant = variantParentMatcher('print', '@media print')

export const variantCustomMedia: VariantObject = {
name: 'media',
match(matcher, { theme }: VariantContext<Theme>) {
const match = matcher.match(/^media-([_\d\w]+)[:-]/)
if (match) {
const media = theme.media?.[match[1]] ?? `(--${match[1]})`
return {
matcher: matcher.slice(match[0].length),
handle: (input, next) => next({
...input,
parent: `${input.parent ? `${input.parent} $$ ` : ''}@media ${media}`,
}),
const variant = variantGetParameter('media', matcher, [':', '-'])
if (variant) {
const [match, rest] = variant

let media = h.bracket(match) ?? ''
if (media === '')
media = theme.media?.[match] ?? ''

if (media) {
return {
matcher: rest,
handle: (input, next) => next({
...input,
parent: `${input.parent ? `${input.parent} $$ ` : ''}@media ${media}`,
}),
}
}
}
},
Expand Down

0 comments on commit a4994ee

Please sign in to comment.