Skip to content
Prev Previous commit
Next Next commit
feat: support for arbitrary values
  • Loading branch information
francoismassart committed Sep 16, 2021
commit ff138899981270630ccfd23491f898ef0fe82114
208 changes: 200 additions & 8 deletions lib/util/groupMethods.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@

'use strict';

// Ambiguous values
// ================
// Supported hints: length, color, angle, list
// -------------------------------------------
//
// border-[color/width]
// text-[color/size]
// ring-[color/width]
// ring-offset-[color/width]
// stroke-[current/width]
// bg-[color/(position/size)]
//
// font-[family/weight]

const angle = require('./types/angle');
const color = require('./types/color');
const length = require('./types/length');

/**
* Escape special chars for regular expressions
*
Expand All @@ -14,6 +32,20 @@ function escapeSpecialChars(str) {
return str.replace(/[\-\.\/]/g, '\\$&');
}

function getOpacitySuffix(propName, config) {
if (['backgroundColor', 'textColor', 'placeholderColor'].includes(propName)) {
Copy link

@smhmd smhmd Nov 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GradientColorStops also support opacity suffixes, @francoismassart.

- if (['backgroundColor', 'textColor', 'placeholderColor'].includes(propName)) {
+ if (['backgroundColor', 'textColor', 'placeholderColor', 'gradientColorStops'].includes(propName)) {

return generateOptionalOpacitySuffix(config);
}
return '';
}
function generateOptionalOpacitySuffix(config) {
const opacityKeys = !config.theme['opacity'] ? [] : Object.keys(config.theme['opacity']);
if (opacityKeys.length === 0) {
return '';
}
return `(\\/(${opacityKeys.join('|')}))?`;
}

/**
* Generate the possible options for the RegEx
*
Expand All @@ -24,45 +56,205 @@ function escapeSpecialChars(str) {
* @returns {String} Generated part of regex exposing the possible values
*/
function generateOptions(propName, keys, config, isNegative = false) {
const arbitraryOption = '\\[.*\\]';
const genericArbitraryOption = '\\[(.*)\\]';
const supportArbitrary = config.mode === 'jit' && !isNegative;
const defaultKeyIndex = keys.findIndex((v) => v === 'DEFAULT');
if (defaultKeyIndex > -1) {
keys.splice(defaultKeyIndex, 1);
}
switch (propName) {
case 'dark':
// Optional `dark` class
return config.darkMode === 'class' ? 'dark' : '';
case 'placeholderColor':
case 'textColor':
case 'backgroundColor':
case 'gradientColorStops':
case 'borderColor':
case 'divideColor':
case 'ringColor':
case 'ringOffsetColor':
case 'textColor':
case 'stroke':
case 'gradientColorStops':
case 'placeholderColor':
// Colors can use segments like 'indigo' and 'indigo-light'
// https://tailwindcss.com/docs/customizing-colors#color-object-syntax
const options = [];
// Conditionnaly generates the opacity suffix
const opacitySuffix = supportArbitrary ? getOpacitySuffix(propName, config) : '';
keys.forEach((k) => {
const color = config.theme[propName][k] || config.theme.colors[k];
if (typeof color === 'string') {
options.push(escapeSpecialChars(k));
options.push(escapeSpecialChars(k) + opacitySuffix);
} else {
const variants = Object.keys(color).map((colorKey) => escapeSpecialChars(colorKey));
const defaultIndex = variants.findIndex((v) => v === 'DEFAULT');
const hasDefault = defaultIndex > -1;
if (hasDefault) {
variants.splice(defaultIndex, 1);
}
options.push(k + '(\\-(' + variants.join('|') + '))' + (hasDefault ? '?' : ''));
options.push(k + '(\\-(' + variants.join('|') + '))' + (hasDefault ? '?' : '') + opacitySuffix);
}
});
if (supportArbitrary) {
options.push(arbitraryOption);
const arbitraryColors = [...color.mergedColorValues];
switch (propName) {
case 'gradientColorStops':
case 'placeholderColor':
arbitraryColors.push(color.RGBAPercentages); // RGBA % 0.5[%]
arbitraryColors.push(color.optionalColorPrefixedVar);
arbitraryColors.push(color.notHSLAPlusWildcard);
break;
default:
arbitraryColors.push(color.mandatoryColorPrefixed);
}
options.push(`\\[(${arbitraryColors.join('|')})\\]`);
}
return '(' + options.join('|') + ')';
case 'borderWidth':
case 'divideWidth':
case 'fontSize':
case 'ringWidth':
case 'ringOffsetWidth':
if (supportArbitrary) {
keys.push(length.selectedUnitsRegEx);
keys.push(length.anyCalcRegEx);
// Mandatory `length:` prefix + wildcard
keys.push(`\\[length\\:.{1,}\\]`);
}
return '(' + keys.join('|') + ')';
case 'strokeWidth':
if (supportArbitrary) {
keys.push(length.selectedUnitsRegEx);
keys.push(length.anyCalcRegEx);
// Mandatory `length:` prefix + calc + wildcard
keys.push(`\\[length\\:calc\\(.{1,}\\)\\]`);
// Mandatory `length:` prefix + wildcard + optional units
keys.push(`\\[length\\:(.{1,})(${length.selectedUnits.join('|')})?\\]`);
}
return '(' + keys.join('|') + ')';
case 'gap':
case 'height':
case 'lineHeight':
case 'maxHeight':
case 'maxWidth':
case 'minHeight':
case 'minWidth':
case 'padding':
case 'width':
case 'inset':
case 'letterSpacing':
case 'margin':
case 'space':
case 'blur':
case 'brightness':
case 'contrast':
case 'grayscale':
case 'hueRotate':
case 'invert':
case 'saturate':
case 'sepia':
case 'backdropBlur':
case 'backdropBrightness':
case 'backdropContrast':
case 'backdropGrayscale':
case 'backdropHueRotate':
case 'backdropInvert':
case 'backdropOpacity':
case 'backdropSaturate':
case 'backdropSepia':
case 'transitionDuration':
case 'transitionTimingFunction':
case 'transitionDelay':
case 'animation':
case 'transformOrigin':
case 'scale':
case 'translate':
case 'skew':
case 'cursor':
case 'outline':
if (supportArbitrary) {
// All units
keys.push(length.mergedUnitsRegEx);
// Forbidden prefixes
keys.push(`\\[(?!(angle|color|length|list)\:).{1,}\\]`);
}
return '(' + keys.join('|') + ')';
case 'fill':
if (supportArbitrary) {
// All units
keys.push(length.mergedUnitsRegEx);
// Forbidden prefixes
keys.push(`\\[(?!(angle|length|list)\:).{1,}\\]`);
}
return '(' + keys.join('|') + ')';
case 'placeholderOpacity':
case 'textOpacity':
case 'backgroundOpacity':
case 'borderOpacity':
case 'divideOpacity':
case 'ringOpacity':
case 'opacity':
if (supportArbitrary) {
// 0 ... .5 ... 1
keys.push(`\\[(0(\\.\\d{1,})?|\\.\\d{1,}|1)\\]`);
keys.push(length.anyCalcRegEx);
// Unprefixed var()
keys.push(`\\[var\\(\\-\\-[A-Za-z\\-]{1,}\\)\\]`);
}
return '(' + keys.join('|') + ')';
case 'rotate':
if (supportArbitrary) {
keys.push(`\\[(${angle.mergedAngleValues.join('|')})\\]`);
}
return '(' + keys.join('|') + ')';
case 'gridTemplateColumns':
case 'gridColumn':
case 'gridColumnStart':
case 'gridColumnEnd':
case 'gridTemplateRows':
case 'gridRow':
case 'gridRowStart':
case 'gridRowEnd':
case 'gridAutoColumns':
case 'gridAutoRows':
if (supportArbitrary) {
// Forbidden prefixes
keys.push(`\\[(?!(angle|color|length)\:).{1,}\\]`);
}
return '(' + keys.join('|') + ')';
case 'listStyleType':
if (supportArbitrary) {
// Forbidden prefixes
keys.push(`\\[(?!(angle|color|length|list)\:).{1,}\\]`);
}
return '(' + keys.join('|') + ')';
case 'objectPosition':
if (supportArbitrary) {
// Forbidden prefixes
keys.push(`\\[(?!(angle|color|length)\:).{1,}\\]`);
}
return '(' + keys.join('|') + ')';
case 'backgroundPosition':
case 'backgroundSize':
case 'backgroundImage':
case 'fontFamily':
case 'fontWeight':
case 'boxShadow':
case 'dropShadow':
case 'transitionProperty':
case 'typography':
case 'aspectRatio':
case 'lineClamp':
// Cannot be arbitrary?
return '(' + keys.join('|') + ')';
case 'flexGrow':
case 'flexShrink':
case 'order':
case 'zIndex':
case 'flex':
case 'borderRadius':
default:
if (supportArbitrary) {
keys.push(arbitraryOption);
keys.push(genericArbitraryOption);
}
return '(' + keys.join('|') + ')';
}
Expand Down
11 changes: 11 additions & 0 deletions lib/util/types/angle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const units = ['deg', 'grad', 'rad', 'turn'];

const mergedAngleValues = [
`\\-?(\\d{1,}(\\.\\d{1,})?|\\.\\d{1,})(${units.join('|')})`,
`calc\\(.{1,}\\)`,
`var\\(\\-\\-[A-Za-z\\-]{1,}\\)`,
];

module.exports = {
mergedAngleValues,
};
Loading