/** * @module @twind/preset-tailwind/rules */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import type { MatchResult, Rule, MaybeArray, CSSProperties, CSSObject, CSSBase, ThemeMatchResult, ThemeRuleResolver, ColorFromThemeValue, AutocompleteProvider, } from '@twind/core' import { DEV } from 'distilt/env' import { mql, match, matchTheme, matchColor, toColorValue, toCSS, asArray, arbitrary, withAutocomplete, } from '@twind/core' import type { TailwindTheme } from './types' // indirection wrapper to remove autocomplete functions from production bundles function withAutocomplete$( rule: Rule, autocomplete: AutocompleteProvider | false, ): Rule { if (DEV) { return withAutocomplete(rule, autocomplete) } return rule } const rules: Rule[] = [ /* arbitrary properties: [paint-order:markers] */ match('\\[([-\\w]+):(.+)]', ({ 1: $1, 2: $2 }, context) => ({ '@layer overrides': { '&': { [$1]: arbitrary(`[${$2}]`, $1, context), }, }, })), /* Styling based on parent and peer state */ withAutocomplete$( match('(group|peer)(~[^-[]+)?', ({ input }, { h }) => [{ c: h(input) }]), DEV && (() => ['group', 'peer']), ), /* LAYOUT */ matchTheme('aspect-', 'aspectRatio'), match('container', (_, { theme }) => { const { screens = theme('screens'), center, padding } = theme('container') const rules = { width: '100%', marginRight: center && 'auto', marginLeft: center && 'auto', ...paddingFor('xs'), } as CSSObject for (const screen in screens) { const value = screens[screen] if (typeof value == 'string') { rules[mql(value)] = { '&': { maxWidth: value, ...paddingFor(screen), }, } } } return rules function paddingFor(screen: string): CSSProperties | undefined { const value = padding && (typeof padding == 'string' ? padding : padding[screen] || padding.DEFAULT) if (value) { return { paddingRight: value, paddingLeft: value, } } } }), // Content matchTheme('content-', 'content', ({ _ }) => ({ '--tw-content': _, content: 'var(--tw-content)', })), // Box Decoration Break match('(?:box-)?decoration-(slice|clone)', 'boxDecorationBreak'), // Box Sizing match('box-(border|content)', 'boxSizing', ({ 1: $1 }) => $1 + '-box'), // Display match('hidden', { display: 'none' }), // Table Layout match('table-(auto|fixed)', 'tableLayout'), match( [ '(block|flex|table|grid|inline|contents|flow-root|list-item)', '(inline-(block|flex|table|grid))', '(table-(caption|cell|column|row|(column|row|footer|header)-group))', ], 'display', ), // Floats '(float)-(left|right|none)', // Clear '(clear)-(left|right|none|both)', // Overflow '(overflow(?:-[xy])?)-(auto|hidden|clip|visible|scroll)', // Isolation '(isolation)-(auto)', // Isolation match('isolate', 'isolation'), // Object Fit match('object-(contain|cover|fill|none|scale-down)', 'objectFit'), // Object Position matchTheme('object-', 'objectPosition'), match('object-(top|bottom|center|(left|right)(-(top|bottom))?)', 'objectPosition', spacify), // Overscroll Behavior match('overscroll(-[xy])?-(auto|contain|none)', ({ 1: $1 = '', 2: $2 }) => ({ [('overscroll-behavior' + $1) as 'overscroll-behavior-x']: $2 as 'auto', })), // Position match('(static|fixed|absolute|relative|sticky)', 'position'), // Top / Right / Bottom / Left matchTheme('-?inset(-[xy])?(?:$|-)', 'inset', ({ 1: $1, _ }) => ({ top: $1 != '-x' && _, right: $1 != '-y' && _, bottom: $1 != '-x' && _, left: $1 != '-y' && _, })), matchTheme('-?(top|bottom|left|right)(?:$|-)', 'inset'), // Visibility match('visible', 'visibility'), match('invisible', { visibility: 'hidden' }), // Z-Index matchTheme('-?z-', 'zIndex'), /* FLEXBOX */ // Flex Direction match('flex-((row|col)(-reverse)?)', 'flexDirection', columnify), match('flex-(wrap|wrap-reverse|nowrap)', 'flexWrap'), matchTheme('(flex-(?:grow|shrink))(?:$|-)' /*, 'flex-grow' | flex-shrink */), matchTheme('(flex)-' /*, 'flex' */), matchTheme('grow(?:$|-)', 'flexGrow'), matchTheme('shrink(?:$|-)', 'flexShrink'), matchTheme('basis-', 'flexBasis'), matchTheme('-?(order)-' /*, 'order' */), withAutocomplete$('-?(order)-(\\d+)', DEV && (() => range({ end: 12 }))), /* GRID */ // Grid Template Columns matchTheme('grid-cols-', 'gridTemplateColumns'), withAutocomplete$( match('grid-cols-(\\d+)', 'gridTemplateColumns', gridTemplate), DEV && (() => range({ end: 6 })), ), // Grid Column Start / End matchTheme('col-', 'gridColumn'), withAutocomplete$( match('col-(span)-(\\d+)', 'gridColumn', span), DEV && (() => range({ end: 12 })), ), matchTheme('col-start-', 'gridColumnStart'), withAutocomplete$( match('col-start-(auto|\\d+)', 'gridColumnStart'), DEV && (({ 1: $1 }) => ($1 === 'auto' ? [''] : range({ end: 13 }))), ), matchTheme('col-end-', 'gridColumnEnd'), withAutocomplete$( match('col-end-(auto|\\d+)', 'gridColumnEnd'), DEV && (({ 1: $1 }) => ($1 === 'auto' ? [''] : range({ end: 13 }))), ), // Grid Template Rows matchTheme('grid-rows-', 'gridTemplateRows'), withAutocomplete$( match('grid-rows-(\\d+)', 'gridTemplateRows', gridTemplate), DEV && (() => range({ end: 6 })), ), // Grid Row Start / End matchTheme('row-', 'gridRow'), withAutocomplete$(match('row-(span)-(\\d+)', 'gridRow', span), DEV && (() => range({ end: 6 }))), matchTheme('row-start-', 'gridRowStart'), withAutocomplete$( match('row-start-(auto|\\d+)', 'gridRowStart'), DEV && (({ 1: $1 }) => ($1 === 'auto' ? [''] : range({ end: 7 }))), ), matchTheme('row-end-', 'gridRowEnd'), withAutocomplete$( match('row-end-(auto|\\d+)', 'gridRowEnd'), DEV && (({ 1: $1 }) => ($1 === 'auto' ? [''] : range({ end: 7 }))), ), // Grid Auto Flow match('grid-flow-((row|col)(-dense)?)', 'gridAutoFlow', (match) => spacify(columnify(match))), match('grid-flow-(dense)', 'gridAutoFlow'), // Grid Auto Columns matchTheme('auto-cols-', 'gridAutoColumns'), // Grid Auto Rows matchTheme('auto-rows-', 'gridAutoRows'), // Gap matchTheme('gap-x(?:$|-)', 'gap', 'columnGap'), matchTheme('gap-y(?:$|-)', 'gap', 'rowGap'), matchTheme('gap(?:$|-)', 'gap'), /* BOX ALIGNMENT */ // Justify Items // Justify Self withAutocomplete$( '(justify-(?:items|self))-', DEV && (({ 1: $1 }) => $1.endsWith('-items-') ? ['start', 'end', 'center', 'stretch'] : /* '-self-' */ ['auto', 'start', 'end', 'center', 'stretch']), ), // Justify Content withAutocomplete$( match('justify-', 'justifyContent', convertContentValue), DEV && (() => ['start', 'end', 'center', 'between', 'around', 'evenly']), ), // Align Content // Align Items // Align Self withAutocomplete$( match('(content|items|self)-', (match) => ({ [('align-' + match[1]) as 'align-content']: convertContentValue(match), })), DEV && (({ 1: $1 }) => $1 == 'content' ? ['center', 'start', 'end', 'between', 'around', 'evenly'] : $1 == 'items' ? ['start', 'end', 'center', 'baseline', 'stretch'] : /* $1 == 'self' */ ['auto', 'start', 'end', 'center', 'stretch', 'baseline']), ), // Place Content // Place Items // Place Self withAutocomplete$( match('(place-(content|items|self))-', ({ 1: $1, $$ }) => ({ [$1 as 'place-content']: ('wun'.includes($$[3]) ? 'space-' : '') + $$, })), DEV && (({ 1: $1 }) => $1 == 'content' ? ['center', 'start', 'end', 'between', 'around', 'evenly', 'stretch'] : $1 == 'items' ? ['start', 'end', 'center', 'stretch'] : /* $1 == 'self' */ ['auto', 'start', 'end', 'center', 'stretch']), ), /* SPACING */ // Padding matchTheme('p([xytrbl])?(?:$|-)', 'padding', edge('padding')), // Margin matchTheme('-?m([xytrbl])?(?:$|-)', 'margin', edge('margin')), // Space Between matchTheme('-?space-(x|y)(?:$|-)', 'space', ({ 1: $1, _ }) => ({ '&>:not([hidden])~:not([hidden])': { [`--tw-space-${$1}-reverse`]: '0', ['margin-' + { y: 'top', x: 'left' }[ $1 as 'y' | 'x' ]]: `calc(${_} * calc(1 - var(--tw-space-${$1}-reverse)))`, ['margin-' + { y: 'bottom', x: 'right' }[$1 as 'y' | 'x']]: `calc(${_} * var(--tw-space-${$1}-reverse))`, }, })), match('space-(x|y)-reverse', ({ 1: $1 }) => ({ '&>:not([hidden])~:not([hidden])': { [`--tw-space-${$1}-reverse`]: '1', }, })), /* SIZING */ // Width matchTheme('w-', 'width'), // Min-Width matchTheme('min-w-', 'minWidth'), // Max-Width matchTheme('max-w-', 'maxWidth'), // Height matchTheme('h-', 'height'), // Min-Height matchTheme('min-h-', 'minHeight'), // Max-Height matchTheme('max-h-', 'maxHeight'), /* TYPOGRAPHY */ // Font Weight matchTheme('font-', 'fontWeight'), // Font Family matchTheme('font-', 'fontFamily', 'fontFamily', join), // Font Smoothing match('antialiased', { WebkitFontSmoothing: 'antialiased', MozOsxFontSmoothing: 'grayscale', }), match('subpixel-antialiased', { WebkitFontSmoothing: 'auto', MozOsxFontSmoothing: 'auto', }), // Font Style match('italic', 'fontStyle'), match('not-italic', { fontStyle: 'normal' }), // Font Variant Numeric match( '(ordinal|slashed-zero|(normal|lining|oldstyle|proportional|tabular)-nums|(diagonal|stacked)-fractions)', ({ 1: $1, 2: $2 = '', 3: $3 }) => // normal-nums $2 == 'normal' ? { fontVariantNumeric: 'normal' } : { [('--tw-' + ($3 // diagonal-fractions, stacked-fractions ? 'numeric-fraction' : 'pt'.includes($2[0]) // proportional-nums, tabular-nums ? 'numeric-spacing' : $2 // lining-nums, oldstyle-nums ? 'numeric-figure' : // ordinal, slashed-zero $1)) as 'numeric-spacing']: $1, fontVariantNumeric: 'var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)', '@layer defaults': { '*,::before,::after,::backdrop': { '--tw-ordinal': 'var(--tw-empty,/*!*/ /*!*/)', '--tw-slashed-zero': 'var(--tw-empty,/*!*/ /*!*/)', '--tw-numeric-figure': 'var(--tw-empty,/*!*/ /*!*/)', '--tw-numeric-spacing': 'var(--tw-empty,/*!*/ /*!*/)', '--tw-numeric-fraction': 'var(--tw-empty,/*!*/ /*!*/)', }, }, }, ), // Letter Spacing matchTheme('tracking-', 'letterSpacing'), // Line Height matchTheme('leading-', 'lineHeight'), // List Style Position match('list-(inside|outside)', 'listStylePosition'), // List Style Type matchTheme('list-', 'listStyleType'), withAutocomplete$(match('list-', 'listStyleType'), DEV && (() => ['none', 'disc', 'decimal'])), // Placeholder Opacity matchTheme('placeholder-opacity-', 'placeholderOpacity', ({ _ }) => ({ ['&::placeholder']: { '--tw-placeholder-opacity': _ }, })), // Placeholder Color matchColor('placeholder-', { property: 'color', selector: '&::placeholder' }), // Text Alignment match('text-(left|center|right|justify|start|end)', 'textAlign'), match('text-(ellipsis|clip)', 'textOverflow'), // Text Opacity matchTheme('text-opacity-', 'textOpacity', '--tw-text-opacity'), // Text Color matchColor('text-', { property: 'color' }), // Font Size matchTheme('text-', 'fontSize', ({ _ }) => typeof _ == 'string' ? { fontSize: _ } : { fontSize: _[0], ...(typeof _[1] == 'string' ? { lineHeight: _[1] } : _[1]), }, ), // Text Indent matchTheme('indent-', 'textIndent'), // Text Decoration match('(overline|underline|line-through)', 'textDecorationLine'), match('no-underline', { textDecorationLine: 'none' }), // Text Underline offset matchTheme('underline-offset-', 'textUnderlineOffset'), // Text Decoration Color matchColor('decoration-', { section: 'textDecorationColor', opacityVariable: false, opacitySection: 'opacity', }), // Text Decoration Thickness matchTheme('decoration-', 'textDecorationThickness'), // Text Decoration Style withAutocomplete$( match('decoration-', 'textDecorationStyle'), DEV && (() => ['solid', 'double', 'dotted', 'dashed', 'wavy']), ), // Text Transform match('(uppercase|lowercase|capitalize)', 'textTransform'), match('normal-case', { textTransform: 'none' }), // Text Overflow match('truncate', { overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis', }), // Vertical Alignment withAutocomplete$( match('align-', 'verticalAlign'), DEV && (() => ['baseline', 'top', 'middle', 'bottom', 'text-top', 'text-bottom', 'sub', 'super']), ), // Whitespace withAutocomplete$( match('whitespace-', 'whiteSpace'), DEV && (() => ['normal', 'nowrap', 'pre', 'pre-line', 'pre-wrap']), ), // Word Break match('break-normal', { wordBreak: 'normal', overflowWrap: 'normal' }), match('break-words', { overflowWrap: 'break-word' }), match('break-all', { wordBreak: 'break-all' }), // Caret Color matchColor('caret-', { // section: 'caretColor', opacityVariable: false, opacitySection: 'opacity', }), // Accent Color matchColor('accent-', { // section: 'accentColor', opacityVariable: false, opacitySection: 'opacity', }), // Gradient Color Stops match( 'bg-gradient-to-([trbl]|[tb][rl])', 'backgroundImage', ({ 1: $1 }) => `linear-gradient(to ${position($1, ' ')},var(--tw-gradient-stops))`, ), matchColor( 'from-', { section: 'gradientColorStops', opacityVariable: false, opacitySection: 'opacity', }, ({ _ }) => ({ '--tw-gradient-from': _.value, '--tw-gradient-to': _.color({ opacityValue: '0' }), '--tw-gradient-stops': `var(--tw-gradient-from),var(--tw-gradient-to)`, }), ), matchColor( 'via-', { section: 'gradientColorStops', opacityVariable: false, opacitySection: 'opacity', }, ({ _ }) => ({ '--tw-gradient-to': _.color({ opacityValue: '0' }), '--tw-gradient-stops': `var(--tw-gradient-from),${_.value},var(--tw-gradient-to)`, }), ), matchColor('to-', { section: 'gradientColorStops', property: '--tw-gradient-to', opacityVariable: false, opacitySection: 'opacity', }), /* BACKGROUNDS */ // Background Attachment match('bg-(fixed|local|scroll)', 'backgroundAttachment'), // Background Origin match('bg-origin-(border|padding|content)', 'backgroundOrigin', ({ 1: $1 }) => $1 + '-box'), // Background Repeat match(['bg-(no-repeat|repeat(-[xy])?)', 'bg-repeat-(round|space)'], 'backgroundRepeat'), // Background Blend Mode withAutocomplete$( match('bg-blend-', 'backgroundBlendMode'), DEV && (() => [ 'normal', 'multiply', 'screen', 'overlay', 'darken', 'lighten', 'color-dodge', 'color-burn', 'hard-light', 'soft-light', 'difference', 'exclusion', 'hue', 'saturation', 'color', 'luminosity', ]), ), // Background Clip match( 'bg-clip-(border|padding|content|text)', 'backgroundClip', ({ 1: $1 }) => $1 + ($1 == 'text' ? '' : '-box'), ), // Background Opacity matchTheme('bg-opacity-', 'backgroundOpacity', '--tw-bg-opacity'), // Background Color // bg-${backgroundColor}/${backgroundOpacity} matchColor('bg-', { section: 'backgroundColor' }), // Background Image // supported arbitrary types are: length, color, angle, list matchTheme('bg-', 'backgroundImage'), // Background Position matchTheme('bg-', 'backgroundPosition'), match('bg-(top|bottom|center|(left|right)(-(top|bottom))?)', 'backgroundPosition', spacify), // Background Size matchTheme('bg-', 'backgroundSize'), /* BORDERS */ // Border Radius matchTheme('rounded(?:$|-)', 'borderRadius'), matchTheme('rounded-([trbl]|[tb][rl])(?:$|-)', 'borderRadius', ({ 1: $1, _ }) => { const corners = ( { t: ['tl', 'tr'], r: ['tr', 'br'], b: ['bl', 'br'], l: ['bl', 'tl'], } as const )[$1] || [$1, $1] return { [`border-${position(corners[0])}-radius` as 'border-top-left-radius']: _, [`border-${position(corners[1])}-radius` as 'border-top-right-radius']: _, } }), // Border Collapse match('border-(collapse|separate)', 'borderCollapse'), // Border Opacity matchTheme('border-opacity(?:$|-)', 'borderOpacity', '--tw-border-opacity'), // Border Style match('border-(solid|dashed|dotted|double|none)', 'borderStyle'), // Border Spacing matchTheme('border-spacing(-[xy])?(?:$|-)', 'borderSpacing', ({ 1: $1, _ }) => ({ '@layer defaults': { '*,::before,::after,::backdrop': { '--tw-border-spacing-x': 0, '--tw-border-spacing-y': 0, }, }, [('--tw-border-spacing' + ($1 || '-x')) as '--tw-border-spacing-x']: _, [('--tw-border-spacing' + ($1 || '-y')) as '--tw-border-spacing-y']: _, 'border-spacing': 'var(--tw-border-spacing-x) var(--tw-border-spacing-y)', })), // Border Color matchColor('border-([xytrbl])-', { section: 'borderColor' }, edge('border', 'Color')), matchColor('border-'), // Border Width matchTheme('border-([xytrbl])(?:$|-)', 'borderWidth', edge('border', 'Width')), matchTheme('border(?:$|-)', 'borderWidth'), // Divide Opacity matchTheme('divide-opacity(?:$|-)', 'divideOpacity', ({ _ }) => ({ '&>:not([hidden])~:not([hidden])': { '--tw-divide-opacity': _ }, })), // Divide Style match('divide-(solid|dashed|dotted|double|none)', ({ 1: $1 }) => ({ '&>:not([hidden])~:not([hidden])': { borderStyle: $1 }, })), // Divide Width match('divide-([xy]-reverse)', ({ 1: $1 }) => ({ '&>:not([hidden])~:not([hidden])': { ['--tw-divide-' + $1]: '1' }, })), matchTheme('divide-([xy])(?:$|-)', 'divideWidth', ({ 1: $1, _ }) => { const edges = ( { x: 'lr', y: 'tb', } as const )[$1 as 'x' | 'y'] return { '&>:not([hidden])~:not([hidden])': { [`--tw-divide-${$1}-reverse`]: '0', [`border-${position( edges[0], )}Width`]: `calc(${_} * calc(1 - var(--tw-divide-${$1}-reverse)))`, [`border-${position(edges[1])}Width`]: `calc(${_} * var(--tw-divide-${$1}-reverse))`, }, } }), // Divide Color matchColor('divide-', { // section: $0.replace('-', 'Color') -> 'divideColor' property: 'borderColor', // opacityVariable: '--tw-border-opacity', // opacitySection: section.replace('Color', 'Opacity') -> 'divideOpacity' selector: '&>:not([hidden])~:not([hidden])', }), // Ring Offset Opacity matchTheme('ring-opacity(?:$|-)', 'ringOpacity', '--tw-ring-opacity'), // Ring Offset Color matchColor('ring-offset-', { // section: 'ringOffsetColor', property: '--tw-ring-offset-color', opacityVariable: false, // opacitySection: section.replace('Color', 'Opacity') -> 'ringOffsetOpacity' }), // Ring Offset Width matchTheme('ring-offset(?:$|-)', 'ringOffsetWidth', '--tw-ring-offset-width'), // Ring Inset match('ring-inset', { '--tw-ring-inset': 'inset' }), // Ring Color matchColor('ring-', { // section: 'ringColor', property: '--tw-ring-color', // opacityVariable: '--tw-ring-opacity', // opacitySection: section.replace('Color', 'Opacity') -> 'ringOpacity' }), // Ring Width matchTheme('ring(?:$|-)', 'ringWidth', ({ _ }, { theme }) => ({ '--tw-ring-offset-shadow': `var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)`, '--tw-ring-shadow': `var(--tw-ring-inset) 0 0 0 calc(${_} + var(--tw-ring-offset-width)) var(--tw-ring-color)`, boxShadow: `var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)`, '@layer defaults': { '*,::before,::after,::backdrop': { '--tw-ring-offset-shadow': '0 0 #0000', '--tw-ring-shadow': '0 0 #0000', '--tw-shadow': '0 0 #0000', '--tw-shadow-colored': '0 0 #0000', // Within own declaration to have the defaults above to be merged with defaults from shadow '&': { '--tw-ring-inset': 'var(--tw-empty,/*!*/ /*!*/)', '--tw-ring-offset-width': theme('ringOffsetWidth', '', '0px'), '--tw-ring-offset-color': toColorValue(theme('ringOffsetColor', '', '#fff')), '--tw-ring-color': toColorValue(theme('ringColor', '', '#93c5fd'), { opacityVariable: '--tw-ring-opacity', }), '--tw-ring-opacity': theme('ringOpacity', '', '0.5'), }, }, }, })), /* EFFECTS */ // Box Shadow Color matchColor( 'shadow-', { section: 'boxShadowColor', opacityVariable: false, opacitySection: 'opacity', }, ({ _ }) => ({ '--tw-shadow-color': _.value, '--tw-shadow': 'var(--tw-shadow-colored)', }), ), // Box Shadow matchTheme('shadow(?:$|-)', 'boxShadow', ({ _ }) => ({ '--tw-shadow': join(_), // replace all colors with reference to --tw-shadow-colored // this matches colors after non-comma char (keyword, offset) before comma or the end '--tw-shadow-colored': (join(_) as string).replace( /([^,]\s+)(?:#[a-f\d]+|(?:(?:hsl|rgb)a?|hwb|lab|lch|color|var)\(.+?\)|[a-z]+)(,|$)/g, '$1var(--tw-shadow-color)$2', ), boxShadow: `var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)`, '@layer defaults': { '*,::before,::after,::backdrop': { '--tw-ring-offset-shadow': '0 0 #0000', '--tw-ring-shadow': '0 0 #0000', '--tw-shadow': '0 0 #0000', '--tw-shadow-colored': '0 0 #0000', }, }, })), // Opacity matchTheme('(opacity)-' /*, 'opacity' */), // Mix Blend Mode withAutocomplete$( match('mix-blend-', 'mixBlendMode'), DEV && (() => [ 'normal', 'multiply', 'screen', 'overlay', 'darken', 'lighten', 'color-dodge', 'color-burn', 'hard-light', 'soft-light', 'difference', 'exclusion', 'hue', 'saturation', 'color', 'luminosity', ]), ), /* FILTERS */ ...filter(), ...filter('backdrop-'), /* TRANSITIONS AND ANIMATION */ // Transition Property matchTheme('transition(?:$|-)', 'transitionProperty', (match, { theme }) => ({ transitionProperty: join(match), transitionTimingFunction: match._ == 'none' ? undefined : join(theme('transitionTimingFunction', '')), transitionDuration: match._ == 'none' ? undefined : join(theme('transitionDuration', '')), })), // Transition Duration matchTheme('duration(?:$|-)', 'transitionDuration', 'transitionDuration', join), // Transition Timing Function matchTheme('ease(?:$|-)', 'transitionTimingFunction', 'transitionTimingFunction', join), // Transition Delay matchTheme('delay(?:$|-)', 'transitionDelay', 'transitionDelay', join), matchTheme('animate(?:$|-)', 'animation', (match, { theme, h }) => { const animation: string = join(match) // Try to auto inject keyframes const parts = animation.split(' ') const keyframeValues = theme('keyframes', parts[0]) as CSSBase if (keyframeValues) { return { [('@keyframes ' + (parts[0] = h(parts[0]))) as '@keyframes xxx']: keyframeValues, animation: parts.join(' '), } } return { animation } }), /* TRANSFORMS */ // Transform '(transform)-(none)', match('transform', tranformDefaults), match('transform-(cpu|gpu)', ({ 1: $1 }) => ({ '--tw-transform': transformValue($1 == 'gpu'), })), // Scale matchTheme( 'scale(-[xy])?-', 'scale', ({ 1: $1, _ }) => ({ [('--tw-scale' + ($1 || '-x')) as '--tw-scale-x']: _, [('--tw-scale' + ($1 || '-y')) as '--tw-scale-y']: _, ...tranformDefaults(), } as CSSObject), ), // Rotate matchTheme('-?(rotate)-', 'rotate', transform), // Translate matchTheme('-?(translate-[xy])-', 'translate', transform), // Skew matchTheme('-?(skew-[xy])-', 'skew', transform), // Transform Origin match('origin-(center|((top|bottom)(-(left|right))?)|left|right)', 'transformOrigin', spacify), /* INTERACTIVITY */ // Appearance withAutocomplete$('(appearance)-', DEV && (() => ['auto', 'none'])), // Columns matchTheme('(columns)-' /*, 'columns' */), withAutocomplete$('(columns)-(\\d+)', DEV && (() => range({ end: 12 }))), // Break Before, After and Inside withAutocomplete$( '(break-(?:before|after|inside))-', DEV && (({ 1: $1 }) => $1.endsWith('-inside-') ? ['auto', 'avoid', 'avoid-page', 'avoid-column'] : /* before || after */ [ 'auto', 'avoid', 'all', 'avoid-page', 'page', 'left', 'right', 'column', ]), ), // Cursor matchTheme('(cursor)-' /*, 'cursor' */), withAutocomplete$( '(cursor)-', DEV && (() => [ 'alias', 'all-scroll', 'auto', 'cell', 'col-resize', 'context-menu', 'copy', 'crosshair', 'default', 'e-resize', 'ew-resize', 'grab', 'grabbing', 'help', 'move', 'n-resize', 'ne-resize', 'nesw-resize', 'no-drop', 'none', 'not-allowed', 'ns-resize', 'nw-resize', 'nwse-resize', 'pointer', 'progress', 'row-resize', 's-resize', 'se-resize', 'sw-resize', 'text', 'vertical-text', 'w-resize', 'wait', 'zoom-in', 'zoom-out', ]), ), // Scroll Snap Type match('snap-(none)', 'scroll-snap-type'), match('snap-(x|y|both)', ({ 1: $1 }) => ({ 'scroll-snap-type': $1 + ' var(--tw-scroll-snap-strictness)', '@layer defaults': { '*,::before,::after,::backdrop': { '--tw-scroll-snap-strictness': 'proximity', }, }, })), match('snap-(mandatory|proximity)', '--tw-scroll-snap-strictness'), // Scroll Snap Align match('snap-(?:(start|end|center)|align-(none))', 'scroll-snap-align'), // Scroll Snap Stop match('snap-(normal|always)', 'scroll-snap-stop'), match('scroll-(auto|smooth)', 'scroll-behavior'), // Scroll Margin // Padding matchTheme('scroll-p([xytrbl])?(?:$|-)', 'padding', edge('scroll-padding')), // Margin matchTheme( '-?scroll-m([xytrbl])?(?:$|-)', 'scroll-margin', edge('scroll-margin'), ), // Touch Action match('touch-(auto|none|manipulation)', 'touch-action'), match('touch-(pinch-zoom|pan-(?:(x|left|right)|(y|up|down)))', ({ 1: $1, 2: $2, 3: $3 }) => ({ // x, left, right -> pan-x // y, up, down -> pan-y // -> pinch-zoom [`--tw-${$2 ? 'pan-x' : $3 ? 'pan-y' : $1}` as '--tw-pan-x']: $1, 'touch-action': 'var(--tw-touch-action)', '@layer defaults': { '*,::before,::after,::backdrop': { '--tw-pan-x': 'var(--tw-empty,/*!*/ /*!*/)', '--tw-pan-y': 'var(--tw-empty,/*!*/ /*!*/)', '--tw-pinch-zoom': 'var(--tw-empty,/*!*/ /*!*/)', '--tw-touch-action': 'var(--tw-pan-x) var(--tw-pan-y) var(--tw-pinch-zoom)', }, }, })), // Outline Style match('outline-none', { outline: '2px solid transparent', 'outline-offset': '2px', }), match('outline', { outlineStyle: 'solid' }), match('outline-(dashed|dotted|double|hidden)', 'outlineStyle'), // Outline Offset matchTheme('(outline-offset)-' /*, 'outlineOffset'*/), // Outline Color matchColor('outline-', { opacityVariable: false, opacitySection: 'opacity', }), // Outline Width matchTheme('outline-', 'outlineWidth'), // Pointer Events withAutocomplete$('(pointer-events)-', DEV && (() => ['auto', 'none'])), // Will Change matchTheme('(will-change)-' /*, 'willChange' */), withAutocomplete$('(will-change)-', DEV && (() => ['auto', 'contents', 'transform'])), // Resize [ 'resize(?:-(none|x|y))?', 'resize', ({ 1: $1 }) => ({ x: 'horizontal', y: 'vertical' }[$1] || $1 || 'both'), ], // User Select match('select-(none|text|all|auto)', 'userSelect'), /* SVG */ // Fill, Stroke matchColor('fill-', { section: 'fill', opacityVariable: false, opacitySection: 'opacity' }), matchColor('stroke-', { section: 'stroke', opacityVariable: false, opacitySection: 'opacity' }), // Stroke Width matchTheme('stroke-', 'strokeWidth'), /* ACCESSIBILITY */ // Screen Readers match('sr-only', { position: 'absolute', width: '1px', height: '1px', padding: '0', margin: '-1px', overflow: 'hidden', whiteSpace: 'nowrap', clip: 'rect(0,0,0,0)', borderWidth: '0', }), match('not-sr-only', { position: 'static', width: 'auto', height: 'auto', padding: '0', margin: '0', overflow: 'visible', whiteSpace: 'normal', clip: 'auto', }), ] export default rules function spacify(value: string | MatchResult): string { return (typeof value == 'string' ? value : value[1]).replace(/-/g, ' ').trim() } function columnify(value: string | MatchResult): string { return (typeof value == 'string' ? value : value[1]).replace('col', 'column') } function position(shorthand: string, separator = '-'): string { const longhand: string[] = [] for (const short of shorthand) { longhand.push({ t: 'top', r: 'right', b: 'bottom', l: 'left' }[short] as string) } return longhand.join(separator) } function join(match: ThemeMatchResult>): string function join(value: MaybeArray | undefined): string | undefined function join( value: ThemeMatchResult> | MaybeArray | undefined, ): string | undefined { return value && '' + ((value as ThemeMatchResult>)._ || value) } function convertContentValue({ $$ }: MatchResult) { return ( ({ // /* aut*/ o: '', /* sta*/ r /*t*/: 'flex-', /* end*/ '': 'flex-', // /* cen*/ t /*er*/: '', /* bet*/ w /*een*/: 'space-', /* aro*/ u /*nd*/: 'space-', /* eve*/ n /*ly*/: 'space-', // /* str*/ e /*tch*/: '', // /* bas*/ l /*ine*/: '', }[$$[3] || ''] || '') + $$ ) } function edge( propertyPrefix: string, propertySuffix = '', ): ThemeRuleResolver { return ({ 1: $1, _ }) => { const edges = { x: 'lr', y: 'tb', }[$1 as 'x' | 'y'] || $1 + $1 return edges ? { ...toCSS(propertyPrefix + '-' + position(edges[0]) + propertySuffix, _), ...toCSS(propertyPrefix + '-' + position(edges[1]) + propertySuffix, _), } : toCSS(propertyPrefix + propertySuffix, _) } } function filter(prefix = ''): Rule[] { const filters = [ 'blur', 'brightness', 'contrast', 'grayscale', 'hue-rotate', 'invert', prefix && 'opacity', 'saturate', 'sepia', !prefix && 'drop-shadow', ].filter(Boolean) as string[] let defaults = {} as CSSObject // first create properties defaults for (const key of filters) { defaults[`--tw-${prefix}${key}` as '--tw-blur'] = 'var(--tw-empty,/*!*/ /*!*/)' } defaults = { // add default filter which allows standalone usage [`${prefix}filter`]: filters.map((key) => `var(--tw-${prefix}${key})`).join(' '), // move defaults '@layer defaults': { '*,::before,::after,::backdrop': defaults, }, } as CSSObject return [ `(${prefix}filter)-(none)`, match(`${prefix}filter`, defaults), ...filters.map((key) => matchTheme( // hue-rotate can be negated `${key[0] == 'h' ? '-?' : ''}(${prefix}${key})(?:$|-)`, key as 'hueRotate' | 'dropShadow', ({ 1: $1, _ }) => ({ [`--tw-${$1}`]: asArray(_) .map((value) => `${key}(${value})`) .join(' '), ...defaults, } as CSSObject), ), ), ] } function transform({ 1: $1, _ }: ThemeMatchResult): CSSObject { return { ['--tw-' + $1]: _, ...tranformDefaults(), } as CSSObject } function tranformDefaults(): CSSObject { return { transform: 'var(--tw-transform)', '@layer defaults': { '*,::before,::after,::backdrop': { '--tw-translate-x': '0', '--tw-translate-y': '0', '--tw-rotate': '0', '--tw-skew-x': '0', '--tw-skew-y': '0', '--tw-scale-x': '1', '--tw-scale-y': '1', '--tw-transform': transformValue(), }, }, } } function transformValue(gpu?: boolean): string { return [ gpu // -gpu ? 'translate3d(var(--tw-translate-x),var(--tw-translate-y),0)' : 'translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y))', 'rotate(var(--tw-rotate))', 'skewX(var(--tw-skew-x))', 'skewY(var(--tw-skew-y))', 'scaleX(var(--tw-scale-x))', 'scaleY(var(--tw-scale-y))', ].join(' ') } function span({ 1: $1, 2: $2 }: MatchResult) { return `${$1} ${$2} / ${$1} ${$2}` } function gridTemplate({ 1: $1 }: MatchResult) { return `repeat(${$1},minmax(0,1fr))` } function range({ start = 1, end, step = 1, }: { start?: number end: number step?: number }): string[] { const result: string[] = [] for (let index = start; index <= end; index += step) { result.push(`${index}`) } return result }