Skip to content

Commit

Permalink
fix(preset-mini): use array cache sorted breakpoints (#3260)
Browse files Browse the repository at this point in the history
  • Loading branch information
zyyv committed Oct 23, 2023
1 parent da5ec25 commit 3c65363
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 60 deletions.
66 changes: 61 additions & 5 deletions docs/config/theme.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: Theme
description: UnoCSS also supports the theming system that you might be familiar with in Tailwind / Windi.
outline: deep
---

# Theme
Expand All @@ -22,8 +23,11 @@ theme: {
},
}
```
::: tip
During the parsing process, `theme` will always exist in `context`, you can deconstruct and use it.
:::

## Usage in `rules`
### Usage in `rules`

To consume the theme in rules:

Expand All @@ -36,7 +40,41 @@ rules: [
]
```

One exception is that UnoCSS gives full control of `breakpoints` to users. When a custom `breakpoints` is provided, the default will be overridden instead of merging. For example:
### Usage in `variants`

To consume the theme in variants:

```ts
variants: [
{
name: 'variant-name',
match(matcher, { theme }) {
// ...
}
}
]
```

### Usage in `shortcuts`

To consume the theme in dynamic shortcuts:

```ts
shortcuts: [
[/^badge-(.*)$/, ([, c], { theme }) => {
if (Object.keys(theme.colors).includes(c))
return `bg-${c}4:10 text-${c}5 rounded`
}]
]
```

## Breakpoints

::: warning
One exception is that UnoCSS gives full control of `breakpoints` to users. When a custom `breakpoints` is provided, the default will be overridden instead of merging.
:::

With the following example, you will be able to only use the `sm:` and `md:` breakpoint variants:

<!--eslint-skip-->

Expand All @@ -47,9 +85,27 @@ theme: {
sm: '320px',
md: '640px',
},
}
},
```

Right now, you can only use the `sm:` and `md:` breakpoint variants.

::: info
`verticalBreakpoints` is same as `breakpoints` but for vertical layout.
:::

In addition we will sort screen points by size (same unit). For screen points in different units, in order to avoid errors, please use unified units in the configuration.

<!--eslint-skip-->

```ts
theme: {
// ...
breakpoints: {
sm: '320px',
// Because uno does not support comparison sorting of different unit sizes, please convert to the same unit.
// md: '40rem',
md: `${40 * 16}px`,
lg: '960px',
},
},
```

33 changes: 0 additions & 33 deletions docs/presets/mini.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,39 +132,6 @@ presetMini({
})
```

To consume the theme in rules:

```ts
rules: [
[/^text-(.*)$/, ([, c], { theme }) => {
if (theme.colors[c])
return { color: theme.colors[c] }
}],
]
```

::: warning
One exception is that UnoCSS gives full control of `breakpoints` to users. When a custom `breakpoints` is provided, the default will be overridden instead of merging.
:::

With the following example, you will be able to only use the `sm:` and `md:` breakpoint variants:

```ts
presetMini({
theme: {
// ...
breakpoints: {
sm: '320px',
md: '640px',
},
},
})
```

::: info
`verticalBreakpoints` is same as `breakpoints` but for vertical layout.
:::

## Options

