Skip to content

Commit dd1545b

Browse files
authored
feat(preset-typography): support element modifiers (#4341)
1 parent 9e0d699 commit dd1545b

File tree

4 files changed

+120
-3
lines changed

4 files changed

+120
-3
lines changed

packages/preset-typography/src/index.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { CSSObject, Preset } from '@unocss/core'
22
import type { Theme } from '@unocss/preset-mini'
33
import type { TypographyCompatibilityOptions } from './types/compatibilityOptions'
44
import { definePreset, toEscapedSelector } from '@unocss/core'
5-
import { getPreflights } from './preflights'
5+
import { getElements, getPreflights } from './preflights'
66

77
/**
88
* @public
@@ -77,6 +77,7 @@ export const presetTypography = definePreset((options?: TypographyOptions): Pres
7777
const selectorNameRE = new RegExp(`^${selectorName}$`)
7878
const colorsRE = new RegExp(`^${selectorName}-([-\\w]+)$`)
7979
const invertRE = new RegExp(`^${selectorName}-invert$`)
80+
const disableNotUtility = options?.compatibility?.noColonNot || options?.compatibility?.noColonWhere
8081

8182
return {
8283
name: '@unocss/preset-typography',
@@ -142,6 +143,32 @@ export const presetTypography = definePreset((options?: TypographyOptions): Pres
142143
{ layer: 'typography' },
143144
],
144145
],
146+
variants: [
147+
{
148+
name: 'typography element modifiers',
149+
match: (matcher) => {
150+
if (matcher.startsWith(`${selectorName}-`)) {
151+
const modifyRe = new RegExp(`^${selectorName}-(\\w+)[:-].+$`)
152+
const modifier = matcher.match(modifyRe)?.[1]
153+
if (modifier) {
154+
const elements = getElements(modifier)
155+
if (elements?.length) {
156+
return {
157+
matcher: matcher.slice(selectorName.length + modifier.length + 2),
158+
selector: (s) => {
159+
const notProseSelector = `:not(:where(.not-${selectorName},.not-${selectorName} *))`
160+
const escapedSelector = disableNotUtility
161+
? elements.map(e => `${s} ${e}`).join(',')
162+
: `${s} :is(:where(${elements})${notProseSelector})`
163+
return escapedSelector
164+
},
165+
}
166+
}
167+
}
168+
}
169+
},
170+
},
171+
],
145172
preflights: [
146173
{
147174
layer: 'typography',

packages/preset-typography/src/preflights/default.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,41 @@ export function DEFAULT(theme: Theme) {
160160
},
161161
}
162162
}
163+
164+
const modifiers = [
165+
['headings', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'th'],
166+
['h1'],
167+
['h2'],
168+
['h3'],
169+
['h4'],
170+
['h5'],
171+
['h6'],
172+
['p'],
173+
['a'],
174+
['blockquote'],
175+
['figure'],
176+
['figcaption'],
177+
['strong'],
178+
['em'],
179+
['kbd'],
180+
['code'],
181+
['pre'],
182+
['ol'],
183+
['ul'],
184+
['li'],
185+
['table'],
186+
['thead'],
187+
['tr'],
188+
['th'],
189+
['td'],
190+
['img'],
191+
['video'],
192+
['hr'],
193+
]
194+
195+
export function getElements(modifier: string) {
196+
for (const [name, ...selectors] of modifiers) {
197+
if (name === modifier)
198+
return selectors.length > 0 ? selectors : [name]
199+
}
200+
}

packages/preset-typography/src/preflights/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import type { TypographyCompatibilityOptions } from '../types/compatibilityOptio
44
import { mergeDeep } from '@unocss/core'
55
import { DEFAULT } from './default'
66

7+
export { getElements } from './default'
8+
79
function getCSS(
810
options: {
911
escapedSelector: string[]

test/preset-typography.test.ts

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,16 +139,66 @@ const testConfigs: {
139139
describe('typography', () => {
140140
for (const tc of testConfigs) {
141141
it(tc.name, async () => {
142-
const generator = await createGenerator({
142+
const uno = await createGenerator({
143143
presets: [
144144
presetAttributify(tc.attributifyOptions),
145145
presetUno({ preflight: false }),
146146
presetTypography(tc.typographyOptions),
147147
],
148148
})
149149

150-
const { css } = await generator.generate(tc.input)
150+
const { css } = await uno.generate(tc.input)
151151
expect(css).toMatchSnapshot()
152152
})
153153
}
154154
})
155+
156+
describe('typography elements modify', () => {
157+
it('basic', async () => {
158+
const uno = await createGenerator({
159+
presets: [
160+
presetAttributify(),
161+
presetUno({ preflight: false }),
162+
presetTypography(),
163+
],
164+
})
165+
166+
const { css } = await uno.generate('<div prose-headings:text-red prose-img:rounded hover:prose-p-m2></div>', { preflights: false })
167+
168+
expect(css).toMatchInlineSnapshot(`
169+
"/* layer: default */
170+
[hover\\:prose-p-m2=""] :is(:where(p):not(:where(.not-prose,.not-prose *))):hover{margin:0.5rem;}
171+
.prose-img\\:rounded :is(:where(img):not(:where(.not-prose,.not-prose *))),
172+
[prose-img\\:rounded=""] :is(:where(img):not(:where(.not-prose,.not-prose *))){border-radius:0.25rem;}
173+
.prose-headings\\:text-red :is(:where(h1,h2,h3,h4,h5,h6,th):not(:where(.not-prose,.not-prose *))),
174+
[prose-headings\\:text-red=""] :is(:where(h1,h2,h3,h4,h5,h6,th):not(:where(.not-prose,.not-prose *))){--un-text-opacity:1;color:rgb(248 113 113 / var(--un-text-opacity));}"
175+
`)
176+
})
177+
178+
it('basic without compatibility', async () => {
179+
const uno = await createGenerator({
180+
presets: [
181+
presetAttributify(),
182+
presetUno({ preflight: false }),
183+
presetTypography({
184+
compatibility: {
185+
noColonIs: true,
186+
noColonWhere: true,
187+
noColonNot: true,
188+
},
189+
}),
190+
],
191+
})
192+
193+
const { css } = await uno.generate('<div prose-headings:text-red prose-img:rounded hover:prose-p-m2></div>', { preflights: false })
194+
195+
expect(css).toMatchInlineSnapshot(`
196+
"/* layer: default */
197+
[hover\\:prose-p-m2=""] p:hover{margin:0.5rem;}
198+
.prose-img\\:rounded img,
199+
[prose-img\\:rounded=""] img{border-radius:0.25rem;}
200+
.prose-headings\\:text-red h1,.prose-headings\\:text-red h2,.prose-headings\\:text-red h3,.prose-headings\\:text-red h4,.prose-headings\\:text-red h5,.prose-headings\\:text-red h6,.prose-headings\\:text-red th,
201+
[prose-headings\\:text-red=""] h1,[prose-headings\\:text-red=""] h2,[prose-headings\\:text-red=""] h3,[prose-headings\\:text-red=""] h4,[prose-headings\\:text-red=""] h5,[prose-headings\\:text-red=""] h6,[prose-headings\\:text-red=""] th{--un-text-opacity:1;color:rgb(248 113 113 / var(--un-text-opacity));}"
202+
`)
203+
})
204+
})

0 commit comments

Comments
 (0)