ivt
Version:
Ivt Components Library
1,501 lines (1,500 loc) • 138 kB
JavaScript
/**
* Concatenates two arrays faster than the array spread operator.
*/ const concatArrays = (array1, array2)=>{
// Pre-allocate for better V8 optimization
const combinedArray = new Array(array1.length + array2.length);
for(let i = 0; i < array1.length; i++){
combinedArray[i] = array1[i];
}
for(let i = 0; i < array2.length; i++){
combinedArray[array1.length + i] = array2[i];
}
return combinedArray;
};
// Factory function ensures consistent object shapes
const createClassValidatorObject = (classGroupId, validator)=>({
classGroupId,
validator
});
// Factory ensures consistent ClassPartObject shape
const createClassPartObject = (nextPart = new Map(), validators = null, classGroupId)=>({
nextPart,
validators,
classGroupId
});
const CLASS_PART_SEPARATOR = '-';
const EMPTY_CONFLICTS = [];
// I use two dots here because one dot is used as prefix for class groups in plugins
const ARBITRARY_PROPERTY_PREFIX = 'arbitrary..';
const createClassGroupUtils = (config)=>{
const classMap = createClassMap(config);
const { conflictingClassGroups, conflictingClassGroupModifiers } = config;
const getClassGroupId = (className)=>{
if (className.startsWith('[') && className.endsWith(']')) {
return getGroupIdForArbitraryProperty(className);
}
const classParts = className.split(CLASS_PART_SEPARATOR);
// Classes like `-inset-1` produce an empty string as first classPart. We assume that classes for negative values are used correctly and skip it.
const startIndex = classParts[0] === '' && classParts.length > 1 ? 1 : 0;
return getGroupRecursive(classParts, startIndex, classMap);
};
const getConflictingClassGroupIds = (classGroupId, hasPostfixModifier)=>{
if (hasPostfixModifier) {
const modifierConflicts = conflictingClassGroupModifiers[classGroupId];
const baseConflicts = conflictingClassGroups[classGroupId];
if (modifierConflicts) {
if (baseConflicts) {
// Merge base conflicts with modifier conflicts
return concatArrays(baseConflicts, modifierConflicts);
}
// Only modifier conflicts
return modifierConflicts;
}
// Fall back to without postfix if no modifier conflicts
return baseConflicts || EMPTY_CONFLICTS;
}
return conflictingClassGroups[classGroupId] || EMPTY_CONFLICTS;
};
return {
getClassGroupId,
getConflictingClassGroupIds
};
};
const getGroupRecursive = (classParts, startIndex, classPartObject)=>{
const classPathsLength = classParts.length - startIndex;
if (classPathsLength === 0) {
return classPartObject.classGroupId;
}
const currentClassPart = classParts[startIndex];
const nextClassPartObject = classPartObject.nextPart.get(currentClassPart);
if (nextClassPartObject) {
const result = getGroupRecursive(classParts, startIndex + 1, nextClassPartObject);
if (result) return result;
}
const validators = classPartObject.validators;
if (validators === null) {
return undefined;
}
// Build classRest string efficiently by joining from startIndex onwards
const classRest = startIndex === 0 ? classParts.join(CLASS_PART_SEPARATOR) : classParts.slice(startIndex).join(CLASS_PART_SEPARATOR);
const validatorsLength = validators.length;
for(let i = 0; i < validatorsLength; i++){
const validatorObj = validators[i];
if (validatorObj.validator(classRest)) {
return validatorObj.classGroupId;
}
}
return undefined;
};
/**
* Get the class group ID for an arbitrary property.
*
* @param className - The class name to get the group ID for. Is expected to be string starting with `[` and ending with `]`.
*/ const getGroupIdForArbitraryProperty = (className)=>className.slice(1, -1).indexOf(':') === -1 ? undefined : (()=>{
const content = className.slice(1, -1);
const colonIndex = content.indexOf(':');
const property = content.slice(0, colonIndex);
return property ? ARBITRARY_PROPERTY_PREFIX + property : undefined;
})();
/**
* Exported for testing only
*/ const createClassMap = (config)=>{
const { theme, classGroups } = config;
return processClassGroups(classGroups, theme);
};
// Split into separate functions to maintain monomorphic call sites
const processClassGroups = (classGroups, theme)=>{
const classMap = createClassPartObject();
for(const classGroupId in classGroups){
const group = classGroups[classGroupId];
processClassesRecursively(group, classMap, classGroupId, theme);
}
return classMap;
};
const processClassesRecursively = (classGroup, classPartObject, classGroupId, theme)=>{
const len = classGroup.length;
for(let i = 0; i < len; i++){
const classDefinition = classGroup[i];
processClassDefinition(classDefinition, classPartObject, classGroupId, theme);
}
};
// Split into separate functions for each type to maintain monomorphic call sites
const processClassDefinition = (classDefinition, classPartObject, classGroupId, theme)=>{
if (typeof classDefinition === 'string') {
processStringDefinition(classDefinition, classPartObject, classGroupId);
return;
}
if (typeof classDefinition === 'function') {
processFunctionDefinition(classDefinition, classPartObject, classGroupId, theme);
return;
}
processObjectDefinition(classDefinition, classPartObject, classGroupId, theme);
};
const processStringDefinition = (classDefinition, classPartObject, classGroupId)=>{
const classPartObjectToEdit = classDefinition === '' ? classPartObject : getPart(classPartObject, classDefinition);
classPartObjectToEdit.classGroupId = classGroupId;
};
const processFunctionDefinition = (classDefinition, classPartObject, classGroupId, theme)=>{
if (isThemeGetter(classDefinition)) {
processClassesRecursively(classDefinition(theme), classPartObject, classGroupId, theme);
return;
}
if (classPartObject.validators === null) {
classPartObject.validators = [];
}
classPartObject.validators.push(createClassValidatorObject(classGroupId, classDefinition));
};
const processObjectDefinition = (classDefinition, classPartObject, classGroupId, theme)=>{
const entries = Object.entries(classDefinition);
const len = entries.length;
for(let i = 0; i < len; i++){
const [key, value] = entries[i];
processClassesRecursively(value, getPart(classPartObject, key), classGroupId, theme);
}
};
const getPart = (classPartObject, path)=>{
let current = classPartObject;
const parts = path.split(CLASS_PART_SEPARATOR);
const len = parts.length;
for(let i = 0; i < len; i++){
const part = parts[i];
let next = current.nextPart.get(part);
if (!next) {
next = createClassPartObject();
current.nextPart.set(part, next);
}
current = next;
}
return current;
};
// Type guard maintains monomorphic check
const isThemeGetter = (func)=>'isThemeGetter' in func && func.isThemeGetter === true;
// LRU cache implementation using plain objects for simplicity
const createLruCache = (maxCacheSize)=>{
if (maxCacheSize < 1) {
return {
get: ()=>undefined,
set: ()=>{}
};
}
let cacheSize = 0;
let cache = Object.create(null);
let previousCache = Object.create(null);
const update = (key, value)=>{
cache[key] = value;
cacheSize++;
if (cacheSize > maxCacheSize) {
cacheSize = 0;
previousCache = cache;
cache = Object.create(null);
}
};
return {
get (key) {
let value = cache[key];
if (value !== undefined) {
return value;
}
if ((value = previousCache[key]) !== undefined) {
update(key, value);
return value;
}
},
set (key, value) {
if (key in cache) {
cache[key] = value;
} else {
update(key, value);
}
}
};
};
const IMPORTANT_MODIFIER = '!';
const MODIFIER_SEPARATOR = ':';
const EMPTY_MODIFIERS = [];
// Pre-allocated result object shape for consistency
const createResultObject = (modifiers, hasImportantModifier, baseClassName, maybePostfixModifierPosition, isExternal)=>({
modifiers,
hasImportantModifier,
baseClassName,
maybePostfixModifierPosition,
isExternal
});
const createParseClassName = (config)=>{
const { prefix, experimentalParseClassName } = config;
/**
* Parse class name into parts.
*
* Inspired by `splitAtTopLevelOnly` used in Tailwind CSS
* @see https://github.com/tailwindlabs/tailwindcss/blob/v3.2.2/src/util/splitAtTopLevelOnly.js
*/ let parseClassName = (className)=>{
// Use simple array with push for better performance
const modifiers = [];
let bracketDepth = 0;
let parenDepth = 0;
let modifierStart = 0;
let postfixModifierPosition;
const len = className.length;
for(let index = 0; index < len; index++){
const currentCharacter = className[index];
if (bracketDepth === 0 && parenDepth === 0) {
if (currentCharacter === MODIFIER_SEPARATOR) {
modifiers.push(className.slice(modifierStart, index));
modifierStart = index + 1;
continue;
}
if (currentCharacter === '/') {
postfixModifierPosition = index;
continue;
}
}
if (currentCharacter === '[') bracketDepth++;
else if (currentCharacter === ']') bracketDepth--;
else if (currentCharacter === '(') parenDepth++;
else if (currentCharacter === ')') parenDepth--;
}
const baseClassNameWithImportantModifier = modifiers.length === 0 ? className : className.slice(modifierStart);
// Inline important modifier check
let baseClassName = baseClassNameWithImportantModifier;
let hasImportantModifier = false;
if (baseClassNameWithImportantModifier.endsWith(IMPORTANT_MODIFIER)) {
baseClassName = baseClassNameWithImportantModifier.slice(0, -1);
hasImportantModifier = true;
} else if (/**
* In Tailwind CSS v3 the important modifier was at the start of the base class name. This is still supported for legacy reasons.
* @see https://github.com/dcastil/tailwind-merge/issues/513#issuecomment-2614029864
*/ baseClassNameWithImportantModifier.startsWith(IMPORTANT_MODIFIER)) {
baseClassName = baseClassNameWithImportantModifier.slice(1);
hasImportantModifier = true;
}
const maybePostfixModifierPosition = postfixModifierPosition && postfixModifierPosition > modifierStart ? postfixModifierPosition - modifierStart : undefined;
return createResultObject(modifiers, hasImportantModifier, baseClassName, maybePostfixModifierPosition);
};
if (prefix) {
const fullPrefix = prefix + MODIFIER_SEPARATOR;
const parseClassNameOriginal = parseClassName;
parseClassName = (className)=>className.startsWith(fullPrefix) ? parseClassNameOriginal(className.slice(fullPrefix.length)) : createResultObject(EMPTY_MODIFIERS, false, className, undefined, true);
}
if (experimentalParseClassName) {
const parseClassNameOriginal = parseClassName;
parseClassName = (className)=>experimentalParseClassName({
className,
parseClassName: parseClassNameOriginal
});
}
return parseClassName;
};
/**
* Sorts modifiers according to following schema:
* - Predefined modifiers are sorted alphabetically
* - When an arbitrary variant appears, it must be preserved which modifiers are before and after it
*/ const createSortModifiers = (config)=>{
// Pre-compute weights for all known modifiers for O(1) comparison
const modifierWeights = new Map();
// Assign weights to sensitive modifiers (highest priority, but preserve order)
config.orderSensitiveModifiers.forEach((mod, index)=>{
modifierWeights.set(mod, 1000000 + index); // High weights for sensitive mods
});
return (modifiers)=>{
const result = [];
let currentSegment = [];
// Process modifiers in one pass
for(let i = 0; i < modifiers.length; i++){
const modifier = modifiers[i];
// Check if modifier is sensitive (starts with '[' or in orderSensitiveModifiers)
const isArbitrary = modifier[0] === '[';
const isOrderSensitive = modifierWeights.has(modifier);
if (isArbitrary || isOrderSensitive) {
// Sort and flush current segment alphabetically
if (currentSegment.length > 0) {
currentSegment.sort();
result.push(...currentSegment);
currentSegment = [];
}
result.push(modifier);
} else {
// Regular modifier - add to current segment for batch sorting
currentSegment.push(modifier);
}
}
// Sort and add any remaining segment items
if (currentSegment.length > 0) {
currentSegment.sort();
result.push(...currentSegment);
}
return result;
};
};
const createConfigUtils = (config)=>({
cache: createLruCache(config.cacheSize),
parseClassName: createParseClassName(config),
sortModifiers: createSortModifiers(config),
...createClassGroupUtils(config)
});
const SPLIT_CLASSES_REGEX = /\s+/;
const mergeClassList = (classList, configUtils)=>{
const { parseClassName, getClassGroupId, getConflictingClassGroupIds, sortModifiers } = configUtils;
/**
* Set of classGroupIds in following format:
* `{importantModifier}{variantModifiers}{classGroupId}`
* @example 'float'
* @example 'hover:focus:bg-color'
* @example 'md:!pr'
*/ const classGroupsInConflict = [];
const classNames = classList.trim().split(SPLIT_CLASSES_REGEX);
let result = '';
for(let index = classNames.length - 1; index >= 0; index -= 1){
const originalClassName = classNames[index];
const { isExternal, modifiers, hasImportantModifier, baseClassName, maybePostfixModifierPosition } = parseClassName(originalClassName);
if (isExternal) {
result = originalClassName + (result.length > 0 ? ' ' + result : result);
continue;
}
let hasPostfixModifier = !!maybePostfixModifierPosition;
let classGroupId = getClassGroupId(hasPostfixModifier ? baseClassName.substring(0, maybePostfixModifierPosition) : baseClassName);
if (!classGroupId) {
if (!hasPostfixModifier) {
// Not a Tailwind class
result = originalClassName + (result.length > 0 ? ' ' + result : result);
continue;
}
classGroupId = getClassGroupId(baseClassName);
if (!classGroupId) {
// Not a Tailwind class
result = originalClassName + (result.length > 0 ? ' ' + result : result);
continue;
}
hasPostfixModifier = false;
}
// Fast path: skip sorting for empty or single modifier
const variantModifier = modifiers.length === 0 ? '' : modifiers.length === 1 ? modifiers[0] : sortModifiers(modifiers).join(':');
const modifierId = hasImportantModifier ? variantModifier + IMPORTANT_MODIFIER : variantModifier;
const classId = modifierId + classGroupId;
if (classGroupsInConflict.indexOf(classId) > -1) {
continue;
}
classGroupsInConflict.push(classId);
const conflictGroups = getConflictingClassGroupIds(classGroupId, hasPostfixModifier);
for(let i = 0; i < conflictGroups.length; ++i){
const group = conflictGroups[i];
classGroupsInConflict.push(modifierId + group);
}
// Tailwind class not in conflict
result = originalClassName + (result.length > 0 ? ' ' + result : result);
}
return result;
};
/**
* The code in this file is copied from https://github.com/lukeed/clsx and modified to suit the needs of tailwind-merge better.
*
* Specifically:
* - Runtime code from https://github.com/lukeed/clsx/blob/v1.2.1/src/index.js
* - TypeScript types from https://github.com/lukeed/clsx/blob/v1.2.1/clsx.d.ts
*
* Original code has MIT license: Copyright (c) Luke Edwards <luke.edwards05@gmail.com> (lukeed.com)
*/ const twJoin = (...classLists)=>{
let index = 0;
let argument;
let resolvedValue;
let string = '';
while(index < classLists.length){
if (argument = classLists[index++]) {
if (resolvedValue = toValue(argument)) {
string && (string += ' ');
string += resolvedValue;
}
}
}
return string;
};
const toValue = (mix)=>{
// Fast path for strings
if (typeof mix === 'string') {
return mix;
}
let resolvedValue;
let string = '';
for(let k = 0; k < mix.length; k++){
if (mix[k]) {
if (resolvedValue = toValue(mix[k])) {
string && (string += ' ');
string += resolvedValue;
}
}
}
return string;
};
const createTailwindMerge = (createConfigFirst, ...createConfigRest)=>{
let configUtils;
let cacheGet;
let cacheSet;
let functionToCall;
const initTailwindMerge = (classList)=>{
const config = createConfigRest.reduce((previousConfig, createConfigCurrent)=>createConfigCurrent(previousConfig), createConfigFirst());
configUtils = createConfigUtils(config);
cacheGet = configUtils.cache.get;
cacheSet = configUtils.cache.set;
functionToCall = tailwindMerge;
return tailwindMerge(classList);
};
const tailwindMerge = (classList)=>{
const cachedResult = cacheGet(classList);
if (cachedResult) {
return cachedResult;
}
const result = mergeClassList(classList, configUtils);
cacheSet(classList, result);
return result;
};
functionToCall = initTailwindMerge;
return (...args)=>functionToCall(twJoin(...args));
};
const fallbackThemeArr = [];
const fromTheme = (key)=>{
const themeGetter = (theme)=>theme[key] || fallbackThemeArr;
themeGetter.isThemeGetter = true;
return themeGetter;
};
const arbitraryValueRegex = /^\[(?:(\w[\w-]*):)?(.+)\]$/i;
const arbitraryVariableRegex = /^\((?:(\w[\w-]*):)?(.+)\)$/i;
const fractionRegex = /^\d+\/\d+$/;
const tshirtUnitRegex = /^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/;
const lengthUnitRegex = /\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/;
const colorFunctionRegex = /^(rgba?|hsla?|hwb|(ok)?(lab|lch)|color-mix)\(.+\)$/;
// Shadow always begins with x and y offset separated by underscore optionally prepended by inset
const shadowRegex = /^(inset_)?-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/;
const imageRegex = /^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\(.+\)$/;
const isFraction = (value)=>fractionRegex.test(value);
const isNumber = (value)=>!!value && !Number.isNaN(Number(value));
const isInteger = (value)=>!!value && Number.isInteger(Number(value));
const isPercent = (value)=>value.endsWith('%') && isNumber(value.slice(0, -1));
const isTshirtSize = (value)=>tshirtUnitRegex.test(value);
const isAny = ()=>true;
const isLengthOnly = (value)=>// `colorFunctionRegex` check is necessary because color functions can have percentages in them which which would be incorrectly classified as lengths.
// For example, `hsl(0 0% 0%)` would be classified as a length without this check.
// I could also use lookbehind assertion in `lengthUnitRegex` but that isn't supported widely enough.
lengthUnitRegex.test(value) && !colorFunctionRegex.test(value);
const isNever = ()=>false;
const isShadow = (value)=>shadowRegex.test(value);
const isImage = (value)=>imageRegex.test(value);
const isAnyNonArbitrary = (value)=>!isArbitraryValue(value) && !isArbitraryVariable(value);
const isArbitrarySize = (value)=>getIsArbitraryValue(value, isLabelSize, isNever);
const isArbitraryValue = (value)=>arbitraryValueRegex.test(value);
const isArbitraryLength = (value)=>getIsArbitraryValue(value, isLabelLength, isLengthOnly);
const isArbitraryNumber = (value)=>getIsArbitraryValue(value, isLabelNumber, isNumber);
const isArbitraryPosition = (value)=>getIsArbitraryValue(value, isLabelPosition, isNever);
const isArbitraryImage = (value)=>getIsArbitraryValue(value, isLabelImage, isImage);
const isArbitraryShadow = (value)=>getIsArbitraryValue(value, isLabelShadow, isShadow);
const isArbitraryVariable = (value)=>arbitraryVariableRegex.test(value);
const isArbitraryVariableLength = (value)=>getIsArbitraryVariable(value, isLabelLength);
const isArbitraryVariableFamilyName = (value)=>getIsArbitraryVariable(value, isLabelFamilyName);
const isArbitraryVariablePosition = (value)=>getIsArbitraryVariable(value, isLabelPosition);
const isArbitraryVariableSize = (value)=>getIsArbitraryVariable(value, isLabelSize);
const isArbitraryVariableImage = (value)=>getIsArbitraryVariable(value, isLabelImage);
const isArbitraryVariableShadow = (value)=>getIsArbitraryVariable(value, isLabelShadow, true);
// Helpers
const getIsArbitraryValue = (value, testLabel, testValue)=>{
const result = arbitraryValueRegex.exec(value);
if (result) {
if (result[1]) {
return testLabel(result[1]);
}
return testValue(result[2]);
}
return false;
};
const getIsArbitraryVariable = (value, testLabel, shouldMatchNoLabel = false)=>{
const result = arbitraryVariableRegex.exec(value);
if (result) {
if (result[1]) {
return testLabel(result[1]);
}
return shouldMatchNoLabel;
}
return false;
};
// Labels
const isLabelPosition = (label)=>label === 'position' || label === 'percentage';
const isLabelImage = (label)=>label === 'image' || label === 'url';
const isLabelSize = (label)=>label === 'length' || label === 'size' || label === 'bg-size';
const isLabelLength = (label)=>label === 'length';
const isLabelNumber = (label)=>label === 'number';
const isLabelFamilyName = (label)=>label === 'family-name';
const isLabelShadow = (label)=>label === 'shadow';
const getDefaultConfig = ()=>{
/**
* Theme getters for theme variable namespaces
* @see https://tailwindcss.com/docs/theme#theme-variable-namespaces
*/ /***/ const themeColor = fromTheme('color');
const themeFont = fromTheme('font');
const themeText = fromTheme('text');
const themeFontWeight = fromTheme('font-weight');
const themeTracking = fromTheme('tracking');
const themeLeading = fromTheme('leading');
const themeBreakpoint = fromTheme('breakpoint');
const themeContainer = fromTheme('container');
const themeSpacing = fromTheme('spacing');
const themeRadius = fromTheme('radius');
const themeShadow = fromTheme('shadow');
const themeInsetShadow = fromTheme('inset-shadow');
const themeTextShadow = fromTheme('text-shadow');
const themeDropShadow = fromTheme('drop-shadow');
const themeBlur = fromTheme('blur');
const themePerspective = fromTheme('perspective');
const themeAspect = fromTheme('aspect');
const themeEase = fromTheme('ease');
const themeAnimate = fromTheme('animate');
/**
* Helpers to avoid repeating the same scales
*
* We use functions that create a new array every time they're called instead of static arrays.
* This ensures that users who modify any scale by mutating the array (e.g. with `array.push(element)`) don't accidentally mutate arrays in other parts of the config.
*/ /***/ const scaleBreak = ()=>[
'auto',
'avoid',
'all',
'avoid-page',
'page',
'left',
'right',
'column'
];
const scalePosition = ()=>[
'center',
'top',
'bottom',
'left',
'right',
'top-left',
// Deprecated since Tailwind CSS v4.1.0, see https://github.com/tailwindlabs/tailwindcss/pull/17378
'left-top',
'top-right',
// Deprecated since Tailwind CSS v4.1.0, see https://github.com/tailwindlabs/tailwindcss/pull/17378
'right-top',
'bottom-right',
// Deprecated since Tailwind CSS v4.1.0, see https://github.com/tailwindlabs/tailwindcss/pull/17378
'right-bottom',
'bottom-left',
// Deprecated since Tailwind CSS v4.1.0, see https://github.com/tailwindlabs/tailwindcss/pull/17378
'left-bottom'
];
const scalePositionWithArbitrary = ()=>[
...scalePosition(),
isArbitraryVariable,
isArbitraryValue
];
const scaleOverflow = ()=>[
'auto',
'hidden',
'clip',
'visible',
'scroll'
];
const scaleOverscroll = ()=>[
'auto',
'contain',
'none'
];
const scaleUnambiguousSpacing = ()=>[
isArbitraryVariable,
isArbitraryValue,
themeSpacing
];
const scaleInset = ()=>[
isFraction,
'full',
'auto',
...scaleUnambiguousSpacing()
];
const scaleGridTemplateColsRows = ()=>[
isInteger,
'none',
'subgrid',
isArbitraryVariable,
isArbitraryValue
];
const scaleGridColRowStartAndEnd = ()=>[
'auto',
{
span: [
'full',
isInteger,
isArbitraryVariable,
isArbitraryValue
]
},
isInteger,
isArbitraryVariable,
isArbitraryValue
];
const scaleGridColRowStartOrEnd = ()=>[
isInteger,
'auto',
isArbitraryVariable,
isArbitraryValue
];
const scaleGridAutoColsRows = ()=>[
'auto',
'min',
'max',
'fr',
isArbitraryVariable,
isArbitraryValue
];
const scaleAlignPrimaryAxis = ()=>[
'start',
'end',
'center',
'between',
'around',
'evenly',
'stretch',
'baseline',
'center-safe',
'end-safe'
];
const scaleAlignSecondaryAxis = ()=>[
'start',
'end',
'center',
'stretch',
'center-safe',
'end-safe'
];
const scaleMargin = ()=>[
'auto',
...scaleUnambiguousSpacing()
];
const scaleSizing = ()=>[
isFraction,
'auto',
'full',
'dvw',
'dvh',
'lvw',
'lvh',
'svw',
'svh',
'min',
'max',
'fit',
...scaleUnambiguousSpacing()
];
const scaleColor = ()=>[
themeColor,
isArbitraryVariable,
isArbitraryValue
];
const scaleBgPosition = ()=>[
...scalePosition(),
isArbitraryVariablePosition,
isArbitraryPosition,
{
position: [
isArbitraryVariable,
isArbitraryValue
]
}
];
const scaleBgRepeat = ()=>[
'no-repeat',
{
repeat: [
'',
'x',
'y',
'space',
'round'
]
}
];
const scaleBgSize = ()=>[
'auto',
'cover',
'contain',
isArbitraryVariableSize,
isArbitrarySize,
{
size: [
isArbitraryVariable,
isArbitraryValue
]
}
];
const scaleGradientStopPosition = ()=>[
isPercent,
isArbitraryVariableLength,
isArbitraryLength
];
const scaleRadius = ()=>[
// Deprecated since Tailwind CSS v4.0.0
'',
'none',
'full',
themeRadius,
isArbitraryVariable,
isArbitraryValue
];
const scaleBorderWidth = ()=>[
'',
isNumber,
isArbitraryVariableLength,
isArbitraryLength
];
const scaleLineStyle = ()=>[
'solid',
'dashed',
'dotted',
'double'
];
const scaleBlendMode = ()=>[
'normal',
'multiply',
'screen',
'overlay',
'darken',
'lighten',
'color-dodge',
'color-burn',
'hard-light',
'soft-light',
'difference',
'exclusion',
'hue',
'saturation',
'color',
'luminosity'
];
const scaleMaskImagePosition = ()=>[
isNumber,
isPercent,
isArbitraryVariablePosition,
isArbitraryPosition
];
const scaleBlur = ()=>[
// Deprecated since Tailwind CSS v4.0.0
'',
'none',
themeBlur,
isArbitraryVariable,
isArbitraryValue
];
const scaleRotate = ()=>[
'none',
isNumber,
isArbitraryVariable,
isArbitraryValue
];
const scaleScale = ()=>[
'none',
isNumber,
isArbitraryVariable,
isArbitraryValue
];
const scaleSkew = ()=>[
isNumber,
isArbitraryVariable,
isArbitraryValue
];
const scaleTranslate = ()=>[
isFraction,
'full',
...scaleUnambiguousSpacing()
];
return {
cacheSize: 500,
theme: {
animate: [
'spin',
'ping',
'pulse',
'bounce'
],
aspect: [
'video'
],
blur: [
isTshirtSize
],
breakpoint: [
isTshirtSize
],
color: [
isAny
],
container: [
isTshirtSize
],
'drop-shadow': [
isTshirtSize
],
ease: [
'in',
'out',
'in-out'
],
font: [
isAnyNonArbitrary
],
'font-weight': [
'thin',
'extralight',
'light',
'normal',
'medium',
'semibold',
'bold',
'extrabold',
'black'
],
'inset-shadow': [
isTshirtSize
],
leading: [
'none',
'tight',
'snug',
'normal',
'relaxed',
'loose'
],
perspective: [
'dramatic',
'near',
'normal',
'midrange',
'distant',
'none'
],
radius: [
isTshirtSize
],
shadow: [
isTshirtSize
],
spacing: [
'px',
isNumber
],
text: [
isTshirtSize
],
'text-shadow': [
isTshirtSize
],
tracking: [
'tighter',
'tight',
'normal',
'wide',
'wider',
'widest'
]
},
classGroups: {
// --------------
// --- Layout ---
// --------------
/**
* Aspect Ratio
* @see https://tailwindcss.com/docs/aspect-ratio
*/ aspect: [
{
aspect: [
'auto',
'square',
isFraction,
isArbitraryValue,
isArbitraryVariable,
themeAspect
]
}
],
/**
* Container
* @see https://tailwindcss.com/docs/container
* @deprecated since Tailwind CSS v4.0.0
*/ container: [
'container'
],
/**
* Columns
* @see https://tailwindcss.com/docs/columns
*/ columns: [
{
columns: [
isNumber,
isArbitraryValue,
isArbitraryVariable,
themeContainer
]
}
],
/**
* Break After
* @see https://tailwindcss.com/docs/break-after
*/ 'break-after': [
{
'break-after': scaleBreak()
}
],
/**
* Break Before
* @see https://tailwindcss.com/docs/break-before
*/ 'break-before': [
{
'break-before': scaleBreak()
}
],
/**
* Break Inside
* @see https://tailwindcss.com/docs/break-inside
*/ 'break-inside': [
{
'break-inside': [
'auto',
'avoid',
'avoid-page',
'avoid-column'
]
}
],
/**
* Box Decoration Break
* @see https://tailwindcss.com/docs/box-decoration-break
*/ 'box-decoration': [
{
'box-decoration': [
'slice',
'clone'
]
}
],
/**
* Box Sizing
* @see https://tailwindcss.com/docs/box-sizing
*/ box: [
{
box: [
'border',
'content'
]
}
],
/**
* Display
* @see https://tailwindcss.com/docs/display
*/ display: [
'block',
'inline-block',
'inline',
'flex',
'inline-flex',
'table',
'inline-table',
'table-caption',
'table-cell',
'table-column',
'table-column-group',
'table-footer-group',
'table-header-group',
'table-row-group',
'table-row',
'flow-root',
'grid',
'inline-grid',
'contents',
'list-item',
'hidden'
],
/**
* Screen Reader Only
* @see https://tailwindcss.com/docs/display#screen-reader-only
*/ sr: [
'sr-only',
'not-sr-only'
],
/**
* Floats
* @see https://tailwindcss.com/docs/float
*/ float: [
{
float: [
'right',
'left',
'none',
'start',
'end'
]
}
],
/**
* Clear
* @see https://tailwindcss.com/docs/clear
*/ clear: [
{
clear: [
'left',
'right',
'both',
'none',
'start',
'end'
]
}
],
/**
* Isolation
* @see https://tailwindcss.com/docs/isolation
*/ isolation: [
'isolate',
'isolation-auto'
],
/**
* Object Fit
* @see https://tailwindcss.com/docs/object-fit
*/ 'object-fit': [
{
object: [
'contain',
'cover',
'fill',
'none',
'scale-down'
]
}
],
/**
* Object Position
* @see https://tailwindcss.com/docs/object-position
*/ 'object-position': [
{
object: scalePositionWithArbitrary()
}
],
/**
* Overflow
* @see https://tailwindcss.com/docs/overflow
*/ overflow: [
{
overflow: scaleOverflow()
}
],
/**
* Overflow X
* @see https://tailwindcss.com/docs/overflow
*/ 'overflow-x': [
{
'overflow-x': scaleOverflow()
}
],
/**
* Overflow Y
* @see https://tailwindcss.com/docs/overflow
*/ 'overflow-y': [
{
'overflow-y': scaleOverflow()
}
],
/**
* Overscroll Behavior
* @see https://tailwindcss.com/docs/overscroll-behavior
*/ overscroll: [
{
overscroll: scaleOverscroll()
}
],
/**
* Overscroll Behavior X
* @see https://tailwindcss.com/docs/overscroll-behavior
*/ 'overscroll-x': [
{
'overscroll-x': scaleOverscroll()
}
],
/**
* Overscroll Behavior Y
* @see https://tailwindcss.com/docs/overscroll-behavior
*/ 'overscroll-y': [
{
'overscroll-y': scaleOverscroll()
}
],
/**
* Position
* @see https://tailwindcss.com/docs/position
*/ position: [
'static',
'fixed',
'absolute',
'relative',
'sticky'
],
/**
* Top / Right / Bottom / Left
* @see https://tailwindcss.com/docs/top-right-bottom-left
*/ inset: [
{
inset: scaleInset()
}
],
/**
* Right / Left
* @see https://tailwindcss.com/docs/top-right-bottom-left
*/ 'inset-x': [
{
'inset-x': scaleInset()
}
],
/**
* Top / Bottom
* @see https://tailwindcss.com/docs/top-right-bottom-left
*/ 'inset-y': [
{
'inset-y': scaleInset()
}
],
/**
* Start
* @see https://tailwindcss.com/docs/top-right-bottom-left
*/ start: [
{
start: scaleInset()
}
],
/**
* End
* @see https://tailwindcss.com/docs/top-right-bottom-left
*/ end: [
{
end: scaleInset()
}
],
/**
* Top
* @see https://tailwindcss.com/docs/top-right-bottom-left
*/ top: [
{
top: scaleInset()
}
],
/**
* Right
* @see https://tailwindcss.com/docs/top-right-bottom-left
*/ right: [
{
right: scaleInset()
}
],
/**
* Bottom
* @see https://tailwindcss.com/docs/top-right-bottom-left
*/ bottom: [
{
bottom: scaleInset()
}
],
/**
* Left
* @see https://tailwindcss.com/docs/top-right-bottom-left
*/ left: [
{
left: scaleInset()
}
],
/**
* Visibility
* @see https://tailwindcss.com/docs/visibility
*/ visibility: [
'visible',
'invisible',
'collapse'
],
/**
* Z-Index
* @see https://tailwindcss.com/docs/z-index
*/ z: [
{
z: [
isInteger,
'auto',
isArbitraryVariable,
isArbitraryValue
]
}
],
// ------------------------
// --- Flexbox and Grid ---
// ------------------------
/**
* Flex Basis
* @see https://tailwindcss.com/docs/flex-basis
*/ basis: [
{
basis: [
isFraction,
'full',
'auto',
themeContainer,
...scaleUnambiguousSpacing()
]
}
],
/**
* Flex Direction
* @see https://tailwindcss.com/docs/flex-direction
*/ 'flex-direction': [
{
flex: [
'row',
'row-reverse',
'col',
'col-reverse'
]
}
],
/**
* Flex Wrap
* @see https://tailwindcss.com/docs/flex-wrap
*/ 'flex-wrap': [
{
flex: [
'nowrap',
'wrap',
'wrap-reverse'
]
}
],
/**
* Flex
* @see https://tailwindcss.com/docs/flex
*/ flex: [
{
flex: [
isNumber,
isFraction,
'auto',
'initial',
'none',
isArbitraryValue
]
}
],
/**
* Flex Grow
* @see https://tailwindcss.com/docs/flex-grow
*/ grow: [
{
grow: [
'',
isNumber,
isArbitraryVariable,
isArbitraryValue
]
}
],
/**
* Flex Shrink
* @see https://tailwindcss.com/docs/flex-shrink
*/ shrink: [
{
shrink: [
'',
isNumber,
isArbitraryVariable,
isArbitraryValue
]
}
],
/**
* Order
* @see https://tailwindcss.com/docs/order
*/ order: [
{
order: [
isInteger,
'first',
'last',
'none',
isArbitraryVariable,
isArbitraryValue
]
}
],
/**
* Grid Template Columns
* @see https://tailwindcss.com/docs/grid-template-columns
*/ 'grid-cols': [
{
'grid-cols': scaleGridTemplateColsRows()
}
],
/**
* Grid Column Start / End
* @see https://tailwindcss.com/docs/grid-column
*/ 'col-start-end': [
{
col: scaleGridColRowStartAndEnd()
}
],
/**
* Grid Column Start
* @see https://tailwindcss.com/docs/grid-column
*/ 'col-start': [
{
'col-start': scaleGridColRowStartOrEnd()
}
],
/**
* Grid Column End
* @see https://tailwindcss.com/docs/grid-column
*/ 'col-end': [
{
'col-end': scaleGridColRowStartOrEnd()
}
],
/**
* Grid Template Rows
* @see https://tailwindcss.com/docs/grid-template-rows
*/ 'grid-rows': [
{
'grid-rows': scaleGridTemplateColsRows()
}
],
/**
* Grid Row Start / End
* @see https://tailwindcss.com/docs/grid-row
*/ 'row-start-end': [
{
row: scaleGridColRowStartAndEnd()
}
],
/**
* Grid Row Start
* @see https://tailwindcss.com/docs/grid-row
*/ 'row-start': [
{
'row-start': scaleGridColRowStartOrEnd()
}
],
/**
* Grid Row End
* @see https://tailwindcss.com/docs/grid-row
*/ 'row-end': [
{
'row-end': scaleGridColRowStartOrEnd()
}
],
/**
* Grid Auto Flow
* @see https://tailwindcss.com/docs/grid-auto-flow
*/ 'grid-flow': [
{
'grid-flow': [
'row',
'col',
'dense',
'row-dense',
'col-dense'
]
}
],
/**
* Grid Auto Columns
* @see https://tailwindcss.com/docs/grid-auto-columns
*/ 'auto-cols': [
{
'auto-cols': scaleGridAutoColsRows()
}
],
/**
* Grid Auto Rows
* @see https://tailwindcss.com/docs/grid-auto-rows
*/ 'auto-rows': [
{
'auto-rows': scaleGridAutoColsRows()
}
],
/**
* Gap
* @see https://tailwindcss.com/docs/gap
*/ gap: [
{
gap: scaleUnambiguousSpacing()
}
],
/**
* Gap X
* @see https://tailwindcss.com/docs/gap
*/ 'gap-x': [
{
'gap-x': scaleUnambiguousSpacing()
}
],
/**
* Gap Y
* @see https://tailwindcss.com/docs/gap
*/ 'gap-y': [
{
'gap-y': scaleUnambiguousSpacing()
}
],
/**
* Justify Content
* @see https://tailwindcss.com/docs/justify-content
*/ 'justify-content': [
{
justify: [
...scaleAlignPrimaryAxis(),
'normal'
]
}
],
/**
* Justify Items
* @see https://tailwindcss.com/docs/justify-items
*/ 'justify-items': [
{
'justify-items': [
...scaleAlignSecondaryAxis(),
'normal'
]
}
],
/**
* Justify Self
* @see https://tailwindcss.com/docs/justify-self
*/ 'justify-self': [
{
'justify-self': [
'auto',
...scaleAlignSecondaryAxis()
]
}
],
/**
* Align Content
* @see https://tailwindcss.com/docs/align-content
*/ 'align-content': [
{
content: [
'normal',
...scaleAlignPrimaryAxis()
]
}
],
/**
* Align Items
* @see https://tailwindcss.com/docs/align-items
*/ 'align-items': [
{
items: [
...scaleAlignSecondaryAxis(),
{
baseline: [
'',
'last'
]
}
]
}