Skip to content

Commit

Permalink
feat(core): support inline css in shortcuts
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Jul 2, 2022
1 parent 7800d39 commit 5c05c4e
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 24 deletions.
41 changes: 23 additions & 18 deletions packages/core/src/generator/index.ts
@@ -1,7 +1,7 @@
import { createNanoEvents } from '../utils/events'
import type { CSSEntries, CSSObject, ExtractorContext, GenerateOptions, GenerateResult, ParsedUtil, PreflightContext, PreparedRule, RawUtil, ResolvedConfig, Rule, RuleContext, RuleMeta, Shortcut, StringifiedUtil, UserConfig, UserConfigDefaults, UtilObject, Variant, VariantContext, VariantHandler, VariantHandlerContext, VariantMatchedResult } from '../types'
import type { CSSEntries, CSSObject, ExtractorContext, GenerateOptions, GenerateResult, ParsedUtil, PreflightContext, PreparedRule, RawUtil, ResolvedConfig, Rule, RuleContext, RuleMeta, Shortcut, ShortcutValue, StringifiedUtil, UserConfig, UserConfigDefaults, UtilObject, Variant, VariantContext, VariantHandler, VariantHandlerContext, VariantMatchedResult } from '../types'
import { resolveConfig } from '../config'
import { CONTROL_SHORTCUT_NO_MERGE, TwoKeyMap, e, entriesToCss, expandVariantGroup, isRawUtil, isStaticShortcut, noop, normalizeCSSEntries, normalizeCSSValues, notNull, uniq, warnOnce } from '../utils'
import { CONTROL_SHORTCUT_NO_MERGE, TwoKeyMap, e, entriesToCss, expandVariantGroup, isRawUtil, isStaticShortcut, isString, noop, normalizeCSSEntries, normalizeCSSValues, notNull, uniq, warnOnce } from '../utils'
import { version } from '../../package.json'
import { LAYER_DEFAULT, LAYER_PREFLIGHTS } from '../constants'

Expand Down Expand Up @@ -134,7 +134,7 @@ export class UnoGenerator {
minify = false,
} = options

