const UPPERCASE = /[\p{Lu}]/u; const LOWERCASE = /[\p{Ll}]/u; const LEADING_CAPITAL = /^[\p{Lu}](?![\p{Lu}])/gu; const IDENTIFIER = /([\p{Alpha}\p{N}_]|$)/u; const SEPARATORS = /[_.\- ]+/; const LEADING_SEPARATORS = new RegExp("^" + SEPARATORS.source); const SEPARATORS_AND_IDENTIFIER = new RegExp( SEPARATORS.source + IDENTIFIER.source, "gu" ); const NUMBERS_AND_IDENTIFIER = new RegExp("\\d+" + IDENTIFIER.source, "gu"); const preserveCamelCase = ( string, toLowerCase, toUpperCase, preserveConsecutiveUppercase ) => { let isLastCharLower = false; let isLastCharUpper = false; let isLastLastCharUpper = false; let isLastLastCharPreserved = false; for (let index = 0; index < string.length; index++) { const character = string[index]; isLastLastCharPreserved = index > 2 ? string[index - 3] === "-" : true; if (isLastCharLower && UPPERCASE.test(character)) { string = string.slice(0, index) + "-" + string.slice(index); isLastCharLower = false; isLastLastCharUpper = isLastCharUpper; isLastCharUpper = true; index++; } else if ( isLastCharUpper && isLastLastCharUpper && LOWERCASE.test(character) && (!isLastLastCharPreserved || preserveConsecutiveUppercase) ) { string = string.slice(0, index - 1) + "-" + string.slice(index - 1); isLastLastCharUpper = isLastCharUpper; isLastCharUpper = false; isLastCharLower = true; } else { isLastCharLower = toLowerCase(character) === character && toUpperCase(character) !== character; isLastLastCharUpper = isLastCharUpper; isLastCharUpper = toUpperCase(character) === character && toLowerCase(character) !== character; } } return string; }; const preserveConsecutiveUppercase = (input, toLowerCase) => { LEADING_CAPITAL.lastIndex = 0; return input.replace(LEADING_CAPITAL, (m1) => toLowerCase(m1)); }; const postProcess = (input, toUpperCase) => { SEPARATORS_AND_IDENTIFIER.lastIndex = 0; NUMBERS_AND_IDENTIFIER.lastIndex = 0; return input .replace(SEPARATORS_AND_IDENTIFIER, (_, identifier) => toUpperCase(identifier) ) .replace(NUMBERS_AND_IDENTIFIER, (m) => toUpperCase(m)); }; function camelCase(input, options) { if (!(typeof input === "string" || Array.isArray(input))) { throw new TypeError("Expected the input to be `string | string[]`"); } options = { pascalCase: true, preserveConsecutiveUppercase: false, ...options, }; if (Array.isArray(input)) { input = input .map((x) => x.trim()) .filter((x) => x.length) .join("-"); } else { input = input.trim(); } if (input.length === 0) { return ""; } const toLowerCase = options.locale === false ? (string) => string.toLowerCase() : (string) => string.toLocaleLowerCase(options.locale); const toUpperCase = options.locale === false ? (string) => string.toUpperCase() : (string) => string.toLocaleUpperCase(options.locale); if (input.length === 1) { if (SEPARATORS.test(input)) { return ""; } return options.pascalCase ? toUpperCase(input) : toLowerCase(input); } const hasUpperCase = input !== toLowerCase(input); if (hasUpperCase) { input = preserveCamelCase( input, toLowerCase, toUpperCase, options.preserveConsecutiveUppercase ); } input = input.replace(LEADING_SEPARATORS, ""); input = options.preserveConsecutiveUppercase ? preserveConsecutiveUppercase(input, toLowerCase) : toLowerCase(input); if (options.pascalCase) { input = toUpperCase(input.charAt(0)) + input.slice(1); } return postProcess(input, toUpperCase); } module.exports = { camelCase, };