Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
introduce css data types
Ref: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Types

These data types will be used to "guess" the type of an arbitrary value
if there is some ambiguity going on. For example:

```
bg-[#0088cc]      -> This is a `color`  -> `background-color`
bg-[url('...')]   -> This is a `url`    -> `background-image`
```

If you are using css variables, then there is no way of knowing which
type it is referring to, in that case you can be explicit:

```
bg-[color:var(--value)]   -> This is a `color`  -> `background-color`
bg-[url:var(--value)]     -> This is a `url`    -> `background-image`
```

When you explicitly pass a data type, then we bypass the type system and
assume you are right. This is nice in a way because now we don't have to
run all of the guessing type code. On the other hand, you can introduce
runtime issues that we are not able to detect:

```
:root {
  --value: 12px;
}

/* Later... */
bg-[color:var(--value)] -> Assumes `color` -> *eventually* -> `background-color: 12px`
```
  • Loading branch information
RobinMalfait committed Sep 24, 2021
commit d07a3e9cd8852e4734ad4eec2aa5b9c123dfbd46
30 changes: 15 additions & 15 deletions src/corePlugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ import {
transformAllSelectors,
transformAllClasses,
transformLastClasses,
asLength,
asURL,
asLookupValue,
} from './util/pluginUtils'
import packageJson from '../package.json'
import log from './util/log'
Expand Down Expand Up @@ -617,12 +614,15 @@ export let display = ({ addUtilities }) => {
}

export let aspectRatio = createUtilityPlugin('aspectRatio', [['aspect', ['aspect-ratio']]])

export let height = createUtilityPlugin('height', [['h', ['height']]])
export let maxHeight = createUtilityPlugin('maxHeight', [['max-h', ['maxHeight']]])
export let minHeight = createUtilityPlugin('minHeight', [['min-h', ['minHeight']]])

export let width = createUtilityPlugin('width', [['w', ['width']]])
export let minWidth = createUtilityPlugin('minWidth', [['min-w', ['minWidth']]])
export let maxWidth = createUtilityPlugin('maxWidth', [['max-w', ['maxWidth']]])

export let flex = createUtilityPlugin('flex')
export let flexShrink = createUtilityPlugin('flexShrink', [['flex-shrink', ['flex-shrink']]])
export let flexGrow = createUtilityPlugin('flexGrow', [['flex-grow', ['flex-grow']]])
Expand Down Expand Up @@ -1013,7 +1013,7 @@ export let divideWidth = ({ matchUtilities, addUtilities, theme }) => {
}
},
},
{ values: theme('divideWidth'), type: 'length' }
{ values: theme('divideWidth'), type: ['line-width', 'length'] }
)

addUtilities({
Expand Down Expand Up @@ -1199,7 +1199,7 @@ export let borderWidth = createUtilityPlugin(
['border-l', [['@defaults border-width', {}], 'border-left-width']],
],
],
{ resolveArbitraryValue: asLength }
{ type: ['line-width', 'length'] }
)

export let borderStyle = ({ addUtilities }) => {
Expand Down Expand Up @@ -1249,7 +1249,7 @@ export let borderColor = ({ addBase, matchUtilities, theme, corePlugins }) => {
},
{
values: (({ DEFAULT: _, ...colors }) => colors)(flattenColorPalette(theme('borderColor'))),
type: 'color',
type: ['color'],
}
)