### dark
Expand Down
6 changes: 5 additions & 1 deletion packages/postcss/src/screen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,12 @@ export async function parseScreen(root: Root, uno: UnoGenerator, directiveName:
breakpoints = (uno.config.theme as Theme).breakpoints

return breakpoints
? Object.entries(breakpoints)
.sort((a, b) => Number.parseInt(a[1].replace(/[a-z]+/gi, '')) - Number.parseInt(b[1].replace(/[a-z]+/gi, '')))
.map(([point, size]) => ({ point, size }))
: undefined
}
const variantEntries: Array<[string, string, number]> = Object.entries(resolveBreakpoints() ?? {}).map(([point, size], idx) => [point, size, idx])
const variantEntries: Array<[string, string, number]> = (resolveBreakpoints() ?? []).map(({ point, size }, idx) => [point, size, idx])
const generateMediaQuery = (breakpointName: string, prefix?: string) => {
const [, size, idx] = variantEntries.find(i => i[0] === breakpointName)!
if (prefix) {
Expand Down
14 changes: 10 additions & 4 deletions packages/preset-mini/src/_rules/size.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Rule } from '@unocss/core'
import type { Rule, RuleContext } from '@unocss/core'
import type { Theme } from '../theme'
import { h, resolveBreakpoints, resolveVerticalBreakpoints } from '../utils'
import { h, resolveBreakpoints } from '../utils'

const sizeMapping: Record<string, string> = {
h: 'height',
Expand Down Expand Up @@ -43,8 +43,8 @@ export const sizes: Rule<Theme>[] = [
'(max|min)-(w|h)-full',
],
}],
[/^(?:size-)?(min-|max-)?(h)-screen-(.+)$/, ([, m, w, s], context) => ({ [getPropName(m, w)]: resolveVerticalBreakpoints(context)?.[s] })],
[/^(?:size-)?(min-|max-)?(w)-screen-(.+)$/, ([, m, w, s], context) => ({ [getPropName(m, w)]: resolveBreakpoints(context)?.[s] }), {
[/^(?:size-)?(min-|max-)?(h)-screen-(.+)$/, ([, m, h, p], context) => ({ [getPropName(m, h)]: handleBreakpoint(context, p, 'verticalBreakpoints') })],
[/^(?:size-)?(min-|max-)?(w)-screen-(.+)$/, ([, m, w, p], context) => ({ [getPropName(m, w)]: handleBreakpoint(context, p) }), {
autocomplete: [
'(w|h)-screen',
'(min|max)-(w|h)-screen',
Expand All @@ -56,6 +56,12 @@ export const sizes: Rule<Theme>[] = [
}],
]

function handleBreakpoint(context: Readonly<RuleContext<Theme>>, point: string, key: 'breakpoints' | 'verticalBreakpoints' = 'breakpoints') {
const bp = resolveBreakpoints(context, key)
if (bp)
return bp.find(i => i.point === point)?.size
}

function getAspectRatio(prop: string) {
if (/^\d+\/\d+$/.test(prop))
return prop
Expand Down
21 changes: 9 additions & 12 deletions packages/preset-mini/src/_utils/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,26 +218,23 @@ export function hasParseableColor(color: string | undefined, theme: Theme) {
return color != null && !!parseColor(color, theme)?.color
}

export function resolveBreakpoints({ theme, generator }: Readonly<VariantContext<Theme>>) {
export function resolveBreakpoints({ theme, generator }: Readonly<VariantContext<Theme>>, key: 'breakpoints' | 'verticalBreakpoints' = 'breakpoints') {
let breakpoints: Record<string, string> | undefined
if (generator.userConfig && generator.userConfig.theme)
breakpoints = (generator.userConfig.theme as any).breakpoints
breakpoints = (generator.userConfig.theme as any)[key]

if (!breakpoints)
breakpoints = theme.breakpoints
breakpoints = theme[key]

return breakpoints
? Object.entries(breakpoints)
.sort((a, b) => Number.parseInt(a[1].replace(/[a-z]+/gi, '')) - Number.parseInt(b[1].replace(/[a-z]+/gi, '')))
.map(([point, size]) => ({ point, size }))
: undefined
}

export function resolveVerticalBreakpoints({ theme, generator }: Readonly<VariantContext<Theme>>) {
let verticalBreakpoints: Record<string, string> | undefined
if (generator.userConfig && generator.userConfig.theme)
verticalBreakpoints = (generator.userConfig.theme as any).verticalBreakpoints

if (!verticalBreakpoints)
verticalBreakpoints = theme.verticalBreakpoints

return verticalBreakpoints
export function resolveVerticalBreakpoints(context: Readonly<VariantContext<Theme>>) {
return resolveBreakpoints(context, 'verticalBreakpoints')
}

export function makeGlobalStaticRules(prefix: string, property?: string): StaticRule[] {
Expand Down
2 changes: 1 addition & 1 deletion packages/preset-mini/src/_variants/breakpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function variantBreakpoints(): VariantObject {
name: 'breakpoints',
match(matcher, context) {
const variantEntries: Array<[string, string, number]>
= Object.entries(resolveBreakpoints(context) ?? {}).map(([point, size], idx) => [point, size, idx])
= (resolveBreakpoints(context) ?? []).map(({ point, size }, idx) => [point, size, idx])
for (const [point, size, idx] of variantEntries) {
if (!regexCache[point])
regexCache[point] = new RegExp(`^((?:([al]t-|[<~]|max-))?${point}(?:${context.generator.config.separators.join('|')}))`)
Expand Down
6 changes: 3 additions & 3 deletions packages/preset-wind/src/rules/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ export const container: Rule<Theme>[] = [
if (isString(query)) {
const match = query.match(queryMatcher)?.[1]
if (match) {
const bp = resolveBreakpoints(context) ?? {}
const matchBp = Object.keys(bp).find(key => bp[key] === match)
const bp = resolveBreakpoints(context) ?? []
const matchBp = bp.find(i => i.size === match)?.point

if (!themeMaxWidth)
maxWidth = match
Expand Down Expand Up @@ -67,7 +67,7 @@ export const container: Rule<Theme>[] = [

export const containerShortcuts: Shortcut<Theme>[] = [
[/^(?:(\w+)[:-])?container$/, ([, bp], context) => {
let points = Object.keys(resolveBreakpoints(context) ?? {})
let points = (resolveBreakpoints(context) ?? []).map(i => i.point)
if (bp) {
if (!points.includes(bp))
return
Expand Down
6 changes: 5 additions & 1 deletion packages/transformer-directives/src/screen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,12 @@ export function handleScreen({ code, uno }: TransformerDirectivesContext, node:
breakpoints = (uno.config.theme as Theme).breakpoints

return breakpoints
? Object.entries(breakpoints)
.sort((a, b) => Number.parseInt(a[1].replace(/[a-z]+/gi, '')) - Number.parseInt(b[1].replace(/[a-z]+/gi, '')))
.map(([point, size]) => ({ point, size }))
: undefined
}
const variantEntries: Array<[string, string, number]> = Object.entries(resolveBreakpoints() ?? {}).map(([point, size], idx) => [point, size, idx])
const variantEntries: Array<[string, string, number]> = (resolveBreakpoints() ?? []).map(({ point, size }, idx) => [point, size, idx])
const generateMediaQuery = (breakpointName: string, prefix?: string) => {
const [, size, idx] = variantEntries.find(i => i[0] === breakpointName)!
if (prefix) {
Expand Down
6 changes: 6 additions & 0 deletions playground/src/auto-imports.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ declare global {
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
const init: typeof import('./composables/uno')['init']
const inject: typeof import('vue')['inject']
const injectLocal: typeof import('@vueuse/core')['injectLocal']
const inputHTML: typeof import('./composables/url')['inputHTML']
const isCSSPrettify: typeof import('./composables/prettier')['isCSSPrettify']
const isCollapsed: typeof import('./composables/panel')['isCollapsed']
Expand Down Expand Up @@ -99,6 +100,7 @@ declare global {
const panelSizes: typeof import('./composables/panel')['panelSizes']
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
const provide: typeof import('vue')['provide']
const provideLocal: typeof import('@vueuse/core')['provideLocal']
const reactify: typeof import('@vueuse/core')['reactify']
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
const reactive: typeof import('vue')['reactive']
Expand Down Expand Up @@ -396,6 +398,7 @@ declare module 'vue' {
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
readonly init: UnwrapRef<typeof import('./composables/uno')['init']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
readonly inputHTML: UnwrapRef<typeof import('./composables/url')['inputHTML']>
readonly isCSSPrettify: UnwrapRef<typeof import('./composables/prettier')['isCSSPrettify']>
readonly isCollapsed: UnwrapRef<typeof import('./composables/panel')['isCollapsed']>
Expand Down Expand Up @@ -436,6 +439,7 @@ declare module 'vue' {
readonly panelSizes: UnwrapRef<typeof import('./composables/panel')['panelSizes']>
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
Expand Down Expand Up @@ -727,6 +731,7 @@ declare module '@vue/runtime-core' {
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
readonly init: UnwrapRef<typeof import('./composables/uno')['init']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
readonly inputHTML: UnwrapRef<typeof import('./composables/url')['inputHTML']>
readonly isCSSPrettify: UnwrapRef<typeof import('./composables/prettier')['isCSSPrettify']>
readonly isCollapsed: UnwrapRef<typeof import('./composables/panel')['isCollapsed']>
Expand Down Expand Up @@ -767,6 +772,7 @@ declare module '@vue/runtime-core' {
readonly panelSizes: UnwrapRef<typeof import('./composables/panel')['panelSizes']>
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
Expand Down
1 change: 1 addition & 0 deletions test/preset-mini.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ describe('preset-mini', () => {
},
},
})

expect((await uno.generate('z-header', { preflights: false })).css)
.toMatchInlineSnapshot(`
"/* layer: default */
Expand Down
35 changes: 35 additions & 0 deletions test/preset-uno.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,38 @@ it('empty prefix', async () => {

await expect(css).toMatchFileSnapshot('./assets/output/preset-uno-empty-prefix.css')
})

it('define breakpoints with irregular sorting', async () => {
const uno = createGenerator({
presets: [
presetUno(),
],
theme: {
breakpoints: {
'xxs': '320px',
'sm': '640px',
'xs': '480px',
'xl': '1280px',
'2xl': '1536px',
'md': '768px',
'lg': '1024px',
},
container: {
center: true,
padding: {
'DEFAULT': '1rem',
'xl': '5rem',
'2xl': '6rem',
},
},
},
})

expect((await uno.generate('2xl:container', { preflights: false })).css)
.toMatchInlineSnapshot(`
"/* layer: shortcuts */
@media (min-width: 1536px){
.\\\\32 xl\\\\:container{max-width:1536px;margin-left:auto;margin-right:auto;padding-left:6rem;padding-right:6rem;}
}"
`)
})

0 comments on commit 3c65363

Please sign in to comment.