107 lines
3.7 KiB
TypeScript
107 lines
3.7 KiB
TypeScript
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 { 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: string): ParsedClassName => {
|
|
const modifiers = []
|
|
|
|
let bracketDepth = 0
|
|
let parenDepth = 0
|
|
let modifierStart = 0
|
|
let postfixModifierPosition: number | undefined
|
|
|
|
for (let index = 0; index < className.length; index++) {
|
|
let currentCharacter = className[index]
|
|
|
|
if (bracketDepth === 0 && parenDepth === 0) {
|
|
if (currentCharacter === MODIFIER_SEPARATOR) {
|
|
modifiers.push(className.slice(modifierStart, index))
|
|
modifierStart = index + MODIFIER_SEPARATOR_LENGTH
|
|
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.substring(modifierStart)
|
|
const baseClassName = stripImportantModifier(baseClassNameWithImportantModifier)
|
|
const hasImportantModifier = baseClassName !== baseClassNameWithImportantModifier
|
|
const maybePostfixModifierPosition =
|
|
postfixModifierPosition && postfixModifierPosition > modifierStart
|
|
? postfixModifierPosition - modifierStart
|
|
: undefined
|
|
|
|
return {
|
|
modifiers,
|
|
hasImportantModifier,
|
|
baseClassName,
|
|
maybePostfixModifierPosition,
|
|
}
|
|
}
|
|
|
|
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) {
|
|
const parseClassNameOriginal = parseClassName
|
|
parseClassName = (className) =>
|
|
experimentalParseClassName({ className, parseClassName: parseClassNameOriginal })
|
|
}
|
|
|
|
return parseClassName
|
|
}
|
|
|
|
const stripImportantModifier = (baseClassName: string) => {
|
|
if (baseClassName.endsWith(IMPORTANT_MODIFIER)) {
|
|
return baseClassName.substring(0, baseClassName.length - 1)
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
}
|
|
|
|
return baseClassName
|
|
}
|