New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

tailwind-merge

Package Overview
Dependencies
Maintainers
0
Versions
325
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

tailwind-merge - npm Package Compare versions

Comparing version 2.6.0 to 3.0.0-dev.7378c16adcc261599fa2debe8c18d77071c946f4

src/lib/sort-modifiers.ts

14

package.json
{
"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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc