@base-ui/react
Version:
Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.
74 lines (71 loc) • 3.05 kB
JavaScript
const OTP_VALIDATION_CONFIG = {
numeric: {
slotPattern: '\\d{1}',
getRootPattern: length => `\\d{${length}}`,
regexp: /[^\d]/g,
inputMode: 'numeric'
},
alpha: {
slotPattern: '[a-zA-Z]{1}',
getRootPattern: length => `[a-zA-Z]{${length}}`,
regexp: /[^a-zA-Z]/g,
inputMode: 'text'
},
alphanumeric: {
slotPattern: '[a-zA-Z0-9]{1}',
getRootPattern: length => `[a-zA-Z0-9]{${length}}`,
regexp: /[^a-zA-Z0-9]/g,
inputMode: 'text'
}
};
export function getOTPValidationConfig(validationType) {
if (validationType === 'none') {
return null;
}
return OTP_VALIDATION_CONFIG[validationType];
}
export function stripOTPWhitespace(value) {
return (value ?? '').replace(/\s/g, '');
}
function applyOTPValidation(value, validation) {
return validation ? value.replace(validation.regexp, '') : value;
}
/**
* Normalizes user-entered OTP text by stripping whitespace, applying validation and custom
* normalization, and clamping the final value to the configured slot count.
*/
export function normalizeOTPValueWithDetails(value, length, validationType, normalizeValue) {
const strippedValue = stripOTPWhitespace(value);
const validation = getOTPValidationConfig(validationType);
let normalizedValue = applyOTPValidation(strippedValue, validation);
let didRejectCharacters = strippedValue.length > normalizedValue.length;
if (normalizeValue) {
const customNormalizedValue = normalizeValue(normalizedValue);
didRejectCharacters ||= normalizedValue.length > customNormalizedValue.length;
normalizedValue = applyOTPValidation(customNormalizedValue, validation);
didRejectCharacters ||= customNormalizedValue.length > normalizedValue.length;
}
// Slice by Unicode code points so multi-byte characters do not split across OTP slots.
const maxLength = length < 0 ? 0 : length;
const normalizedCharacters = Array.from(normalizedValue);
return [normalizedCharacters.slice(0, maxLength).join(''), didRejectCharacters || normalizedCharacters.length > maxLength];
}
export function normalizeOTPValue(value, length, validationType, normalizeValue) {
return normalizeOTPValueWithDetails(value, length, validationType, normalizeValue)[0];
}
/**
* Replaces characters starting at the provided slot index, then re-normalizes the final OTP value
* so paste and multi-character edits stay contiguous and length-bounded.
*/
export function replaceOTPValue(currentValue, index, nextValue, length, validationType, normalizeValue) {
const normalizedValue = normalizeOTPValue(nextValue, length, validationType, normalizeValue);
const prefix = currentValue.slice(0, index);
const suffix = currentValue.slice(index + normalizedValue.length);
return normalizeOTPValue(`${prefix}${normalizedValue}${suffix}`, length, validationType, normalizeValue);
}
export function removeOTPCharacter(currentValue, index) {
if (index < 0 || index >= currentValue.length) {
return currentValue;
}
return `${currentValue.slice(0, index)}${currentValue.slice(index + 1)}`;
}