Skip to content

Commit 01d3fa4

Browse files
authored
feat(preset-mini): nested tagged variants (#4789)
1 parent 4e7e7fd commit 01d3fa4

File tree

6 files changed

+370
-41
lines changed

6 files changed

+370
-41
lines changed

packages-presets/preset-mini/src/_variants/aria.ts

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { Variant, VariantContext, VariantObject } from '@unocss/core'
2+
import type { PresetMiniOptions } from '..'
23
import type { Theme } from '../theme'
4+
import { escapeRegExp, escapeSelector } from '@unocss/core'
35
import { h, variantGetParameter } from '../utils'
46

57
export const variantAria: VariantObject = {
@@ -20,28 +22,91 @@ export const variantAria: VariantObject = {
2022
multiPass: true,
2123
}
2224

23-
function taggedAria(tagName: string): Variant {
25+
function taggedAria(tagName: string, combinator: string, options: PresetMiniOptions = {}): Variant {
2426
return {
2527
name: `${tagName}-aria`,
2628
match(matcher, ctx: VariantContext<Theme>) {
2729
const variant = variantGetParameter(`${tagName}-aria-`, matcher, ctx.generator.config.separators)
2830
if (variant) {
2931
const [match, rest, label] = variant
3032
const ariaAttribute = h.bracket(match) ?? ctx.theme.aria?.[match] ?? ''
33+
if (ariaAttribute) {
34+
const attributify = !!options?.attributifyPseudo
35+
let firstPrefix = options?.prefix ?? ''
36+
firstPrefix = (Array.isArray(firstPrefix) ? firstPrefix : [firstPrefix]).filter(Boolean)[0] ?? ''
37+
38+
const parent = `${attributify ? `[${firstPrefix}${tagName}=""]` : `.${firstPrefix}${tagName}`}`
39+
const escapedLabel = escapeSelector(label ? `/${label}` : '')
40+
41+
return {
42+
matcher: rest,
43+
handle: (input, next) => {
44+
const regexp = new RegExp(`${escapeRegExp(parent)}${escapeRegExp(escapedLabel)}(?:\\[.+?\\])+`)
45+
const match = input.prefix.match(regexp)
46+
47+
let nextPrefix
48+
if (match) {
49+
const insertIndex = (match.index ?? 0) + parent.length + escapedLabel.length
50+
nextPrefix = [
51+
input.prefix.slice(0, insertIndex),
52+
`[aria-${ariaAttribute}]`,
53+
input.prefix.slice(insertIndex),
54+
].join('')
55+
}
56+
else {
57+
const prefixGroupIndex = Math.max(input.prefix.indexOf(parent), 0)
58+
nextPrefix = [
59+
input.prefix.slice(0, prefixGroupIndex),
60+
parent,
61+
escapedLabel,
62+
`[aria-${ariaAttribute}]`,
63+
combinator,
64+
input.prefix.slice(prefixGroupIndex),
65+
].join('')
66+
}
67+
68+
return next({
69+
...input,
70+
prefix: nextPrefix,
71+
})
72+
},
73+
}
74+
}
75+
}
76+
},
77+
multiPass: true,
78+
}
79+
}
80+
81+
function taggedHasAria(): Variant {
82+
return {
83+
name: 'has-aria',
84+
match(matcher, ctx: VariantContext<Theme>) {
85+
const variant = variantGetParameter('has-aria-', matcher, ctx.generator.config.separators)
86+
if (variant) {
87+
const [match, rest] = variant
88+
const ariaAttribute = h.bracket(match) ?? ctx.theme.aria?.[match] ?? ''
3189
if (ariaAttribute) {
3290
return {
33-
matcher: `${tagName}-[[aria-${ariaAttribute}]]${label ? `/${label}` : ''}:${rest}`,
91+
matcher: rest,
92+
handle: (input, next) => next({
93+
...input,
94+
pseudo: `${input.pseudo}:has([aria-${ariaAttribute}])`,
95+
}),
3496
}
3597
}
3698
}
3799
},
100+
multiPass: true,
38101
}
39102
}
40103

41-
export const variantTaggedAriaAttributes: Variant[] = [
42-
taggedAria('group'),
43-
taggedAria('peer'),
44-
taggedAria('parent'),
45-
taggedAria('previous'),
46-
taggedAria('has'),
47-
]
104+
export function variantTaggedAriaAttributes(options: PresetMiniOptions = {}): Variant[] {
105+
return [
106+
taggedAria('group', ' ', options),
107+
taggedAria('peer', '~', options),
108+
taggedAria('parent', '>', options),
109+
taggedAria('previous', '+', options),
110+
taggedHasAria(),
111+
]
112+
}

packages-presets/preset-mini/src/_variants/data.ts

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { Variant, VariantContext, VariantObject } from '@unocss/core'
2+
import type { PresetMiniOptions } from '..'
23
import type { Theme } from '../theme'
4+
import { escapeRegExp, escapeSelector } from '@unocss/core'
35
import { h, variantGetParameter } from '../utils'
46

57
export const variantDataAttribute: VariantObject = {
@@ -20,28 +22,91 @@ export const variantDataAttribute: VariantObject = {
2022
multiPass: true,
2123
}
2224

23-
function taggedData(tagName: string): Variant {
25+
function taggedData(tagName: string, combinator: string, options: PresetMiniOptions = {}): Variant {
2426
return {
2527
name: `${tagName}-data`,
2628
match(matcher, ctx: VariantContext<Theme>) {
2729
const variant = variantGetParameter(`${tagName}-data-`, matcher, ctx.generator.config.separators)
2830
if (variant) {
2931
const [match, rest, label] = variant
3032
const dataAttribute = h.bracket(match) ?? ctx.theme.data?.[match] ?? ''
33+
if (dataAttribute) {
34+
const attributify = !!options?.attributifyPseudo
35+
let firstPrefix = options?.prefix ?? ''
36+
firstPrefix = (Array.isArray(firstPrefix) ? firstPrefix : [firstPrefix]).filter(Boolean)[0] ?? ''
37+
38+
const parent = `${attributify ? `[${firstPrefix}${tagName}=""]` : `.${firstPrefix}${tagName}`}`
39+
const escapedLabel = escapeSelector(label ? `/${label}` : '')
40+
41+
return {
42+
matcher: rest,
43+
handle: (input, next) => {
44+
const regexp = new RegExp(`${escapeRegExp(parent)}${escapeRegExp(escapedLabel)}(?:\\[.+?\\])+`)
45+
const match = input.prefix.match(regexp)
46+
47+
let nextPrefix
48+
if (match) {
49+
const insertIndex = (match.index ?? 0) + parent.length + escapedLabel.length
50+
nextPrefix = [
51+
input.prefix.slice(0, insertIndex),
52+
`[data-${dataAttribute}]`,
53+
input.prefix.slice(insertIndex),
54+
].join('')
55+
}
56+
else {
57+
const prefixGroupIndex = Math.max(input.prefix.indexOf(parent), 0)
58+
nextPrefix = [
59+
input.prefix.slice(0, prefixGroupIndex),
60+
parent,
61+
escapedLabel,
62+
`[data-${dataAttribute}]`,
63+
combinator,
64+
input.prefix.slice(prefixGroupIndex),
65+
].join('')
66+
}
67+
68+
return next({
69+
...input,
70+
prefix: nextPrefix,
71+
})
72+
},
73+
}
74+
}
75+
}
76+
},
77+
multiPass: true,
78+
}
79+
}
80+
81+
function taggedHasData(): Variant {
82+
return {
83+
name: 'has-data',
84+
match(matcher, ctx: VariantContext<Theme>) {
85+
const variant = variantGetParameter('has-data-', matcher, ctx.generator.config.separators)
86+
if (variant) {
87+
const [match, rest] = variant
88+
const dataAttribute = h.bracket(match) ?? ctx.theme.data?.[match] ?? ''
3189
if (dataAttribute) {
3290
return {
33-
matcher: `${tagName}-[[data-${dataAttribute}]]${label ? `/${label}` : ''}:${rest}`,
91+
matcher: rest,
92+
handle: (input, next) => next({
93+
...input,
94+
pseudo: `${input.pseudo}:has([data-${dataAttribute}])`,
95+
}),
3496
}
3597
}
3698
}
3799
},
100+
multiPass: true,
38101
}
39102
}
40103

41-
export const variantTaggedDataAttributes: Variant[] = [
42-
taggedData('group'),
43-
taggedData('peer'),
44-
taggedData('parent'),
45-
taggedData('previous'),
46-
taggedData('has'),
47-
]
104+
export function variantTaggedDataAttributes(options: PresetMiniOptions = {}): Variant[] {
105+
return [
106+
taggedData('group', ' ', options),
107+
taggedData('peer', '~', options),
108+
taggedData('parent', '>', options),
109+
taggedData('previous', '+', options),
110+
taggedHasData(),
111+
]
112+
}

packages-presets/preset-mini/src/_variants/default.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ export function variants(options: PresetMiniOptions): Variant<Theme>[] {
4646

4747
variantContainerQuery,
4848
variantVariables,
49-
...variantTaggedDataAttributes,
50-
...variantTaggedAriaAttributes,
49+
...variantTaggedDataAttributes(options),
50+
...variantTaggedAriaAttributes(options),
5151

5252
variantTheme,
5353
]

test/assets/output/preset-mini-targets.css

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -479,13 +479,13 @@ unocss .scope-\[unocss\]\:block{display:block;}
479479
.break-normal{overflow-wrap:normal;word-break:normal;}
480480
.break-words{overflow-wrap:break-word;}
481481
.break-keep{word-break:keep-all;}
482-
.peer[data-state=closed]~.peer-data-\[state\=closed\]\:border-3,
483-
.border-width-3{border-width:3px;}
484482
.b-2,
485483
.border-size-2{border-width:2px;}
486484
.border{border-width:1px;}
487485
.border-4,
488486
.has-data-\[state\=closed\]\:border-4:has([data-state=closed]){border-width:4px;}
487+
.border-width-3,
488+
.peer[data-state=closed]~.peer-data-\[state\=closed\]\:border-3{border-width:3px;}
489489
.border-x{border-left-width:1px;border-right-width:1px;}
490490
.border-b{border-bottom-width:1px;}
491491
.border-e-4{border-inline-end-width:4px;}
@@ -869,19 +869,13 @@ unocss .scope-\[unocss\]\:block{display:block;}
869869
.group:hover .group-\[\:hover\]\:font-11{font-weight:11;}
870870
.group.not-parent .group-\[\.not-parent\]\:font-14{font-weight:14;}
871871
.group[data-attr] .group-\[\[data-attr\]\]\:font-12{font-weight:12;}
872-
.group[data-state=open] .group-data-\[state\=open\]\:font-bold{font-weight:700;}
873872
.group\/label:hover .group-\[\:hover\]\/label\:font-16{font-weight:16;}
874873
.group\/label.not-parent .group-\[\.not-parent\]\/label\:font-19{font-weight:19;}
875874
.group\/label[data-attr] .group-\[\[data-attr\]\]\/label\:font-17,
876875
.group\/named[data-x=y] .group-data-\[x\=y\]\/named\:font-17,
877876
.parent\/named[data-x=y]>.parent-data-\[x\=y\]\/named\:font-17,
878877
.peer\/named[data-x=y]~.peer-data-\[x\=y\]\/named\:font-17,
879878
.previous\/named[data-x=y]+.previous-data-\[x\=y\]\/named\:font-17{font-weight:17;}
880-
.group\/named[aria-level="1"] .group-aria-\[level\=\"1\"\]\/named\:font-21,
881-
.parent\/named[aria-level="3"]>.parent-aria-\[level\=\"3\"\]\/named\:font-21,
882-
.peer\/named[aria-level="2"]~.peer-aria-\[level\=\"2\"\]\/named\:font-21{font-weight:21;}
883-
.group\/named[data-state=open] .group-data-\[state\=open\]\/named\:font-medium{font-weight:500;}
884-
.previous\/named[aria-level="4"]+.previous-aria-\[level\=\"4\"\]\/named\:hover\:font-21:hover{font-weight:21;}
885879
.font-050,
886880
.font-50,
887881
.fw-050,
@@ -891,9 +885,15 @@ unocss .scope-\[unocss\]\:block{display:block;}
891885
.fw-900{font-weight:900;}
892886
.font-thin{font-weight:100;}
893887
.fw-inherit{font-weight:inherit;}
888+
.group[data-state=open] .group-data-\[state\=open\]\:font-bold{font-weight:700;}
889+
.group\/named[aria-level="1"] .group-aria-\[level\=\"1\"\]\/named\:font-21,
890+
.parent\/named[aria-level="3"]>.parent-aria-\[level\=\"3\"\]\/named\:font-21,
891+
.peer\/named[aria-level="2"]~.peer-aria-\[level\=\"2\"\]\/named\:font-21{font-weight:21;}
892+
.group\/named[data-state=open] .group-data-\[state\=open\]\/named\:font-medium{font-weight:500;}
894893
.has-aria-\[hidden\=false\]\:font-20:has([aria-hidden=false]){font-weight:20;}
895894
.group:hover .group-hover\:font-10{font-weight:10;}
896895
.group\/label:hover .group-hover\/label\:font-15{font-weight:15;}
896+
.previous\/named[aria-level="4"]+.previous-aria-\[level\=\"4\"\]\/named\:hover\:font-21:hover{font-weight:21;}
897897
.font-leading-2,
898898
.leading-2{line-height:0.5rem;}
899899
.leading-\$variable,

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy