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-mini): add container query syntax #1821

Merged
merged 5 commits into from Nov 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/preset-mini/src/_rules/container.ts
@@ -0,0 +1,13 @@
import type { Rule } from '@unocss/core'
import { warnOnce } from '@unocss/core'

export const containerParent: Rule[] = [
[/^@container(?:\/(\w+))?(?:-(normal))?$/, ([, l, v]) => {
warnOnce('The container query rule is experimental and may not follow semver.')

return {
'container-type': v ?? 'inline-size',
'container-name': l,
}
}],
]
2 changes: 2 additions & 0 deletions packages/preset-mini/src/_rules/default.ts
Expand Up @@ -20,6 +20,7 @@ import { textAligns, verticalAligns } from './align'
import { appearance, outline, willChange } from './behaviors'
import { textDecorations } from './decoration'
import { svgUtilities } from './svg'
import { containerParent } from './container'

export const rules: Rule[] = [
cssVariables,
Expand Down Expand Up @@ -75,6 +76,7 @@ export const rules: Rule[] = [
transitions,
transforms,
willChange,
containerParent,

// should be the last
questionMark,
Expand Down
1 change: 1 addition & 0 deletions packages/preset-mini/src/_rules/index.ts
Expand Up @@ -3,6 +3,7 @@ export * from './align'
export * from './behaviors'
export * from './border'
export * from './color'
export * from './container'
export * from './default'
export * from './flex'
export * from './gap'
Expand Down
3 changes: 2 additions & 1 deletion packages/preset-mini/src/_theme/default.ts
Expand Up @@ -2,7 +2,7 @@ import { colors } from './colors'
import { fontFamily, fontSize, letterSpacing, lineHeight, textIndent, textShadow, textStrokeWidth, wordSpacing } from './font'
import { borderRadius, boxShadow, breakpoints, duration, easing, lineWidth, ringWidth, spacing, verticalBreakpoints } from './misc'
import { blur, dropShadow } from './filters'
import { height, maxHeight, maxWidth, width } from './size'
import { containers, height, maxHeight, maxWidth, width } from './size'
import type { Theme } from './types'
import { preflightBase } from './preflight'

Expand Down Expand Up @@ -40,4 +40,5 @@ export const theme: Theme = {
duration,
ringWidth,
preflightBase,
containers,
}
2 changes: 2 additions & 0 deletions packages/preset-mini/src/_theme/size.ts
Expand Up @@ -36,3 +36,5 @@ export const maxHeight = {
...baseSize,
screen: '100vh',
}

export const containers = Object.fromEntries(Object.entries(baseSize).map(([k, v]) => [k, `(min-width: ${v})`]))
2 changes: 2 additions & 0 deletions packages/preset-mini/src/_theme/types.ts
Expand Up @@ -49,6 +49,8 @@ export interface Theme {
media?: Record<string, string>
// supports queries
supports?: Record<string, string>
// container queries
containers?: Record<string, string>
// animation
animation?: ThemeAnimation
// grids
Expand Down
33 changes: 21 additions & 12 deletions packages/preset-mini/src/_utils/variants.ts
Expand Up @@ -42,9 +42,9 @@ export const variantParentMatcher = (name: string, parent: string): VariantObjec
}
}

export const variantGetBracket = (name: string, matcher: string, separators: string[]): string[] | undefined => {
if (matcher.startsWith(`${name}-[`)) {
const [match, rest] = getBracket(matcher.slice(name.length + 1), '[', ']') ?? []
export const variantGetBracket = (prefix: string, matcher: string, separators: string[]): string[] | undefined => {
if (matcher.startsWith(`${prefix}[`)) {
const [match, rest] = getBracket(matcher.slice(prefix.length), '[', ']') ?? []
if (match && rest) {
for (const separator of separators) {
if (rest.startsWith(separator))
Expand All @@ -55,15 +55,24 @@ export const variantGetBracket = (name: string, matcher: string, separators: str
}
}

export const variantGetParameter = (name: string, matcher: string, separators: string[]): string[] | undefined => {
if (matcher.startsWith(`${name}-`)) {
const body = variantGetBracket(name, matcher, separators)
if (body)
return body
for (const separator of separators) {
const pos = matcher.indexOf(separator, name.length + 1)
if (pos !== -1)
return [matcher.slice(name.length + 1, pos), matcher.slice(pos + separator.length)]
export const variantGetParameter = (prefix: string, matcher: string, separators: string[]): string[] | undefined => {
if (matcher.startsWith(prefix)) {
const body = variantGetBracket(prefix, matcher, separators)
if (body) {
const [label = '', rest = body[1]] = variantGetParameter('/', body[1], separators) ?? []
return [body[0], rest, label]
}
for (const separator of separators.filter(x => x !== '/')) {
const pos = matcher.indexOf(separator, prefix.length)
if (pos !== -1) {
const labelPos = matcher.indexOf('/', prefix.length)
const unlabelled = labelPos === -1 || pos <= labelPos
return [
matcher.slice(prefix.length, unlabelled ? pos : labelPos),
matcher.slice(pos + separator.length),
unlabelled ? '' : matcher.slice(labelPos + 1, pos),
]
}
}
}
}
2 changes: 1 addition & 1 deletion packages/preset-mini/src/_variants/aria.ts
Expand Up @@ -5,7 +5,7 @@ import { handler as h, variantGetParameter } from '../utils'
export const variantAria: VariantObject = {
name: 'aria',
match(matcher, { theme }: VariantContext<Theme>) {
const variant = variantGetParameter('aria', matcher, [':', '-'])
const variant = variantGetParameter('aria-', matcher, [':', '-'])
if (variant) {
const [match, rest] = variant
const aria = h.bracket(match) ?? theme.aria?.[match] ?? ''
Expand Down
2 changes: 1 addition & 1 deletion packages/preset-mini/src/_variants/combinators.ts
Expand Up @@ -7,7 +7,7 @@ const scopeMatcher = (name: string, combinator: string): VariantObject => ({
if (!matcher.startsWith(name))
return

let body = variantGetBracket(name, matcher, [':', '-'])
let body = variantGetBracket(`${name}-`, matcher, [':', '-'])
if (!body) {
for (const separator of [':', '-']) {
if (matcher.startsWith(`${name}${separator}`)) {
Expand Down
39 changes: 39 additions & 0 deletions packages/preset-mini/src/_variants/container.ts
@@ -0,0 +1,39 @@
import type { VariantContext, VariantObject } from '@unocss/core'
import { warnOnce } from '@unocss/core'
import type { Theme } from '../theme'
import { handler as h, variantGetParameter } from '../utils'

export const variantContainerQuery: VariantObject = {
name: '@',
match(matcher, { theme }: VariantContext<Theme>) {
if (matcher.startsWith('@container'))
return

const variant = variantGetParameter('@', matcher, [':', '-'])
if (variant) {
const [match, rest, label] = variant
const unbracket = h.bracket(match)
let container: string | undefined
if (unbracket) {
const minWidth = h.numberWithUnit(unbracket)
if (minWidth)
container = `(min-width: ${minWidth})`
}
else {
container = theme.containers?.[match] ?? ''
}

if (container) {
warnOnce('The container query variant is experimental and may not follow semver.')
return {
matcher: rest,
handle: (input, next) => next({
...input,
parent: `${input.parent ? `${input.parent} $$ ` : ''}@container${label ? ` ${label} ` : ' '}${container}`,
}),
}
}
}
},
multiPass: true,
}
2 changes: 1 addition & 1 deletion packages/preset-mini/src/_variants/data.ts
Expand Up @@ -5,7 +5,7 @@ import { handler as h, variantGetParameter } from '../utils'
export const variantDataAttribute: VariantObject = {
name: 'data',
match(matcher, { theme }: VariantContext<Theme>) {
const variant = variantGetParameter('data', matcher, [':', '-'])
const variant = variantGetParameter('data-', matcher, [':', '-'])
if (variant) {
const [match, rest] = variant
const dataAttribute = h.bracket(match) ?? theme.data?.[match] ?? ''
Expand Down
2 changes: 2 additions & 0 deletions packages/preset-mini/src/_variants/default.ts
Expand Up @@ -13,6 +13,7 @@ import { variantSupports } from './supports'
import { partClasses, variantPseudoClassFunctions, variantPseudoClassesAndElements, variantTaggedPseudoClasses } from './pseudo'
import { variantAria } from './aria'
import { variantDataAttribute } from './data'
import { variantContainerQuery } from './container'

export const variants = (options: PresetMiniOptions): Variant<Theme>[] => [
variantAria,
Expand All @@ -38,5 +39,6 @@ export const variants = (options: PresetMiniOptions): Variant<Theme>[] => [
...variantLanguageDirections,
variantScope,

variantContainerQuery,
variantVariables,
]
1 change: 1 addition & 0 deletions packages/preset-mini/src/_variants/index.ts
Expand Up @@ -2,6 +2,7 @@
export * from './aria'
export * from './breakpoints'
export * from './combinators'
export * from './container'
export * from './data'
export * from './media'
export * from './supports'
Expand Down
2 changes: 1 addition & 1 deletion packages/preset-mini/src/_variants/media.ts
Expand Up @@ -7,7 +7,7 @@ export const variantPrint: Variant = variantParentMatcher('print', '@media print
export const variantCustomMedia: VariantObject = {
name: 'media',
match(matcher, { theme }: VariantContext<Theme>) {
const variant = variantGetParameter('media', matcher, [':', '-'])
const variant = variantGetParameter('media-', matcher, [':', '-'])
if (variant) {
const [match, rest] = variant

Expand Down
8 changes: 4 additions & 4 deletions packages/preset-mini/src/_variants/misc.ts
Expand Up @@ -4,7 +4,7 @@ import { getBracket, handler as h, variantGetBracket, variantGetParameter } from
export const variantSelector: Variant = {
name: 'selector',
match(matcher) {
const variant = variantGetBracket('selector', matcher, [':', '-'])
const variant = variantGetBracket('selector-', matcher, [':', '-'])
if (variant) {
const [match, rest] = variant
const selector = h.bracket(match)
Expand All @@ -21,7 +21,7 @@ export const variantSelector: Variant = {
export const variantCssLayer: Variant = {
name: 'layer',
match(matcher) {
const variant = variantGetParameter('layer', matcher, [':', '-'])
const variant = variantGetParameter('layer-', matcher, [':', '-'])
if (variant) {
const [match, rest] = variant
const layer = h.bracket(match) ?? match
Expand All @@ -41,7 +41,7 @@ export const variantCssLayer: Variant = {
export const variantInternalLayer: Variant = {
name: 'uno-layer',
match(matcher) {
const variant = variantGetParameter('uno-layer', matcher, [':', '-'])
const variant = variantGetParameter('uno-layer-', matcher, [':', '-'])
if (variant) {
const [match, rest] = variant
const layer = h.bracket(match) ?? match
Expand All @@ -58,7 +58,7 @@ export const variantInternalLayer: Variant = {
export const variantScope: Variant = {
name: 'scope',
match(matcher) {
const variant = variantGetBracket('scope', matcher, [':', '-'])
const variant = variantGetBracket('scope-', matcher, [':', '-'])
if (variant) {
const [match, rest] = variant
const scope = h.bracket(match)
Expand Down
4 changes: 2 additions & 2 deletions packages/preset-mini/src/_variants/pseudo.ts
Expand Up @@ -88,7 +88,7 @@ const taggedPseudoClassMatcher = (tag: string, parent: string, combinator: strin
const pseudoColonRE = new RegExp(`^${tag}-(?:(?:(${PseudoClassFunctionsStr})-)?(${PseudoClassesColonStr}))(?:(/\\w+))?[:]`)

const matchBracket = (input: string) => {
const body = variantGetBracket(tag, input, [])
const body = variantGetBracket(`${tag}-`, input, [])
if (!body)
return

Expand Down Expand Up @@ -137,7 +137,7 @@ const taggedPseudoClassMatcher = (tag: string, parent: string, combinator: strin

const [label, matcher, prefix, sort] = result as [string, string, string, number | undefined]
if (label !== '')
warnOnce('The labeled pseudo is experimental and may be changed in breaking ways at any time.')
warnOnce('The labeled variant is experimental and may not follow semver.')

return {
matcher,
Expand Down
2 changes: 1 addition & 1 deletion packages/preset-mini/src/_variants/supports.ts
Expand Up @@ -5,7 +5,7 @@ import { handler as h, variantGetParameter } from '../utils'
export const variantSupports: VariantObject = {
name: 'supports',
match(matcher, { theme }: VariantContext<Theme>) {
const variant = variantGetParameter('supports', matcher, [':', '-'])
const variant = variantGetParameter('supports-', matcher, [':', '-'])
if (variant) {
const [match, rest] = variant

Expand Down
2 changes: 2 additions & 0 deletions packages/preset-wind/src/rules/default.ts
Expand Up @@ -8,6 +8,7 @@ import {
borders,
boxShadows,
boxSizing,
containerParent,
contentVisibility,
contents,
cssProperty,
Expand Down Expand Up @@ -155,6 +156,7 @@ export const rules: Rule[] = [
contentVisibility,
contents,
placeholders,
containerParent,

// should be the last
questionMark,
Expand Down
19 changes: 19 additions & 0 deletions test/__snapshots__/preset-mini.test.ts.snap
Expand Up @@ -914,6 +914,25 @@ unocss .scope-\\\\[unocss\\\\]\\\\:block{display:block;}
.will-change-scroll{will-change:scroll-position;}
.will-change-transform{will-change:transform;}
.will-change-unset{will-change:unset;}
.\\\\@container{container-type:inline-size;}
.\\\\@container-normal{container-type:normal;}
.\\\\@container\\\\/label{container-type:inline-size;container-name:label;}
.\\\\@container\\\\/label-normal{container-type:normal;container-name:label;}
@container (min-width: 10.5rem){
.\\\\@\\\\[10\\\\.5rem\\\\]-text-red{--un-text-opacity:1;color:rgba(248,113,113,var(--un-text-opacity));}
}
@container (min-width: 24rem){
.\\\\@sm\\\\:text-red{--un-text-opacity:1;color:rgba(248,113,113,var(--un-text-opacity));}
}
@container (min-width: 32rem){
.\\\\@lg-text-red{--un-text-opacity:1;color:rgba(248,113,113,var(--un-text-opacity));}
}
@container label (min-width: 100px){
.\\\\@\\\\[100px\\\\]\\\\/label\\\\:text-green{--un-text-opacity:1;color:rgba(74,222,128,var(--un-text-opacity));}
}
@container label (min-width: 20rem){
.\\\\@xs\\\\/label\\\\:text-green{--un-text-opacity:1;color:rgba(74,222,128,var(--un-text-opacity));}
}
@layer base{
.layer-base\\\\:translate-0{--un-translate-x:0rem;--un-translate-y:0rem;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));}
}
Expand Down
13 changes: 13 additions & 0 deletions test/assets/preset-mini-targets.ts
Expand Up @@ -1007,6 +1007,19 @@ export const presetMiniTargets: string[] = [
// variants - data
'data-[inline]:inline',
'data-[invalid~=grammar]:underline-green-600',

// variants - container parent
'@container',
'@container/label',
'@container-normal',
'@container/label-normal',

// variants - container query (@)
'@sm:text-red',
'@lg-text-red',
'@[10.5rem]-text-red',
'@xs/label:text-green',
'@[100px]/label:text-green',
]

export const presetMiniNonTargets = [
Expand Down