const tokens: Readonly<Set<string>> = typeof input === 'string'
const tokens: Readonly<Set<string>> = isString(input)
? await this.applyExtractors(input, id)
: Array.isArray(input)
? new Set(input)
Expand Down Expand Up @@ -323,7 +323,7 @@ export class UnoGenerator {
let handler = v.match(processed, context)
if (!handler)
continue
if (typeof handler === 'string')
if (isString(handler))
handler = { matcher: handler }
processed = handler.matcher
handlers.unshift(handler)
Expand Down Expand Up @@ -394,7 +394,7 @@ export class UnoGenerator {

constructCustomCSS(context: Readonly<RuleContext>, body: CSSObject | CSSEntries, overrideSelector?: string) {
const normalizedBody = normalizeCSSEntries(body)
if (typeof normalizedBody === 'string')
if (isString(normalizedBody))
return normalizedBody

const { selector, entries, parent } = this.applyVariants([0, overrideSelector || context.rawSelector, normalizedBody, undefined, context.variantHandlers])
Expand All @@ -405,7 +405,7 @@ export class UnoGenerator {
}

async parseUtil(input: string | VariantMatchedResult, context: RuleContext, internal = false): Promise<(ParsedUtil | RawUtil)[] | undefined> {
const [raw, processed, variantHandlers] = typeof input === 'string'
const [raw, processed, variantHandlers] = isString(input)
? this.matchVariants(input)
: input

Expand All @@ -424,7 +424,7 @@ export class UnoGenerator {
const index = staticMatch[0]
const entry = normalizeCSSEntries(staticMatch[1])
const meta = staticMatch[2]
if (typeof entry === 'string')
if (isString(entry))
return [[index, entry, meta]]
else
return [[index, raw, entry, meta, variantHandlers]]
Expand Down Expand Up @@ -465,7 +465,7 @@ export class UnoGenerator {
const entries = normalizeCSSValues(result).filter(i => i.length)
if (entries.length) {
return entries.map((e) => {
if (typeof e === 'string')
if (isString(e))
return [i, e, meta]
else
return [i, raw, e, meta, variantHandlers]
Expand Down Expand Up @@ -495,7 +495,7 @@ export class UnoGenerator {
return [parsed[0], selector, body, parent, ruleMeta, this.config.details ? context : undefined]
}

expandShortcut(input: string, context: RuleContext, depth = 5): [string[], RuleMeta | undefined] | undefined {
expandShortcut(input: string, context: RuleContext, depth = 5): [ShortcutValue[], RuleMeta | undefined] | undefined {
if (depth === 0)
return

Expand All @@ -507,7 +507,7 @@ export class UnoGenerator {
: noop

let meta: RuleMeta | undefined
let result: string | string[] | undefined
let result: string | ShortcutValue[] | undefined
for (const s of this.config.shortcuts) {
const unprefixed = s[2]?.prefix ? input.slice(s[2].prefix.length) : input
if (isStaticShortcut(s)) {
Expand All @@ -531,18 +531,18 @@ export class UnoGenerator {
}

// expand nested shotcuts
if (typeof result === 'string')
if (isString(result))
result = expandVariantGroup(result).split(/\s+/g)

// expand nested shortcuts with variants
if (!result) {
const [raw, inputWithoutVariant] = typeof input === 'string'
const [raw, inputWithoutVariant] = isString(input)
? this.matchVariants(input)
: input
if (raw !== inputWithoutVariant) {
const expanded = this.expandShortcut(inputWithoutVariant, context, depth - 1)
if (expanded)
result = expanded[0].map(item => raw.replace(inputWithoutVariant, item))
result = expanded[0].map(item => isString(item) ? raw.replace(inputWithoutVariant, item) : item)
}
}

Expand All @@ -551,7 +551,7 @@ export class UnoGenerator {

return [
result
.flatMap(r => this.expandShortcut(r, context, depth - 1)?.[0] || [r])
.flatMap(r => (isString(r) ? this.expandShortcut(r, context, depth - 1)?.[0] : undefined) || [r])
.filter(Boolean),
meta,
]
Expand All @@ -560,17 +560,22 @@ export class UnoGenerator {
async stringifyShortcuts(
parent: VariantMatchedResult,
context: RuleContext,
expanded: string[],
expanded: ShortcutValue[],
meta: RuleMeta = { layer: this.config.shortcutsLayer },
): Promise<StringifiedUtil[] | undefined> {
const selectorMap = new TwoKeyMap<string, string | undefined, [[CSSEntries, boolean, number][], number]>()
const parsed = (
await Promise.all(uniq(expanded)
.map(async (i) => {
const result = await this.parseUtil(i, context, true)
const result = isString(i)
// rule
? await this.parseUtil(i, context, true) as ParsedUtil[]
// inline CSS value in shortcut
: [[Infinity, '{inline}', normalizeCSSEntries(i), undefined, []] as ParsedUtil]

if (!result)
warnOnce(`unmatched utility "${i}" in shortcut "${parent[1]}"`)
return (result || []) as ParsedUtil[]
return result || []
})))
.flat(1)
.filter(Boolean)
Expand Down Expand Up @@ -618,7 +623,7 @@ export class UnoGenerator {
}

isBlocked(raw: string) {
return !raw || this.config.blocklist.some(e => typeof e === 'string' ? e === raw : e.test(raw))
return !raw || this.config.blocklist.some(e => isString(e) ? e === raw : e.test(raw))
}
}

Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/types.ts
Expand Up @@ -177,13 +177,14 @@ export type DynamicRule<Theme extends {} = {}> = [RegExp, DynamicMatcher<Theme>]
export type StaticRule = [string, CSSObject | CSSEntries] | [string, CSSObject | CSSEntries, RuleMeta]
export type Rule<Theme extends {} = {}> = DynamicRule<Theme> | StaticRule

export type DynamicShortcutMatcher<Theme extends {} = {}> = ((match: RegExpMatchArray, context: Readonly<RuleContext<Theme>>) => (string | string [] | undefined))
export type DynamicShortcutMatcher<Theme extends {} = {}> = ((match: RegExpMatchArray, context: Readonly<RuleContext<Theme>>) => (string | ShortcutValue[] | undefined))

export type StaticShortcut = [string, string | string[]] | [string, string | string[], RuleMeta]
export type StaticShortcutMap = Record<string, string | string[]>
export type StaticShortcut = [string, string | ShortcutValue[]] | [string, string | ShortcutValue[], RuleMeta]
export type StaticShortcutMap = Record<string, string | ShortcutValue[]>
export type DynamicShortcut<Theme extends {} = {}> = [RegExp, DynamicShortcutMatcher<Theme>] | [RegExp, DynamicShortcutMatcher<Theme>, RuleMeta]
export type UserShortcuts<Theme extends {} = {}> = StaticShortcutMap | (StaticShortcut | DynamicShortcut<Theme> | StaticShortcutMap)[]
export type Shortcut<Theme extends {} = {}> = StaticShortcut | DynamicShortcut<Theme>
export type ShortcutValue = string | CSSValue

export type FilterPattern = ReadonlyArray<string | RegExp> | string | RegExp | null

Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/utils/basic.ts
Expand Up @@ -10,3 +10,7 @@ export function mergeSet<T>(target: Set<T>, append: Set<T>): Set<T> {
append.forEach(i => target.add(i))
return target
}

export function isString(s: any): s is string {
return typeof s === 'string'
}
7 changes: 4 additions & 3 deletions packages/core/src/utils/object.ts
@@ -1,7 +1,8 @@
import type { CSSEntries, CSSObject, CSSValue, DeepPartial, Rule, Shortcut, StaticRule, StaticShortcut } from '../types'
import { isString } from './basic'

export function normalizeCSSEntries(obj: string | CSSEntries | CSSObject): string | CSSEntries {
if (typeof obj === 'string')
if (isString(obj))
return obj
return (!Array.isArray(obj) ? Object.entries(obj) : obj).filter(i => i[1] != null)
}
Expand Down Expand Up @@ -101,9 +102,9 @@ export function clone<T>(val: T): T {
}

export function isStaticRule(rule: Rule): rule is StaticRule {
return typeof rule[0] === 'string'
return isString(rule[0])
}

export function isStaticShortcut(sc: Shortcut): sc is StaticShortcut {
return typeof sc[0] === 'string'
return isString(sc[0])
}
8 changes: 8 additions & 0 deletions test/__snapshots__/shortcuts.test.ts.snap
Expand Up @@ -82,6 +82,14 @@ exports[`shortcuts > shortcut of nested pseudo 1`] = `
.hover\\\\:btn3:hover:hover{--un-bg-opacity:1;background-color:rgba(0,0,0,var(--un-bg-opacity));--un-text-opacity:1;color:rgba(21,94,117,var(--un-text-opacity));text-decoration-line:underline;}"
`;

exports[`shortcuts > shortcut with inline body 1`] = `
"/* layer: shortcuts */
.hover\\\\:shortcut-inline-body:hover,
.shortcut-inline-body{padding:0.5rem;margin:3px;}
.shortcut-inline-dynamic-1{padding:0.25rem;margin:1px;}
.shortcut-inline-dynamic-2{padding:0.5rem;margin:2px;}"
`;

exports[`shortcuts > static 1`] = `
"/* layer: shortcuts */
.sh1{margin:0.75rem;padding-left:0.5rem;padding-right:0.5rem;padding-top:0.75rem;padding-bottom:0.75rem;}
Expand Down
12 changes: 12 additions & 0 deletions test/shortcuts.test.ts
Expand Up @@ -28,6 +28,8 @@ describe('shortcuts', () => {
['shortcut-hover-active-1', 'focus:bg-green-300 hover:bg-green-300 active:bg-green-300'],
['shortcut-hover-active-2', 'focus:bg-red-300 hover:bg-yellow-300 active:bg-blue-300'],
['loading', 'animate-spin duration-1000'],
['shortcut-inline-body', ['p2', { margin: '3px' }]],
[/^shortcut-inline-dynamic-(\d)$/, ([,d]) => [`p${d}`, { margin: `${d}px` }]],
],
presets: [
presetUno(),
Expand Down Expand Up @@ -109,4 +111,14 @@ describe('shortcuts', () => {
const { css } = await uno.generate('btn3 focus:btn3 hover:btn3 focus:hover:btn3', { preflights: false })
expect(css).toMatchSnapshot()
})

test('shortcut with inline body', async () => {
const { css } = await uno.generate(`
shortcut-inline-body
hover:shortcut-inline-body
shortcut-inline-dynamic-1
shortcut-inline-dynamic-2
`, { preflights: false })
expect(css).toMatchSnapshot()
})
})

0 comments on commit 5c05c4e

Please sign in to comment.