* simplify `inset` plugin
* run `prettier` on stub file
* simplify `align` utility
* improve arbitrary support for outline
This will allow us to use `outline-[OUTLINE,OPTIONAL_OFFSET]`
Input:
```html
outline-[2px_solid_black]
```
Output:
```css
.outline-\[2px_solid_black\] {
outline: 2px solid black;
outline-offset: 0;
}
```
---
Input:
```html
outline-[2px_solid_black,2px]
```
Output:
```css
.outline-\[2px_solid_black\2c 2px\] {
outline: 2px solid black;
outline-offset: 2px;
}
```
* remove default `type`
* simplify createUtilityPlugin, use types directly
* find first matching type when coercing the value
* 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`
```
* add a bunch of new tests for advanced arbitrary values
216 lines
4.5 KiB
JavaScript
216 lines
4.5 KiB
JavaScript
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)
|
|
}
|