Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(preset-mini)!: use getComponent for variant parameter #1704

Merged
merged 6 commits into from Oct 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
67 changes: 54 additions & 13 deletions packages/preset-mini/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 type { Theme } from '../theme'
import { colorOpacityToString, colorToString, parseCssColor } from './colors'
import { handler as h } from './handlers'
Expand Down Expand Up @@ -227,32 +227,75 @@ 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 @@ -262,16 +305,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 packages/preset-mini/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 packages/preset-mini/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 packages/preset-mini/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: PresetMiniOptions): Variant<Theme>[] => [
variantVariables,
variantCssLayer,

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

variantVariables,
]
27 changes: 17 additions & 10 deletions packages/preset-mini/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