parse-class-name.ts
3.7 KB
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
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
}