UNPKG

@razorpay/blade

Version:

The Design System that powers Razorpay

658 lines (613 loc) 22.7 kB
import _objectWithoutProperties from '@babel/runtime/helpers/objectWithoutProperties'; import dayjs from 'dayjs'; import customParseFormat from 'dayjs/plugin/customParseFormat'; var _excluded = ["formatter"]; dayjs.extend(customParseFormat); var dayjs_locales = ['af', 'ar', 'ar-dz', 'ar-kw', 'ar-ly', 'ar-ma', 'ar-sa', 'ar-tn', 'az', 'be', 'bg', 'bm', 'bn', 'bo', 'br', 'bs', 'ca', 'cs', 'cv', 'cy', 'da', 'de', 'de-at', 'de-ch', 'dv', 'el', 'en', 'en-au', 'en-ca', 'en-gb', 'en-ie', 'en-il', 'en-nz', 'en-SG', 'eo', 'es', 'es-do', 'es-us', 'et', 'eu', 'fa', 'fi', 'fo', 'fr', 'fr-ca', 'fr-ch', 'fy', 'ga', 'gd', 'gl', 'gom-latn', 'gu', 'he', 'hi', 'hr', 'hu', 'hy-am', 'id', 'is', 'it', 'it-ch', 'ja', 'jv', 'ka', 'kk', 'km', 'kn', 'ko', 'ku', 'ky', 'lb', 'lo', 'lt', 'lv', 'me', 'mi', 'mk', 'ml', 'mn', 'mr', 'ms', 'ms-my', 'mt', 'my', 'nb', 'ne', 'nl', 'nl-be', 'nn', 'oc-lnc', 'pa-in', 'pl', 'pt', 'pt-br', 'ro', 'ru', 'sd', 'se', 'si', 'sk', 'sl', 'sq', 'sr', 'sr-cyrl', 'ss', 'sv', 'sw', 'ta', 'te', 'tet', 'tg', 'th', 'tl-ph', 'tlh', 'tr', 'tzl', 'tzm', 'tzm-latn', 'ug-cn', 'uk', 'ur', 'uz', 'uz-latn', 'vi', 'x-pseudo', 'yo', 'zh-cn', 'zh-hk', 'zh-tw']; var MONTH_NAMES = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; // https://github.com/iamkun/dayjs/issues/732#issuecomment-554383261 function patchLocale(locale) { if (['en', 'en-us'].includes(locale)) return 'en'; if (locale === 'zn') return 'zh-cn'; if (locale === 'no') return 'nb'; if (dayjs_locales.includes(locale)) return locale; return false; } function convertIntlToDayjsLocale(lang) { lang = lang.toLowerCase(); var locale = patchLocale(lang) || lang.includes('-') && patchLocale(lang.split('-')[0]); if (!locale) return 'en'; return "".concat(locale); } function isScriptLoaded(src) { return Boolean(document.querySelector("script[src=\"".concat(src, "\"]"))); } /** * Used to dynamically load a script */ function loadScript(src, callback) { if (isScriptLoaded(src)) { callback === null || callback === void 0 || callback(); return; } var localeScript = document.createElement('script'); localeScript.async = true; localeScript.src = src; localeScript.onload = function () { return callback === null || callback === void 0 ? void 0 : callback(); }; document.head.appendChild(localeScript); } function defaultDateFormatter(_ref) { var type = _ref.type, date = _ref.date, locale = _ref.locale, format = _ref.format, labelSeparator = _ref.labelSeparator; var formatDate = function formatDate(value) { return dayjs(value).locale(locale).format(format); }; if (type === 'default') { return date === null ? '' : formatDate(date); } if (type === 'range' && Array.isArray(date)) { if (date[0] && date[1]) { return "".concat(formatDate(date[0]), " ").concat(labelSeparator, " ").concat(formatDate(date[1])); } if (date[0]) { return "".concat(formatDate(date[0]), " ").concat(labelSeparator, " "); } return ''; } return ''; } function getFormattedDate(_ref2) { var formatter = _ref2.formatter, others = _objectWithoutProperties(_ref2, _excluded); return (formatter || defaultDateFormatter)(others); } var rangeFormattedValue = function rangeFormattedValue(startValue, endValue) { // Both values exist if (startValue && endValue) { if (startValue === endValue) { return startValue; // Same date, show single value } return "".concat(startValue, " \u2013 ").concat(endValue); // Different dates, show range } // Only start value exists if (startValue) { return "".concat(startValue, " \u2013 "); } // Only end value exists if (endValue) { return " \u2013 ".concat(endValue); } // No values return ''; }; var rangeInputPlaceHolder = function rangeInputPlaceHolder(placeholder, format) { if (placeholder) { return "".concat(placeholder, " \u2013 ").concat(placeholder); } return "".concat(format, " \u2013 ").concat(format); }; var finalInputFormat = function finalInputFormat(startValue, endValue, format) { // For case: when start and end are the same, return the format if (startValue === endValue && (startValue || endValue)) { return format; } return "".concat(format, " \u2013 ").concat(format); }; /** * Converts date format string to TextInput pattern for masking. * TextInput format only recognizes # as input placeholder, so we replace * date characters (Y,M,D) with # while preserving delimiters. * * @example "DD/MM/YYYY" – "##/##/####" * @example "MMMM" – "################" (longest month: "September") * @example "MMM" – "###" */ var getTextInputFormat = function getTextInputFormat(formatStr, isRangeInput) { if (!formatStr) { return isRangeInput ? '##/##/#### – ##/##/####' : '##/##/####'; } else if (formatStr === 'MMMM') { return formatStr.replace(/MMMM/g, '#########'); } return formatStr.replace(/[YMD]/g, '#'); }; /** * Validates date string using DayJS strict parsing and business rules * * @param dateStr - The date string to validate in DD/MM/YYYY format * @returns Object with validation result, error message if invalid, and parsed Date if valid * * @example * // Valid date * validateDateComponents("25/12/2024") * // → { isValid: true, parsedDate: Date(2024, 11, 25) } * * @example * // Invalid date (non-existent) * validateDateComponents("31/02/2024") * // → { isValid: false, error: "Please enter a valid date" } * * @example * // Invalid year range * validateDateComponents("25/12/999") * // → { isValid: false, error: "Year must be between 1000 and 3000" } * * @example * // Empty input * validateDateComponents("") * // → { isValid: true } */ var validateDateComponents = function validateDateComponents(dateStr) { // Empty strings are considered valid (user hasn't started typing) if (!(dateStr !== null && dateStr !== void 0 && dateStr.trim())) { return { isValid: true }; } // Use DayJS strict parsing to validate DD/MM/YYYY format and date existence var parsed = dayjs(dateStr, 'DD/MM/YYYY', true); // DayJS strict mode catches invalid days/months and non-existent dates (e.g., Feb 30th) if (!parsed.isValid()) { return { isValid: false, error: 'Please enter a valid date' }; } // Business rule: restrict year range to reasonable values for most applications var year = parsed.year(); if (year < 1000 || year > 3000) { return { isValid: false, error: 'Year must be between 1000 and 3000' }; } // Return both validation result AND parsed date to avoid double parsing return { isValid: true, parsedDate: parsed.toDate() }; }; /** * Detects and parses special single date formats (MMM, MMMM, YYYY) * These formats preserve current date/month/year and only change the specified component * * @param inputValue - The input string to parse * @param format - The expected format ('MMM', 'MMMM', or 'YYYY') * @returns Object with special format detection, parsed date, and validation result * * @example * // Year format - preserves current month/day, changes year * parseSpecialSingleFormat("2024", "YYYY") * // → { isSpecialFormat: true, parsedDate: Date(2024, currentMonth, currentDay), shouldBlock: false } * * @example * // Short month format - preserves current day/year, changes month * parseSpecialSingleFormat("Aug", "MMM") * // → { isSpecialFormat: true, parsedDate: Date(currentYear, 7, currentDay), shouldBlock: false } * * @example * // Full month format - preserves current day/year, changes month * parseSpecialSingleFormat("August", "MMMM") * // → { isSpecialFormat: true, parsedDate: Date(currentYear, 7, currentDay), shouldBlock: false } * * @example * // Invalid year * parseSpecialSingleFormat("999", "YYYY") * // → { isSpecialFormat: true, shouldBlock: true } * * @example * // Not a special format * parseSpecialSingleFormat("25/12/2024", "DD/MM/YYYY") * // → { isSpecialFormat: false, shouldBlock: false } */ var parseSpecialSingleFormat = function parseSpecialSingleFormat(inputValue, format) { if (!format) return { isSpecialFormat: false, shouldBlock: false }; var trimmedInput = inputValue.trim(); var today = new Date(); // Handle year-only format (YYYY) - keep current month and day, change year if (format === 'YYYY' && /^\d{4}$/.test(trimmedInput)) { var year = parseInt(trimmedInput, 10); if (year >= 1000 && year <= 3000) { return { isSpecialFormat: true, parsedDate: new Date(year, today.getMonth(), today.getDate()), shouldBlock: false }; } else { return { isSpecialFormat: true, shouldBlock: true }; } } // Handle month formats (MMM, MMMM) - keep current day and year, change month if ((format === 'MMM' || format === 'MMMM') && trimmedInput.length >= 3) { // Using DayJS to parse month names (handles both short "Aug" and full "August") var monthDate = dayjs(trimmedInput, format === 'MMM' ? 'MMM' : 'MMMM', true); if (!monthDate.isValid()) { return { isSpecialFormat: true, shouldBlock: true }; } return { isSpecialFormat: true, parsedDate: new Date(today.getFullYear(), monthDate.month(), today.getDate()), shouldBlock: false }; } return { isSpecialFormat: false, shouldBlock: false }; }; /** * Validates and parses date input with comprehensive error handling and constraint checking * Combines validation + parsing to avoid redundant operations and supports both single dates and ranges * * @param inputValue - The input string to validate and parse * @param isRange - Whether this is a range input (true) or single date (false) * @param format - The expected date format ('DD/MM/YYYY', 'MMM', 'MMMM', 'YYYY') * @param options - Additional validation constraints * @param options.excludeDate - Function to exclude specific dates (single dates only) * @param options.minDate - Minimum allowed date * @param options.maxDate - Maximum allowed date * @returns Object with shouldBlock flag, optional error message, and parsed values if valid * * @example * // Valid single date * validateAndParseDateInput("25/12/2024", false, "DD/MM/YYYY") * // → { shouldBlock: false, parsedValue: Date(2024, 11, 25) } * * @example * // Valid range input * validateAndParseDateInput("25/12/2024 – 31/12/2024", true, "DD/MM/YYYY") * // → { shouldBlock: false, parsedValue: [Date(2024, 11, 25), Date(2024, 11, 31)] } * * @example * // Invalid date with error * validateAndParseDateInput("31/02/2024", false, "DD/MM/YYYY") * // → { shouldBlock: true, error: "Please enter a valid date in DD/MM/YYYY format" } * * @example * // Date excluded by constraint * validateAndParseDateInput("25/12/2024", false, "DD/MM/YYYY", { * excludeDate: (date) => date.getDay() === 0 // No Sundays * }) * // → { shouldBlock: true, error: "This date is not available for selection" } * * @example * // Special format - year only * validateAndParseDateInput("2024", false, "YYYY") * // → { shouldBlock: false, parsedValue: Date(2024, currentMonth, currentDay) } * * @example * // Incomplete input - allow continued typing * validateAndParseDateInput("25/12", false, "DD/MM/YYYY") * // → { shouldBlock: false } (no parsedValue, allows user to continue typing) */ var validateAndParseDateInput = function validateAndParseDateInput(inputValue, isRange, format, options) { // Allow empty input - user hasn't started typing yet if (!(inputValue !== null && inputValue !== void 0 && inputValue.trim())) return { shouldBlock: false }; // Handle special single date formats (MMM, MMMM, YYYY) - skip normal validation if (!isRange && format) { var specialParse = parseSpecialSingleFormat(inputValue, format); if (specialParse.isSpecialFormat && !specialParse.shouldBlock) { return { shouldBlock: false, parsedValue: specialParse.parsedDate || null }; } else if (specialParse.shouldBlock) { return { shouldBlock: true, error: 'Please enter a valid date' }; } } if (isRange) { var _parts$, _parts$2, _parts$5, _parts$6; // Split range input on en dash separator (e.g., "25/12/2024 – 31/12/2024") var parts = inputValue.split(/\s*–\s*/); // Enhanced: Block incomplete input with partial validation if ((_parts$ = parts[0]) !== null && _parts$ !== void 0 && _parts$.trim() && parts[0].trim().length < 10 || (_parts$2 = parts[1]) !== null && _parts$2 !== void 0 && _parts$2.trim() && parts[1].trim().length < 10) { var _parts$3, _parts$4; // Check start date part if it exists and has content if ((_parts$3 = parts[0]) !== null && _parts$3 !== void 0 && _parts$3.trim()) { var startPartialValidation = validatePartialDateSimple(parts[0].trim()); if (!startPartialValidation.isValid) { return { shouldBlock: true, error: "Start date: ".concat(startPartialValidation.error) }; } } // Check end date part if it exists and has content if ((_parts$4 = parts[1]) !== null && _parts$4 !== void 0 && _parts$4.trim()) { var endPartialValidation = validatePartialDateSimple(parts[1].trim()); if (!endPartialValidation.isValid) { return { shouldBlock: true, error: "End date: ".concat(endPartialValidation.error) }; } } // If partial validation passes, allow continued typing return { shouldBlock: false }; } var startDate = null; var endDate = null; // Validate and parse start date if it looks complete (10+ chars = DD/MM/YYYY) if ((_parts$5 = parts[0]) !== null && _parts$5 !== void 0 && _parts$5.trim() && parts[0].trim().length >= 10) { var startValidation = validateDateComponents(parts[0].trim()); if (!startValidation.isValid) { return { shouldBlock: true, error: "Start date: ".concat(startValidation.error) }; } // Use the already-parsed date from validation to avoid double parsing startDate = startValidation.parsedDate || null; } // Validate and parse end date if it looks complete if ((_parts$6 = parts[1]) !== null && _parts$6 !== void 0 && _parts$6.trim() && parts[1].trim().length >= 10) { var endValidation = validateDateComponents(parts[1].trim()); if (!endValidation.isValid) { return { shouldBlock: true, error: "End date: ".concat(endValidation.error) }; } // Use the already-parsed date from validation to avoid double parsing endDate = endValidation.parsedDate || null; } // Business logic: start date cannot be after end date if (startDate && endDate && startDate > endDate) { return { shouldBlock: true, error: 'Start date cannot be greater than end date' }; } // Additional validation for date range constraints if (options) { if (startDate && options.minDate && dayjs(startDate).isBefore(dayjs(options.minDate))) { return { shouldBlock: true, error: 'Start date is before the minimum allowed date' }; } if (startDate && options.maxDate && dayjs(startDate).isAfter(dayjs(options.maxDate))) { return { shouldBlock: true, error: 'Start date is after the maximum allowed date' }; } if (endDate && options.minDate && dayjs(endDate).isBefore(dayjs(options.minDate))) { return { shouldBlock: true, error: 'End date is before the minimum allowed date' }; } if (endDate && options.maxDate && dayjs(endDate).isAfter(dayjs(options.maxDate))) { return { shouldBlock: true, error: 'End date is after the maximum allowed date' }; } } // Return parsed dates ready for setControlledValue return { shouldBlock: false, parsedValue: [startDate, endDate] }; } else if (inputValue.length >= 10) { // Single date: validate and parse if it looks complete var validation = validateDateComponents(inputValue); if (!validation.isValid) { return { shouldBlock: true, error: validation.error }; } var parsedDate = validation.parsedDate; // Additional validation for single date constraints if (options && parsedDate) { var _options$excludeDate; if ((_options$excludeDate = options.excludeDate) !== null && _options$excludeDate !== void 0 && _options$excludeDate.call(options, parsedDate)) { return { shouldBlock: true, error: 'This date is not available for selection' }; } if (options.minDate && dayjs(parsedDate).isBefore(dayjs(options.minDate))) { return { shouldBlock: true, error: 'Date is before the minimum allowed date' }; } if (options.maxDate && dayjs(parsedDate).isAfter(dayjs(options.maxDate))) { return { shouldBlock: true, error: 'Date is after the maximum allowed date' }; } } // Return parsed date ready for setControlledValue return { shouldBlock: false, parsedValue: parsedDate || null }; } else { // Partial validation for incomplete single date var partialValidation = validatePartialDateSimple(inputValue); if (!partialValidation.isValid) { return { shouldBlock: true, error: partialValidation.error }; } // If partial validation passes, allow continued typing return { shouldBlock: false }; } // This line should not be reached, but keeping for safety return { shouldBlock: false }; }; /** * Simple partial date validation using / splitting * Since special formats (MMM, MMMM, YYYY) are handled before this function * * @param input - The partial date input to validate * @returns Object with validation result and optional error message * * @example * // Valid partial input * validatePartialDateSimple("15") * // → { isValid: true } * * @example * // Invalid day * validatePartialDateSimple("35") * // → { isValid: false, error: "Day cannot be greater than 31" } * * @example * // Invalid month * validatePartialDateSimple("15/13") * // → { isValid: false, error: "Month cannot be greater than 12" } * * @example * // Invalid day-month combination * validatePartialDateSimple("30/02") * // → { isValid: false, error: "February cannot have more than 29 days" } */ function validatePartialDateSimple(input) { var _parts$7, _parts$8; // Don't validate empty input - let user start typing if (!(input !== null && input !== void 0 && input.trim())) { return { isValid: true }; } // Split by / to get day, month parts var parts = input.split('/'); // Validate day part (first part) if (((_parts$7 = parts[0]) === null || _parts$7 === void 0 ? void 0 : _parts$7.length) >= 2) { var dayStr = parts[0].trim(); if (dayStr.length > 0) { var day = parseInt(dayStr, 10); // Day must be valid number if (isNaN(day)) { return { isValid: false, error: 'Please enter a valid day (01-31)' }; } // Day cannot be greater than 31 if (day > 31) { return { isValid: false, error: 'Day cannot be greater than 31' }; } // Day cannot be 00 if (day === 0) { return { isValid: false, error: 'Day cannot be 00, please enter 01-31' }; } } } // Validate month part (second part) if (((_parts$8 = parts[1]) === null || _parts$8 === void 0 ? void 0 : _parts$8.length) >= 2) { var monthStr = parts[1].trim(); if (monthStr.length > 0) { var _parts$9; var month = parseInt(monthStr, 10); // Month must be valid number if (isNaN(month)) { return { isValid: false, error: 'Please enter a valid month (01-12)' }; } // Month cannot be greater than 12 if (month > 12) { return { isValid: false, error: 'Month cannot be greater than 12' }; } // Month cannot be 00 if (month === 0) { return { isValid: false, error: 'Month cannot be 00, please enter 01-12' }; } // Basic day-month validation (no DayJS needed) var _day = (_parts$9 = parts[0]) !== null && _parts$9 !== void 0 && _parts$9.trim() ? parseInt(parts[0].trim(), 10) : NaN; if (!isNaN(_day)) { // February cannot have more than 29 days if (month === 2 && _day > 29) { return { isValid: false, error: 'February cannot have more than 29 days' }; } // Months with 30 days: April(4), June(6), September(9), November(11) if ([4, 6, 9, 11].includes(month) && _day > 30) { return { isValid: false, error: "".concat(MONTH_NAMES[month - 1], " cannot have more than 30 days") }; } } } } // If we get here, partial input is valid return { isValid: true }; } /** * Removes date delimiters (slashes) from formatted date strings for internal processing * Used to convert external formatted values to internal state for easier validation and length checks * * @param str - The date string with delimiters * @returns String without delimiters, or empty string if input is null/undefined * * @example * // Remove slashes from single date * stripDelimiters("25/12/2024") * // → "25122024" * * @example * // Handle null/undefined input * stripDelimiters(undefined) * // → "" * * @example * // Used for internal state conversion * stripDelimiters("31/12/2024") * // → "31122024" (easier for length checks: inputValue.length === 8) */ var stripDelimiters = function stripDelimiters(str) { var _str$replace; return (_str$replace = str === null || str === void 0 ? void 0 : str.replace(/\//g, '')) !== null && _str$replace !== void 0 ? _str$replace : ''; }; export { convertIntlToDayjsLocale, finalInputFormat, getFormattedDate, getTextInputFormat, loadScript, rangeFormattedValue, rangeInputPlaceHolder, stripDelimiters, validateAndParseDateInput, validateDateComponents, validatePartialDateSimple }; //# sourceMappingURL=utils.js.map