Skip to content

Commit

Permalink
fix: shortcuts will now respect any layer variants give them (#3592)
Browse files Browse the repository at this point in the history
  • Loading branch information
RebeccaStevens committed Mar 14, 2024
1 parent b798da0 commit 2ffbd01
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 27 deletions.
60 changes: 33 additions & 27 deletions packages/core/src/generator/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createNanoEvents } from '../utils/events'
import type { CSSEntries, CSSObject, DynamicRule, ExtendedTokenInfo, ExtractorContext, GenerateOptions, GenerateResult, ParsedUtil, PreflightContext, PreparedRule, RawUtil, ResolvedConfig, RuleContext, RuleMeta, Shortcut, ShortcutValue, StringifiedUtil, UserConfig, UserConfigDefaults, UtilObject, Variant, VariantContext, VariantHandler, VariantHandlerContext, VariantMatchedResult } from '../types'
import { resolveConfig } from '../config'
import { CONTROL_SHORTCUT_NO_MERGE, CountableSet, TwoKeyMap, e, entriesToCss, expandVariantGroup, isCountableSet, isRawUtil, isStaticShortcut, isString, noop, normalizeCSSEntries, normalizeCSSValues, notNull, toArray, uniq, warnOnce } from '../utils'
import { BetterMap, CONTROL_SHORTCUT_NO_MERGE, CountableSet, TwoKeyMap, e, entriesToCss, expandVariantGroup, isCountableSet, isRawUtil, isStaticShortcut, isString, noop, normalizeCSSEntries, normalizeCSSValues, notNull, toArray, uniq, warnOnce } from '../utils'
import { version } from '../../package.json'
import { LAYER_DEFAULT, LAYER_PREFLIGHTS } from '../constants'