Expand Down Expand Up @@ -1346,7 +1346,7 @@ export let backgroundOpacity = createUtilityPlugin('backgroundOpacity', [
export let backgroundImage = createUtilityPlugin(
'backgroundImage',
[['bg', ['background-image']]],
{ resolveArbitraryValue: [asLookupValue, asURL] }
{ type: ['lookup', 'image', 'url'] }
)
export let gradientColorStops = (() => {
function transparentTo(value) {
Expand Down Expand Up @@ -1399,7 +1399,7 @@ export let boxDecorationBreak = ({ addUtilities }) => {
}

export let backgroundSize = createUtilityPlugin('backgroundSize', [['bg', ['background-size']]], {
resolveArbitraryValue: asLookupValue,
type: ['lookup', 'length', 'percentage'],
})

export let backgroundAttachment = ({ addUtilities }) => {
Expand All @@ -1422,7 +1422,7 @@ export let backgroundClip = ({ addUtilities }) => {
export let backgroundPosition = createUtilityPlugin(
'backgroundPosition',
[['bg', ['background-position']]],
{ resolveArbitraryValue: asLookupValue }
{ type: ['lookup', 'position'] }
)

export let backgroundRepeat = ({ addUtilities }) => {
Expand Down Expand Up @@ -1462,12 +1462,12 @@ export let stroke = ({ matchUtilities, theme }) => {
return { stroke: toColorValue(value) }
},
},
{ values: flattenColorPalette(theme('stroke')), type: 'color' }
{ values: flattenColorPalette(theme('stroke')), type: ['color', 'url'] }
)
}

export let strokeWidth = createUtilityPlugin('strokeWidth', [['stroke', ['stroke-width']]], {
resolveArbitraryValue: [asLength, asURL],
type: ['length', 'number', 'percentage'],
})

export let objectFit = ({ addUtilities }) => {
Expand Down Expand Up @@ -1522,7 +1522,7 @@ export let verticalAlign = ({ addUtilities, matchUtilities }) => {
}

export let fontFamily = createUtilityPlugin('fontFamily', [['font', ['fontFamily']]], {
resolveArbitraryValue: asLookupValue,
type: ['lookup', 'generic-name', 'family-name'],
})

export let fontSize = ({ matchUtilities, theme }) => {
Expand All @@ -1541,12 +1541,12 @@ export let fontSize = ({ matchUtilities, theme }) => {
}
},
},
{ values: theme('fontSize'), type: 'length' }
{ values: theme('fontSize'), type: ['absolute-size', 'relative-size', 'length', 'percentage'] }
)
}

export let fontWeight = createUtilityPlugin('fontWeight', [['font', ['fontWeight']]], {
resolveArbitraryValue: asLookupValue,
type: ['lookup', 'number'],
})

export let textTransform = ({ addUtilities }) => {
Expand Down Expand Up @@ -1859,7 +1859,7 @@ export let ringOpacity = createUtilityPlugin(
export let ringOffsetWidth = createUtilityPlugin(
'ringOffsetWidth',
[['ring-offset', ['--tw-ring-offset-width']]],
{ resolveArbitraryValue: asLength }
{ type: 'length' }
)

export let ringOffsetColor = ({ matchUtilities, theme }) => {
Expand Down
2 changes: 2 additions & 0 deletions src/lib/expandTailwindAtRules.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ let env = sharedState.env
let contentMatchCache = sharedState.contentMatchCache

const PATTERNS = [
/([^<>"'`\s]*\[\w*'[^"`\s]*'?\])/.source, // font-['some_font',sans-serif]
/([^<>"'`\s]*\[\w*"[^"`\s]*"?\])/.source, // font-["some_font",sans-serif]
/([^<>"'`\s]*\[\w*\('[^"'`\s]*'\)\])/.source, // bg-[url('...')]
/([^<>"'`\s]*\[\w*\("[^"'`\s]*"\)\])/.source, // bg-[url("...")]
/([^<>"'`\s]*\['[^"'`\s]*'\])/.source, // `content-['hello']` but not `content-['hello']']`
Expand Down
216 changes: 216 additions & 0 deletions src/util/dataTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
import { parseColor } from './color'

// Ref: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Types

let COMMA = /,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubiz-bezier(a, b, c)` these don't count.
let UNDERSCORE = /_(?![^(]*\))/g // Underscore separator that is not located between brackets. E.g.: `rgba(255,_255,_255)_black` these don't count.

// This is not a data type, but rather a function that can normalize the
// correct values.
export function normalize(value) {
// Convert `_` to ` `, except for escaped underscores `\_`
value = value
.replace(
/([^\\])_+/g,
(fullMatch, characterBefore) => characterBefore + ' '.repeat(fullMatch.length - 1)
)
.replace(/^_/g, ' ')
.replace(/\\_/g, '_')

// Remove leftover whitespace
value = value.trim()

// Keep raw strings if it starts with `url(`
if (value.startsWith('url(')) return value

// Add spaces around operators inside calc() that do not follow an operator
// or '('.
return value.replace(
/(-?\d*\.?\d(?!\b-.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([+\-/*])/g,
'$1 $2 '
)
}

export function url(value) {
return value.startsWith('url(')
}

export function number(value) {
return !isNaN(Number(value))
}

export function percentage(value) {
return /%$/g.test(value) || /^calc\(.+?%\)/g.test(value)
}

let lengthUnits = [
'cm',
'mm',
'Q',
'in',
'pc',
'pt',
'px',
'em',
'ex',
'ch',
'rem',
'lh',
'vw',
'vh',
'vmin',
'vmax',
]
let lengthUnitsPattern = `(?:${lengthUnits.join('|')})`
export function length(value) {
return (
new RegExp(`${lengthUnitsPattern}$`).test(value) ||
new RegExp(`^calc\\(.+?${lengthUnitsPattern}`).test(value)
)
}

let lineWidths = new Set(['thin', 'medium', 'thick'])
export function lineWidth(value) {
return lineWidths.has(value)
}

export function color(value) {
let colors = 0

let result = value.split(UNDERSCORE).every((part) => {
part = normalize(part)

if (part.startsWith('var(')) return true
if (parseColor(part) !== null) return colors++, true

return false
})

if (!result) return false
return colors > 0
}

export function image(value) {
let images = 0
let result = value.split(COMMA).every((part) => {
part = normalize(part)

if (part.startsWith('var(')) return true
if (
url(part) ||
gradient(part) ||
['element(', 'image(', 'cross-fade(', 'image-set('].some((fn) => part.startsWith(fn))
) {
images++
return true
}

return false
})

if (!result) return false
return images > 0
}

let gradientTypes = new Set([
'linear-gradient',
'radial-gradient',
'repeating-linear-gradient',
'repeating-radial-gradient',
'conic-gradient',
])
export function gradient(value) {
value = normalize(value)

for (let type of gradientTypes) {
if (value.startsWith(`${type}(`)) {
return true
}
}
return false
}

let validPositions = new Set(['center', 'top', 'right', 'bottom', 'left'])
export function position(value) {
let positions = 0
let result = value.split(UNDERSCORE).every((part) => {
part = normalize(part)

if (part.startsWith('var(')) return true
if (validPositions.has(part) || length(part) || percentage(part)) {
positions++
return true
}

return false
})

if (!result) return false
return positions > 0
}

export function familyName(value) {
let fonts = 0
let result = value.split(COMMA).every((part) => {
part = normalize(part)

if (part.startsWith('var(')) return true

// If it contains spaces, then it should be quoted
if (part.includes(' ')) {
if (!/(['"])([^"']+)\1/g.test(part)) {
return false
}
}

// If it starts with a number, it's invalid
if (/^\d/g.test(part)) {
return false
}

fonts++

return true
})

if (!result) return false
return fonts > 0
}

let genericNames = new Set([
'serif',
'sans-serif',
'monospace',
'cursive',
'fantasy',
'system-ui',
'ui-serif',
'ui-sans-serif',
'ui-monospace',
'ui-rounded',
'math',
'emoji',
'fangsong',
])
export function genericName(value) {
return genericNames.has(value)
}

let absoluteSizes = new Set([
'xx-small',
'x-small',
'small',
'medium',
'large',
'x-large',
'x-large',
'xxx-large',
])
export function absoluteSize(value) {
return absoluteSizes.has(value)
}

let relativeSizes = new Set(['larger', 'smaller'])
export function relativeSize(value) {
return relativeSizes.has(value)
}
Loading