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(presets): configurable separator #1791

Merged
merged 1 commit into from Jan 25, 2023
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
7 changes: 6 additions & 1 deletion packages/core/src/config.ts
Expand Up @@ -49,7 +49,7 @@ export function resolveConfig<Theme extends {} = {}>(

const layers = Object.assign(DEFAULT_LAYERS, ...rawPresets.map(i => i.layers), userConfig.layers)

function mergePresets<T extends 'rules' | 'variants' | 'extractors' | 'shortcuts' | 'preflights' | 'preprocess' | 'postprocess' | 'extendTheme' | 'safelist'>(key: T): Required<UserConfig<Theme>>[T] {
function mergePresets<T extends 'rules' | 'variants' | 'extractors' | 'shortcuts' | 'preflights' | 'preprocess' | 'postprocess' | 'extendTheme' | 'safelist' | 'separators'>(key: T): Required<UserConfig<Theme>>[T] {
return uniq([
...sortedPresets.flatMap(p => toArray(p[key] || []) as any[]),
...toArray(config[key] || []) as any[],
Expand Down Expand Up @@ -95,6 +95,10 @@ export function resolveConfig<Theme extends {} = {}>(
.sort((a, b) => (a.order || 0) - (b.order || 0)),
}

let separators = toArray(mergePresets('separators'))
if (!separators.length)
separators = [':', '-']

return {
mergeSelectors: true,
warn: true,
Expand All @@ -117,5 +121,6 @@ export function resolveConfig<Theme extends {} = {}>(
shortcuts: resolveShortcuts(mergePresets('shortcuts')),
extractors,
safelist: mergePresets('safelist'),
separators,
}
}
8 changes: 8 additions & 0 deletions packages/core/src/types.ts
Expand Up @@ -307,6 +307,13 @@ export interface ConfigBase<Theme extends {} = {}> {
*/
rules?: Rule<Theme>[]

/**
* Variant separator
*
* @default [':', '-']
*/
separators?: Arrayable<string>

/**
* Variants that preprocess the selectors,
* having the ability to rewrite the CSS object.
Expand Down Expand Up @@ -657,6 +664,7 @@ RequiredByKey<UserConfig<Theme>, 'mergeSelectors' | 'theme' | 'rules' | 'variant
templates: (AutoCompleteFunction | AutoCompleteTemplate)[]
extractors: AutoCompleteExtractor[]
}
separators: string[]
}

export interface GenerateResult {
Expand Down
12 changes: 10 additions & 2 deletions packages/core/src/utils/variantGroup.ts
@@ -1,11 +1,19 @@
import type MagicString from 'magic-string'

export const regexClassGroup = /((?:[!@\w+:_/-]|\[&?>?:?.*\])+?)([:-])\(((?:[~!\w\s:/\\,%#.$?-]|\[.*?\])+?)\)(?!\s*?=>)/gm
const regexCache: Record<string, RegExp> = {}

export function makeRegexClassGroup(separators = ['-', ':']) {
const key = separators.join('|')
if (!regexCache[key])
regexCache[key] = new RegExp(`((?:[!@\\w+:_/-]|\\[&?>?:?.*\\])+?)(${key})\\(((?:[~!\\w\\s:/\\\\,%#.$?-]|\\[.*?\\])+?)\\)(?!\\s*?=>)`, 'gm')
regexCache[key].lastIndex = 0
return regexCache[key]
}

export function expandVariantGroup(str: string, separators?: string[], depth?: number): string
export function expandVariantGroup(str: MagicString, separators?: string[], depth?: number): MagicString
export function expandVariantGroup(str: string | MagicString, separators = ['-', ':'], depth = 5) {
regexClassGroup.lastIndex = 0
const regexClassGroup = makeRegexClassGroup(separators)
let hasChanged = false
let content = str.toString()
do {
Expand Down
14 changes: 10 additions & 4 deletions packages/preset-mini/src/_utils/variants.ts
Expand Up @@ -3,10 +3,13 @@ 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)}[:-]`)
let re: RegExp
return {
name,
match(input) {
match(input, ctx) {
if (!re)
re = new RegExp(`^${escapeRegExp(name)}(?:${ctx.generator.config.separators.join('|')})`)

const match = input.match(re)
if (match) {
return {
Expand All @@ -23,10 +26,13 @@ export const variantMatcher = (name: string, handler: (input: VariantHandlerCont
}

export const variantParentMatcher = (name: string, parent: string): VariantObject => {
const re = new RegExp(`^${escapeRegExp(name)}[:-]`)
let re: RegExp
return {
name,
match(input) {
match(input, ctx) {
if (!re)
re = new RegExp(`^${escapeRegExp(name)}(?:${ctx.generator.config.separators.join('|')})`)

const match = input.match(re)
if (match) {
return {
Expand Down
6 changes: 3 additions & 3 deletions packages/preset-mini/src/_variants/aria.ts
Expand Up @@ -4,11 +4,11 @@ import { handler as h, variantGetParameter } from '../utils'

export const variantAria: VariantObject = {
name: 'aria',
match(matcher, { theme }: VariantContext<Theme>) {
const variant = variantGetParameter('aria-', matcher, [':', '-'])
match(matcher, ctx: VariantContext<Theme>) {
const variant = variantGetParameter('aria-', matcher, ctx.generator.config.separators)
if (variant) {
const [match, rest] = variant
const aria = h.bracket(match) ?? theme.aria?.[match] ?? ''
const aria = h.bracket(match) ?? ctx.theme.aria?.[match] ?? ''
if (aria) {
return {
matcher: rest,
Expand Down
104 changes: 52 additions & 52 deletions packages/preset-mini/src/_variants/breakpoints.ts
@@ -1,8 +1,5 @@
import type { Variant } from '@unocss/core'
import type { VariantObject } from '@unocss/core'
import { resolveBreakpoints } from '../utils'
import type { Theme } from '../theme'

const regexCache: Record<string, RegExp> = {}

export const calcMaxWidthBySize = (size: string) => {
const value = size.match(/^-?[0-9]+\.?[0-9]*/)?.[0] || ''
Expand All @@ -11,69 +8,72 @@ export const calcMaxWidthBySize = (size: string) => {
return Number.isNaN(maxWidth) ? size : `${maxWidth}${unit}`
}

export const variantBreakpoints: Variant<Theme> = {
name: 'breakpoints',
match(matcher, context) {
const variantEntries: Array<[string, string, number]>
= Object.entries(resolveBreakpoints(context) ?? {}).map(([point, size], idx) => [point, size, idx])
for (const [point, size, idx] of variantEntries) {
if (!regexCache[point])
regexCache[point] = new RegExp(`^((?:[al]t-)?${point}[:-])`)
export const variantBreakpoints = (): VariantObject => {
const regexCache: Record<string, RegExp> = {}
return {
name: 'breakpoints',
match(matcher, context) {
const variantEntries: Array<[string, string, number]>
= Object.entries(resolveBreakpoints(context) ?? {}).map(([point, size], idx) => [point, size, idx])
for (const [point, size, idx] of variantEntries) {
if (!regexCache[point])
regexCache[point] = new RegExp(`^((?:[al]t-)?${point}(?:${context.generator.config.separators.join('|')}))`)

const match = matcher.match(regexCache[point])
if (!match)
continue
const match = matcher.match(regexCache[point])
if (!match)
continue

const [, pre] = match
const [, pre] = match

const m = matcher.slice(pre.length)
// container rule is responsive, but also is breakpoint aware
// it is handled on its own module (container.ts) and so we
// exclude it from here
if (m === 'container')
continue
const m = matcher.slice(pre.length)
// container rule is responsive, but also is breakpoint aware
// it is handled on its own module (container.ts) and so we
// exclude it from here
if (m === 'container')
continue

const isLtPrefix = pre.startsWith('lt-')
const isAtPrefix = pre.startsWith('at-')
const isLtPrefix = pre.startsWith('lt-')
const isAtPrefix = pre.startsWith('at-')

let order = 1000 // parseInt(size)
let order = 1000 // parseInt(size)

if (isLtPrefix) {
order -= (idx + 1)
return {
matcher: m,
handle: (input, next) => next({
...input,
parent: `${input.parent ? `${input.parent} $$ ` : ''}@media (max-width: ${calcMaxWidthBySize(size)})`,
parentOrder: order,
}),
if (isLtPrefix) {
order -= (idx + 1)
return {
matcher: m,
handle: (input, next) => next({
...input,
parent: `${input.parent ? `${input.parent} $$ ` : ''}@media (max-width: ${calcMaxWidthBySize(size)})`,
parentOrder: order,
}),
}
}
}

order += (idx + 1)
order += (idx + 1)

// support for windicss @<breakpoint> => last breakpoint will not have the upper bound
if (isAtPrefix && idx < variantEntries.length - 1) {
return {
matcher: m,
handle: (input, next) => next({
...input,
parent: `${input.parent ? `${input.parent} $$ ` : ''}@media (min-width: ${size}) and (max-width: ${calcMaxWidthBySize(variantEntries[idx + 1][1])})`,
parentOrder: order,
}),
}
}

// support for windicss @<breakpoint> => last breakpoint will not have the upper bound
if (isAtPrefix && idx < variantEntries.length - 1) {
return {
matcher: m,
handle: (input, next) => next({
...input,
parent: `${input.parent ? `${input.parent} $$ ` : ''}@media (min-width: ${size}) and (max-width: ${calcMaxWidthBySize(variantEntries[idx + 1][1])})`,
parent: `${input.parent ? `${input.parent} $$ ` : ''}@media (min-width: ${size})`,
parentOrder: order,
}),
}
}

return {
matcher: m,
handle: (input, next) => next({
...input,
parent: `${input.parent ? `${input.parent} $$ ` : ''}@media (min-width: ${size})`,
parentOrder: order,
}),
}
}
},
multiPass: true,
autocomplete: '(at-|lt-|)$breakpoints:',
},
multiPass: true,
autocomplete: '(at-|lt-|)$breakpoints:',
}
}
7 changes: 4 additions & 3 deletions packages/preset-mini/src/_variants/combinators.ts
Expand Up @@ -3,13 +3,14 @@ import { handler as h, variantGetBracket } from '../utils'

const scopeMatcher = (name: string, combinator: string): VariantObject => ({
name: `combinator:${name}`,
match(matcher) {
match(matcher, ctx) {
if (!matcher.startsWith(name))
return

let body = variantGetBracket(`${name}-`, matcher, [':', '-'])
const separators = ctx.generator.config.separators
let body = variantGetBracket(`${name}-`, matcher, separators)
if (!body) {
for (const separator of [':', '-']) {
for (const separator of separators) {
if (matcher.startsWith(`${name}${separator}`)) {
body = ['', matcher.slice(name.length + separator.length)]
break
Expand Down
6 changes: 3 additions & 3 deletions packages/preset-mini/src/_variants/container.ts
Expand Up @@ -5,11 +5,11 @@ import { handler as h, variantGetParameter } from '../utils'

export const variantContainerQuery: VariantObject = {
name: '@',
match(matcher, { theme }: VariantContext<Theme>) {
match(matcher, ctx: VariantContext<Theme>) {
if (matcher.startsWith('@container'))
return

const variant = variantGetParameter('@', matcher, [':', '-'])
const variant = variantGetParameter('@', matcher, ctx.generator.config.separators)
if (variant) {
const [match, rest, label] = variant
const unbracket = h.bracket(match)
Expand All @@ -20,7 +20,7 @@ export const variantContainerQuery: VariantObject = {
container = `(min-width: ${minWidth})`
}
else {
container = theme.containers?.[match] ?? ''
container = ctx.theme.containers?.[match] ?? ''
}

if (container) {
Expand Down
6 changes: 3 additions & 3 deletions packages/preset-mini/src/_variants/data.ts
Expand Up @@ -4,11 +4,11 @@ import { handler as h, variantGetParameter } from '../utils'

export const variantDataAttribute: VariantObject = {
name: 'data',
match(matcher, { theme }: VariantContext<Theme>) {
const variant = variantGetParameter('data-', matcher, [':', '-'])
match(matcher, ctx: VariantContext<Theme>) {
const variant = variantGetParameter('data-', matcher, ctx.generator.config.separators)
if (variant) {
const [match, rest] = variant
const dataAttribute = h.bracket(match) ?? theme.data?.[match] ?? ''
const dataAttribute = h.bracket(match) ?? ctx.theme.data?.[match] ?? ''
if (dataAttribute) {
return {
matcher: rest,
Expand Down
8 changes: 4 additions & 4 deletions packages/preset-mini/src/_variants/default.ts
Expand Up @@ -23,15 +23,15 @@ export const variants = (options: PresetMiniOptions): Variant<Theme>[] => [
variantSelector,
variantInternalLayer,
variantNegative,
variantImportant,
variantImportant(),
variantSupports,
variantPrint,
variantCustomMedia,
variantBreakpoints,
variantBreakpoints(),
...variantCombinators,

variantPseudoClassesAndElements,
variantPseudoClassFunctions,
variantPseudoClassesAndElements(),
variantPseudoClassFunctions(),
...variantTaggedPseudoClasses(options),

partClasses,
Expand Down
49 changes: 27 additions & 22 deletions packages/preset-mini/src/_variants/important.ts
@@ -1,27 +1,32 @@
import type { Variant } from '@unocss/core'
import type { VariantObject } from '@unocss/core'

export const variantImportant: Variant = {
name: 'important',
match(matcher) {
let base: string | undefined
export const variantImportant = (): VariantObject => {
let re: RegExp
return {
name: 'important',
match(matcher, ctx) {
if (!re)
re = new RegExp(`^(important(?:${ctx.generator.config.separators.join('|')})|!)`)

const match = matcher.match(/^(important[:-]|!)/)
if (match)
base = matcher.slice(match[0].length)
else if (matcher.endsWith('!'))
base = matcher.slice(0, -1)
let base: string | undefined
const match = matcher.match(re)
if (match)
base = matcher.slice(match[0].length)
else if (matcher.endsWith('!'))
base = matcher.slice(0, -1)

if (base) {
return {
matcher: base,
body: (body) => {
body.forEach((v) => {
if (v[1])
v[1] += ' !important'
})
return body
},
if (base) {
return {
matcher: base,
body: (body) => {
body.forEach((v) => {
if (v[1])
v[1] += ' !important'
})
return body
},
}
}
}
},
},
}
}