Skip to content

Commit

Permalink
Merge branch 'main' into fix-1215
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Jul 9, 2022
2 parents ab00722 + f5df08a commit f0be678
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 50 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ By default UnoCSS will scan for components files like: `.jsx`, `.tsx`, `.vue`, `
Sometimes you might want have to use dynamic concatenations like:

```html
<div class="p-${size}"></div>
<div class="p-${size}"></div> <!-- this won't work! -->
```

Due the fact that UnoCSS works in build time using static extracting, at the compile time we can't possibility know all the combination of the utilities. For that, you can configure the `safelist` option.
Expand Down
5 changes: 4 additions & 1 deletion packages/preset-mini/src/rules/color.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Rule } from '@unocss/core'
import { colorResolver, handler as h } from '../utils'
import { numberWithUnitRE } from '../utils/handlers/regex'

/**
* @example op10 op-30 opacity-100
Expand All @@ -12,7 +13,9 @@ export const opacity: Rule[] = [
* @example c-red color-red5 text-red-300
*/
export const textColors: Rule[] = [
[/^(?:text|color|c)-(.+)$/, colorResolver('color', 'text'), { autocomplete: '(text|color|c)-$colors' }],
[/^(?:color|c)-(.+)$/, colorResolver('color', 'text'), { autocomplete: '(text|color|c)-$colors' }],
// auto detection and fallback to font-size if the content looks like a size
[/^text-(.+)$/, colorResolver('color', 'text', css => !css.color?.toString().match(numberWithUnitRE)), { autocomplete: '(text|color|c)-$colors' }],
[/^(?:text|color|c)-op(?:acity)?-?(.+)$/, ([, opacity]) => ({ '--un-text-opacity': h.bracket.percent(opacity) }), { autocomplete: '(text|color|c)-(op|opacity)-<percent>' }],
]

Expand Down
5 changes: 1 addition & 4 deletions packages/preset-mini/src/utils/handlers/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { escapeSelector } from '@unocss/core'
import { globalKeywords } from '../mappings'
import { numberRE, numberWithUnitRE, unitOnlyRE } from './regex'

// Not all, but covers most high frequency attributes
const cssProps = [
Expand All @@ -22,10 +23,6 @@ const cssProps = [
'border-radius',
]

const numberWithUnitRE = /^(-?[0-9.]+)(px|pt|pc|rem|em|%|vh|vw|in|cm|mm|ex|ch|vmin|vmax|cqw|cqh|cqi|cqb|cqmin|cqmax|rpx)?$/i
const numberRE = /^(-?[0-9.]+)$/i
const unitOnlyRE = /^(px)$/i

function round(n: number) {
return n.toFixed(10).replace(/\.0+$/, '').replace(/(\.\d+?)0+$/, '$1')
}
Expand Down
3 changes: 3 additions & 0 deletions packages/preset-mini/src/utils/handlers/regex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const numberWithUnitRE = /^(-?[0-9.]+)(px|pt|pc|rem|em|%|vh|vw|in|cm|mm|ex|ch|vmin|vmax|cqw|cqh|cqi|cqb|cqmin|cqmax|rpx)?$/i
export const numberRE = /^(-?[0-9.]+)$/i
export const unitOnlyRE = /^(px)$/i
99 changes: 60 additions & 39 deletions packages/preset-mini/src/utils/utilities.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CSSEntries, CSSObject, ParsedColorValue, Rule, RuleContext, VariantContext } from '@unocss/core'
import type { CSSEntries, CSSObject, DynamicMatcher, ParsedColorValue, Rule, RuleContext, VariantContext } from '@unocss/core'
import { toArray } from '@unocss/core'
import type { Theme } from '../theme'
import { colorOpacityToString, colorToString, parseCssColor } from './colors'
Expand All @@ -11,22 +11,40 @@ export const CONTROL_MINI_NO_NEGATIVE = '$$mini-no-negative'
* Provide {@link DynamicMatcher} function returning spacing definition. See spacing rules.
*
* @param {string} propertyPrefix - Property for the css value to be created. Postfix will be appended according to direction matched.
* @return {@link DynamicMatcher} object.
* @see {@link directionMap}
*/
export const directionSize = (propertyPrefix: string) => ([_, direction, size]: string[], { theme }: RuleContext<Theme>): CSSEntries | undefined => {
const v = theme.spacing?.[size || 'DEFAULT'] ?? h.bracket.cssvar.global.auto.fraction.rem(size)
if (v != null)
return directionMap[direction].map(i => [`${propertyPrefix}${i}`, v])
export function directionSize(propertyPrefix: string): DynamicMatcher {
return ([_, direction, size]: string[], { theme }: RuleContext<Theme>): CSSEntries | undefined => {
const v = theme.spacing?.[size || 'DEFAULT'] ?? h.bracket.cssvar.global.auto.fraction.rem(size)
if (v != null)
return directionMap[direction].map(i => [`${propertyPrefix}${i}`, v])
}
}

/**
* Obtain color from theme by camel-casing colors.
*/
const getThemeColor = (theme: Theme, colors: string[]) =>
theme.colors?.[
colors.join('-').replace(/(-[a-z])/g, n => n.slice(1).toUpperCase())
]
function getThemeColor(theme: Theme, colors: string[]) {
let obj: Theme['colors'] | string = theme.colors
let index = -1

for (const c of colors) {
index += 1
if (obj && typeof obj !== 'string') {
const camel = colors.slice(index).join('-').replace(/(-[a-z])/g, n => n.slice(1).toUpperCase())
if (obj[camel])
return obj[camel]

if (obj[c]) {
obj = obj[c]
continue
}
}
return undefined
}

return obj
}

/**
* Parse color string into {@link ParsedColorValue} (if possible). Color value will first be matched to theme object before parsing.
Expand All @@ -42,7 +60,7 @@ const getThemeColor = (theme: Theme, colors: string[]) =>
* @param {Theme} theme - {@link Theme} object.
* @return {ParsedColorValue|undefined} {@link ParsedColorValue} object if string is parseable.
*/
export const parseColor = (body: string, theme: Theme): ParsedColorValue | undefined => {
export function parseColor(body: string, theme: Theme): ParsedColorValue | undefined {
const split = body.split(/(?:\/|:)/)
let main, opacity
if (split[0] === '[color') {
Expand Down Expand Up @@ -81,19 +99,22 @@ export const parseColor = (body: string, theme: Theme): ParsedColorValue | undef
if (scale.match(/^\d+$/)) {
no = scale
colorData = getThemeColor(theme, colors.slice(0, -1))
if (!colorData || typeof colorData === 'string')
color = undefined
else
color = colorData[no] as string
}
else {
colorData = getThemeColor(theme, colors)
if (!colorData && colors.length <= 2) {
[, no = no] = colors
colorData = getThemeColor(theme, [name])
}
if (typeof colorData === 'string')
color = colorData
else if (no && colorData)
color = colorData[no] as string
}

if (typeof colorData === 'string')
color = colorData
else if (no && colorData)
color = colorData[no]
}

return {
Expand Down Expand Up @@ -131,35 +152,35 @@ export const parseColor = (body: string, theme: Theme): ParsedColorValue | undef
* @param {string} varName - Base name for the opacity variable.
* @return {@link DynamicMatcher} object.
*/
export const colorResolver = (property: string, varName: string) => ([, body]: string[], { theme }: RuleContext<Theme>): CSSObject | undefined => {
const data = parseColor(body, theme)
export function colorResolver(property: string, varName: string, shouldPass?: (css: CSSObject) => boolean): DynamicMatcher {
return ([, body]: string[], { theme }: RuleContext<Theme>): CSSObject | undefined => {
const data = parseColor(body, theme)

if (!data)
return
if (!data)
return

const { alpha, color, cssColor } = data
const { alpha, color, cssColor } = data

if (cssColor) {
if (alpha != null) {
return {
[property]: colorToString(cssColor, alpha),
const css: CSSObject = {}
if (cssColor) {
if (alpha != null) {
css[property] = colorToString(cssColor, alpha)
}
}
else {
return {
[`--un-${varName}-opacity`]: colorOpacityToString(cssColor),
[property]: colorToString(cssColor, `var(--un-${varName}-opacity)`),
else {
css[`--un-${varName}-opacity`] = colorOpacityToString(cssColor)
css[property] = colorToString(cssColor, `var(--un-${varName}-opacity)`)
}
}
}
else if (color) {
return {
[property]: colorToString(color, alpha),
else if (color) {
css[property] = colorToString(color, alpha)
}

if (shouldPass?.(css) !== false)
return css
}
}

export const colorableShadows = (shadows: string | string[], colorVar: string) => {
export function colorableShadows(shadows: string | string[], colorVar: string) {
const colored = []
shadows = toArray(shadows)
for (let i = 0; i < shadows.length; i++) {
Expand All @@ -175,11 +196,11 @@ export const colorableShadows = (shadows: string | string[], colorVar: string) =
return colored
}

export const hasParseableColor = (color: string | undefined, theme: Theme) => {
export function hasParseableColor(color: string | undefined, theme: Theme) {
return color != null && !!parseColor(color, theme)?.color
}

export const resolveBreakpoints = ({ theme, generator }: Readonly<VariantContext<Theme>>) => {
export function resolveBreakpoints({ theme, generator }: Readonly<VariantContext<Theme>>) {
let breakpoints: Record<string, string> | undefined
if (generator.userConfig && generator.userConfig.theme)
breakpoints = (generator.userConfig.theme as any).breakpoints
Expand All @@ -190,7 +211,7 @@ export const resolveBreakpoints = ({ theme, generator }: Readonly<VariantContext
return breakpoints
}

export const resolveVerticalBreakpoints = ({ theme, generator }: Readonly<VariantContext<Theme>>) => {
export function resolveVerticalBreakpoints({ theme, generator }: Readonly<VariantContext<Theme>>) {
let verticalBreakpoints: Record<string, string> | undefined
if (generator.userConfig && generator.userConfig.theme)
verticalBreakpoints = (generator.userConfig.theme as any).verticalBreakpoints
Expand All @@ -201,7 +222,7 @@ export const resolveVerticalBreakpoints = ({ theme, generator }: Readonly<Varian
return verticalBreakpoints
}

export const makeGlobalStaticRules = (prefix: string, property?: string) => {
export function makeGlobalStaticRules(prefix: string, property?: string) {
return globalKeywords.map(keyword => [`${prefix}-${keyword}`, { [property ?? prefix]: keyword }] as Rule)
}

Expand Down
18 changes: 13 additions & 5 deletions test/__snapshots__/preset-mini.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ exports[`preset-mini > custom var prefix 1`] = `
.scale-100{--hi-scale-x:1;--hi-scale-y:1;transform:translateX(var(--hi-translate-x)) translateY(var(--hi-translate-y)) translateZ(var(--hi-translate-z)) rotate(var(--hi-rotate)) rotateX(var(--hi-rotate-x)) rotateY(var(--hi-rotate-y)) rotateZ(var(--hi-rotate-z)) skewX(var(--hi-skew-x)) skewY(var(--hi-skew-y)) scaleX(var(--hi-scale-x)) scaleY(var(--hi-scale-y)) scaleZ(var(--hi-scale-z));}"
`;

exports[`preset-mini > nested theme colors 1`] = `
"/* layer: default */
.bg-a-b-c{--un-bg-opacity:1;background-color:rgba(81,69,67,var(--un-bg-opacity));}
.text-a-b-c{--un-text-opacity:1;color:rgba(81,69,67,var(--un-text-opacity));}
.text-a-camel-case{--un-text-opacity:1;color:rgba(34,51,68,var(--un-text-opacity));}"
`;

exports[`preset-mini > targets 1`] = `
"/* layer: preflights */
*,::before,::after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-ring-offset-shadow:0 0 rgba(0,0,0,0);--un-ring-shadow:0 0 rgba(0,0,0,0);--un-shadow-inset: ;--un-shadow:0 0 rgba(0,0,0,0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgba(147,197,253,0.5);}::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-ring-offset-shadow:0 0 rgba(0,0,0,0);--un-ring-shadow:0 0 rgba(0,0,0,0);--un-shadow-inset: ;--un-shadow:0 0 rgba(0,0,0,0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgba(147,197,253,0.5);}
Expand Down Expand Up @@ -279,6 +286,8 @@ div:hover .group-\\\\[div\\\\:hover\\\\]-\\\\[combinator\\\\:test-4\\\\]{combina
.peer:is(:placeholder-shown)~.peer-is-placeholder-shown\\\\:text-3xl,
.peer:not(:placeholder-shown)~.peer-not-placeholder-shown\\\\:text-3xl{font-size:1.875rem;line-height:2.25rem;}
.peer:not(:placeholder-shown)~.peer-not-placeholder-shown\\\\:text-2xl{font-size:1.5rem;line-height:2rem;}
.text-\\\\[100px\\\\]{font-size:100px;}
.text-\\\\[2em\\\\],
.text-\\\\[length\\\\:2em\\\\],
.text-2em,
.text-size-\\\\[2em\\\\]{font-size:2em;}
Expand Down Expand Up @@ -396,8 +405,6 @@ div:hover .group-\\\\[div\\\\:hover\\\\]-\\\\[combinator\\\\:test-4\\\\]{combina
.c-\\\\$color-variable,
.c-\\\\$color-variable\\\\/\\\\$opacity-variable,
.c-\\\\$color-variable\\\\/10{color:var(--color-variable);}
.checked\\\\:next\\\\:hover\\\\:text-slate-500:hover+*:checked{--un-text-opacity:1;color:rgba(100,116,139,var(--un-text-opacity));}
.checked\\\\:next\\\\:text-slate-100+*:checked{--un-text-opacity:1;color:rgba(241,245,249,var(--un-text-opacity));}
.color-\\\\$red{color:var(--red);}
.color-blue,
.color-blue-400{--un-text-opacity:1;color:rgba(96,165,250,var(--un-text-opacity));}
Expand All @@ -414,16 +421,17 @@ div:hover .group-\\\\[div\\\\:hover\\\\]-\\\\[combinator\\\\:test-4\\\\]{combina
.in-range\\\\:color-pink-100:in-range,
.open\\\\:color-pink-100[open],
.out-of-range\\\\:color-pink-100:out-of-range{--un-text-opacity:1;color:rgba(252,231,243,var(--un-text-opacity));}
.next\\\\:checked\\\\:children\\\\:text-slate-600>*:checked+*{--un-text-opacity:1;color:rgba(71,85,105,var(--un-text-opacity));}
.next\\\\:checked\\\\:text-slate-200:checked+*{--un-text-opacity:1;color:rgba(226,232,240,var(--un-text-opacity));}
.placeholder-color-red-1::placeholder,
.text-red-100,
.text-red100{--un-text-opacity:1;color:rgba(254,226,226,var(--un-text-opacity));}
.placeholder-shown-color-transparent:placeholder-shown,
.placeholder\\\\:color-transparent::placeholder{color:transparent;}
.selection\\\\:color-\\\\[var\\\\(--select-color\\\\)\\\\]::selection{color:var(--select-color);}
.checked\\\\:next\\\\:hover\\\\:text-slate-500:hover+*:checked{--un-text-opacity:1;color:rgba(100,116,139,var(--un-text-opacity));}
.checked\\\\:next\\\\:text-slate-100+*:checked{--un-text-opacity:1;color:rgba(241,245,249,var(--un-text-opacity));}
.next\\\\:checked\\\\:children\\\\:text-slate-600>*:checked+*{--un-text-opacity:1;color:rgba(71,85,105,var(--un-text-opacity));}
.next\\\\:checked\\\\:text-slate-200:checked+*{--un-text-opacity:1;color:rgba(226,232,240,var(--un-text-opacity));}
.text-\\\\[\\\\#124\\\\]{--un-text-opacity:1;color:rgba(17,34,68,var(--un-text-opacity));}
.text-\\\\[2em\\\\]{color:2em;}
.text-\\\\[calc\\\\(1em-1px\\\\)\\\\]{color:calc(1em - 1px);}
.text-\\\\[color\\\\:var\\\\(--color-x\\\\)\\\\]\\\\:\\\\[trick\\\\]{color:var(--color-x);}
.text-\\\\[color\\\\:var\\\\(--color\\\\)\\\\],
Expand Down
1 change: 1 addition & 0 deletions test/assets/preset-mini-targets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ export const presetMiniTargets: string[] = [
'text-[var(--color)]',
'text-[#124]',
'text-[2em]',
'text-[100px]',
'text-[calc(1em-1px)]',
'text-[length:var(--size)]',
'text-[length:2em]',
Expand Down
17 changes: 17 additions & 0 deletions test/preset-mini.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ const uno = createGenerator({
a: 'var(--custom)',
b: 'rgba(var(--custom), %alpha)',
},
a: {
b: {
c: '#514543',
},
camelCase: '#234',
},
},
},
})
Expand Down Expand Up @@ -62,6 +68,17 @@ describe('preset-mini', () => {
expect(css).toMatchSnapshot()
})

test('nested theme colors', async () => {
const { css, matched } = await uno.generate([
'text-a-b-c',
'text-a-camel-case',
'bg-a-b-c',
], { preflights: false })

expect(css).toMatchSnapshot('')
expect(matched.size).toBe(3)
})

test('none targets', async () => {
const { css, matched } = await uno.generate(new Set(presetMiniNonTargets), { minify: true, preflights: false })

Expand Down

0 comments on commit f0be678

Please sign in to comment.