tailwind-merge 3.3.1 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -12
- package/dist/bundle-cjs.js +230 -150
- package/dist/bundle-cjs.js.map +1 -1
- package/dist/bundle-mjs.mjs +229 -150
- package/dist/bundle-mjs.mjs.map +1 -1
- package/dist/es5/bundle-cjs.js +256 -163
- package/dist/es5/bundle-cjs.js.map +1 -1
- package/dist/es5/bundle-mjs.mjs +255 -163
- package/dist/es5/bundle-mjs.mjs.map +1 -1
- package/dist/types.d.ts +2 -2
- package/package.json +20 -19
- package/src/lib/class-group-utils.ts +183 -91
- package/src/lib/create-tailwind-merge.ts +8 -8
- package/src/lib/from-theme.ts +6 -2
- package/src/lib/lru-cache.ts +13 -11
- package/src/lib/merge-classlist.ts +8 -2
- package/src/lib/parse-class-name.ts +53 -45
- package/src/lib/sort-modifiers.ts +37 -24
- package/src/lib/tw-join.ts +5 -5
- package/src/lib/utils.ts +17 -0
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
<br />
|
|
5
5
|
<a href="https://github.com/dcastil/tailwind-merge">
|
|
6
|
-
<img src="https://github.com/dcastil/tailwind-merge/raw/v3.
|
|
6
|
+
<img src="https://github.com/dcastil/tailwind-merge/raw/v3.4.0/assets/logo.svg" alt="tailwind-merge" height="150px" />
|
|
7
7
|
</a>
|
|
8
8
|
</div>
|
|
9
9
|
|
|
@@ -25,14 +25,14 @@ twMerge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]')
|
|
|
25
25
|
|
|
26
26
|
## Get started
|
|
27
27
|
|
|
28
|
-
- [What is it for](https://github.com/dcastil/tailwind-merge/tree/v3.
|
|
29
|
-
- [When and how to use it](https://github.com/dcastil/tailwind-merge/tree/v3.
|
|
30
|
-
- [Features](https://github.com/dcastil/tailwind-merge/tree/v3.
|
|
31
|
-
- [Limitations](https://github.com/dcastil/tailwind-merge/tree/v3.
|
|
32
|
-
- [Configuration](https://github.com/dcastil/tailwind-merge/tree/v3.
|
|
33
|
-
- [Recipes](https://github.com/dcastil/tailwind-merge/tree/v3.
|
|
34
|
-
- [API reference](https://github.com/dcastil/tailwind-merge/tree/v3.
|
|
35
|
-
- [Writing plugins](https://github.com/dcastil/tailwind-merge/tree/v3.
|
|
36
|
-
- [Versioning](https://github.com/dcastil/tailwind-merge/tree/v3.
|
|
37
|
-
- [Contributing](https://github.com/dcastil/tailwind-merge/tree/v3.
|
|
38
|
-
- [Similar packages](https://github.com/dcastil/tailwind-merge/tree/v3.
|
|
28
|
+
- [What is it for](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/what-is-it-for.md)
|
|
29
|
+
- [When and how to use it](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/when-and-how-to-use-it.md)
|
|
30
|
+
- [Features](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/features.md)
|
|
31
|
+
- [Limitations](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/limitations.md)
|
|
32
|
+
- [Configuration](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/configuration.md)
|
|
33
|
+
- [Recipes](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/recipes.md)
|
|
34
|
+
- [API reference](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/api-reference.md)
|
|
35
|
+
- [Writing plugins](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/writing-plugins.md)
|
|
36
|
+
- [Versioning](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/versioning.md)
|
|
37
|
+
- [Contributing](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/contributing.md)
|
|
38
|
+
- [Similar packages](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/similar-packages.md)
|
package/dist/bundle-cjs.js
CHANGED
|
@@ -3,7 +3,37 @@
|
|
|
3
3
|
Object.defineProperty(exports, Symbol.toStringTag, {
|
|
4
4
|
value: 'Module'
|
|
5
5
|
});
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Concatenates two arrays faster than the array spread operator.
|
|
9
|
+
*/
|
|
10
|
+
const concatArrays = (array1, array2) => {
|
|
11
|
+
// Pre-allocate for better V8 optimization
|
|
12
|
+
const combinedArray = new Array(array1.length + array2.length);
|
|
13
|
+
for (let i = 0; i < array1.length; i++) {
|
|
14
|
+
combinedArray[i] = array1[i];
|
|
15
|
+
}
|
|
16
|
+
for (let i = 0; i < array2.length; i++) {
|
|
17
|
+
combinedArray[array1.length + i] = array2[i];
|
|
18
|
+
}
|
|
19
|
+
return combinedArray;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Factory function ensures consistent object shapes
|
|
23
|
+
const createClassValidatorObject = (classGroupId, validator) => ({
|
|
24
|
+
classGroupId,
|
|
25
|
+
validator
|
|
26
|
+
});
|
|
27
|
+
// Factory ensures consistent ClassPartObject shape
|
|
28
|
+
const createClassPartObject = (nextPart = new Map(), validators = null, classGroupId) => ({
|
|
29
|
+
nextPart,
|
|
30
|
+
validators,
|
|
31
|
+
classGroupId
|
|
32
|
+
});
|
|
6
33
|
const CLASS_PART_SEPARATOR = '-';
|
|
34
|
+
const EMPTY_CONFLICTS = [];
|
|
35
|
+
// I use two dots here because one dot is used as prefix for class groups in plugins
|
|
36
|
+
const ARBITRARY_PROPERTY_PREFIX = 'arbitrary..';
|
|
7
37
|
const createClassGroupUtils = config => {
|
|
8
38
|
const classMap = createClassMap(config);
|
|
9
39
|
const {
|
|
@@ -11,54 +41,73 @@ const createClassGroupUtils = config => {
|
|
|
11
41
|
conflictingClassGroupModifiers
|
|
12
42
|
} = config;
|
|
13
43
|
const getClassGroupId = className => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (classParts[0] === '' && classParts.length !== 1) {
|
|
17
|
-
classParts.shift();
|
|
44
|
+
if (className.startsWith('[') && className.endsWith(']')) {
|
|
45
|
+
return getGroupIdForArbitraryProperty(className);
|
|
18
46
|
}
|
|
19
|
-
|
|
47
|
+
const classParts = className.split(CLASS_PART_SEPARATOR);
|
|
48
|
+
// Classes like `-inset-1` produce an empty string as first classPart. We assume that classes for negative values are used correctly and skip it.
|
|
49
|
+
const startIndex = classParts[0] === '' && classParts.length > 1 ? 1 : 0;
|
|
50
|
+
return getGroupRecursive(classParts, startIndex, classMap);
|
|
20
51
|
};
|
|
21
52
|
const getConflictingClassGroupIds = (classGroupId, hasPostfixModifier) => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
53
|
+
if (hasPostfixModifier) {
|
|
54
|
+
const modifierConflicts = conflictingClassGroupModifiers[classGroupId];
|
|
55
|
+
const baseConflicts = conflictingClassGroups[classGroupId];
|
|
56
|
+
if (modifierConflicts) {
|
|
57
|
+
if (baseConflicts) {
|
|
58
|
+
// Merge base conflicts with modifier conflicts
|
|
59
|
+
return concatArrays(baseConflicts, modifierConflicts);
|
|
60
|
+
}
|
|
61
|
+
// Only modifier conflicts
|
|
62
|
+
return modifierConflicts;
|
|
63
|
+
}
|
|
64
|
+
// Fall back to without postfix if no modifier conflicts
|
|
65
|
+
return baseConflicts || EMPTY_CONFLICTS;
|
|
25
66
|
}
|
|
26
|
-
return
|
|
67
|
+
return conflictingClassGroups[classGroupId] || EMPTY_CONFLICTS;
|
|
27
68
|
};
|
|
28
69
|
return {
|
|
29
70
|
getClassGroupId,
|
|
30
71
|
getConflictingClassGroupIds
|
|
31
72
|
};
|
|
32
73
|
};
|
|
33
|
-
const getGroupRecursive = (classParts, classPartObject) => {
|
|
34
|
-
|
|
74
|
+
const getGroupRecursive = (classParts, startIndex, classPartObject) => {
|
|
75
|
+
const classPathsLength = classParts.length - startIndex;
|
|
76
|
+
if (classPathsLength === 0) {
|
|
35
77
|
return classPartObject.classGroupId;
|
|
36
78
|
}
|
|
37
|
-
const currentClassPart = classParts[
|
|
79
|
+
const currentClassPart = classParts[startIndex];
|
|
38
80
|
const nextClassPartObject = classPartObject.nextPart.get(currentClassPart);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return
|
|
81
|
+
if (nextClassPartObject) {
|
|
82
|
+
const result = getGroupRecursive(classParts, startIndex + 1, nextClassPartObject);
|
|
83
|
+
if (result) return result;
|
|
42
84
|
}
|
|
43
|
-
|
|
85
|
+
const validators = classPartObject.validators;
|
|
86
|
+
if (validators === null) {
|
|
44
87
|
return undefined;
|
|
45
88
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (arbitraryPropertyRegex.test(className)) {
|
|
54
|
-
const arbitraryPropertyClassName = arbitraryPropertyRegex.exec(className)[1];
|
|
55
|
-
const property = arbitraryPropertyClassName?.substring(0, arbitraryPropertyClassName.indexOf(':'));
|
|
56
|
-
if (property) {
|
|
57
|
-
// I use two dots here because one dot is used as prefix for class groups in plugins
|
|
58
|
-
return 'arbitrary..' + property;
|
|
89
|
+
// Build classRest string efficiently by joining from startIndex onwards
|
|
90
|
+
const classRest = startIndex === 0 ? classParts.join(CLASS_PART_SEPARATOR) : classParts.slice(startIndex).join(CLASS_PART_SEPARATOR);
|
|
91
|
+
const validatorsLength = validators.length;
|
|
92
|
+
for (let i = 0; i < validatorsLength; i++) {
|
|
93
|
+
const validatorObj = validators[i];
|
|
94
|
+
if (validatorObj.validator(classRest)) {
|
|
95
|
+
return validatorObj.classGroupId;
|
|
59
96
|
}
|
|
60
97
|
}
|
|
98
|
+
return undefined;
|
|
61
99
|
};
|
|
100
|
+
/**
|
|
101
|
+
* Get the class group ID for an arbitrary property.
|
|
102
|
+
*
|
|
103
|
+
* @param className - The class name to get the group ID for. Is expected to be string starting with `[` and ending with `]`.
|
|
104
|
+
*/
|
|
105
|
+
const getGroupIdForArbitraryProperty = className => className.slice(1, -1).indexOf(':') === -1 ? undefined : (() => {
|
|
106
|
+
const content = className.slice(1, -1);
|
|
107
|
+
const colonIndex = content.indexOf(':');
|
|
108
|
+
const property = content.slice(0, colonIndex);
|
|
109
|
+
return property ? ARBITRARY_PROPERTY_PREFIX + property : undefined;
|
|
110
|
+
})();
|
|
62
111
|
/**
|
|
63
112
|
* Exported for testing only
|
|
64
113
|
*/
|
|
@@ -67,54 +116,77 @@ const createClassMap = config => {
|
|
|
67
116
|
theme,
|
|
68
117
|
classGroups
|
|
69
118
|
} = config;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
119
|
+
return processClassGroups(classGroups, theme);
|
|
120
|
+
};
|
|
121
|
+
// Split into separate functions to maintain monomorphic call sites
|
|
122
|
+
const processClassGroups = (classGroups, theme) => {
|
|
123
|
+
const classMap = createClassPartObject();
|
|
74
124
|
for (const classGroupId in classGroups) {
|
|
75
|
-
|
|
125
|
+
const group = classGroups[classGroupId];
|
|
126
|
+
processClassesRecursively(group, classMap, classGroupId, theme);
|
|
76
127
|
}
|
|
77
128
|
return classMap;
|
|
78
129
|
};
|
|
79
130
|
const processClassesRecursively = (classGroup, classPartObject, classGroupId, theme) => {
|
|
80
|
-
classGroup.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
131
|
+
const len = classGroup.length;
|
|
132
|
+
for (let i = 0; i < len; i++) {
|
|
133
|
+
const classDefinition = classGroup[i];
|
|
134
|
+
processClassDefinition(classDefinition, classPartObject, classGroupId, theme);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
// Split into separate functions for each type to maintain monomorphic call sites
|
|
138
|
+
const processClassDefinition = (classDefinition, classPartObject, classGroupId, theme) => {
|
|
139
|
+
if (typeof classDefinition === 'string') {
|
|
140
|
+
processStringDefinition(classDefinition, classPartObject, classGroupId);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (typeof classDefinition === 'function') {
|
|
144
|
+
processFunctionDefinition(classDefinition, classPartObject, classGroupId, theme);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
processObjectDefinition(classDefinition, classPartObject, classGroupId, theme);
|
|
148
|
+
};
|
|
149
|
+
const processStringDefinition = (classDefinition, classPartObject, classGroupId) => {
|
|
150
|
+
const classPartObjectToEdit = classDefinition === '' ? classPartObject : getPart(classPartObject, classDefinition);
|
|
151
|
+
classPartObjectToEdit.classGroupId = classGroupId;
|
|
152
|
+
};
|
|
153
|
+
const processFunctionDefinition = (classDefinition, classPartObject, classGroupId, theme) => {
|
|
154
|
+
if (isThemeGetter(classDefinition)) {
|
|
155
|
+
processClassesRecursively(classDefinition(theme), classPartObject, classGroupId, theme);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (classPartObject.validators === null) {
|
|
159
|
+
classPartObject.validators = [];
|
|
160
|
+
}
|
|
161
|
+
classPartObject.validators.push(createClassValidatorObject(classGroupId, classDefinition));
|
|
162
|
+
};
|
|
163
|
+
const processObjectDefinition = (classDefinition, classPartObject, classGroupId, theme) => {
|
|
164
|
+
const entries = Object.entries(classDefinition);
|
|
165
|
+
const len = entries.length;
|
|
166
|
+
for (let i = 0; i < len; i++) {
|
|
167
|
+
const [key, value] = entries[i];
|
|
168
|
+
processClassesRecursively(value, getPart(classPartObject, key), classGroupId, theme);
|
|
169
|
+
}
|
|
101
170
|
};
|
|
102
171
|
const getPart = (classPartObject, path) => {
|
|
103
|
-
let
|
|
104
|
-
path.split(CLASS_PART_SEPARATOR)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
172
|
+
let current = classPartObject;
|
|
173
|
+
const parts = path.split(CLASS_PART_SEPARATOR);
|
|
174
|
+
const len = parts.length;
|
|
175
|
+
for (let i = 0; i < len; i++) {
|
|
176
|
+
const part = parts[i];
|
|
177
|
+
let next = current.nextPart.get(part);
|
|
178
|
+
if (!next) {
|
|
179
|
+
next = createClassPartObject();
|
|
180
|
+
current.nextPart.set(part, next);
|
|
110
181
|
}
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
return
|
|
182
|
+
current = next;
|
|
183
|
+
}
|
|
184
|
+
return current;
|
|
114
185
|
};
|
|
115
|
-
|
|
186
|
+
// Type guard maintains monomorphic check
|
|
187
|
+
const isThemeGetter = func => 'isThemeGetter' in func && func.isThemeGetter === true;
|
|
116
188
|
|
|
117
|
-
// LRU cache
|
|
189
|
+
// LRU cache implementation using plain objects for simplicity
|
|
118
190
|
const createLruCache = maxCacheSize => {
|
|
119
191
|
if (maxCacheSize < 1) {
|
|
120
192
|
return {
|
|
@@ -123,31 +195,31 @@ const createLruCache = maxCacheSize => {
|
|
|
123
195
|
};
|
|
124
196
|
}
|
|
125
197
|
let cacheSize = 0;
|
|
126
|
-
let cache =
|
|
127
|
-
let previousCache =
|
|
198
|
+
let cache = Object.create(null);
|
|
199
|
+
let previousCache = Object.create(null);
|
|
128
200
|
const update = (key, value) => {
|
|
129
|
-
cache
|
|
201
|
+
cache[key] = value;
|
|
130
202
|
cacheSize++;
|
|
131
203
|
if (cacheSize > maxCacheSize) {
|
|
132
204
|
cacheSize = 0;
|
|
133
205
|
previousCache = cache;
|
|
134
|
-
cache =
|
|
206
|
+
cache = Object.create(null);
|
|
135
207
|
}
|
|
136
208
|
};
|
|
137
209
|
return {
|
|
138
210
|
get(key) {
|
|
139
|
-
let value = cache
|
|
211
|
+
let value = cache[key];
|
|
140
212
|
if (value !== undefined) {
|
|
141
213
|
return value;
|
|
142
214
|
}
|
|
143
|
-
if ((value = previousCache
|
|
215
|
+
if ((value = previousCache[key]) !== undefined) {
|
|
144
216
|
update(key, value);
|
|
145
217
|
return value;
|
|
146
218
|
}
|
|
147
219
|
},
|
|
148
220
|
set(key, value) {
|
|
149
|
-
if (cache
|
|
150
|
-
cache
|
|
221
|
+
if (key in cache) {
|
|
222
|
+
cache[key] = value;
|
|
151
223
|
} else {
|
|
152
224
|
update(key, value);
|
|
153
225
|
}
|
|
@@ -156,7 +228,15 @@ const createLruCache = maxCacheSize => {
|
|
|
156
228
|
};
|
|
157
229
|
const IMPORTANT_MODIFIER = '!';
|
|
158
230
|
const MODIFIER_SEPARATOR = ':';
|
|
159
|
-
const
|
|
231
|
+
const EMPTY_MODIFIERS = [];
|
|
232
|
+
// Pre-allocated result object shape for consistency
|
|
233
|
+
const createResultObject = (modifiers, hasImportantModifier, baseClassName, maybePostfixModifierPosition, isExternal) => ({
|
|
234
|
+
modifiers,
|
|
235
|
+
hasImportantModifier,
|
|
236
|
+
baseClassName,
|
|
237
|
+
maybePostfixModifierPosition,
|
|
238
|
+
isExternal
|
|
239
|
+
});
|
|
160
240
|
const createParseClassName = config => {
|
|
161
241
|
const {
|
|
162
242
|
prefix,
|
|
@@ -169,17 +249,19 @@ const createParseClassName = config => {
|
|
|
169
249
|
* @see https://github.com/tailwindlabs/tailwindcss/blob/v3.2.2/src/util/splitAtTopLevelOnly.js
|
|
170
250
|
*/
|
|
171
251
|
let parseClassName = className => {
|
|
252
|
+
// Use simple array with push for better performance
|
|
172
253
|
const modifiers = [];
|
|
173
254
|
let bracketDepth = 0;
|
|
174
255
|
let parenDepth = 0;
|
|
175
256
|
let modifierStart = 0;
|
|
176
257
|
let postfixModifierPosition;
|
|
177
|
-
|
|
178
|
-
|
|
258
|
+
const len = className.length;
|
|
259
|
+
for (let index = 0; index < len; index++) {
|
|
260
|
+
const currentCharacter = className[index];
|
|
179
261
|
if (bracketDepth === 0 && parenDepth === 0) {
|
|
180
262
|
if (currentCharacter === MODIFIER_SEPARATOR) {
|
|
181
263
|
modifiers.push(className.slice(modifierStart, index));
|
|
182
|
-
modifierStart = index +
|
|
264
|
+
modifierStart = index + 1;
|
|
183
265
|
continue;
|
|
184
266
|
}
|
|
185
267
|
if (currentCharacter === '/') {
|
|
@@ -187,37 +269,31 @@ const createParseClassName = config => {
|
|
|
187
269
|
continue;
|
|
188
270
|
}
|
|
189
271
|
}
|
|
190
|
-
if (currentCharacter === '[')
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
272
|
+
if (currentCharacter === '[') bracketDepth++;else if (currentCharacter === ']') bracketDepth--;else if (currentCharacter === '(') parenDepth++;else if (currentCharacter === ')') parenDepth--;
|
|
273
|
+
}
|
|
274
|
+
const baseClassNameWithImportantModifier = modifiers.length === 0 ? className : className.slice(modifierStart);
|
|
275
|
+
// Inline important modifier check
|
|
276
|
+
let baseClassName = baseClassNameWithImportantModifier;
|
|
277
|
+
let hasImportantModifier = false;
|
|
278
|
+
if (baseClassNameWithImportantModifier.endsWith(IMPORTANT_MODIFIER)) {
|
|
279
|
+
baseClassName = baseClassNameWithImportantModifier.slice(0, -1);
|
|
280
|
+
hasImportantModifier = true;
|
|
281
|
+
} else if (
|
|
282
|
+
/**
|
|
283
|
+
* In Tailwind CSS v3 the important modifier was at the start of the base class name. This is still supported for legacy reasons.
|
|
284
|
+
* @see https://github.com/dcastil/tailwind-merge/issues/513#issuecomment-2614029864
|
|
285
|
+
*/
|
|
286
|
+
baseClassNameWithImportantModifier.startsWith(IMPORTANT_MODIFIER)) {
|
|
287
|
+
baseClassName = baseClassNameWithImportantModifier.slice(1);
|
|
288
|
+
hasImportantModifier = true;
|
|
199
289
|
}
|
|
200
|
-
const baseClassNameWithImportantModifier = modifiers.length === 0 ? className : className.substring(modifierStart);
|
|
201
|
-
const baseClassName = stripImportantModifier(baseClassNameWithImportantModifier);
|
|
202
|
-
const hasImportantModifier = baseClassName !== baseClassNameWithImportantModifier;
|
|
203
290
|
const maybePostfixModifierPosition = postfixModifierPosition && postfixModifierPosition > modifierStart ? postfixModifierPosition - modifierStart : undefined;
|
|
204
|
-
return
|
|
205
|
-
modifiers,
|
|
206
|
-
hasImportantModifier,
|
|
207
|
-
baseClassName,
|
|
208
|
-
maybePostfixModifierPosition
|
|
209
|
-
};
|
|
291
|
+
return createResultObject(modifiers, hasImportantModifier, baseClassName, maybePostfixModifierPosition);
|
|
210
292
|
};
|
|
211
293
|
if (prefix) {
|
|
212
294
|
const fullPrefix = prefix + MODIFIER_SEPARATOR;
|
|
213
295
|
const parseClassNameOriginal = parseClassName;
|
|
214
|
-
parseClassName = className => className.startsWith(fullPrefix) ? parseClassNameOriginal(className.
|
|
215
|
-
isExternal: true,
|
|
216
|
-
modifiers: [],
|
|
217
|
-
hasImportantModifier: false,
|
|
218
|
-
baseClassName: className,
|
|
219
|
-
maybePostfixModifierPosition: undefined
|
|
220
|
-
};
|
|
296
|
+
parseClassName = className => className.startsWith(fullPrefix) ? parseClassNameOriginal(className.slice(fullPrefix.length)) : createResultObject(EMPTY_MODIFIERS, false, className, undefined, true);
|
|
221
297
|
}
|
|
222
298
|
if (experimentalParseClassName) {
|
|
223
299
|
const parseClassNameOriginal = parseClassName;
|
|
@@ -228,19 +304,6 @@ const createParseClassName = config => {
|
|
|
228
304
|
}
|
|
229
305
|
return parseClassName;
|
|
230
306
|
};
|
|
231
|
-
const stripImportantModifier = baseClassName => {
|
|
232
|
-
if (baseClassName.endsWith(IMPORTANT_MODIFIER)) {
|
|
233
|
-
return baseClassName.substring(0, baseClassName.length - 1);
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* In Tailwind CSS v3 the important modifier was at the start of the base class name. This is still supported for legacy reasons.
|
|
237
|
-
* @see https://github.com/dcastil/tailwind-merge/issues/513#issuecomment-2614029864
|
|
238
|
-
*/
|
|
239
|
-
if (baseClassName.startsWith(IMPORTANT_MODIFIER)) {
|
|
240
|
-
return baseClassName.substring(1);
|
|
241
|
-
}
|
|
242
|
-
return baseClassName;
|
|
243
|
-
};
|
|
244
307
|
|
|
245
308
|
/**
|
|
246
309
|
* Sorts modifiers according to following schema:
|
|
@@ -248,26 +311,41 @@ const stripImportantModifier = baseClassName => {
|
|
|
248
311
|
* - When an arbitrary variant appears, it must be preserved which modifiers are before and after it
|
|
249
312
|
*/
|
|
250
313
|
const createSortModifiers = config => {
|
|
251
|
-
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
314
|
+
// Pre-compute weights for all known modifiers for O(1) comparison
|
|
315
|
+
const modifierWeights = new Map();
|
|
316
|
+
// Assign weights to sensitive modifiers (highest priority, but preserve order)
|
|
317
|
+
config.orderSensitiveModifiers.forEach((mod, index) => {
|
|
318
|
+
modifierWeights.set(mod, 1000000 + index); // High weights for sensitive mods
|
|
319
|
+
});
|
|
320
|
+
return modifiers => {
|
|
321
|
+
const result = [];
|
|
322
|
+
let currentSegment = [];
|
|
323
|
+
// Process modifiers in one pass
|
|
324
|
+
for (let i = 0; i < modifiers.length; i++) {
|
|
325
|
+
const modifier = modifiers[i];
|
|
326
|
+
// Check if modifier is sensitive (starts with '[' or in orderSensitiveModifiers)
|
|
327
|
+
const isArbitrary = modifier[0] === '[';
|
|
328
|
+
const isOrderSensitive = modifierWeights.has(modifier);
|
|
329
|
+
if (isArbitrary || isOrderSensitive) {
|
|
330
|
+
// Sort and flush current segment alphabetically
|
|
331
|
+
if (currentSegment.length > 0) {
|
|
332
|
+
currentSegment.sort();
|
|
333
|
+
result.push(...currentSegment);
|
|
334
|
+
currentSegment = [];
|
|
335
|
+
}
|
|
336
|
+
result.push(modifier);
|
|
263
337
|
} else {
|
|
264
|
-
|
|
338
|
+
// Regular modifier - add to current segment for batch sorting
|
|
339
|
+
currentSegment.push(modifier);
|
|
265
340
|
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
|
|
341
|
+
}
|
|
342
|
+
// Sort and add any remaining segment items
|
|
343
|
+
if (currentSegment.length > 0) {
|
|
344
|
+
currentSegment.sort();
|
|
345
|
+
result.push(...currentSegment);
|
|
346
|
+
}
|
|
347
|
+
return result;
|
|
269
348
|
};
|
|
270
|
-
return sortModifiers;
|
|
271
349
|
};
|
|
272
350
|
const createConfigUtils = config => ({
|
|
273
351
|
cache: createLruCache(config.cacheSize),
|
|
@@ -322,10 +400,11 @@ const mergeClassList = (classList, configUtils) => {
|
|
|
322
400
|
}
|
|
323
401
|
hasPostfixModifier = false;
|
|
324
402
|
}
|
|
325
|
-
|
|
403
|
+
// Fast path: skip sorting for empty or single modifier
|
|
404
|
+
const variantModifier = modifiers.length === 0 ? '' : modifiers.length === 1 ? modifiers[0] : sortModifiers(modifiers).join(':');
|
|
326
405
|
const modifierId = hasImportantModifier ? variantModifier + IMPORTANT_MODIFIER : variantModifier;
|
|
327
406
|
const classId = modifierId + classGroupId;
|
|
328
|
-
if (classGroupsInConflict.
|
|
407
|
+
if (classGroupsInConflict.indexOf(classId) > -1) {
|
|
329
408
|
// Tailwind class omitted due to conflict
|
|
330
409
|
continue;
|
|
331
410
|
}
|
|
@@ -350,13 +429,13 @@ const mergeClassList = (classList, configUtils) => {
|
|
|
350
429
|
*
|
|
351
430
|
* Original code has MIT license: Copyright (c) Luke Edwards <luke.edwards05@gmail.com> (lukeed.com)
|
|
352
431
|
*/
|
|
353
|
-
|
|
432
|
+
const twJoin = (...classLists) => {
|
|
354
433
|
let index = 0;
|
|
355
434
|
let argument;
|
|
356
435
|
let resolvedValue;
|
|
357
436
|
let string = '';
|
|
358
|
-
while (index <
|
|
359
|
-
if (argument =
|
|
437
|
+
while (index < classLists.length) {
|
|
438
|
+
if (argument = classLists[index++]) {
|
|
360
439
|
if (resolvedValue = toValue(argument)) {
|
|
361
440
|
string && (string += ' ');
|
|
362
441
|
string += resolvedValue;
|
|
@@ -364,8 +443,9 @@ function twJoin() {
|
|
|
364
443
|
}
|
|
365
444
|
}
|
|
366
445
|
return string;
|
|
367
|
-
}
|
|
446
|
+
};
|
|
368
447
|
const toValue = mix => {
|
|
448
|
+
// Fast path for strings
|
|
369
449
|
if (typeof mix === 'string') {
|
|
370
450
|
return mix;
|
|
371
451
|
}
|
|
@@ -381,20 +461,20 @@ const toValue = mix => {
|
|
|
381
461
|
}
|
|
382
462
|
return string;
|
|
383
463
|
};
|
|
384
|
-
|
|
464
|
+
const createTailwindMerge = (createConfigFirst, ...createConfigRest) => {
|
|
385
465
|
let configUtils;
|
|
386
466
|
let cacheGet;
|
|
387
467
|
let cacheSet;
|
|
388
|
-
let functionToCall
|
|
389
|
-
|
|
468
|
+
let functionToCall;
|
|
469
|
+
const initTailwindMerge = classList => {
|
|
390
470
|
const config = createConfigRest.reduce((previousConfig, createConfigCurrent) => createConfigCurrent(previousConfig), createConfigFirst());
|
|
391
471
|
configUtils = createConfigUtils(config);
|
|
392
472
|
cacheGet = configUtils.cache.get;
|
|
393
473
|
cacheSet = configUtils.cache.set;
|
|
394
474
|
functionToCall = tailwindMerge;
|
|
395
475
|
return tailwindMerge(classList);
|
|
396
|
-
}
|
|
397
|
-
|
|
476
|
+
};
|
|
477
|
+
const tailwindMerge = classList => {
|
|
398
478
|
const cachedResult = cacheGet(classList);
|
|
399
479
|
if (cachedResult) {
|
|
400
480
|
return cachedResult;
|
|
@@ -402,13 +482,13 @@ function createTailwindMerge(createConfigFirst, ...createConfigRest) {
|
|
|
402
482
|
const result = mergeClassList(classList, configUtils);
|
|
403
483
|
cacheSet(classList, result);
|
|
404
484
|
return result;
|
|
405
|
-
}
|
|
406
|
-
return function callTailwindMerge() {
|
|
407
|
-
return functionToCall(twJoin.apply(null, arguments));
|
|
408
485
|
};
|
|
409
|
-
|
|
486
|
+
functionToCall = initTailwindMerge;
|
|
487
|
+
return (...args) => functionToCall(twJoin(...args));
|
|
488
|
+
};
|
|
489
|
+
const fallbackThemeArr = [];
|
|
410
490
|
const fromTheme = key => {
|
|
411
|
-
const themeGetter = theme => theme[key] ||
|
|
491
|
+
const themeGetter = theme => theme[key] || fallbackThemeArr;
|
|
412
492
|
themeGetter.isThemeGetter = true;
|
|
413
493
|
return themeGetter;
|
|
414
494
|
};
|