/** * Concatenates two arrays faster than the array spread operator. */ const concatArrays = (array1, array2) => { // Pre-allocate for better V8 optimization const combinedArray = new Array(array1.length + array2.length); for (let i = 0; i < array1.length; i++) { combinedArray[i] = array1[i]; } for (let i = 0; i < array2.length; i++) { combinedArray[array1.length + i] = array2[i]; } return combinedArray; }; // Factory function ensures consistent object shapes const createClassValidatorObject = (classGroupId, validator) => ({ classGroupId, validator }); // Factory ensures consistent ClassPartObject shape const createClassPartObject = (nextPart = new Map(), validators = null, classGroupId) => ({ nextPart, validators, classGroupId }); const CLASS_PART_SEPARATOR = '-'; const EMPTY_CONFLICTS = []; // I use two dots here because one dot is used as prefix for class groups in plugins const ARBITRARY_PROPERTY_PREFIX = 'arbitrary..'; const createClassGroupUtils = config => { const classMap = createClassMap(config); const { conflictingClassGroups, conflictingClassGroupModifiers } = config; const getClassGroupId = className => { if (className.startsWith('[') && className.endsWith(']')) { return getGroupIdForArbitraryProperty(className); } const classParts = className.split(CLASS_PART_SEPARATOR); // Classes like `-inset-1` produce an empty string as first classPart. We assume that classes for negative values are used correctly and skip it. const startIndex = classParts[0] === '' && classParts.length > 1 ? 1 : 0; return getGroupRecursive(classParts, startIndex, classMap); }; const getConflictingClassGroupIds = (classGroupId, hasPostfixModifier) => { if (hasPostfixModifier) { const modifierConflicts = conflictingClassGroupModifiers[classGroupId]; const baseConflicts = conflictingClassGroups[classGroupId]; if (modifierConflicts) { if (baseConflicts) { // Merge base conflicts with modifier conflicts return concatArrays(baseConflicts, modifierConflicts); } // Only modifier conflicts return modifierConflicts; } // Fall back to without postfix if no modifier conflicts return baseConflicts || EMPTY_CONFLICTS; } return conflictingClassGroups[classGroupId] || EMPTY_CONFLICTS; }; return { getClassGroupId, getConflictingClassGroupIds }; }; const getGroupRecursive = (classParts, startIndex, classPartObject) => { const classPathsLength = classParts.length - startIndex; if (classPathsLength === 0) { return classPartObject.classGroupId; } const currentClassPart = classParts[startIndex]; const nextClassPartObject = classPartObject.nextPart.get(currentClassPart); if (nextClassPartObject) { const result = getGroupRecursive(classParts, startIndex + 1, nextClassPartObject); if (result) return result; } const validators = classPartObject.validators; if (validators === null) { return undefined; } // Build classRest string efficiently by joining from startIndex onwards const classRest = startIndex === 0 ? classParts.join(CLASS_PART_SEPARATOR) : classParts.slice(startIndex).join(CLASS_PART_SEPARATOR); const validatorsLength = validators.length; for (let i = 0; i < validatorsLength; i++) { const validatorObj = validators[i]; if (validatorObj.validator(classRest)) { return validatorObj.classGroupId; } } return undefined; }; /** * Get the class group ID for an arbitrary property. * * @param className - The class name to get the group ID for. Is expected to be string starting with `[` and ending with `]`. */ const getGroupIdForArbitraryProperty = className => className.slice(1, -1).indexOf(':') === -1 ? undefined : (() => { const content = className.slice(1, -1); const colonIndex = content.indexOf(':'); const property = content.slice(0, colonIndex); return property ? ARBITRARY_PROPERTY_PREFIX + property : undefined; })(); /** * Exported for testing only */ const createClassMap = config => { const { theme, classGroups } = config; return processClassGroups(classGroups, theme); }; // Split into separate functions to maintain monomorphic call sites const processClassGroups = (classGroups, theme) => { const classMap = createClassPartObject(); for (const classGroupId in classGroups) { const group = classGroups[classGroupId]; processClassesRecursively(group, classMap, classGroupId, theme); } return classMap; }; const processClassesRecursively = (classGroup, classPartObject, classGroupId, theme) => { const len = classGroup.length; for (let i = 0; i < len; i++) { const classDefinition = classGroup[i]; processClassDefinition(classDefinition, classPartObject, classGroupId, theme); } }; // Split into separate functions for each type to maintain monomorphic call sites const processClassDefinition = (classDefinition, classPartObject, classGroupId, theme) => { if (typeof classDefinition === 'string') { processStringDefinition(classDefinition, classPartObject, classGroupId); return; } if (typeof classDefinition === 'function') { processFunctionDefinition(classDefinition, classPartObject, classGroupId, theme); return; } processObjectDefinition(classDefinition, classPartObject, classGroupId, theme); }; const processStringDefinition = (classDefinition, classPartObject, classGroupId) => { const classPartObjectToEdit = classDefinition === '' ? classPartObject : getPart(classPartObject, classDefinition); classPartObjectToEdit.classGroupId = classGroupId; }; const processFunctionDefinition = (classDefinition, classPartObject, classGroupId, theme) => { if (isThemeGetter(classDefinition)) { processClassesRecursively(classDefinition(theme), classPartObject, classGroupId, theme); return; } if (classPartObject.validators === null) { classPartObject.validators = []; } classPartObject.validators.push(createClassValidatorObject(classGroupId, classDefinition)); }; const processObjectDefinition = (classDefinition, classPartObject, classGroupId, theme) => { const entries = Object.entries(classDefinition); const len = entries.length; for (let i = 0; i < len; i++) { const [key, value] = entries[i]; processClassesRecursively(value, getPart(classPartObject, key), classGroupId, theme); } }; const getPart = (classPartObject, path) => { let current = classPartObject; const parts = path.split(CLASS_PART_SEPARATOR); const len = parts.length; for (let i = 0; i < len; i++) { const part = parts[i]; let next = current.nextPart.get(part); if (!next) { next = createClassPartObject(); current.nextPart.set(part, next); } current = next; } return current; }; // Type guard maintains monomorphic check const isThemeGetter = func => 'isThemeGetter' in func && func.isThemeGetter === true; // LRU cache implementation using plain objects for simplicity const createLruCache = maxCacheSize => { if (maxCacheSize < 1) { return { get: () => undefined, set: () => {} }; } let cacheSize = 0; let cache = Object.create(null); let previousCache = Object.create(null); const update = (key, value) => { cache[key] = value; cacheSize++; if (cacheSize > maxCacheSize) { cacheSize = 0; previousCache = cache; cache = Object.create(null); } }; return { get(key) { let value = cache[key]; if (value !== undefined) { return value; } if ((value = previousCache[key]) !== undefined) { update(key, value); return value; } }, set(key, value) { if (key in cache) { cache[key] = value; } else { update(key, value); } } }; }; const IMPORTANT_MODIFIER = '!'; const MODIFIER_SEPARATOR = ':'; const EMPTY_MODIFIERS = []; // Pre-allocated result object shape for consistency const createResultObject = (modifiers, hasImportantModifier, baseClassName, maybePostfixModifierPosition, isExternal) => ({ modifiers, hasImportantModifier, baseClassName, maybePostfixModifierPosition, isExternal }); const createParseClassName = config => { 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 => { // Use simple array with push for better performance const modifiers = []; let bracketDepth = 0; let parenDepth = 0; let modifierStart = 0; let postfixModifierPosition; 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 => className.startsWith(fullPrefix) ? parseClassNameOriginal(className.slice(fullPrefix.length)) : createResultObject(EMPTY_MODIFIERS, false, className, undefined, true); } if (experimentalParseClassName) { const parseClassNameOriginal = parseClassName; parseClassName = className => experimentalParseClassName({ className, parseClassName: parseClassNameOriginal }); } return parseClassName; }; /** * 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 */ const createSortModifiers = config => { // Pre-compute weights for all known modifiers for O(1) comparison const modifierWeights = new Map(); // Assign weights to sensitive modifiers (highest priority, but preserve order) config.orderSensitiveModifiers.forEach((mod, index) => { modifierWeights.set(mod, 1000000 + index); // High weights for sensitive mods }); return modifiers => { const result = []; let currentSegment = []; // Process modifiers in one pass for (let i = 0; i < modifiers.length; i++) { const modifier = modifiers[i]; // Check if modifier is sensitive (starts with '[' or in orderSensitiveModifiers) const isArbitrary = modifier[0] === '['; const isOrderSensitive = modifierWeights.has(modifier); if (isArbitrary || isOrderSensitive) { // Sort and flush current segment alphabetically if (currentSegment.length > 0) { currentSegment.sort(); result.push(...currentSegment); currentSegment = []; } result.push(modifier); } else { // Regular modifier - add to current segment for batch sorting currentSegment.push(modifier); } } // Sort and add any remaining segment items if (currentSegment.length > 0) { currentSegment.sort(); result.push(...currentSegment); } return result; }; }; const createConfigUtils = config => ({ cache: createLruCache(config.cacheSize), parseClassName: createParseClassName(config), sortModifiers: createSortModifiers(config), postfixLookupClassGroupIds: createPostfixLookupClassGroupIds(config), ...createClassGroupUtils(config) }); const createPostfixLookupClassGroupIds = config => { const lookup = Object.create(null); const classGroupIds = config.postfixLookupClassGroups; if (classGroupIds) { for (let i = 0; i < classGroupIds.length; i++) { lookup[classGroupIds[i]] = true; } } return lookup; }; const SPLIT_CLASSES_REGEX = /\s+/; const mergeClassList = (classList, configUtils) => { const { parseClassName, getClassGroupId, getConflictingClassGroupIds, sortModifiers, postfixLookupClassGroupIds } = configUtils; /** * Set of classGroupIds in following format: * `{importantModifier}{variantModifiers}{classGroupId}` * @example 'float' * @example 'hover:focus:bg-color' * @example 'md:!pr' */ const classGroupsInConflict = []; const classNames = classList.trim().split(SPLIT_CLASSES_REGEX); let result = ''; for (let index = classNames.length - 1; index >= 0; index -= 1) { const originalClassName = classNames[index]; const { isExternal, modifiers, hasImportantModifier, baseClassName, maybePostfixModifierPosition } = parseClassName(originalClassName); if (isExternal) { result = originalClassName + (result.length > 0 ? ' ' + result : result); continue; } let hasPostfixModifier = !!maybePostfixModifierPosition; let classGroupId; if (hasPostfixModifier) { const baseClassNameWithoutPostfix = baseClassName.substring(0, maybePostfixModifierPosition); classGroupId = getClassGroupId(baseClassNameWithoutPostfix); const classGroupIdWithPostfix = classGroupId && postfixLookupClassGroupIds[classGroupId] ? getClassGroupId(baseClassName) : undefined; if (classGroupIdWithPostfix && classGroupIdWithPostfix !== classGroupId) { classGroupId = classGroupIdWithPostfix; hasPostfixModifier = false; } } else { classGroupId = getClassGroupId(baseClassName); } if (!classGroupId) { if (!hasPostfixModifier) { // Not a Tailwind class result = originalClassName + (result.length > 0 ? ' ' + result : result); continue; } classGroupId = getClassGroupId(baseClassName); if (!classGroupId) { // Not a Tailwind class result = originalClassName + (result.length > 0 ? ' ' + result : result); continue; } hasPostfixModifier = false; } // Fast path: skip sorting for empty or single modifier const variantModifier = modifiers.length === 0 ? '' : modifiers.length === 1 ? modifiers[0] : sortModifiers(modifiers).join(':'); const modifierId = hasImportantModifier ? variantModifier + IMPORTANT_MODIFIER : variantModifier; const classId = modifierId + classGroupId; if (classGroupsInConflict.indexOf(classId) > -1) { // Tailwind class omitted due to conflict continue; } classGroupsInConflict.push(classId); const conflictGroups = getConflictingClassGroupIds(classGroupId, hasPostfixModifier); for (let i = 0; i < conflictGroups.length; ++i) { const group = conflictGroups[i]; classGroupsInConflict.push(modifierId + group); } // Tailwind class not in conflict result = originalClassName + (result.length > 0 ? ' ' + result : result); } return result; }; /** * Observes every successful class-map lookup needed to merge a single candidate. Build-time pruning needs intermediate base matches as well as a final slash match, or removing a base matcher can change runtime precedence. * Reuse the merge engine rather than reproducing its decisions. Instrumentation stays in this tooling-only entry point and adds no work to normal merging. */ const createClassGroupLookup = config => { const configUtils = createConfigUtils(config); return className => { const matches = []; mergeClassList(className, { ...configUtils, getClassGroupId(lookupName) { const classGroupId = configUtils.getClassGroupId(lookupName); if (classGroupId) { matches.push({ className: lookupName, classGroupId }); } return classGroupId; } }); return matches; }; }; export { createClassGroupLookup, createClassGroupUtils, createParseClassName }; //# sourceMappingURL=unstable-mjs.mjs.map