@codecademy/gamut
72.5.072.5.1-alpha.7ca70c.0
dist/DatePicker/DatePickerInput/utils/validation.js+
dist/DatePicker/DatePickerInput/utils/validation.jsNew file+149
Index: package/dist/DatePicker/DatePickerInput/utils/validation.js
===================================================================
--- package/dist/DatePicker/DatePickerInput/utils/validation.js
+++ package/dist/DatePicker/DatePickerInput/utils/validation.js
@@ -0,0 +1,149 @@
+import { rangeContainsDisabled } from '../../DatePickerCalendar/utils/dateSelect';
+import { parseSegmentsToDate } from '../Segment/utils';
+/**
+ * Detects the specific reason why a date is invalid.
+ * Assumes segments are "complete enough" (all three fields have at least some input).
+ */
+function detectValidationIssue(segments) {
+ const {
+ month,
+ day,
+ year
+ } = segments;
+
+ // Parse to numbers for validation
+ const monthNumber = parseInt(month, 10);
+ const dayNumber = parseInt(day, 10);
+ const yearNumber = parseInt(year, 10);
+
+ // Check month validity
+ if (monthNumber < 1 || monthNumber > 12) return 'invalid-month';
+
+ // Try to create the date - if it fails, it's either invalid day or date rollover
+ const parsed = new Date(yearNumber, monthNumber - 1, dayNumber);
+ if (parsed.getFullYear() !== yearNumber || parsed.getMonth() !== monthNumber - 1) {
+ return 'date-rollover';
+ }
+ if (parsed.getDate() !== dayNumber) {
+ return 'invalid-day';
+ }
+ return null;
+}
+
+/**
+ * Generates a human-readable error message based on the validation issue.
+ */
+export function generateErrorMessage(reason, segments, translations) {
+ const {
+ month,
+ day
+ } = segments;
+ const monthNumber = parseInt(month, 10);
+ switch (reason) {
+ case 'incomplete':
+ return translations.invalidDateIncomplete || 'Incomplete date';
+ case 'invalid-month':
+ return translations.invalidDateInvalidMonth || 'Month must be between 1 and 12';
+ case 'invalid-day':
+ {
+ const monthName = new Date(2024, monthNumber - 1, 1).toLocaleString('en-US', {
+ month: 'long'
+ });
+ const message = translations.invalidDateInvalidDay || '{{month}} does not have {{day}} days';
+ return message.replace('{{month}}', monthName).replace('{{day}}', day);
+ }
+ case 'date-rollover':
+ {
+ const monthName = new Date(2024, monthNumber - 1, 1).toLocaleString('en-US', {
+ month: 'long'
+ });
+ const message = translations.invalidDateRollover || '{{month}} does not have {{day}} days';
+ return message.replace('{{month}}', monthName).replace('{{day}}', day);
+ }
+ case 'disabled-date':
+ return translations.invalidDateNotAvailable || 'This date is not available';
+ case 'range-contains-disabled-date':
+ return translations.invalidDateRangeContainsDisabledDate || 'This date range contains unavailable dates';
+ default:
+ return 'Invalid date';
+ }
+}
+
+/**
+ * Validates a set of date segments and returns either a valid date or an error message.
+ * Requires: year to be exactly 4 digits, month and day to have digits and parse to valid numbers.
+ * Checks for structural validity (date exists), disabled dates, and generates specific error messages.
+ */
+export function validateSegments(segments, translations, disableDate) {
+ const {
+ month,
+ day,
+ year
+ } = segments;
+
+ // Require complete year (4 digits), month, and day to have input
+ const hasCompleteYear = year.length === 4;
+ const hasMonthAndDay = month.length > 0 && day.length > 0;
+ if (!hasCompleteYear || !hasMonthAndDay) {
+ // Incomplete entry - return as valid (no error), but with no date
+ return {
+ isValid: false,
+ errorMessage: '',
+ reason: 'incomplete'
+ };
+ }
+
+ // Entry is complete (year has 4 digits, month and day have input) - validate it
+ const parsed = parseSegmentsToDate(segments);
+ if (parsed) {
+ // Date is structurally valid, now check if it's disabled
+ if (disableDate?.(parsed)) {
+ return {
+ isValid: false,
+ errorMessage: generateErrorMessage('disabled-date', segments, translations),
+ reason: 'disabled-date'
+ };
+ }
+ // Date is valid and not disabled
+ return {
+ isValid: true,
+ date: parsed
+ };
+ }
+
+ // Date is not valid - detect specific reason (must be one since entry is complete)
+ const reason = detectValidationIssue(segments) || 'date-rollover';
+ const errorMessage = generateErrorMessage(reason, segments, translations);
+ return {
+ isValid: false,
+ errorMessage,
+ reason
+ };
+}
+
+/**
+ * Validates a committed date range to ensure no dates within it are disabled.
+ * Returns null when the range is complete and clear (or incomplete), or a
+ * `range-contains-disabled-date` result when any date in the span is disabled.
+ * Delegates the span walk to `rangeContainsDisabled` (the same check the
+ * calendar uses) so typed and clicked ranges stay consistent.
+ */
+export function validateDateRange(startDate, endDate, translations, disableDate) {
+ if (!startDate || !endDate || !disableDate) return null;
+ if (rangeContainsDisabled({
+ startDate,
+ endDate,
+ disableDate
+ })) {
+ return {
+ isValid: false,
+ errorMessage: generateErrorMessage('range-contains-disabled-date', {
+ month: '',
+ day: '',
+ year: ''
+ }, translations),
+ reason: 'range-contains-disabled-date'
+ };
+ }
+ return null;
+}
\ No newline at end of file