From 91ea249ea583b7403bf3256f1705ae0966ab3360 Mon Sep 17 00:00:00 2001 From: Saya Date: Sun, 19 Jun 2022 21:29:29 +0800 Subject: [PATCH] feat(core,presets)!: update to use the recursive callback --- packages/core/src/generator/index.ts | 15 ++++-- packages/core/src/types.ts | 10 +++- packages/preset-mini/src/utils/variants.ts | 14 +++-- .../preset-mini/src/variants/breakpoints.ts | 18 +++++-- .../preset-mini/src/variants/combinators.ts | 5 +- packages/preset-mini/src/variants/dark.ts | 4 +- .../preset-mini/src/variants/directions.ts | 4 +- .../preset-mini/src/variants/important.ts | 6 +-- packages/preset-mini/src/variants/media.ts | 5 +- packages/preset-mini/src/variants/misc.ts | 20 ++++++-- packages/preset-mini/src/variants/negative.ts | 39 ++++++++------ packages/preset-mini/src/variants/pseudo.ts | 30 +++++++---- packages/preset-tagify/src/variant.ts | 26 ++++++---- packages/preset-uno/src/variants/mix.ts | 6 +-- packages/preset-wind/src/rules/container.ts | 5 +- .../preset-wind/src/variants/combinators.ts | 2 +- packages/preset-wind/src/variants/dark.ts | 4 +- packages/preset-wind/src/variants/misc.ts | 7 +-- test/__snapshots__/order.test.ts.snap | 16 ++++-- test/__snapshots__/preset-mini.test.ts.snap | 14 ++--- .../variant-handler.test.ts.snap | 16 ++++-- test/assets/preset-mini-targets.ts | 4 ++ test/order.test.ts | 51 ++++++++++++++++--- test/selector-no-merge.test.ts | 4 +- test/variant-handler.test.ts | 26 ++++++++++ 25 files changed, 254 insertions(+), 97 deletions(-) diff --git a/packages/core/src/generator/index.ts b/packages/core/src/generator/index.ts index 82637eb8c0..73a4acd480 100644 --- a/packages/core/src/generator/index.ts +++ b/packages/core/src/generator/index.ts @@ -322,7 +322,7 @@ export class UnoGenerator { if (typeof handler === 'string') handler = { matcher: handler } processed = handler.matcher - handlers.unshift(handler) + handlers.push(handler) variants.add(v) applied = true break @@ -352,6 +352,7 @@ export class UnoGenerator { ? (Array.isArray(v.parent) ? v.parent : [v.parent ?? '', undefined]) : [input.parent, input.parentOrder] return (v.handler ?? defaultHandler)({ + ...input, entries, selector: v.selector?.(input.selector, entries) || input.selector, parent: parents[0], @@ -364,16 +365,22 @@ export class UnoGenerator { ) const variantContextResult = handler({ - entries: parsed[2], + prefix: '', selector: toEscapedSelector(raw), + pseudo: '', + entries: parsed[2], }) - const { parent, parentOrder, selector } = variantContextResult + const { parent, parentOrder } = variantContextResult if (parent != null && parentOrder != null) this.parentOrders.set(parent, parentOrder) const obj: UtilObject = { - selector: movePseudoElementsEnd(selector), + selector: movePseudoElementsEnd([ + variantContextResult.prefix, + variantContextResult.selector, + variantContextResult.pseudo, + ].join('')), entries: variantContextResult.entries, parent, layer: variantContextResult.layer, diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 4fae800779..6786d2df4e 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -191,9 +191,17 @@ export type BlocklistRule = string | RegExp export interface VariantHandlerContext { /** - * Rewrite the output selector. Often be used to append pesudo classes or parents. + * Rewrite the output selector. Often be used to append parents. + */ + prefix: string + /** + * Rewrite the output selector. Often be used to append pesudo classes. */ selector: string + /** + * Rewrite the output selector. Often be used to append pesudo elements. + */ + pseudo: string /** * Rewrite the output css body. The input come in [key,value][] pairs. */ diff --git a/packages/preset-mini/src/utils/variants.ts b/packages/preset-mini/src/utils/variants.ts index a274311f08..0a6ad7e3ba 100644 --- a/packages/preset-mini/src/utils/variants.ts +++ b/packages/preset-mini/src/utils/variants.ts @@ -1,7 +1,7 @@ -import type { VariantHandler, VariantObject } from '@unocss/core' +import type { VariantHandler, VariantHandlerContext, VariantObject } from '@unocss/core' import { escapeRegExp } from '@unocss/core' -export const variantMatcher = (name: string, selector?: (input: string) => string | undefined): VariantObject => { +export const variantMatcher = (name: string, handler: (input: VariantHandlerContext) => Record): VariantObject => { const re = new RegExp(`^${escapeRegExp(name)}[:-]`) return { name, @@ -10,7 +10,10 @@ export const variantMatcher = (name: string, selector?: (input: string) => strin if (match) { return { matcher: input.slice(match[0].length), - selector, + handler: (input, next) => next({ + ...input, + ...handler(input), + }), } } }, @@ -27,7 +30,10 @@ export const variantParentMatcher = (name: string, parent: string): VariantObjec if (match) { return { matcher: input.slice(match[0].length), - parent, + handler: (input, next) => next({ + ...input, + parent, + }), } } }, diff --git a/packages/preset-mini/src/variants/breakpoints.ts b/packages/preset-mini/src/variants/breakpoints.ts index 636e51bf8e..cfd62e4376 100644 --- a/packages/preset-mini/src/variants/breakpoints.ts +++ b/packages/preset-mini/src/variants/breakpoints.ts @@ -42,7 +42,11 @@ export const variantBreakpoints: Variant = { order -= (idx + 1) return { matcher: m, - parent: [`@media (max-width: ${calcMaxWidthBySize(size)})`, order], + handler: (input, next) => next({ + ...input, + parent: `@media (max-width: ${calcMaxWidthBySize(size)})`, + parentOrder: order, + }), } } @@ -52,13 +56,21 @@ export const variantBreakpoints: Variant = { if (isAtPrefix && idx < variantEntries.length - 1) { return { matcher: m, - parent: [`@media (min-width: ${size}) and (max-width: ${calcMaxWidthBySize(variantEntries[idx + 1][1])})`, order], + handler: (input, next) => next({ + ...input, + parent: `@media (min-width: ${size}) and (max-width: ${calcMaxWidthBySize(variantEntries[idx + 1][1])})`, + parentOrder: order, + }), } } return { matcher: m, - parent: [`@media (min-width: ${size})`, order], + handler: (input, next) => next({ + ...input, + parent: `@media (min-width: ${size})`, + parentOrder: order, + }), } } }, diff --git a/packages/preset-mini/src/variants/combinators.ts b/packages/preset-mini/src/variants/combinators.ts index 2dfceff153..1696f96741 100644 --- a/packages/preset-mini/src/variants/combinators.ts +++ b/packages/preset-mini/src/variants/combinators.ts @@ -11,7 +11,10 @@ const scopeMatcher = (strict: boolean, name: string, template: string): VariantO if (match) { return { matcher: matcher.slice(match[0].length), - selector: s => template.replace('&&-s', s).replace('&&-c', match[1] ?? '*'), + handler: (input, next) => next({ + ...input, + selector: template.replace('&&-s', input.selector).replace('&&-c', match[1] ?? '*'), + }), } } }, diff --git a/packages/preset-mini/src/variants/dark.ts b/packages/preset-mini/src/variants/dark.ts index 501ac64afc..24681480e1 100644 --- a/packages/preset-mini/src/variants/dark.ts +++ b/packages/preset-mini/src/variants/dark.ts @@ -5,8 +5,8 @@ import { variantMatcher, variantParentMatcher } from '../utils' export const variantColorsMediaOrClass = (options: PresetMiniOptions = {}): Variant[] => { if (options?.dark === 'class') { return [ - variantMatcher('dark', input => `.dark $$ ${input}`), - variantMatcher('light', input => `.light $$ ${input}`), + variantMatcher('dark', input => ({ prefix: `${input.prefix}.dark $$ ` })), + variantMatcher('light', input => ({ prefix: `${input.prefix}.light $$ ` })), ] } diff --git a/packages/preset-mini/src/variants/directions.ts b/packages/preset-mini/src/variants/directions.ts index e7d158bebd..e38393f171 100644 --- a/packages/preset-mini/src/variants/directions.ts +++ b/packages/preset-mini/src/variants/directions.ts @@ -2,6 +2,6 @@ import type { Variant } from '@unocss/core' import { variantMatcher } from '../utils' export const variantLanguageDirections: Variant[] = [ - variantMatcher('rtl', input => `[dir="rtl"] $$ ${input}`), - variantMatcher('ltr', input => `[dir="ltr"] $$ ${input}`), + variantMatcher('rtl', input => ({ prefix: `${input.prefix}[dir="rtl"] $$ ` })), + variantMatcher('ltr', input => ({ prefix: `${input.prefix}[dir="ltr"] $$ ` })), ] diff --git a/packages/preset-mini/src/variants/important.ts b/packages/preset-mini/src/variants/important.ts index ae8cfd7f2c..c81e8c8119 100644 --- a/packages/preset-mini/src/variants/important.ts +++ b/packages/preset-mini/src/variants/important.ts @@ -14,12 +14,12 @@ export const variantImportant: Variant = { if (base) { return { matcher: base, - body: (body) => { - body.forEach((v) => { + handler: (input, next) => { + input.entries.forEach((v) => { if (v[1]) v[1] += ' !important' }) - return body + return next(input) }, } } diff --git a/packages/preset-mini/src/variants/media.ts b/packages/preset-mini/src/variants/media.ts index 48ab62d16b..074c7e19d4 100644 --- a/packages/preset-mini/src/variants/media.ts +++ b/packages/preset-mini/src/variants/media.ts @@ -12,7 +12,10 @@ export const variantCustomMedia: VariantObject = { const media = theme.media?.[match[1]] ?? `(--${match[1]})` return { matcher: matcher.slice(match[0].length), - parent: `@media ${media}`, + handler: (input, next) => next({ + ...input, + parent: `@media ${media}`, + }), } } }, diff --git a/packages/preset-mini/src/variants/misc.ts b/packages/preset-mini/src/variants/misc.ts index 1e2e65eff4..f42c084906 100644 --- a/packages/preset-mini/src/variants/misc.ts +++ b/packages/preset-mini/src/variants/misc.ts @@ -7,7 +7,10 @@ export const variantSelector: Variant = { if (match) { return { matcher: matcher.slice(match[0].length), - selector: () => match[1], + handler: (input, next) => next({ + ...input, + selector: match[1], + }), } } }, @@ -20,7 +23,10 @@ export const variantCssLayer: Variant = { if (match) { return { matcher: matcher.slice(match[0].length), - parent: `@layer ${match[1]}`, + handler: (input, next) => next({ + ...input, + parent: `@layer ${match[1]}`, + }), } } }, @@ -33,7 +39,10 @@ export const variantInternalLayer: Variant = { if (match) { return { matcher: matcher.slice(match[0].length), - layer: match[1], + handler: (input, next) => next({ + ...input, + layer: match[1], + }), } } }, @@ -46,7 +55,10 @@ export const variantScope: Variant = { if (match) { return { matcher: matcher.slice(match[0].length), - selector: s => `.${match[1]} $$ ${s}`, + handler: (input, next) => next({ + ...input, + prefix: `${input.prefix}.${match[1]} $$ `, + }), } } }, diff --git a/packages/preset-mini/src/variants/negative.ts b/packages/preset-mini/src/variants/negative.ts index f29b435229..d862168d5e 100644 --- a/packages/preset-mini/src/variants/negative.ts +++ b/packages/preset-mini/src/variants/negative.ts @@ -15,24 +15,31 @@ export const variantNegative: Variant = { return { matcher: matcher.slice(1), - body: (body) => { - if (body.find(v => v[0] === CONTROL_MINI_NO_NEGATIVE)) - return - let changed = false - body.forEach((v) => { - const value = v[1]?.toString() - if (!value || value === '0') + handler: (input, next) => { + const body = ((body) => { + if (body.find(v => v[0] === CONTROL_MINI_NO_NEGATIVE)) return - if (ignoreProps.some(i => v[0].match(i))) - return - if (numberRE.test(value)) { - v[1] = value.replace(numberRE, i => `-${i}`) - changed = true - } + let changed = false + body.forEach((v) => { + const value = v[1]?.toString() + if (!value || value === '0') + return + if (ignoreProps.some(i => v[0].match(i))) + return + if (numberRE.test(value)) { + v[1] = value.replace(numberRE, i => `-${i}`) + changed = true + } + }) + if (changed) + return body + return [] + })(input.entries) + + return next({ + ...input, + entries: body ?? input.entries, }) - if (changed) - return body - return [] }, } }, diff --git a/packages/preset-mini/src/variants/pseudo.ts b/packages/preset-mini/src/variants/pseudo.ts index 35564a76ae..9425714566 100644 --- a/packages/preset-mini/src/variants/pseudo.ts +++ b/packages/preset-mini/src/variants/pseudo.ts @@ -82,7 +82,7 @@ const sortValue = (pseudo: string) => { } const taggedPseudoClassMatcher = (tag: string, parent: string, combinator: string): VariantObject => { - const rawRe = new RegExp(`^${escapeRegExp(parent)}:`) + const rawRe = new RegExp(`${escapeRegExp(parent)}:`) const pseudoRE = new RegExp(`^${tag}-((?:(${PseudoClassFunctionsStr})-)?(${PseudoClassesStr}))[:-]`) const pseudoColonRE = new RegExp(`^${tag}-((?:(${PseudoClassFunctionsStr})-)?(${PseudoClassesColonStr}))[:]`) return { @@ -95,10 +95,13 @@ const taggedPseudoClassMatcher = (tag: string, parent: string, combinator: strin pseudo = `:${match[2]}(${pseudo})` return { matcher: input.slice(match[0].length), - selector: s => rawRe.test(s) - ? s.replace(rawRe, `${parent}${pseudo}:`) - : `${parent}${pseudo}${combinator}${s}`, - sort: sortValue(match[3]), + handler: (input, next) => next({ + ...input, + prefix: rawRe.test(input.prefix) + ? input.prefix.replace(rawRe, `${parent}${pseudo}:`) + : `${input.prefix}${parent}${pseudo}${combinator}`, + sort: sortValue(match[3]), + }), } } }, @@ -117,8 +120,11 @@ export const variantPseudoClassesAndElements: VariantObject = { const pseudo = PseudoClasses[match[1]] || PseudoClassesColon[match[1]] || `:${match[1]}` return { matcher: input.slice(match[0].length), - selector: s => `${s}${pseudo}`, - sort: sortValue(match[1]), + handler: (input, next) => next({ + ...input, + selector: `${input.selector}${pseudo}`, + sort: sortValue(match[1]), + }), } } }, @@ -136,7 +142,10 @@ export const variantPseudoClassFunctions: VariantObject = { const pseudo = PseudoClasses[match[2]] || PseudoClassesColon[match[2]] || `:${match[2]}` return { matcher: input.slice(match[0].length), - selector: s => `${s}:${fn}(${pseudo})`, + handler: (input, next) => next({ + ...input, + selector: `${input.selector}:${fn}(${pseudo})`, + }), } } }, @@ -175,7 +184,10 @@ export const partClasses: VariantObject = { const part = `part(${match[2]})` return { matcher: input.slice(match[1].length), - selector: s => `${s}::${part}`, + handler: (input, next) => next({ + ...input, + selector: `${input.selector}::${part}`, + }), } } }, diff --git a/packages/preset-tagify/src/variant.ts b/packages/preset-tagify/src/variant.ts index 0bf42e1ea4..2b9f4b032e 100644 --- a/packages/preset-tagify/src/variant.ts +++ b/packages/preset-tagify/src/variant.ts @@ -1,4 +1,4 @@ -import type { VariantHandler, VariantObject } from '@unocss/core' +import type { VariantObject } from '@unocss/core' import type { TagifyOptions } from './types' import { MARKER } from './extractor' @@ -13,19 +13,23 @@ export const variantTagify = (options: TagifyOptions): VariantObject => { return const matcher = input.slice(prefix.length) - const handler: VariantHandler = { + + return { matcher, - selector: i => i.slice(MARKER.length + 1), - } + handler: (input, next) => { + if (extraProperties) { + if (typeof extraProperties === 'function') + input.entries.push(...Object.entries(extraProperties(matcher) ?? {})) + else + input.entries.push(...Object.entries(extraProperties)) + } - if (extraProperties) { - if (typeof extraProperties === 'function') - handler.body = entries => [...entries, ...Object.entries(extraProperties(matcher) ?? {})] - else - handler.body = entries => [...entries, ...Object.entries(extraProperties)] + return next({ + ...input, + selector: input.selector.slice(MARKER.length + 1), + }) + }, } - - return handler }, } } diff --git a/packages/preset-uno/src/variants/mix.ts b/packages/preset-uno/src/variants/mix.ts index 54a176e17b..c7b65f64e4 100644 --- a/packages/preset-uno/src/variants/mix.ts +++ b/packages/preset-uno/src/variants/mix.ts @@ -64,8 +64,8 @@ export const variantColorMix: Variant = (matcher) => { if (m) { return { matcher: matcher.slice(m[0].length), - body: (body) => { - body.forEach((v) => { + handler: (input, next) => { + input.entries.forEach((v) => { if (v[1]) { const color = parseCssColor(`${v[1]}`) if (color) { @@ -75,7 +75,7 @@ export const variantColorMix: Variant = (matcher) => { } } }) - return body + return next(input) }, } } diff --git a/packages/preset-wind/src/rules/container.ts b/packages/preset-wind/src/rules/container.ts index 9140d3b859..477dc3f475 100644 --- a/packages/preset-wind/src/rules/container.ts +++ b/packages/preset-wind/src/rules/container.ts @@ -1,5 +1,4 @@ -import type { Rule, Shortcut } from '@unocss/core' -import { toArray } from '@unocss/core' +import type { Rule, Shortcut, VariantHandlerContext } from '@unocss/core' import type { Theme } from '@unocss/preset-mini' import { resolveBreakpoints } from '@unocss/preset-mini/utils' @@ -11,7 +10,7 @@ export const container: Rule[] = [ (m, { variantHandlers }) => { let width = '100%' for (const v of variantHandlers) { - const query = toArray(v.parent || [])[0] + const query = v.handler?.({} as VariantHandlerContext, x => x).parent if (typeof query === 'string') { const match = query.match(queryMatcher)?.[1] if (match) diff --git a/packages/preset-wind/src/variants/combinators.ts b/packages/preset-wind/src/variants/combinators.ts index 713e13d4b6..9cc8612799 100644 --- a/packages/preset-wind/src/variants/combinators.ts +++ b/packages/preset-wind/src/variants/combinators.ts @@ -2,5 +2,5 @@ import type { Variant } from '@unocss/core' import { variantMatcher } from '@unocss/preset-mini/utils' export const variantCombinators: Variant[] = [ - variantMatcher('svg', input => `${input} svg`), + variantMatcher('svg', input => ({ selector: `${input.selector} svg` })), ] diff --git a/packages/preset-wind/src/variants/dark.ts b/packages/preset-wind/src/variants/dark.ts index 56b42b83f2..6343505042 100644 --- a/packages/preset-wind/src/variants/dark.ts +++ b/packages/preset-wind/src/variants/dark.ts @@ -2,8 +2,8 @@ import type { Variant } from '@unocss/core' import { variantMatcher, variantParentMatcher } from '@unocss/preset-mini/utils' export const variantColorsScheme: Variant[] = [ - variantMatcher('.dark', input => `.dark $$ ${input}`), - variantMatcher('.light', input => `.light $$ ${input}`), + variantMatcher('.dark', input => ({ prefix: `${input.prefix}.dark $$ ` })), + variantMatcher('.light', input => ({ prefix: `${input.prefix}.light $$ ` })), variantParentMatcher('@dark', '@media (prefers-color-scheme: dark)'), variantParentMatcher('@light', '@media (prefers-color-scheme: light)'), ] diff --git a/packages/preset-wind/src/variants/misc.ts b/packages/preset-wind/src/variants/misc.ts index 92120bf2e0..80f504d226 100644 --- a/packages/preset-wind/src/variants/misc.ts +++ b/packages/preset-wind/src/variants/misc.ts @@ -4,9 +4,10 @@ export const variantSpaceAndDivide: Variant = (matcher) => { if (/^space-?([xy])-?(-?.+)$/.test(matcher) || /^divide-/.test(matcher)) { return { matcher, - selector: (input) => { - return `${input}>:not([hidden])~:not([hidden])` - }, + handler: (input, next) => next({ + ...input, + selector: `${input.selector}>:not([hidden])~:not([hidden])`, + }), } } } diff --git a/test/__snapshots__/order.test.ts.snap b/test/__snapshots__/order.test.ts.snap index 4f7787ee0f..626a361fc9 100644 --- a/test/__snapshots__/order.test.ts.snap +++ b/test/__snapshots__/order.test.ts.snap @@ -14,18 +14,26 @@ exports[`order > movePseudoElementsEnd 1`] = `".marker\\\\:file\\\\:hover\\\\:se exports[`order > multiple variant sorting 1`] = ` "/* layer: default */ -.dark .group:hover:focus-within .dark\\\\:group-hover\\\\:group-focus-within\\\\:bg-blue-600{--un-bg-opacity:1;background-color:rgba(37,99,235,var(--un-bg-opacity));} -.group:hover:focus-within .dark .group-hover\\\\:group-focus-within\\\\:dark\\\\:bg-red-600{--un-bg-opacity:1;background-color:rgba(220,38,38,var(--un-bg-opacity));}" +.dark .group:focus-within:hover .dark\\\\:group-hover\\\\:group-focus-within\\\\:bg-blue-600{--un-bg-opacity:1;background-color:rgba(37,99,235,var(--un-bg-opacity));} +.group:focus-within:hover .dark .group-hover\\\\:group-focus-within\\\\:dark\\\\:bg-red-600{--un-bg-opacity:1;background-color:rgba(220,38,38,var(--un-bg-opacity));}" `; exports[`order > variant ordering 1`] = ` "/* layer: default */ -.dark .group .dark\\\\:group\\\\:foo-3{name:foo-3;} -.dark .group .group\\\\:dark\\\\:foo-4{name:foo-4;} +.group .dark .dark\\\\:group\\\\:foo-3{name:foo-3;} +.group .dark .group\\\\:dark\\\\:foo-4{name:foo-4;} .group .light .group\\\\:light\\\\:foo-2{name:foo-2;} .light .group .light\\\\:group\\\\:foo-1{name:foo-1;}" `; +exports[`order > variant ordering reversed 1`] = ` +"/* layer: default */ +.dark .group .dark\\\\:group\\\\:foo-3{name:foo-3;} +.dark .group .group\\\\:dark\\\\:foo-4{name:foo-4;} +.group .light .light\\\\:group\\\\:foo-1{name:foo-1;} +.light .group .group\\\\:light\\\\:foo-2{name:foo-2;}" +`; + exports[`order > variant sorting 1`] = ` "/* layer: default */ pre .pre\\\\:foo-1, diff --git a/test/__snapshots__/preset-mini.test.ts.snap b/test/__snapshots__/preset-mini.test.ts.snap index a0092d9457..b994901825 100644 --- a/test/__snapshots__/preset-mini.test.ts.snap +++ b/test/__snapshots__/preset-mini.test.ts.snap @@ -112,12 +112,12 @@ div:hover .group-\\\\[div\\\\:hover\\\\]-\\\\[combinator\\\\:test-4\\\\]{combina .file\\\\:bg-violet-50::file-selector-button{--un-bg-opacity:1;background-color:rgba(245,243,255,var(--un-bg-opacity));} .first-letter\\\\:bg-green-400::first-letter, .first-line\\\\:bg-green-400::first-line{--un-bg-opacity:1;background-color:rgba(74,222,128,var(--un-bg-opacity));} -.focus-within\\\\:has-first\\\\:checked\\\\:bg-gray\\\\/20:checked:has(:first-child):focus-within, -.focus-within\\\\:where-first\\\\:checked\\\\:bg-gray\\\\/20:checked:where(:first-child):focus-within{background-color:rgba(156,163,175,0.2);} +.focus-within\\\\:has-first\\\\:checked\\\\:bg-gray\\\\/20:focus-within:has(:first-child):checked, +.focus-within\\\\:where-first\\\\:checked\\\\:bg-gray\\\\/20:focus-within:where(:first-child):checked{background-color:rgba(156,163,175,0.2);} .hover\\\\:file\\\\:bg-violet-100:hover::file-selector-button{--un-bg-opacity:1;background-color:rgba(237,233,254,var(--un-bg-opacity));} -.hover\\\\:is-first\\\\:checked\\\\:bg-true-gray\\\\/10:checked:is(:first-child):hover, -.hover\\\\:not-first\\\\:checked\\\\:bg-true-gray\\\\/10:checked:not(:first-child):hover{background-color:rgba(163,163,163,0.1);} -.hover\\\\:not-first\\\\:checked\\\\:bg-red\\\\/10:checked:not(:first-child):hover{background-color:rgba(248,113,113,0.1);} +.hover\\\\:is-first\\\\:checked\\\\:bg-true-gray\\\\/10:hover:is(:first-child):checked, +.hover\\\\:not-first\\\\:checked\\\\:bg-true-gray\\\\/10:hover:not(:first-child):checked{background-color:rgba(163,163,163,0.1);} +.hover\\\\:not-first\\\\:checked\\\\:bg-red\\\\/10:hover:not(:first-child):checked{background-color:rgba(248,113,113,0.1);} .marker\\\\:bg-violet-200::marker{--un-bg-opacity:1;background-color:rgba(221,214,254,var(--un-bg-opacity));} .peer:checked~.peer-checked\\\\:bg-blue-500{--un-bg-opacity:1;background-color:rgba(59,130,246,var(--un-bg-opacity));} .previous:checked+.previous-checked\\\\:bg-red-500{--un-bg-opacity:1;background-color:rgba(239,68,68,var(--un-bg-opacity));} @@ -331,7 +331,7 @@ div:hover .group-\\\\[div\\\\:hover\\\\]-\\\\[combinator\\\\:test-4\\\\]{combina .text-shadow-color-op-30{--un-text-shadow-opacity:0.3;} .case-upper{text-transform:uppercase;} .case-normal{text-transform:none;} -.group:hover:focus .group-hover\\\\:group-focus\\\\:text-center, +.group:focus:hover .group-hover\\\\:group-focus\\\\:text-center, .parent:hover>.parent-hover\\\\:text-center{text-align:center;} .text-left, [dir=\\"ltr\\"] .ltr\\\\:text-left{text-align:left;} @@ -367,6 +367,7 @@ 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\\\\: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));} @@ -383,6 +384,7 @@ 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\\\\: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));} diff --git a/test/__snapshots__/variant-handler.test.ts.snap b/test/__snapshots__/variant-handler.test.ts.snap index 42e13942d4..ed01871c83 100644 --- a/test/__snapshots__/variant-handler.test.ts.snap +++ b/test/__snapshots__/variant-handler.test.ts.snap @@ -1,10 +1,18 @@ // Vitest Snapshot v1 +exports[`variants > selector section is merged in order 1`] = ` +"/* layer: default */ +.prefix .pre\\\\:back\\\\:foo::pseudo, +.prefix .replaced, +.prefix .replaced::pseudo, +.replaced::pseudo{name:bar;}" +`; + exports[`variants > variant can stack 1`] = ` "/* layer: default */ -.first\\\\:second\\\\:third\\\\:foo > :third > :second > :first, -.first\\\\:three\\\\:two\\\\:foo > :first + :three + :two, -.one\\\\:two\\\\:three\\\\:foo + :one + :two + :three{name:bar;}" +.first\\\\:second\\\\:third\\\\:foo > :first > :second > :third, +.first\\\\:three\\\\:two\\\\:foo > :first + :two + :three, +.one\\\\:two\\\\:three\\\\:foo + :three + :two + :one{name:bar;}" `; exports[`variants > variant context is propagated 1`] = ` @@ -12,6 +20,6 @@ exports[`variants > variant context is propagated 1`] = ` .foo{name:bar;} /* layer: variant */ @supports{ -.selector{name:bar !important;} +:prefix > .selector::pseudo{name:bar !important;} }" `; diff --git a/test/assets/preset-mini-targets.ts b/test/assets/preset-mini-targets.ts index 7db0a7af92..d199042a19 100644 --- a/test/assets/preset-mini-targets.ts +++ b/test/assets/preset-mini-targets.ts @@ -872,6 +872,10 @@ export const presetMiniTargets: string[] = [ 'peer-checked:bg-blue-500', 'parent-hover:text-center', 'previous-checked:bg-red-500', + + // variants - tagged + 'checked:next:text-slate-100', + 'next:checked:text-slate-200', ] export const presetMiniNonTargets = [ diff --git a/test/order.test.ts b/test/order.test.ts index 329fd1e58b..66cdc53982 100644 --- a/test/order.test.ts +++ b/test/order.test.ts @@ -35,10 +35,42 @@ describe('order', () => { ], presets: [], variants: [ - variantMatcher('light', input => `.light $$ ${input}`), - variantMatcher('group', input => `.group ${input}`), + variantMatcher('light', input => ({ prefix: `${input.prefix}.light $$ ` })), + variantMatcher('group', input => ({ prefix: `${input.prefix}.group ` })), (v, ctx) => { - const match = variantMatcher('dark', input => `.dark $$ ${input}`).match(v, ctx) + const match = variantMatcher('dark', input => ({ prefix: `${input.prefix}.dark $$ ` })).match(v, ctx) + if (typeof match === 'object') { + return { + ...match, + order: 1, + } + } + }, + ], + }) + const code = [ + 'light:group:foo-1', + 'group:light:foo-2', + 'dark:group:foo-3', + 'group:dark:foo-4', + ].join(' ') + const { css } = await uno.generate(code, { preflights: false }) + const { css: css2 } = await uno.generate(code, { preflights: false }) + expect(css).toMatchSnapshot() + expect(css).toEqual(css2) + }) + + test('variant ordering reversed', async () => { + const uno = createGenerator({ + rules: [ + [/^foo-.$/, ([m]) => ({ name: m })], + ], + presets: [], + variants: [ + variantMatcher('light', input => ({ prefix: `.light $$ ${input.prefix}` })), + variantMatcher('group', input => ({ prefix: `.group ${input.prefix}` })), + (v, ctx) => { + const match = variantMatcher('dark', input => ({ prefix: `.dark $$ ${input.prefix}` })).match(v, ctx) if (typeof match === 'object') { return { ...match, @@ -92,11 +124,14 @@ describe('order', () => { if (m) { return { matcher: input.slice(m[0].length), - selector: s => `${m[1]} ${s}`, - sort: { - pre: -1, - post: 1, - }[m[1]], + handler: (input, next) => next({ + ...input, + selector: `${m[1]} ${input.selector}`, + sort: { + pre: -1, + post: 1, + }[m[1]], + }), } } }, diff --git a/test/selector-no-merge.test.ts b/test/selector-no-merge.test.ts index 951136133c..ad5daef706 100644 --- a/test/selector-no-merge.test.ts +++ b/test/selector-no-merge.test.ts @@ -25,8 +25,8 @@ describe('variant', () => { [/^m3-(.+)$/, ([, s]) => `moz:${s} merge-candidate-early`], ], variants: [ - variantMatcher('moz', s => `${s}::non-breaking`), - variantMatcher('webkit', s => `${s}::breaking`), + variantMatcher('moz', s => ({ pseudo: `${s.pseudo}::non-breaking` })), + variantMatcher('webkit', s => ({ pseudo: `${s.pseudo}::breaking` })), ], rules: [ [/^no-merge$/, () => ({ merged: 1 }), { noMerge: true }], diff --git a/test/variant-handler.test.ts b/test/variant-handler.test.ts index c97b710285..7cfcb16627 100644 --- a/test/variant-handler.test.ts +++ b/test/variant-handler.test.ts @@ -1,5 +1,6 @@ import { createGenerator } from '@unocss/core' import { describe, expect, test } from 'vitest' +import { variantMatcher } from '@unocss/preset-mini/utils' describe('variants', () => { test('variant context is propagated', async () => { @@ -15,7 +16,9 @@ describe('variants', () => { return { matcher: input.slice(match[0].length), handler: (input, next) => next({ + prefix: ':prefix > ', selector: '.selector', + pseudo: '::pseudo', entries: input.entries.map((entry) => { entry[1] += ' !important' return entry @@ -38,6 +41,29 @@ describe('variants', () => { expect(css).toMatchSnapshot() }) + test('selector section is merged in order', async () => { + const uno = createGenerator({ + rules: [ + ['foo', { name: 'bar' }], + ], + variants: [ + variantMatcher('pre', () => ({ prefix: '.prefix ' })), + variantMatcher('main', () => ({ selector: '.replaced' })), + variantMatcher('back', () => ({ pseudo: '::pseudo' })), + ], + }) + + const { css } = await uno.generate([ + 'pre:main:foo', + 'pre:back:foo', + 'main:back:foo', + 'pre:main:back:foo', + 'back:main:pre:foo', + ].join(' '), { preflights: false }) + + expect(css).toMatchSnapshot() + }) + test('variant can stack', async () => { const uno = createGenerator({ rules: [