File size: 4,067 Bytes
fea495a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | import { AnyConfig, ParsedClassName } from './types'
export const IMPORTANT_MODIFIER = '!'
const MODIFIER_SEPARATOR = ':'
const EMPTY_MODIFIERS: string[] = []
// Pre-allocated result object shape for consistency
const createResultObject = (
modifiers: string[],
hasImportantModifier: boolean,
baseClassName: string,
maybePostfixModifierPosition?: number,
isExternal?: boolean,
): ParsedClassName => ({
modifiers,
hasImportantModifier,
baseClassName,
maybePostfixModifierPosition,
isExternal,
})
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 => {
// Use simple array with push for better performance
const modifiers: string[] = []
let bracketDepth = 0
let parenDepth = 0
let modifierStart = 0
let postfixModifierPosition: number | undefined
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: string) =>
className.startsWith(fullPrefix)
? parseClassNameOriginal(className.slice(fullPrefix.length))
: createResultObject(EMPTY_MODIFIERS, false, className, undefined, true)
}
if (experimentalParseClassName) {
const parseClassNameOriginal = parseClassName
parseClassName = (className: string) =>
experimentalParseClassName({ className, parseClassName: parseClassNameOriginal })
}
return parseClassName
}
|