Expand Down Expand Up @@ -674,7 +674,8 @@ export class UnoGenerator<Theme extends object = object> {
expanded: ShortcutValue[],
meta: RuleMeta = { layer: this.config.shortcutsLayer },
): Promise<StringifiedUtil<Theme>[] | undefined> {
const selectorMap = new TwoKeyMap<string, string | undefined, [[CSSEntries, boolean, number][], number]>()
const layerMap = new BetterMap<string | undefined, TwoKeyMap<string, string | undefined, [[CSSEntries, boolean, number][], number]>>()

const parsed = (
await Promise.all(uniq(expanded)
.map(async (i) => {
Expand All @@ -699,38 +700,43 @@ export class UnoGenerator<Theme extends object = object> {
rawStringifiedUtil.push([item[0], undefined, item[1], undefined, item[2], context, undefined])
continue
}
const { selector, entries, parent, sort, noMerge } = this.applyVariants(item, [...item[4], ...parentVariants], raw)
const { selector, entries, parent, sort, noMerge, layer } = this.applyVariants(item, [...item[4], ...parentVariants], raw)

// find existing layer and merge
const selectorMap = layerMap.getFallback(layer ?? meta.layer, new TwoKeyMap())
// find existing selector/mediaQuery pair and merge
const mapItem = selectorMap.getFallback(selector, parent, [[], item[0]])
// add entries
mapItem[0].push([entries, !!(noMerge ?? item[3]?.noMerge), sort ?? 0])
}
return rawStringifiedUtil.concat(selectorMap
.map(([e, index], selector, joinedParents) => {
const stringify = (flatten: boolean, noMerge: boolean, entrySortPair: [CSSEntries, number][]): (StringifiedUtil<Theme> | undefined)[] => {
const maxSort = Math.max(...entrySortPair.map(e => e[1]))
const entriesList = entrySortPair.map(e => e[0])
return (flatten ? [entriesList.flat(1)] : entriesList).map((entries: CSSEntries): StringifiedUtil<Theme> | undefined => {
const body = entriesToCss(entries)
if (body)
return [index, selector, body, joinedParents, { ...meta, noMerge, sort: maxSort }, context, undefined]
return undefined
return rawStringifiedUtil.concat(layerMap
.flatMap((selectorMap, layer) =>
selectorMap
.map(([e, index], selector, joinedParents) => {
const stringify = (flatten: boolean, noMerge: boolean, entrySortPair: [CSSEntries, number][]): (StringifiedUtil<Theme> | undefined)[] => {
const maxSort = Math.max(...entrySortPair.map(e => e[1]))
const entriesList = entrySortPair.map(e => e[0])
return (flatten ? [entriesList.flat(1)] : entriesList).map((entries: CSSEntries): StringifiedUtil<Theme> | undefined => {
const body = entriesToCss(entries)
if (body)
return [index, selector, body, joinedParents, { ...meta, noMerge, sort: maxSort, layer }, context, undefined]
return undefined
})
}

const merges = [
[e.filter(([, noMerge]) => noMerge).map(([entries,, sort]) => [entries, sort]), true],
[e.filter(([, noMerge]) => !noMerge).map(([entries,, sort]) => [entries, sort]), false],
] as [[CSSEntries, number][], boolean][]

return merges.map(([e, noMerge]) => [
...stringify(false, noMerge, e.filter(([entries]) => entries.some(entry => entry[0] === CONTROL_SHORTCUT_NO_MERGE))),
...stringify(true, noMerge, e.filter(([entries]) => entries.every(entry => entry[0] !== CONTROL_SHORTCUT_NO_MERGE))),
])
})
}

const merges = [
[e.filter(([, noMerge]) => noMerge).map(([entries,, sort]) => [entries, sort]), true],
[e.filter(([, noMerge]) => !noMerge).map(([entries,, sort]) => [entries, sort]), false],
] as [[CSSEntries, number][], boolean][]

return merges.map(([e, noMerge]) => [
...stringify(false, noMerge, e.filter(([entries]) => entries.some(entry => entry[0] === CONTROL_SHORTCUT_NO_MERGE))),
...stringify(true, noMerge, e.filter(([entries]) => entries.every(entry => entry[0] !== CONTROL_SHORTCUT_NO_MERGE))),
])
})
.flat(2)
.filter(Boolean) as StringifiedUtil<Theme>[])
.flat(2)
.filter(Boolean) as StringifiedUtil<Theme>[],
))
}

isBlocked(raw: string): boolean {
Expand Down
17 changes: 17 additions & 0 deletions packages/core/src/utils/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,28 @@ export class TwoKeyMap<K1, K2, V> {
}

export class BetterMap<K, V> extends Map<K, V> {
getFallback(key: K, fallback: V): V {
const v = this.get(key)
if (v === undefined) {
this.set(key, fallback)
return fallback
}
return v
}

map<R>(mapFn: (value: V, key: K) => R): R[] {
const result: R[] = []
this.forEach((v, k) => {
result.push(mapFn(v, k))
})
return result
}

flatMap<R extends readonly unknown[]>(mapFn: (value: V, key: K) => R): R[number][] {
const result: R[number][] = []
this.forEach((v, k) => {
result.push(...mapFn(v, k))
})
return result
}
}
16 changes: 16 additions & 0 deletions test/assets/output/shortcuts-layer.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* layer: shortcuts */
.sh2{font-size:0.875rem;line-height:1.25rem;font-size:1.125rem;line-height:1.75rem;}
.sh2:hover{font-size:1.125rem;line-height:1.75rem;}
/* layer: l1 */
.uno-layer-l1\:sh1{margin:0.75rem;padding-left:0.5rem;padding-right:0.5rem;padding-top:0.75rem;padding-bottom:0.75rem;}
@media (min-width: 640px){
.uno-layer-l1\:sh1{margin:0.5rem;}
}
/* layer: l2 */
.focus\:uno-layer-l2\:sh2:focus{font-size:0.875rem;line-height:1.25rem;font-size:1.125rem;line-height:1.75rem;}
.focus\:uno-layer-l2\:sh2:hover:focus{font-size:1.125rem;line-height:1.75rem;}
/* layer: l3 */
.uno-layer-l3\:sh3{margin:0.75rem;}
@media (min-width: 640px){
.uno-layer-l3\:sh3{margin:0.5rem;}
}
5 changes: 5 additions & 0 deletions test/shortcuts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,4 +204,9 @@ describe('shortcuts', () => {
.space-x-2px>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(2px * calc(1 - var(--un-space-x-reverse)));margin-right:calc(2px * var(--un-space-x-reverse));}"
`)
})

it('layer', async () => {
const { css } = await uno.generate('uno-layer-l1:sh1 sh2 focus:uno-layer-l2:sh2 uno-layer-l3:sh3', { preflights: false })
await expect(css).toMatchFileSnapshot('./assets/output/shortcuts-layer.css')
})
})

0 comments on commit 2ffbd01

Please sign in to comment.