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(core)!: support string in rule that returns array #1093

Merged
merged 5 commits into from Jun 12, 2022
Merged
Show file tree
Hide file tree
Changes from 3 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
66 changes: 47 additions & 19 deletions packages/core/src/generator/index.ts
@@ -1,5 +1,5 @@
import { createNanoEvents } from '../utils/events'
import type { CSSEntries, CSSObject, ExtractorContext, GenerateOptions, GenerateResult, ParsedUtil, PreflightContext, RawUtil, ResolvedConfig, Rule, RuleContext, RuleMeta, Shortcut, StringifiedUtil, UserConfig, UserConfigDefaults, UtilObject, Variant, VariantContext, VariantHandler, VariantMatchedResult } from '../types'
import type { CSSEntries, CSSObject, ExtractorContext, GenerateOptions, GenerateResult, ParsedUtil, PreflightContext, PreparedRule, RawUtil, ResolvedConfig, Rule, RuleContext, RuleMeta, Shortcut, StringifiedUtil, UserConfig, UserConfigDefaults, UtilObject, Variant, VariantContext, VariantHandler, 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 { version } from '../../package.json'
Expand Down Expand Up @@ -219,28 +219,44 @@ export class UnoGenerator {
.sort((a, b) => ((this.parentOrders.get(a[0]) ?? 0) - (this.parentOrders.get(b[0]) ?? 0)) || a[0]?.localeCompare(b[0] || '') || 0)
.map(([parent, items]) => {
const size = items.length
const sorted = items
const sorted: PreparedRule[] = items
.filter(i => (i[4]?.layer || 'default') === layer)
.sort((a, b) => a[0] - b[0] || (a[4]?.sort || 0) - (b[4]?.sort || 0) || a[1]?.localeCompare(b[1] || '') || 0)
.map(a => [a[1] ? applyScope(a[1], scope) : a[1], a[2], !!a[4]?.noMerge])
.map(a => [a[0] == null ? a[0] : [a[0]], a[1], a[2]]) as [string[] | undefined, string, boolean][]
.sort((a, b) => a[0] - b[0] || (a[4]?.sort || 0) - (b[4]?.sort || 0) || a[1]?.localeCompare(b[1] || '') || a[2]?.localeCompare(b[2] || '') || 0)
.map(([, selector, body,, meta]) => {
const scopedSelector = selector ? applyScope(selector, scope) : selector
return [
[[scopedSelector ?? '', meta?.sort ?? 0]],
body,
!!meta?.noMerge,
]
})
if (!sorted.length)
return undefined
const rules = sorted
.reverse()
.map(([selector, body, noMerge], idx) => {
if (!noMerge && selector && this.config.mergeSelectors) {
.map(([selectorSortPair, body, noMerge], idx) => {
if (!noMerge && this.config.mergeSelectors) {
// search for rules that has exact same body, and merge them
for (let i = idx + 1; i < size; i++) {
const current = sorted[i]
if (current && !current[2] && current[0] && current[1] === body) {
current[0].push(...selector)
if (current && !current[2] && ((selectorSortPair && current[0]) || (selectorSortPair == null && current[0] == null)) && current[1] === body) {
if (selectorSortPair && current[0])
current[0].push(...selectorSortPair)
return null
}
}
}
return selector
? `${[...new Set(selector)].join(`,${nl}`)}{${body}}`

const selectors = selectorSortPair
? [...new Set(selectorSortPair
.sort((a, b) => a[1] - b[1] || a[0]?.localeCompare(b[0] || '') || 0)
.map(pair => pair[0])
.filter(Boolean),
)]
: []

return selectors.length
? `${selectors.join(`,${nl}`)}{${body}}`
: body
})
.filter(Boolean)
Expand Down Expand Up @@ -341,16 +357,18 @@ export class UnoGenerator {
}

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

const { selector, entries, parent } = this.applyVariants([0, overrideSelector || context.rawSelector, body, undefined, context.variantHandlers])
const { selector, entries, parent } = this.applyVariants([0, overrideSelector || context.rawSelector, normalizedBody, undefined, context.variantHandlers])
const cssBody = `${selector}{${entriesToCss(entries)}}`
if (parent)
return `${parent}{${cssBody}}`
return cssBody
}

async parseUtil(input: string | VariantMatchedResult, context: RuleContext, internal = false): Promise<ParsedUtil[] | RawUtil[] | undefined> {
async parseUtil(input: string | VariantMatchedResult, context: RuleContext, internal = false): Promise<(ParsedUtil | RawUtil)[] | undefined> {
const [raw, processed, variantHandlers] = typeof input === 'string'
? this.matchVariants(input)
: input
Expand All @@ -367,7 +385,13 @@ export class UnoGenerator {
if (staticMatch) {
if (staticMatch[1] && (internal || !staticMatch[2]?.internal)) {
recordRule(staticMatch[3])
return [[staticMatch[0], raw, normalizeCSSEntries(staticMatch[1]), staticMatch[2], variantHandlers]]
const index = staticMatch[0]
const entry = normalizeCSSEntries(staticMatch[1])
const meta = staticMatch[2]
if (typeof entry === 'string')
return [[index, entry, meta]]
else
return [[index, raw, entry, meta, variantHandlers]]
}
}

Expand Down Expand Up @@ -399,11 +423,15 @@ export class UnoGenerator {

recordRule(rule)

if (typeof result === 'string')
return [[i, result, meta]]
const entries = normalizeCSSValues(result).filter(i => i.length)
if (entries.length)
return entries.map(e => [i, raw, e, meta, variantHandlers])
if (entries.length) {
return entries.map((e) => {
if (typeof e === 'string')
return [i, e, meta]
else
return [i, raw, e, meta, variantHandlers]
})
}
}
}

Expand Down
11 changes: 9 additions & 2 deletions packages/core/src/types.ts
Expand Up @@ -164,9 +164,10 @@ export interface RuleMeta {
internal?: boolean
}

export type CSSValues = CSSObject | CSSEntries | (CSSObject | CSSEntries)[]
export type CSSValue = CSSObject | CSSEntries
export type CSSValues = CSSValue | CSSValue[]

export type DynamicMatcher<Theme extends {} = {}> = ((match: RegExpMatchArray, context: Readonly<RuleContext<Theme>>) => Awaitable<CSSValues | string | undefined>)
export type DynamicMatcher<Theme extends {} = {}> = ((match: RegExpMatchArray, context: Readonly<RuleContext<Theme>>) => Awaitable<CSSValue | string | (CSSValue | string)[] | undefined>)
export type DynamicRule<Theme extends {} = {}> = [RegExp, DynamicMatcher<Theme>] | [RegExp, DynamicMatcher<Theme>, RuleMeta]
export type StaticRule = [string, CSSObject | CSSEntries] | [string, CSSObject | CSSEntries, RuleMeta]
export type Rule<Theme extends {} = {}> = DynamicRule<Theme> | StaticRule
Expand Down Expand Up @@ -599,6 +600,12 @@ export type StringifiedUtil = readonly [
context: RuleContext | undefined,
]

export type PreparedRule = readonly [
selector: [string, number][],
body: string,
noMerge: boolean,
]

export interface UtilObject {
selector: string
entries: CSSEntries
Expand Down
12 changes: 7 additions & 5 deletions packages/core/src/utils/object.ts
@@ -1,16 +1,18 @@
import type { CSSEntries, CSSObject, CSSValues, DeepPartial, Rule, Shortcut, StaticRule, StaticShortcut } from '../types'
import type { CSSEntries, CSSObject, CSSValue, DeepPartial, Rule, Shortcut, StaticRule, StaticShortcut } from '../types'

export function normalizeCSSEntries(obj: CSSEntries | CSSObject): CSSEntries {
export function normalizeCSSEntries(obj: string | CSSEntries | CSSObject): string | CSSEntries {
if (typeof obj === 'string')
return obj
return (!Array.isArray(obj) ? Object.entries(obj) : obj).filter(i => i[1] != null)
}

export function normalizeCSSValues(obj: CSSValues): CSSEntries[] {
export function normalizeCSSValues(obj: CSSValue | string | (CSSValue | string)[]): (string | CSSEntries)[] {
if (Array.isArray(obj)) {
// @ts-expect-error type cast
if (obj.find(i => !Array.isArray(i) || Array.isArray(i[0])))
return (obj as any).map((i: any) => normalizeCSSEntries(i))
return (obj as (string | CSSValue)[]).map(i => normalizeCSSEntries(i))
else
return [obj as any]
return [obj as CSSEntries]
}
else {
return [normalizeCSSEntries(obj)]
Expand Down
23 changes: 16 additions & 7 deletions packages/preset-wind/src/rules/animation.ts
Expand Up @@ -3,21 +3,30 @@ import { handler as h } from '@unocss/preset-mini/utils'
import type { Theme } from '@unocss/preset-mini'

export const animations: Rule<Theme>[] = [
[/^(?:animate-)?keyframes-(.+)$/, ([, name], { theme, constructCSS }) => {
[/^(?:animate-)?keyframes-(.+)$/, ([, name], { theme }) => {
const kf = theme.animation?.keyframes?.[name]
if (kf)
return `@keyframes ${name}${kf}\n${constructCSS({ animation: `${name}` })}`
if (kf) {
return [
`@keyframes ${name}${kf}`,
{ animation: name },
]
}
}, { autocomplete: ['animate-keyframes-$animation.keyframes', 'keyframes-$animation.keyframes'] }],

[/^animate-(.+)$/, ([, name], { theme, constructCSS }) => {
[/^animate-(.+)$/, ([, name], { theme }) => {
const kf = theme.animation?.keyframes?.[name]
if (kf) {
const duration = theme.animation?.durations?.[name] ?? '1s'
const timing = theme.animation?.timingFns?.[name] ?? 'linear'
const props = theme.animation?.properties?.[name]
const count = theme.animation?.counts?.[name] ?? 1
return `@keyframes ${name}${kf}\n${constructCSS(
Object.assign({ animation: `${name} ${duration} ${timing} ${count}` }, props))}`
const props = theme.animation?.properties?.[name]
return [
`@keyframes ${name}${kf}`,
{
animation: `${name} ${duration} ${timing} ${count}`,
...props,
},
]
}
return { animation: h.bracket.cssvar(name) }
}, { autocomplete: 'animate-$animation.keyframes' }],
Expand Down
2 changes: 1 addition & 1 deletion test/__snapshots__/layer.test.ts.snap
Expand Up @@ -10,8 +10,8 @@ exports[`layers > static 1`] = `
/* layer: c */
.c5{name:5;}
/* layer: d */
/* RAW 4 */
/* RAW 3 */
/* RAW 4 */
/* layer: s */
.abcd{name:bar1;name:bar2;name:2;}"
`;
20 changes: 10 additions & 10 deletions test/__snapshots__/preset-attributify.test.ts.snap
Expand Up @@ -41,37 +41,37 @@ exports[`attributify > autocomplete extractor 5`] = `
exports[`attributify > compatible with full controlled rules 1`] = `
"/* layer: default */

[custom~=\\"\\\\31 \\"] {
font-size: 1px;
.custom-2 {
font-size: 2px;
}
/* you can have multiple rules */
[custom~=\\"\\\\31 \\"]::after {
.custom-2::after {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems not correct 🤔

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the breaking change noted above. The result was swapped with its second result to its bottom.

That particular rule is based on string return, so it will not have selector in StringifiedUtil hence now sorted by its body. Before this PR this condition is not checked and returned as is as css value. That portion of rule start with the captured token used as selector:

[/^custom-(\d+)$/, ([_, value], { rawSelector }) => {
// return a string instead of an object
const selector = e(rawSelector)
return `
${selector} {

The string to extract was <div custom="1" class="custom-2"> yielding [custom~="1"] and .custom-2. Sorting them leads to . < [ hence the swapped result.

content: 'after';
}
.foo > [custom~=\\"\\\\31 \\"] {
.foo > .custom-2 {
color: red;
}
/* or media queries */
@media (min-width: 680px) {
[custom~=\\"\\\\31 \\"] {
.custom-2 {
font-size: 16px;
}
}


.custom-2 {
font-size: 2px;
[custom~=\\"\\\\31 \\"] {
font-size: 1px;
}
/* you can have multiple rules */
.custom-2::after {
[custom~=\\"\\\\31 \\"]::after {
content: 'after';
}
.foo > .custom-2 {
.foo > [custom~=\\"\\\\31 \\"] {
color: red;
}
/* or media queries */
@media (min-width: 680px) {
.custom-2 {
[custom~=\\"\\\\31 \\"] {
font-size: 16px;
}
}
Expand Down
12 changes: 6 additions & 6 deletions test/__snapshots__/preset-mini.test.ts.snap
Expand Up @@ -409,10 +409,10 @@ div:hover .group-\\\\[div\\\\:hover\\\\]-\\\\[combinator\\\\:test-4\\\\]{combina
.text-rose{--un-text-opacity:1;color:rgba(251,113,133,var(--un-text-opacity));}
.placeholder-color-opacity-60::placeholder{--un-text-opacity:0.6;}
.text-opacity-\\\\[13\\\\.3333333\\\\%\\\\]{--un-text-opacity:13.3333333%;}
.italic,
.font-italic{font-style:italic;}
.oblique,
.font-oblique{font-style:oblique;}
.font-italic,
.italic{font-style:italic;}
.font-oblique,
.oblique{font-style:oblique;}
.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-smoothing:grayscale;}
.shadow{--un-shadow:var(--un-shadow-inset) 0 1px 3px 0 var(--un-shadow-color, rgba(0,0,0,0.1)),var(--un-shadow-inset) 0 1px 2px -1px var(--un-shadow-color, rgba(0,0,0,0.1));box-shadow:var(--un-ring-offset-shadow, 0 0 #0000), var(--un-ring-shadow, 0 0 #0000), var(--un-shadow);}
.shadow-none{--un-shadow:0 0 var(--un-shadow-color, rgba(0,0,0,0));box-shadow:var(--un-ring-offset-shadow, 0 0 #0000), var(--un-ring-shadow, 0 0 #0000), var(--un-shadow);}
Expand Down Expand Up @@ -678,8 +678,8 @@ div:hover .group-\\\\[div\\\\:hover\\\\]-\\\\[combinator\\\\:test-4\\\\]{combina
.float-none{float:none;}
.clear-both{clear:both;}
.clear-none{clear:none;}
.z0,
.z-0{z-index:0;}
.z-0,
.z0{z-index:0;}
.-z-1{z-index:-1;}
.z-\\\\$variable{z-index:var(--variable);}
.z-1{z-index:1;}
Expand Down
20 changes: 8 additions & 12 deletions test/__snapshots__/preset-wind.test.ts.snap
Expand Up @@ -66,23 +66,19 @@ exports[`preset-wind > targets 1`] = `
.table-auto{table-layout:auto;}
.table-empty-cells-visible{empty-cells:show;}
.table-empty-cells-hidden{empty-cells:hide;}
@keyframes pulse-alt{from{transform:scale3d(1,1,1)}50%{transform:scale3d(1.05,1.05,1.05)}to{transform:scale3d(1,1,1)}}
@keyframes pulse{0%, 100% {opacity:1} 50% {opacity:.5}}
.animate-keyframes-pulse,
.keyframes-pulse{animation:pulse;}
@keyframes pulse-alt{from{transform:scale3d(1,1,1)}50%{transform:scale3d(1.05,1.05,1.05)}to{transform:scale3d(1,1,1)}}
.animate-keyframes-pulse-alt,
.keyframes-pulse-alt{animation:pulse-alt;}
@keyframes pulse{0%, 100% {opacity:1} 50% {opacity:.5}}
.animate-keyframes-pulse{animation:pulse;}
@keyframes pulse-alt{from{transform:scale3d(1,1,1)}50%{transform:scale3d(1.05,1.05,1.05)}to{transform:scale3d(1,1,1)}}
.animate-keyframes-pulse-alt{animation:pulse-alt;}
@keyframes pulse{0%, 100% {opacity:1} 50% {opacity:.5}}
.animate-pulse{animation:pulse 2s cubic-bezier(0.4,0,.6,1) infinite;}
@keyframes pulse-alt{from{transform:scale3d(1,1,1)}50%{transform:scale3d(1.05,1.05,1.05)}to{transform:scale3d(1,1,1)}}
.animate-pulse-alt{animation:pulse-alt 1s ease-in-out infinite;}
.animate-\\\\[4s_linear_0s_infinite_alternate_move\\\\\\\\_eye\\\\]{animation:4s linear 0s infinite alternate move\\\\_eye;}
.animate-\\\\$variable{animation:var(--variable);}
@keyframes bounce{0%, 100% {transform:translateY(-25%);animation-timing-function:cubic-bezier(0.8,0,1,1)} 50% {transform:translateY(0);animation-timing-function:cubic-bezier(0,0,0.2,1)}}
@keyframes ping{0%{transform:scale(1);opacity:1}75%,100%{transform:scale(2);opacity:0}}
.\\\\!animate-ping{animation:ping 1s cubic-bezier(0,0,.2,1) infinite !important;}
@keyframes bounce{0%, 100% {transform:translateY(-25%);animation-timing-function:cubic-bezier(0.8,0,1,1)} 50% {transform:translateY(0);animation-timing-function:cubic-bezier(0,0,0.2,1)}}
.animate-\\\\[4s_linear_0s_infinite_alternate_move\\\\\\\\_eye\\\\]{animation:4s linear 0s infinite alternate move\\\\_eye;}
.animate-\\\\$variable{animation:var(--variable);}
.animate-pulse{animation:pulse 2s cubic-bezier(0.4,0,.6,1) infinite;}
.animate-pulse-alt{animation:pulse-alt 1s ease-in-out infinite;}
.hover\\\\:animate-bounce:hover{animation:bounce 1s linear infinite;}
.animate-name-move{animation-name:move;}
.animate-duration-\\\\$variable{animation-duration:var(--variable);}
Expand Down
4 changes: 2 additions & 2 deletions test/__snapshots__/selector-no-merge.test.ts.snap
Expand Up @@ -2,8 +2,8 @@

exports[`selector > rules split selector 1`] = `
"/* layer: default */
.to-merge,
.merge-candidate{merged:1;}
.merge-candidate,
.to-merge{merged:1;}
.not-merged{merged:1;}"
`;

Expand Down
11 changes: 5 additions & 6 deletions test/__snapshots__/shortcuts.test.ts.snap
Expand Up @@ -4,10 +4,9 @@ exports[`shortcuts > animate 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-pan-x:var(--un-empty,/*!*/ /*!*/);--un-pan-y:var(--un-empty,/*!*/ /*!*/);--un-pinch-zoom:var(--un-empty,/*!*/ /*!*/);--un-scroll-snap-strictness:proximity;--un-ordinal:var(--un-empty,/*!*/ /*!*/);--un-slashed-zero:var(--un-empty,/*!*/ /*!*/);--un-numeric-figure:var(--un-empty,/*!*/ /*!*/);--un-numeric-spacing:var(--un-empty,/*!*/ /*!*/);--un-numeric-fraction:var(--un-empty,/*!*/ /*!*/);--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 #0000;--un-ring-shadow:0 0 #0000;--un-shadow-inset:var(--un-empty,/*!*/ /*!*/);--un-shadow:0 0 #0000;--un-ring-inset:var(--un-empty,/*!*/ /*!*/);--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgba(147,197,253,0.5);--un-blur:var(--un-empty,/*!*/ /*!*/);--un-brightness:var(--un-empty,/*!*/ /*!*/);--un-contrast:var(--un-empty,/*!*/ /*!*/);--un-drop-shadow:var(--un-empty,/*!*/ /*!*/);--un-grayscale:var(--un-empty,/*!*/ /*!*/);--un-hue-rotate:var(--un-empty,/*!*/ /*!*/);--un-invert:var(--un-empty,/*!*/ /*!*/);--un-saturate:var(--un-empty,/*!*/ /*!*/);--un-sepia:var(--un-empty,/*!*/ /*!*/);--un-backdrop-blur:var(--un-empty,/*!*/ /*!*/);--un-backdrop-brightness:var(--un-empty,/*!*/ /*!*/);--un-backdrop-contrast:var(--un-empty,/*!*/ /*!*/);--un-backdrop-grayscale:var(--un-empty,/*!*/ /*!*/);--un-backdrop-hue-rotate:var(--un-empty,/*!*/ /*!*/);--un-backdrop-invert:var(--un-empty,/*!*/ /*!*/);--un-backdrop-opacity:var(--un-empty,/*!*/ /*!*/);--un-backdrop-saturate:var(--un-empty,/*!*/ /*!*/);--un-backdrop-sepia:var(--un-empty,/*!*/ /*!*/);}
/* layer: shortcuts */
.loading{transition-duration:1000ms;}
.loading{animation:spin 1s linear infinite;transition-duration:1000ms;}
/* layer: default */
@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}
.loading{animation:spin 1s linear infinite;}"
@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}"
`;

exports[`shortcuts > dynamic 1`] = `
Expand Down Expand Up @@ -125,9 +124,9 @@ exports[`shortcuts > no-merge 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-pan-x:var(--un-empty,/*!*/ /*!*/);--un-pan-y:var(--un-empty,/*!*/ /*!*/);--un-pinch-zoom:var(--un-empty,/*!*/ /*!*/);--un-scroll-snap-strictness:proximity;--un-ordinal:var(--un-empty,/*!*/ /*!*/);--un-slashed-zero:var(--un-empty,/*!*/ /*!*/);--un-numeric-figure:var(--un-empty,/*!*/ /*!*/);--un-numeric-spacing:var(--un-empty,/*!*/ /*!*/);--un-numeric-fraction:var(--un-empty,/*!*/ /*!*/);--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 #0000;--un-ring-shadow:0 0 #0000;--un-shadow-inset:var(--un-empty,/*!*/ /*!*/);--un-shadow:0 0 #0000;--un-ring-inset:var(--un-empty,/*!*/ /*!*/);--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgba(147,197,253,0.5);--un-blur:var(--un-empty,/*!*/ /*!*/);--un-brightness:var(--un-empty,/*!*/ /*!*/);--un-contrast:var(--un-empty,/*!*/ /*!*/);--un-drop-shadow:var(--un-empty,/*!*/ /*!*/);--un-grayscale:var(--un-empty,/*!*/ /*!*/);--un-hue-rotate:var(--un-empty,/*!*/ /*!*/);--un-invert:var(--un-empty,/*!*/ /*!*/);--un-saturate:var(--un-empty,/*!*/ /*!*/);--un-sepia:var(--un-empty,/*!*/ /*!*/);--un-backdrop-blur:var(--un-empty,/*!*/ /*!*/);--un-backdrop-brightness:var(--un-empty,/*!*/ /*!*/);--un-backdrop-contrast:var(--un-empty,/*!*/ /*!*/);--un-backdrop-grayscale:var(--un-empty,/*!*/ /*!*/);--un-backdrop-hue-rotate:var(--un-empty,/*!*/ /*!*/);--un-backdrop-invert:var(--un-empty,/*!*/ /*!*/);--un-backdrop-opacity:var(--un-empty,/*!*/ /*!*/);--un-backdrop-saturate:var(--un-empty,/*!*/ /*!*/);--un-backdrop-sepia:var(--un-empty,/*!*/ /*!*/);}
/* layer: default */
.with-no-merge{no-merge:1;}
.with-no-merge,
.merge-candidate{merged:1;}"
.merge-candidate,
.with-no-merge{merged:1;}
.with-no-merge{no-merge:1;}"
`;

exports[`shortcuts > static 1`] = `
Expand Down