tailwind-merge
Advanced tools
Comparing version 2.6.0 to 3.0.0-dev.7378c16adcc261599fa2debe8c18d77071c946f4
{ | ||
"name": "tailwind-merge", | ||
"version": "2.6.0", | ||
"version": "3.0.0-dev.7378c16adcc261599fa2debe8c18d77071c946f4", | ||
"description": "Merge Tailwind CSS classes without style conflicts", | ||
@@ -70,12 +70,12 @@ "keywords": [ | ||
"@rollup/plugin-node-resolve": "^16.0.0", | ||
"@rollup/plugin-typescript": "^12.1.1", | ||
"@rollup/plugin-typescript": "^12.1.2", | ||
"@vitest/coverage-v8": "^2.1.8", | ||
"@vitest/eslint-plugin": "^1.1.14", | ||
"@vitest/eslint-plugin": "^1.1.22", | ||
"babel-plugin-annotate-pure-calls": "^0.4.0", | ||
"babel-plugin-polyfill-regenerator": "^0.6.3", | ||
"eslint": "^9.16.0", | ||
"eslint": "^9.17.0", | ||
"eslint-plugin-import": "^2.31.0", | ||
"globby": "^11.1.0", | ||
"prettier": "^3.4.2", | ||
"rollup": "^4.28.1", | ||
"rollup": "^4.29.1", | ||
"rollup-plugin-delete": "^2.1.0", | ||
@@ -85,5 +85,5 @@ "rollup-plugin-dts": "^6.1.1", | ||
"typescript": "^5.7.2", | ||
"typescript-eslint": "^8.17.0", | ||
"typescript-eslint": "^8.19.0", | ||
"vitest": "^2.1.8", | ||
"zx": "^8.2.4" | ||
"zx": "^8.3.0" | ||
}, | ||
@@ -90,0 +90,0 @@ "publishConfig": { |
@@ -6,3 +6,3 @@ <!-- This file is autogenerated. If you want to change this content, please do the changes in `./docs/README.md` instead. --> | ||
<a href="https://github.com/dcastil/tailwind-merge"> | ||
<img src="https://github.com/dcastil/tailwind-merge/raw/v2.6.0/assets/logo.svg" alt="tailwind-merge" height="150px" /> | ||
<img src="https://github.com/dcastil/tailwind-merge/raw/7378c16adcc261599fa2debe8c18d77071c946f4/assets/logo.svg" alt="tailwind-merge" height="150px" /> | ||
</a> | ||
@@ -29,12 +29,12 @@ </div> | ||
- [What is it for](https://github.com/dcastil/tailwind-merge/tree/v2.6.0/docs/what-is-it-for.md) | ||
- [When and how to use it](https://github.com/dcastil/tailwind-merge/tree/v2.6.0/docs/when-and-how-to-use-it.md) | ||
- [Features](https://github.com/dcastil/tailwind-merge/tree/v2.6.0/docs/features.md) | ||
- [Limitations](https://github.com/dcastil/tailwind-merge/tree/v2.6.0/docs/limitations.md) | ||
- [Configuration](https://github.com/dcastil/tailwind-merge/tree/v2.6.0/docs/configuration.md) | ||
- [Recipes](https://github.com/dcastil/tailwind-merge/tree/v2.6.0/docs/recipes.md) | ||
- [API reference](https://github.com/dcastil/tailwind-merge/tree/v2.6.0/docs/api-reference.md) | ||
- [Writing plugins](https://github.com/dcastil/tailwind-merge/tree/v2.6.0/docs/writing-plugins.md) | ||
- [Versioning](https://github.com/dcastil/tailwind-merge/tree/v2.6.0/docs/versioning.md) | ||
- [Contributing](https://github.com/dcastil/tailwind-merge/tree/v2.6.0/docs/contributing.md) | ||
- [Similar packages](https://github.com/dcastil/tailwind-merge/tree/v2.6.0/docs/similar-packages.md) | ||
- [What is it for](https://github.com/dcastil/tailwind-merge/tree/7378c16adcc261599fa2debe8c18d77071c946f4/docs/what-is-it-for.md) | ||
- [When and how to use it](https://github.com/dcastil/tailwind-merge/tree/7378c16adcc261599fa2debe8c18d77071c946f4/docs/when-and-how-to-use-it.md) | ||
- [Features](https://github.com/dcastil/tailwind-merge/tree/7378c16adcc261599fa2debe8c18d77071c946f4/docs/features.md) | ||
- [Limitations](https://github.com/dcastil/tailwind-merge/tree/7378c16adcc261599fa2debe8c18d77071c946f4/docs/limitations.md) | ||
- [Configuration](https://github.com/dcastil/tailwind-merge/tree/7378c16adcc261599fa2debe8c18d77071c946f4/docs/configuration.md) | ||
- [Recipes](https://github.com/dcastil/tailwind-merge/tree/7378c16adcc261599fa2debe8c18d77071c946f4/docs/recipes.md) | ||
- [API reference](https://github.com/dcastil/tailwind-merge/tree/7378c16adcc261599fa2debe8c18d77071c946f4/docs/api-reference.md) | ||
- [Writing plugins](https://github.com/dcastil/tailwind-merge/tree/7378c16adcc261599fa2debe8c18d77071c946f4/docs/writing-plugins.md) | ||
- [Versioning](https://github.com/dcastil/tailwind-merge/tree/7378c16adcc261599fa2debe8c18d77071c946f4/docs/versioning.md) | ||
- [Contributing](https://github.com/dcastil/tailwind-merge/tree/7378c16adcc261599fa2debe8c18d77071c946f4/docs/contributing.md) | ||
- [Similar packages](https://github.com/dcastil/tailwind-merge/tree/7378c16adcc261599fa2debe8c18d77071c946f4/docs/similar-packages.md) |
@@ -15,4 +15,4 @@ export { createTailwindMerge } from './lib/create-tailwind-merge' | ||
type ExperimentalParseClassNameParam, | ||
type ExperimentalParsedClassName, | ||
type ParsedClassName as ExperimentalParsedClassName, | ||
} from './lib/types' | ||
export * as validators from './lib/validators' |
@@ -107,3 +107,3 @@ import { | ||
export const createClassMap = (config: Config<AnyClassGroupIds, AnyThemeGroupIds>) => { | ||
const { theme, prefix } = config | ||
const { theme, classGroups } = config | ||
const classMap: ClassPartObject = { | ||
@@ -114,11 +114,6 @@ nextPart: new Map<string, ClassPartObject>(), | ||
const prefixedClassGroupEntries = getPrefixedClassGroupEntries( | ||
Object.entries(config.classGroups), | ||
prefix, | ||
) | ||
for (const classGroupId in classGroups) { | ||
processClassesRecursively(classGroups[classGroupId]!, classMap, classGroupId, theme) | ||
} | ||
prefixedClassGroupEntries.forEach(([classGroupId, classGroup]) => { | ||
processClassesRecursively(classGroup, classMap, classGroupId, theme) | ||
}) | ||
return classMap | ||
@@ -190,28 +185,1 @@ } | ||
(func as ThemeGetter).isThemeGetter | ||
const getPrefixedClassGroupEntries = ( | ||
classGroupEntries: Array<[classGroupId: string, classGroup: ClassGroup<AnyThemeGroupIds>]>, | ||
prefix: string | undefined, | ||
): Array<[classGroupId: string, classGroup: ClassGroup<AnyThemeGroupIds>]> => { | ||
if (!prefix) { | ||
return classGroupEntries | ||
} | ||
return classGroupEntries.map(([classGroupId, classGroup]) => { | ||
const prefixedClassGroup = classGroup.map((classDefinition) => { | ||
if (typeof classDefinition === 'string') { | ||
return prefix + classDefinition | ||
} | ||
if (typeof classDefinition === 'object') { | ||
return Object.fromEntries( | ||
Object.entries(classDefinition).map(([key, value]) => [prefix + key, value]), | ||
) | ||
} | ||
return classDefinition | ||
}) | ||
return [classGroupId, prefixedClassGroup] | ||
}) | ||
} |
import { createClassGroupUtils } from './class-group-utils' | ||
import { createLruCache } from './lru-cache' | ||
import { createParseClassName } from './parse-class-name' | ||
import { createSortModifiers } from './sort-modifiers' | ||
import { AnyConfig } from './types' | ||
@@ -11,3 +12,4 @@ | ||
parseClassName: createParseClassName(config), | ||
sortModifiers: createSortModifiers(config), | ||
...createClassGroupUtils(config), | ||
}) |
import { ConfigUtils } from './config-utils' | ||
import { IMPORTANT_MODIFIER, sortModifiers } from './parse-class-name' | ||
import { IMPORTANT_MODIFIER } from './parse-class-name' | ||
@@ -7,3 +7,4 @@ const SPLIT_CLASSES_REGEX = /\s+/ | ||
export const mergeClassList = (classList: string, configUtils: ConfigUtils) => { | ||
const { parseClassName, getClassGroupId, getConflictingClassGroupIds } = configUtils | ||
const { parseClassName, getClassGroupId, getConflictingClassGroupIds, sortModifiers } = | ||
configUtils | ||
@@ -25,6 +26,16 @@ /** | ||
const { modifiers, hasImportantModifier, baseClassName, maybePostfixModifierPosition } = | ||
parseClassName(originalClassName) | ||
const { | ||
isExternal, | ||
modifiers, | ||
hasImportantModifier, | ||
baseClassName, | ||
maybePostfixModifierPosition, | ||
} = parseClassName(originalClassName) | ||
let hasPostfixModifier = Boolean(maybePostfixModifierPosition) | ||
if (isExternal) { | ||
result = originalClassName + (result.length > 0 ? ' ' + result : result) | ||
continue | ||
} | ||
let hasPostfixModifier = !!maybePostfixModifierPosition | ||
let classGroupId = getClassGroupId( | ||
@@ -31,0 +42,0 @@ hasPostfixModifier |
@@ -1,2 +0,2 @@ | ||
import { AnyConfig, ConfigExtension } from './types' | ||
import { AnyConfig, ConfigExtension, NoInfer } from './types' | ||
@@ -12,3 +12,2 @@ /** | ||
prefix, | ||
separator, | ||
experimentalParseClassName, | ||
@@ -21,18 +20,21 @@ extend = {}, | ||
overrideProperty(baseConfig, 'prefix', prefix) | ||
overrideProperty(baseConfig, 'separator', separator) | ||
overrideProperty(baseConfig, 'experimentalParseClassName', experimentalParseClassName) | ||
for (const configKey in override) { | ||
overrideConfigProperties( | ||
baseConfig[configKey as keyof typeof override], | ||
override[configKey as keyof typeof override], | ||
) | ||
} | ||
overrideConfigProperties(baseConfig.theme, override.theme) | ||
overrideConfigProperties(baseConfig.classGroups, override.classGroups) | ||
overrideConfigProperties(baseConfig.conflictingClassGroups, override.conflictingClassGroups) | ||
overrideConfigProperties( | ||
baseConfig.conflictingClassGroupModifiers, | ||
override.conflictingClassGroupModifiers, | ||
) | ||
overrideProperty(baseConfig, 'orderSensitiveModifiers', override.orderSensitiveModifiers) | ||
for (const key in extend) { | ||
mergeConfigProperties( | ||
baseConfig[key as keyof typeof extend], | ||
extend[key as keyof typeof extend], | ||
) | ||
} | ||
mergeConfigProperties(baseConfig.theme, extend.theme) | ||
mergeConfigProperties(baseConfig.classGroups, extend.classGroups) | ||
mergeConfigProperties(baseConfig.conflictingClassGroups, extend.conflictingClassGroups) | ||
mergeConfigProperties( | ||
baseConfig.conflictingClassGroupModifiers, | ||
extend.conflictingClassGroupModifiers, | ||
) | ||
mergeArrayProperties(baseConfig, extend, 'orderSensitiveModifiers') | ||
@@ -69,9 +71,17 @@ return baseConfig | ||
for (const key in mergeObject) { | ||
const mergeValue = mergeObject[key] | ||
if (mergeValue !== undefined) { | ||
baseObject[key] = (baseObject[key] || []).concat(mergeValue) | ||
} | ||
mergeArrayProperties(baseObject, mergeObject, key) | ||
} | ||
} | ||
} | ||
const mergeArrayProperties = <Key extends string>( | ||
baseObject: Partial<Record<NoInfer<Key>, readonly unknown[]>>, | ||
mergeObject: Partial<Record<NoInfer<Key>, readonly unknown[]>>, | ||
key: Key, | ||
) => { | ||
const mergeValue = mergeObject[key] | ||
if (mergeValue !== undefined) { | ||
baseObject[key] = baseObject[key] ? baseObject[key].concat(mergeValue) : mergeValue | ||
} | ||
} |
@@ -1,16 +0,21 @@ | ||
import { AnyConfig } from './types' | ||
import { AnyConfig, ParsedClassName } from './types' | ||
export const IMPORTANT_MODIFIER = '!' | ||
const MODIFIER_SEPARATOR = ':' | ||
const MODIFIER_SEPARATOR_LENGTH = MODIFIER_SEPARATOR.length | ||
export const createParseClassName = (config: AnyConfig) => { | ||
const { separator, experimentalParseClassName } = config | ||
const isSeparatorSingleCharacter = separator.length === 1 | ||
const firstSeparatorCharacter = separator[0] | ||
const separatorLength = separator.length | ||
const { prefix, experimentalParseClassName } = config | ||
// parseClassName inspired by https://github.com/tailwindlabs/tailwindcss/blob/v3.2.2/src/util/splitAtTopLevelOnly.js | ||
const parseClassName = (className: string) => { | ||
/** | ||
* 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: string): ParsedClassName => { | ||
const modifiers = [] | ||
let bracketDepth = 0 | ||
let parenDepth = 0 | ||
let modifierStart = 0 | ||
@@ -22,10 +27,6 @@ let postfixModifierPosition: number | undefined | ||
if (bracketDepth === 0) { | ||
if ( | ||
currentCharacter === firstSeparatorCharacter && | ||
(isSeparatorSingleCharacter || | ||
className.slice(index, index + separatorLength) === separator) | ||
) { | ||
if (bracketDepth === 0 && parenDepth === 0) { | ||
if (currentCharacter === MODIFIER_SEPARATOR) { | ||
modifiers.push(className.slice(modifierStart, index)) | ||
modifierStart = index + separatorLength | ||
modifierStart = index + MODIFIER_SEPARATOR_LENGTH | ||
continue | ||
@@ -44,2 +45,6 @@ } | ||
bracketDepth-- | ||
} else if (currentCharacter === '(') { | ||
parenDepth++ | ||
} else if (currentCharacter === ')') { | ||
parenDepth-- | ||
} | ||
@@ -50,8 +55,4 @@ } | ||
modifiers.length === 0 ? className : className.substring(modifierStart) | ||
const hasImportantModifier = | ||
baseClassNameWithImportantModifier.startsWith(IMPORTANT_MODIFIER) | ||
const baseClassName = hasImportantModifier | ||
? baseClassNameWithImportantModifier.substring(1) | ||
: baseClassNameWithImportantModifier | ||
const baseClassName = stripImportantModifier(baseClassNameWithImportantModifier) | ||
const hasImportantModifier = baseClassName !== baseClassNameWithImportantModifier | ||
const maybePostfixModifierPosition = | ||
@@ -70,4 +71,21 @@ postfixModifierPosition && postfixModifierPosition > modifierStart | ||
if (prefix) { | ||
const fullPrefix = prefix + MODIFIER_SEPARATOR | ||
const parseClassNameOriginal = parseClassName | ||
parseClassName = (className) => | ||
className.startsWith(fullPrefix) | ||
? parseClassNameOriginal(className.substring(fullPrefix.length)) | ||
: { | ||
isExternal: true, | ||
modifiers: [], | ||
hasImportantModifier: false, | ||
baseClassName: className, | ||
maybePostfixModifierPosition: undefined, | ||
} | ||
} | ||
if (experimentalParseClassName) { | ||
return (className: string) => experimentalParseClassName({ className, parseClassName }) | ||
const parseClassNameOriginal = parseClassName | ||
parseClassName = (className) => | ||
experimentalParseClassName({ className, parseClassName: parseClassNameOriginal }) | ||
} | ||
@@ -78,29 +96,16 @@ | ||
/** | ||
* 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 | ||
*/ | ||
export const sortModifiers = (modifiers: string[]) => { | ||
if (modifiers.length <= 1) { | ||
return modifiers | ||
const stripImportantModifier = (baseClassName: string) => { | ||
if (baseClassName.endsWith(IMPORTANT_MODIFIER)) { | ||
return baseClassName.substring(0, baseClassName.length - 1) | ||
} | ||
const sortedModifiers: string[] = [] | ||
let unsortedModifiers: string[] = [] | ||
/** | ||
* 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 | ||
*/ | ||
if (baseClassName.startsWith(IMPORTANT_MODIFIER)) { | ||
return baseClassName.substring(1) | ||
} | ||
modifiers.forEach((modifier) => { | ||
const isArbitraryVariant = modifier[0] === '[' | ||
if (isArbitraryVariant) { | ||
sortedModifiers.push(...unsortedModifiers.sort(), modifier) | ||
unsortedModifiers = [] | ||
} else { | ||
unsortedModifiers.push(modifier) | ||
} | ||
}) | ||
sortedModifiers.push(...unsortedModifiers.sort()) | ||
return sortedModifiers | ||
return baseClassName | ||
} |
@@ -24,7 +24,2 @@ /** | ||
/** | ||
* Custom separator for modifiers in Tailwind classes | ||
* @see https://tailwindcss.com/docs/configuration#separator | ||
*/ | ||
separator: string | ||
/** | ||
* Allows to customize parsing of individual classes passed to `twMerge`. | ||
@@ -35,3 +30,3 @@ * All classes passed to `twMerge` outside of cache hits are passed to this function before it is determined whether the class is a valid Tailwind CSS class. | ||
*/ | ||
experimentalParseClassName?(param: ExperimentalParseClassNameParam): ExperimentalParsedClassName | ||
experimentalParseClassName?(param: ExperimentalParseClassNameParam): ParsedClassName | ||
} | ||
@@ -46,3 +41,3 @@ | ||
className: string | ||
parseClassName(className: string): ExperimentalParsedClassName | ||
parseClassName(className: string): ParsedClassName | ||
} | ||
@@ -55,4 +50,10 @@ | ||
*/ | ||
export interface ExperimentalParsedClassName { | ||
export interface ParsedClassName { | ||
/** | ||
* Whether the class is external and merging logic should be sipped. | ||
* | ||
* If this is `true`, the class will be treated as if it wasn't a Tailwind class and will be passed through as is. | ||
*/ | ||
isExternal?: boolean | ||
/** | ||
* Modifiers of the class in the order they appear in the class. | ||
@@ -94,2 +95,3 @@ * | ||
* Theme scales used in classGroups. | ||
* | ||
* The keys are the same as in the Tailwind config but the values are sometimes defined more broadly. | ||
@@ -100,2 +102,3 @@ */ | ||
* Object with groups of classes. | ||
* | ||
* @example | ||
@@ -112,2 +115,3 @@ * { | ||
* Conflicting classes across groups. | ||
* | ||
* The key is ID of class group which creates conflict, values are IDs of class groups which receive a conflict. | ||
@@ -120,2 +124,3 @@ * A class group ID is the key of a class group in classGroups object. | ||
* Postfix modifiers conflicting with other class groups. | ||
* | ||
* A class group ID is the key of a class group in classGroups object. | ||
@@ -127,2 +132,8 @@ * @example { 'font-size': ['leading'] } | ||
> | ||
/** | ||
* Modifiers whose order among multiple modifiers should be preserved because their order changes which element gets targeted. | ||
* | ||
* tailwind-merge makes sure that classes with these modifiers are not overwritten by classes with the same modifiers with order-sensitive modifiers being in a different position. | ||
*/ | ||
orderSensitiveModifiers: string[] | ||
} | ||
@@ -140,3 +151,3 @@ | ||
type PartialPartial<T> = { | ||
[P in keyof T]?: Partial<T[P]> | ||
[P in keyof T]?: T[P] extends any[] ? T[P] : Partial<T[P]> | ||
} | ||
@@ -181,27 +192,20 @@ | ||
export type DefaultThemeGroupIds = | ||
| 'animate' | ||
| 'aspect' | ||
| 'blur' | ||
| 'borderColor' | ||
| 'borderRadius' | ||
| 'borderSpacing' | ||
| 'borderWidth' | ||
| 'brightness' | ||
| 'colors' | ||
| 'contrast' | ||
| 'gap' | ||
| 'gradientColorStopPositions' | ||
| 'gradientColorStops' | ||
| 'grayscale' | ||
| 'hueRotate' | ||
| 'inset' | ||
| 'invert' | ||
| 'margin' | ||
| 'opacity' | ||
| 'padding' | ||
| 'saturate' | ||
| 'scale' | ||
| 'sepia' | ||
| 'skew' | ||
| 'space' | ||
| 'breakpoint' | ||
| 'color' | ||
| 'container' | ||
| 'drop-shadow' | ||
| 'ease' | ||
| 'font-weight' | ||
| 'font' | ||
| 'inset-shadow' | ||
| 'leading' | ||
| 'perspective' | ||
| 'radius' | ||
| 'shadow' | ||
| 'spacing' | ||
| 'translate' | ||
| 'text' | ||
| 'tracking' | ||
@@ -231,2 +235,3 @@ /** | ||
| 'backdrop-sepia' | ||
| 'backface' | ||
| 'basis' | ||
@@ -238,3 +243,2 @@ | 'bg-attachment' | ||
| 'bg-image' | ||
| 'bg-opacity' | ||
| 'bg-origin' | ||
@@ -255,3 +259,2 @@ | 'bg-position' | ||
| 'border-color' | ||
| 'border-opacity' | ||
| 'border-spacing-x' | ||
@@ -284,2 +287,3 @@ | 'border-spacing-y' | ||
| 'col-start' | ||
| 'color-scheme' | ||
| 'columns' | ||
@@ -293,3 +297,2 @@ | 'container' | ||
| 'divide-color' | ||
| 'divide-opacity' | ||
| 'divide-style' | ||
@@ -304,2 +307,3 @@ | 'divide-x-reverse' | ||
| 'end' | ||
| 'field-sizing' | ||
| 'fill' | ||
@@ -314,2 +318,3 @@ | 'filter' | ||
| 'font-smoothing' | ||
| 'font-stretch' | ||
| 'font-style' | ||
@@ -342,2 +347,6 @@ | 'font-weight' | ||
| 'indent' | ||
| 'inset-ring-color' | ||
| 'inset-ring-w' | ||
| 'inset-shadow-color' | ||
| 'inset-shadow' | ||
| 'inset-x' | ||
@@ -388,2 +397,4 @@ | 'inset-y' | ||
| 'pe' | ||
| 'perspective-origin' | ||
| 'perspective' | ||
| 'pl' | ||
@@ -394,3 +405,2 @@ | 'place-content' | ||
| 'placeholder-color' | ||
| 'placeholder-opacity' | ||
| 'pointer-events' | ||
@@ -408,5 +418,7 @@ | 'position' | ||
| 'ring-offset-w' | ||
| 'ring-opacity' | ||
| 'ring-w-inset' | ||
| 'ring-w' | ||
| 'rotate-x' | ||
| 'rotate-y' | ||
| 'rotate-z' | ||
| 'rotate' | ||
@@ -432,4 +444,6 @@ | 'rounded-b' | ||
| 'saturate' | ||
| 'scale-3d' | ||
| 'scale-x' | ||
| 'scale-y' | ||
| 'scale-z' | ||
| 'scale' | ||
@@ -463,2 +477,3 @@ | 'scroll-behavior' | ||
| 'skew-y' | ||
| 'skew' | ||
| 'snap-align' | ||
@@ -483,3 +498,2 @@ | 'snap-stop' | ||
| 'text-decoration' | ||
| 'text-opacity' | ||
| 'text-overflow' | ||
@@ -495,6 +509,11 @@ | 'text-transform' | ||
| 'transform-origin' | ||
| 'transform-style' | ||
| 'transform' | ||
| 'transition-behavior' | ||
| 'transition' | ||
| 'translate-none' | ||
| 'translate-x' | ||
| 'translate-y' | ||
| 'translate-z' | ||
| 'translate' | ||
| 'underline-offset' | ||
@@ -501,0 +520,0 @@ | 'vertical-align' |
@@ -1,4 +0,4 @@ | ||
const arbitraryValueRegex = /^\[(?:([a-z-]+):)?(.+)\]$/i | ||
const arbitraryValueRegex = /^\[(?:(\w[\w-]*):)?(.+)\]$/i | ||
const arbitraryVariableRegex = /^\((?:(\w[\w-]*):)?(.+)\)$/i | ||
const fractionRegex = /^\d+\/\d+$/ | ||
const stringLengths = new Set(['px', 'full', 'screen']) | ||
const tshirtUnitRegex = /^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/ | ||
@@ -13,12 +13,6 @@ const lengthUnitRegex = | ||
export const isLength = (value: string) => | ||
isNumber(value) || stringLengths.has(value) || fractionRegex.test(value) | ||
export const isFraction = (value: string) => fractionRegex.test(value) | ||
export const isArbitraryLength = (value: string) => | ||
getIsArbitraryValue(value, 'length', isLengthOnly) | ||
export const isNumber = (value: string) => Boolean(value) && !Number.isNaN(Number(value)) | ||
export const isArbitraryNumber = (value: string) => getIsArbitraryValue(value, 'number', isNumber) | ||
export const isInteger = (value: string) => Boolean(value) && Number.isInteger(Number(value)) | ||
@@ -28,24 +22,62 @@ | ||
export const isTshirtSize = (value: string) => tshirtUnitRegex.test(value) | ||
export const isAny = () => true | ||
const isLengthOnly = (value: string) => | ||
// `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: string) => shadowRegex.test(value) | ||
const isImage = (value: string) => imageRegex.test(value) | ||
export const isAnyNonArbitrary = (value: string) => | ||
!isArbitraryValue(value) && !isArbitraryVariable(value) | ||
export const isArbitrarySize = (value: string) => getIsArbitraryValue(value, isLabelSize, isNever) | ||
export const isArbitraryValue = (value: string) => arbitraryValueRegex.test(value) | ||
export const isTshirtSize = (value: string) => tshirtUnitRegex.test(value) | ||
export const isArbitraryLength = (value: string) => | ||
getIsArbitraryValue(value, isLabelLength, isLengthOnly) | ||
const sizeLabels = new Set(['length', 'size', 'percentage']) | ||
export const isArbitraryNumber = (value: string) => | ||
getIsArbitraryValue(value, isLabelNumber, isNumber) | ||
export const isArbitrarySize = (value: string) => getIsArbitraryValue(value, sizeLabels, isNever) | ||
export const isArbitraryPosition = (value: string) => | ||
getIsArbitraryValue(value, 'position', isNever) | ||
getIsArbitraryValue(value, isLabelPosition, isNever) | ||
const imageLabels = new Set(['image', 'url']) | ||
export const isArbitraryImage = (value: string) => getIsArbitraryValue(value, isLabelImage, isImage) | ||
export const isArbitraryImage = (value: string) => getIsArbitraryValue(value, imageLabels, isImage) | ||
export const isArbitraryShadow = (value: string) => getIsArbitraryValue(value, isNever, isShadow) | ||
export const isArbitraryShadow = (value: string) => getIsArbitraryValue(value, '', isShadow) | ||
export const isArbitraryVariable = (value: string) => arbitraryVariableRegex.test(value) | ||
export const isAny = () => true | ||
export const isArbitraryVariableLength = (value: string) => | ||
getIsArbitraryVariable(value, isLabelLength) | ||
export const isArbitraryVariableFamilyName = (value: string) => | ||
getIsArbitraryVariable(value, isLabelFamilyName) | ||
export const isArbitraryVariablePosition = (value: string) => | ||
getIsArbitraryVariable(value, isLabelPosition) | ||
export const isArbitraryVariableSize = (value: string) => getIsArbitraryVariable(value, isLabelSize) | ||
export const isArbitraryVariableImage = (value: string) => | ||
getIsArbitraryVariable(value, isLabelImage) | ||
export const isArbitraryVariableShadow = (value: string) => | ||
getIsArbitraryVariable(value, isLabelShadow, true) | ||
// Helpers | ||
const getIsArbitraryValue = ( | ||
value: string, | ||
label: string | Set<string>, | ||
testLabel: (label: string) => boolean, | ||
testValue: (value: string) => boolean, | ||
@@ -57,3 +89,3 @@ ) => { | ||
if (result[1]) { | ||
return typeof label === 'string' ? result[1] === label : label.has(result[1]) | ||
return testLabel(result[1]) | ||
} | ||
@@ -67,12 +99,37 @@ | ||
const isLengthOnly = (value: string) => | ||
// `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 getIsArbitraryVariable = ( | ||
value: string, | ||
testLabel: (label: string) => boolean, | ||
shouldMatchNoLabel = false, | ||
) => { | ||
const result = arbitraryVariableRegex.exec(value) | ||
const isNever = () => false | ||
if (result) { | ||
if (result[1]) { | ||
return testLabel(result[1]) | ||
} | ||
return shouldMatchNoLabel | ||
} | ||
const isShadow = (value: string) => shadowRegex.test(value) | ||
return false | ||
} | ||
const isImage = (value: string) => imageRegex.test(value) | ||
// Labels | ||
const isLabelPosition = (label: string) => label === 'position' | ||
const imageLabels = new Set(['image', 'url']) | ||
const isLabelImage = (label: string) => imageLabels.has(label) | ||
const sizeLabels = new Set(['length', 'size', 'percentage']) | ||
const isLabelSize = (label: string) => sizeLabels.has(label) | ||
const isLabelLength = (label: string) => label === 'length' | ||
const isLabelNumber = (label: string) => label === 'number' | ||
const isLabelFamilyName = (label: string) => label === 'family-name' | ||
const isLabelShadow = (label: string) => label === 'shadow' |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
826928
28
17232
1