diff --git a/packages/preset-mini/src/rules/color.ts b/packages/preset-mini/src/rules/color.ts index d2cec8a929..78b9438ad8 100644 --- a/packages/preset-mini/src/rules/color.ts +++ b/packages/preset-mini/src/rules/color.ts @@ -1,5 +1,6 @@ import type { Rule } from '@unocss/core' import { colorResolver, handler as h } from '../utils' +import { numberWithUnitRE } from '../utils/handlers/handlers' /** * @example op10 op-30 opacity-100 @@ -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)-' }], ] diff --git a/packages/preset-mini/src/utils/handlers/handlers.ts b/packages/preset-mini/src/utils/handlers/handlers.ts index f3e069a45f..8768eaf220 100644 --- a/packages/preset-mini/src/utils/handlers/handlers.ts +++ b/packages/preset-mini/src/utils/handlers/handlers.ts @@ -22,9 +22,9 @@ 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 +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 function round(n: number) { return n.toFixed(10).replace(/\.0+$/, '').replace(/(\.\d+?)0+$/, '$1') diff --git a/packages/preset-mini/src/utils/utilities.ts b/packages/preset-mini/src/utils/utilities.ts index 30dac481f9..ebc05472eb 100644 --- a/packages/preset-mini/src/utils/utilities.ts +++ b/packages/preset-mini/src/utils/utilities.ts @@ -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' @@ -11,19 +11,20 @@ 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): 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): 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[]) => { +function getThemeColor(theme: Theme, colors: string[]) { let obj: Theme['colors'] | string = theme.colors let index = -1 @@ -59,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') { @@ -151,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): CSSObject | undefined => { - const data = parseColor(body, theme) +export function colorResolver(property: string, varName: string, shouldPass?: (css: CSSObject) => boolean): DynamicMatcher { + return ([, body]: string[], { theme }: RuleContext): 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++) { @@ -195,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>) => { +export function resolveBreakpoints({ theme, generator }: Readonly>) { let breakpoints: Record | undefined if (generator.userConfig && generator.userConfig.theme) breakpoints = (generator.userConfig.theme as any).breakpoints @@ -210,7 +211,7 @@ export const resolveBreakpoints = ({ theme, generator }: Readonly>) => { +export function resolveVerticalBreakpoints({ theme, generator }: Readonly>) { let verticalBreakpoints: Record | undefined if (generator.userConfig && generator.userConfig.theme) verticalBreakpoints = (generator.userConfig.theme as any).verticalBreakpoints @@ -221,7 +222,7 @@ export const resolveVerticalBreakpoints = ({ theme, generator }: Readonly { +export function makeGlobalStaticRules(prefix: string, property?: string) { return globalKeywords.map(keyword => [`${prefix}-${keyword}`, { [property ?? prefix]: keyword }] as Rule) } diff --git a/test/__snapshots__/preset-mini.test.ts.snap b/test/__snapshots__/preset-mini.test.ts.snap index d59156ad4a..ece9a09915 100644 --- a/test/__snapshots__/preset-mini.test.ts.snap +++ b/test/__snapshots__/preset-mini.test.ts.snap @@ -286,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;} @@ -403,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));} @@ -421,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\\\\)\\\\], diff --git a/test/assets/preset-mini-targets.ts b/test/assets/preset-mini-targets.ts index 960bf6ef67..6698f8289e 100644 --- a/test/assets/preset-mini-targets.ts +++ b/test/assets/preset-mini-targets.ts @@ -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]',