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(preset-typography): compatibility mode(#2051) #2064

Merged
merged 14 commits into from
Jan 20, 2023
23 changes: 23 additions & 0 deletions packages/preset-typography/README.md
Expand Up @@ -99,6 +99,14 @@ export default defineConfig({
the `table` element **(NOTE: `not` utility is only usable in class since it is
only used in CSS** **selector & not scanned by UnoCSS)**.

- **Compatibility Options**

This preset used some pseudo-classes which are not widely supported, but you
can disable them.

- If you enabled `noColonNot` or `noColonWhere`, `not-prose` will be unavailable.
- If you enabled `noColonIs`, attributify mode will have a wrong behavior.

## Utilities

| Rule | Styles by this rule |
Expand Down Expand Up @@ -146,6 +154,12 @@ The CSS declarations passed to `cssExtend` will
### Type of `TypographyOptions`

```ts
export interface TypographyCompatibilityOptions {
noColonWhere?: boolean
noColonIs?: boolean
noColonNot?: boolean
}

export interface TypographyOptions {
/**
* The class name to use the typographic utilities.
Expand All @@ -164,6 +178,15 @@ export interface TypographyOptions {
* @defaultValue undefined
*/
cssExtend?: Record<string, CSSObject>

/**
* Compatibility option. Notice that it will affect some features.
* For more instructions, see
* [README](https://github.com/unocss/unocss/tree/main/packages/preset-typography)
*
* @defaultValue undefined
*/
compatibility?: TypographyCompatibilityOptions
}
```

Expand Down
30 changes: 20 additions & 10 deletions packages/preset-typography/src/index.ts
@@ -1,7 +1,8 @@
import type { CSSObject, Preset, RuleContext } from '@unocss/core'
import type { Theme } from '@unocss/preset-mini'
import { toEscapedSelector } from '@unocss/core'
import type { Theme } from '@unocss/preset-mini'
import { getPreflights } from './preflights'
import type { TypographyCompatibilityOptions } from './types/compatibilityOptions'

/**
* @public
Expand All @@ -24,6 +25,16 @@ export interface TypographyOptions {
* @defaultValue undefined
*/
cssExtend?: Record<string, CSSObject>

/**
* Compatibility option. Notice that it will affect some features.
* For more instructions, see
* [README](https://github.com/unocss/unocss/tree/main/packages/preset-typography)
*
* @defaultValue undefined
*/
compatibility?: TypographyCompatibilityOptions

/**
* @deprecated use `selectorName` instead. It will be removed in 1.0.
*/
Expand Down Expand Up @@ -52,15 +63,15 @@ export interface TypographyOptions {
export function presetTypography(options?: TypographyOptions): Preset {
if (options?.className) {
console.warn('[unocss:preset-typography] "className" is deprecated. '
+ 'Use "selectorName" instead.')
+ 'Use "selectorName" instead.')
}
let hasProseClass = false
let escapedSelector = ''
const escapedSelectores = new Set<string>()
const selectorName = options?.selectorName || options?.className || 'prose'
const selectorNameRE = new RegExp(`^${selectorName}$`)
const colorsRE = new RegExp(`^${selectorName}-([-\\w]+)$`)
const invertRE = new RegExp(`^${selectorName}-invert$`)
const cssExtend = options?.cssExtend
const compatibility = options?.compatibility

return {
name: '@unocss/preset-typography',
Expand All @@ -70,8 +81,7 @@ export function presetTypography(options?: TypographyOptions): Preset {
[
selectorNameRE,
(_, { rawSelector }) => {
hasProseClass = true
escapedSelector = toEscapedSelector(rawSelector)
escapedSelectores.add(toEscapedSelector(rawSelector))
return { 'color': 'var(--un-prose-body)', 'max-width': '65ch' }
},
{ layer: 'typography' },
Expand Down Expand Up @@ -132,10 +142,10 @@ export function presetTypography(options?: TypographyOptions): Preset {
preflights: [
{
layer: 'typography',
getCSS: () =>
hasProseClass
? getPreflights(escapedSelector, selectorName, cssExtend)
: undefined,
getCSS: () => {
if (escapedSelectores.size > 0)
return getPreflights({ escapedSelectores, selectorName, cssExtend, compatibility })
},
},
],
}
Expand Down
45 changes: 33 additions & 12 deletions packages/preset-typography/src/preflights/index.ts
@@ -1,13 +1,20 @@
import { mergeDeep } from '@unocss/core'
import type { TypographyCompatibilityOptions } from '../types/compatibilityOptions'
import { DEFAULT } from './default'

function getCSS(
escapedSelector: string,
selectorName: string,
preflights: object,
options: {
escapedSelector: string[]
selectorName: string
preflights: object
compatibility?: TypographyCompatibilityOptions
},
): string {
let css = ''

const { escapedSelector, selectorName, preflights, compatibility } = options
const disableNotUtility = compatibility?.noColonNot || compatibility?.noColonWhere

for (const selector in preflights) {
// @ts-expect-error preflights do not have definitive keys
const cssDeclarationBlock = preflights[selector]
Expand All @@ -25,7 +32,11 @@ function getCSS(
if (match) {
const matchStr = match[0]
s = s.replace(matchStr, '')
return `${escapedSelector} :where(${s})${notProseSelector}${matchStr}`
return escapedSelector.map(e =>
disableNotUtility
? `${e} ${s}${matchStr}`
: `${e} :where(${s})${notProseSelector}${matchStr}`,
).join(',')
}
return null
})
Expand All @@ -38,7 +49,11 @@ function getCSS(
}
else {
// directly from css declaration
css += `${escapedSelector} :where(${selector})${notProseSelector}`
css += escapedSelector.map(e =>
disableNotUtility
? selector.split(',').map(s => `${e} ${s}`).join(',')
: `${e} :where(${selector})${notProseSelector}`,
).join(',')
}

css += '{'
Expand All @@ -54,16 +69,22 @@ function getCSS(
}

export function getPreflights(
escapedSelector: string,
selectorName: string,
cssExtend?: object | undefined,
options: {
escapedSelectores: Set<string>
selectorName: string
cssExtend?: object | undefined
compatibility?: TypographyCompatibilityOptions
},
): string {
const { escapedSelectores, selectorName, cssExtend, compatibility } = options
let escapedSelector = Array.from(escapedSelectores)

// attribute mode -> add class selector with `:is()` pseudo-class function
if (!escapedSelector.startsWith('.'))
escapedSelector = `:is(${escapedSelector},.${selectorName})`
if (!escapedSelector[escapedSelector.length - 1].startsWith('.') && !compatibility?.noColonIs)
escapedSelector = [`:is(${escapedSelector[escapedSelector.length - 1]},.${selectorName})`]

if (cssExtend)
return getCSS(escapedSelector, selectorName, mergeDeep(DEFAULT, cssExtend))
return getCSS({ escapedSelector, selectorName, preflights: mergeDeep(DEFAULT, cssExtend), compatibility })

return getCSS(escapedSelector, selectorName, DEFAULT)
return getCSS({ escapedSelector, selectorName, preflights: DEFAULT, compatibility })
}
@@ -0,0 +1,6 @@
/** @public */
export interface TypographyCompatibilityOptions {
noColonWhere?: boolean
noColonIs?: boolean
noColonNot?: boolean
}
1 change: 1 addition & 0 deletions playground/src/auto-imports.d.ts
Expand Up @@ -111,6 +111,7 @@ declare global {
const useArrayMap: typeof import('@vueuse/core')['useArrayMap']
const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce']
const useArraySome: typeof import('@vueuse/core')['useArraySome']
const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique']
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
const useAttrs: typeof import('vue')['useAttrs']
Expand Down