@codecademy/gamut
72.5.072.5.1-alpha.7ca70c.0
+
Added (2 files)
~
Modified (8 files)
Index: package/dist/DatePicker/DatePickerCalendar/Calendar/CalendarBody.js
===================================================================
--- package/dist/DatePicker/DatePickerCalendar/Calendar/CalendarBody.js
+++ package/dist/DatePicker/DatePickerCalendar/Calendar/CalendarBody.js
@@ -125,8 +125,9 @@
children: [/*#__PURE__*/_jsx("thead", {
children: /*#__PURE__*/_jsx("tr", {
children: weekdayLabels.map((label, i) => /*#__PURE__*/_jsx(TableHeader, {
abbr: weekdayFullNames[i],
+ "aria-label": weekdayFullNames[i],
scope: "col",
children: label
}, label))
}) Index: package/dist/DatePicker/DatePicker.js
===================================================================
--- package/dist/DatePicker/DatePicker.js
+++ package/dist/DatePicker/DatePicker.js
@@ -24,8 +24,9 @@
const [isCalendarOpen, setIsCalendarOpen] = useState(false);
const [focusGridSignal, setFocusGridSignal] = useState(false);
const [gridFocusRequested, setGridFocusRequested] = useState(false);
const [activeRangePart, setActiveRangePart] = useState(null);
+ const [hasError, setHasError] = useState(false);
const inputRef = useRef(null);
const dialogId = useId();
const calendarDialogId = `datepicker-dialog-${dialogId.replace(/:/g, '')}`;
const isRtl = useElementDir() === 'rtl';
@@ -75,9 +76,11 @@
closeCalendar,
locale: resolvedLocale,
disableDate,
translations,
- quickActions: quickActions === null ? [] : resolvedQuickActions
+ quickActions: quickActions === null ? [] : resolvedQuickActions,
+ hasError,
+ setHasError
};
return mode === 'range' ? {
...base,
mode: 'range',
@@ -94,9 +97,9 @@
mode: 'single',
selectedDate: props.selectedDate,
onSelection: props.onSelected
};
- }, [translationsProp, quickActions, mode, resolvedLocale, isCalendarOpen, openCalendar, focusCalendar, focusGridSignal, gridFocusRequested, clearGridFocusRequest, closeCalendar, disableDate, props, activeRangePart]);
+ }, [translationsProp, quickActions, mode, resolvedLocale, isCalendarOpen, openCalendar, focusCalendar, focusGridSignal, gridFocusRequested, clearGridFocusRequest, closeCalendar, disableDate, props, activeRangePart, hasError, setHasError]);
const content = children !== undefined ? children : /*#__PURE__*/_jsxs(_Fragment, {
children: [/*#__PURE__*/_jsx(FlexBox, {
gap: inputSize === 'small' ? 4 : 8,
ref: inputRef,
@@ -129,9 +132,9 @@
invertAxis: "x",
isOpen: isCalendarOpen,
targetRef: inputRef,
x: -20,
- y: -16,
+ y: isCalendarOpen && hasError ? 0 : -16,
onRequestClose: closeCalendar,
children: /*#__PURE__*/_jsx("div", {
"aria-label": contextValue.translations.calendarDialogAriaLabel,
id: calendarDialogId, Index: package/dist/DatePicker/DatePickerInput/index.js
===================================================================
--- package/dist/DatePicker/DatePickerInput/index.js
+++ package/dist/DatePicker/DatePickerInput/index.js
@@ -1,7 +1,11 @@
+import _styled from "@emotion/styled/base";
import { MiniCalendarIcon } from '@codecademy/gamut-icons';
+import { css } from '@codecademy/gamut-styles';
import { forwardRef, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
import { FlexBox } from '../../Box';
+import { IconButton } from '../../Button';
+import { FormError } from '../../Form/elements/FormError';
import { FormGroup } from '../../Form/elements/FormGroup';
import { isSameDay } from '../DatePickerCalendar/Calendar/utils/dateGrid';
import { handleDateSelectRange } from '../DatePickerCalendar/utils/dateSelect';
import { useDatePicker } from '../DatePickerContext';
@@ -9,12 +13,27 @@
import { DatePickerInputSegment } from './Segment';
import { SegmentLiteral } from './Segment/elements';
import { getDateSegmentsFromDate, normalizeSegmentValues, parseSegmentsToDate } from './Segment/utils';
import { formatDateISO8601DateOnly, getDateFieldOrder, getDateFormatLayout } from './utils';
+import { validateDateRange, validateSegments } from './utils/validation';
+
+/* FormError's `absolute` variant takes the message out of layout flow (anchored
+ to FormGroup's position: relative container), so the input never changes height:
+ the range-mode arrow stays aligned and no space is reserved when there's no error. */
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
+const AbsoluteFormError = /*#__PURE__*/_styled(FormError, {
+ target: "ev4y6f30",
+ label: "AbsoluteFormError"
+})(css({
+ // max-content keeps the message on one line rather than wrapping into the
+ // narrow input width.
+ width: 'max-content',
+ // The `absolute` variant anchors at top: calc(100% - 8px), which overlaps the
+ // bottom of the input. Nudge it down below the input with a small gap.
+ mt: 12
+}), process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9EYXRlUGlja2VyL0RhdGVQaWNrZXJJbnB1dC9pbmRleC50c3giXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBMEMwQiIsImZpbGUiOiIuLi8uLi8uLi9zcmMvRGF0ZVBpY2tlci9EYXRlUGlja2VySW5wdXQvaW5kZXgudHN4Iiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgTWluaUNhbGVuZGFySWNvbiB9IGZyb20gJ0Bjb2RlY2FkZW15L2dhbXV0LWljb25zJztcbmltcG9ydCB7IGNzcyB9IGZyb20gJ0Bjb2RlY2FkZW15L2dhbXV0LXN0eWxlcyc7XG5pbXBvcnQgc3R5bGVkIGZyb20gJ0BlbW90aW9uL3N0eWxlZCc7XG5pbXBvcnQge1xuICB0eXBlIEZvY3VzRXZlbnQsXG4gIGZvcndhcmRSZWYsXG4gIHVzZUNhbGxiYWNrLFxuICB1c2VFZmZlY3QsXG4gIHVzZUlkLFxuICB1c2VNZW1vLFxuICB1c2VSZWYsXG4gIHVzZVN0YXRlLFxufSBmcm9tICdyZWFjdCc7XG5cbmltcG9ydCB7IEZsZXhCb3ggfSBmcm9tICcuLi8uLi9Cb3gnO1xuaW1wb3J0IHsgSWNvbkJ1dHRvbiB9IGZyb20gJy4uLy4uL0J1dHRvbic7XG5pbXBvcnQgeyBGb3JtRXJyb3IgfSBmcm9tICcuLi8uLi9Gb3JtL2VsZW1lbnRzL0Zvcm1FcnJvcic7XG5pbXBvcnQgeyBGb3JtR3JvdXAgfSBmcm9tICcuLi8uLi9Gb3JtL2VsZW1lbnRzL0Zvcm1Hcm91cCc7XG5pbXBvcnQgdHlwZSB7IElucHV0V3JhcHBlclByb3BzIH0gZnJvbSAnLi4vLi4vRm9ybS9pbnB1dHMvSW5wdXQnO1xuaW1wb3J0IHsgaXNTYW1lRGF5IH0gZnJvbSAnLi4vRGF0ZVBpY2tlckNhbGVuZGFyL0NhbGVuZGFyL3V0aWxzL2RhdGVHcmlkJztcbmltcG9ydCB7IGhhbmRsZURhdGVTZWxlY3RSYW5nZSB9IGZyb20gJy4uL0RhdGVQaWNrZXJDYWxlbmRhci91dGlscy9kYXRlU2VsZWN0JztcbmltcG9ydCB7IHVzZURhdGVQaWNrZXIgfSBmcm9tICcuLi9EYXRlUGlja2VyQ29udGV4dCc7XG5pbXBvcnQgeyBTZWdtZW50ZWRTaGVsbCB9IGZyb20gJy4vZWxlbWVudHMnO1xuaW1wb3J0IHsgRGF0ZVBpY2tlcklucHV0U2VnbWVudCB9IGZyb20gJy4vU2VnbWVudCc7XG5pbXBvcnQgeyBTZWdtZW50TGl0ZXJhbCB9IGZyb20gJy4vU2VnbWVudC9lbGVtZW50cyc7XG5pbXBvcnQge1xuICB0eXBlIFNlZ21lbnRWYWx1ZXMsXG4gIGdldERhdGVTZWdtZW50c0Zyb21EYXRlLFxuICBub3JtYWxpemVTZWdtZW50VmFsdWVzLFxuICBwYXJzZVNlZ21lbnRzVG9EYXRlLFxufSBmcm9tICcuL1NlZ21lbnQvdXRpbHMnO1xuaW1wb3J0IHtcbiAgdHlwZSBEYXRlUGFydEtpbmQsXG4gIGZvcm1hdERhdGVJU084NjAxRGF0ZU9ubHksXG4gIGdldERhdGVGaWVsZE9yZGVyLFxuICBnZXREYXRlRm9ybWF0TGF5b3V0LFxufSBmcm9tICcuL3V0aWxzJztcbmltcG9ydCB7IHZhbGlkYXRlRGF0ZVJhbmdlLCB2YWxpZGF0ZVNlZ21lbnRzIH0gZnJvbSAnLi91dGlscy92YWxpZGF0aW9uJztcblxuLyogRm9ybUVycm9yJ3MgYGFic29sdXRlYCB2YXJpYW50IHRha2VzIHRoZSBtZXNzYWdlIG91dCBvZiBsYXlvdXQgZmxvdyAoYW5jaG9yZWRcbiAgIHRvIEZvcm1Hcm91cCdzIHBvc2l0aW9uOiByZWxhdGl2ZSBjb250YWluZXIpLCBzbyB0aGUgaW5wdXQgbmV2ZXIgY2hhbmdlcyBoZWlnaHQ6XG4gICB0aGUgcmFuZ2UtbW9kZSBhcnJvdyBzdGF5cyBhbGlnbmVkIGFuZCBubyBzcGFjZSBpcyByZXNlcnZlZCB3aGVuIHRoZXJlJ3Mgbm8gZXJyb3IuICovXG5jb25zdCBBYnNvbHV0ZUZvcm1FcnJvciA9IHN0eWxlZChGb3JtRXJyb3IpKFxuICBjc3Moe1xuICAgIC8vIG1heC1jb250ZW50IGtlZXBzIHRoZSBtZXNzYWdlIG9uIG9uZSBsaW5lIHJhdGhlciB0aGFuIHdyYXBwaW5nIGludG8gdGhlXG4gICAgLy8gbmFycm93IGlucHV0IHdpZHRoLlxuICAgIHdpZHRoOiAnbWF4LWNvbnRlbnQnLFxuICAgIC8vIFRoZSBgYWJzb2x1dGVgIHZhcmlhbnQgYW5jaG9ycyBhdCB0b3A6IGNhbGMoMTAwJSAtIDhweCksIHdoaWNoIG92ZXJsYXBzIHRoZVxuICAgIC8vIGJvdHRvbSBvZiB0aGUgaW5wdXQuIE51ZGdlIGl0IGRvd24gYmVsb3cgdGhlIGlucHV0IHdpdGggYSBzbWFsbCBnYXAuXG4gICAgbXQ6IDEyLFxuICB9KVxuKTtcblxuZXhwb3J0IHR5cGUgRGF0ZVBpY2tlcklucHV0UHJvcHMgPSBPbWl0PFxuICBJbnB1dFdyYXBwZXJQcm9wcyxcbiAgJ2NsYXNzTmFtZScgfCAndHlwZScgfCAnaWNvbicgfCAndmFsdWUnIHwgJ29uQ2hhbmdlJyB8ICdjb2xvcicgfCAnZXJyb3InXG4+ICYge1xuICAvKiogSW4gcmFuZ2UgbW9kZTogd2hpY2ggcGFydCBvZiB0aGUgcmFuZ2UgdGhpcyBpbnB1dCBlZGl0cy4gT21pdCBmb3Igc2luZ2xlLWRhdGUgb3IgY29tYmluZWQgZGlzcGxheS4gKi9cbiAgcmFuZ2VQYXJ0PzogJ3N0YXJ0JyB8ICdlbmQnO1xufTtcblxuZXhwb3J0IGNvbnN0IERhdGVQaWNrZXJJbnB1dCA9IGZvcndhcmRSZWY8SFRNTERpdkVsZW1lbnQsIERhdGVQaWNrZXJJbnB1dFByb3BzPihcbiAgKHsgZGlzYWJsZWQsIGZvcm0sIGxhYmVsLCBuYW1lLCByYW5nZVBhcnQsIHNpemUgPSAnYmFzZScsIC4uLnJlc3QgfSwgcmVmKSA9PiB7XG4gICAgY29uc3QgY29udGV4dCA9IHVzZURhdGVQaWNrZXIoKTtcblxuICAgIGlmIChjb250ZXh0ID09PSBudWxsKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICdEYXRlUGlja2VySW5wdXQgbXVzdCBiZSB1c2VkIGluc2lkZSBhIERhdGVQaWNrZXIgKGl0IHJlYWRzIHNoYXJlZCBzdGF0ZSBmcm9tIGNvbnRleHQpLidcbiAgICAgICk7XG4gICAgfVxuXG4gICAgY29uc3Qge1xuICAgICAgbW9kZSxcbiAgICAgIG9wZW5DYWxlbmRhcixcbiAgICAgIGZvY3VzQ2FsZW5kYXIsXG4gICAgICBsb2NhbGUsXG4gICAgICBpc0NhbGVuZGFyT3BlbixcbiAgICAgIHRyYW5zbGF0aW9ucyxcbiAgICAgIGRpc2FibGVEYXRlLFxuICAgICAgc2V0SGFzRXJyb3IsXG4gICAgfSA9IGNvbnRleHQ7XG5cbiAgICBjb25zdCBpc1JhbmdlID0gbW9kZSA9PT0gJ3JhbmdlJztcbiAgICBjb25zdCBlbmREYXRlID0gaXNSYW5nZSA/IGNvbnRleHQuZW5kRGF0ZSA6IG51bGw7XG4gICAgY29uc3QgZGF0ZSA9IGlzUmFuZ2UgPyBjb250ZXh0LnN0YXJ0RGF0ZSA6IGNvbnRleHQuc2VsZWN0ZWREYXRlO1xuXG4gICAgY29uc3QgaW5wdXRJRCA9IHVzZUlkKCk7XG4gICAgY29uc3QgaW5wdXRJZCA9IGBkYXRlcGlja2VyLWlucHV0LSR7aW5wdXRJRC5yZXBsYWNlKC86L2csICcnKX1gO1xuXG4gICAgY29uc3QgeyBsYXlvdXQsIGZpZWxkT3JkZXIgfSA9IHVzZU1lbW8oKCkgPT4ge1xuICAgICAgY29uc3QgbGF5b3V0ID0gZ2V0RGF0ZUZvcm1hdExheW91dChsb2NhbGUpO1xuICAgICAgcmV0dXJuIHsgbGF5b3V0LCBmaWVsZE9yZGVyOiBnZXREYXRlRmllbGRPcmRlcihsYXlvdXQpIH07XG4gICAgfSwgW2xvY2FsZV0pO1xuXG4gICAgY29uc3QgZGVmYXVsdExhYmVsID0gIWlzUmFuZ2VcbiAgICAgID8gdHJhbnNsYXRpb25zLmRhdGVMYWJlbFxuICAgICAgOiByYW5nZVBhcnQgPT09ICdlbmQnXG4gICAgICA/IHRyYW5zbGF0aW9ucy5lbmREYXRlTGFiZWxcbiAgICAgIDogdHJhbnNsYXRpb25zLnN0YXJ0RGF0ZUxhYmVsO1xuXG4gICAgY29uc3QgYm91bmREYXRlID0gaXNSYW5nZSAmJiByYW5nZVBhcnQgPT09ICdlbmQnID8gZW5kRGF0ZSA6IGRhdGU7XG4gICAgY29uc3Qgc2VnbWVudHNGcm9tQm91bmQgPSB1c2VNZW1vKFxuICAgICAgKCkgPT4gZ2V0RGF0ZVNlZ21lbnRzRnJvbURhdGUoYm91bmREYXRlKSxcbiAgICAgIFtib3VuZERhdGVdXG4gICAgKTtcbiAgICBjb25zdCBbc2VnbWVudHMsIHNldFNlZ21lbnRzXSA9IHVzZVN0YXRlPFNlZ21lbnRWYWx1ZXM+KHNlZ21lbnRzRnJvbUJvdW5kKTtcbiAgICBjb25zdCBbaW5wdXRFcnJvciwgc2V0SW5wdXRFcnJvcl0gPSB1c2VTdGF0ZTxzdHJpbmc+KCcnKTtcblxuICAgIGNvbnN0IHBhcnNlZEZvckhpZGRlbiA9IHBhcnNlU2VnbWVudHNUb0RhdGUoc2VnbWVudHMpO1xuICAgIGNvbnN0IGhpZGRlblZhbHVlID0gcGFyc2VkRm9ySGlkZGVuXG4gICAgICA/IGZvcm1hdERhdGVJU084NjAxRGF0ZU9ubHkocGFyc2VkRm9ySGlkZGVuKVxuICAgICAgOiAnJztcblxuICAgIGNvbnN0IGlzSW5wdXRGb2N1c2VkUmVmID0gdXNlUmVmKGZhbHNlKTtcbiAgICBjb25zdCBjb250YWluZXJSZWYgPSB1c2VSZWY8SFRNTERpdkVsZW1lbnQgfCBudWxsPihudWxsKTtcbiAgICBjb25zdCBidXR0b25SZWYgPSB1c2VSZWY8SFRNTEJ1dHRvbkVsZW1lbnQ+KG51bGwpO1xuICAgIGNvbnN0IHNlZ21lbnRFbFJlZnMgPSB1c2VSZWY8XG4gICAgICBQYXJ0aWFsPFJlY29yZDxEYXRlUGFydEtpbmQsIEhUTUxTcGFuRWxlbWVudCB8IG51bGw+PlxuICAgID4oe30pO1xuXG4gICAgY29uc3QgYXNzaWduU2VnbWVudFJlZiA9IHVzZUNhbGxiYWNrKFxuICAgICAgKGZpZWxkOiBEYXRlUGFydEtpbmQsIGVsOiBIVE1MU3BhbkVsZW1lbnQgfCBudWxsKSA9PiB7XG4gICAgICAgIHNlZ21lbnRFbFJlZnMuY3VycmVudFtmaWVsZF0gPSBlbDtcbiAgICAgIH0sXG4gICAgICBbc2VnbWVudEVsUmVmc11cbiAgICApO1xuXG4gICAgY29uc3Qgb25TaWJsaW5nU2VnbWVudEZvY3VzID0gdXNlQ2FsbGJhY2soXG4gICAgICAoZmllbGQ6IERhdGVQYXJ0S2luZCkgPT4ge1xuICAgICAgICBzZWdtZW50RWxSZWZzLmN1cnJlbnRbZmllbGRdPy5mb2N1cygpO1xuICAgICAgfSxcbiAgICAgIFtzZWdtZW50RWxSZWZzXVxuICAgICk7XG5cbiAgICBjb25zdCBzaGVsbFJlZiA9IHVzZUNhbGxiYWNrKFxuICAgICAgKGVsOiBIVE1MRGl2RWxlbWVudCB8IG51bGwpID0+IHtcbiAgICAgICAgY29udGFpbmVyUmVmLmN1cnJlbnQgPSBlbDtcbiAgICAgICAgaWYgKHR5cGVvZiByZWYgPT09ICdmdW5jdGlvbicpIHJlZihlbCk7XG4gICAgICAgIGVsc2UgaWYgKHJlZiAhPT0gbnVsbCkgcmVmLmN1cnJlbnQgPSBlbDtcbiAgICAgIH0sXG4gICAgICBbcmVmXVxuICAgICk7XG5cbiAgICB1c2VFZmZlY3QoKCkgPT4ge1xuICAgICAgaWYgKCFpc0lucHV0Rm9jdXNlZFJlZi5jdXJyZW50KSB7XG4gICAgICAgIHNldFNlZ21lbnRzKHNlZ21lbnRzRnJvbUJvdW5kKTtcbiAgICAgIH1cbiAgICB9LCBbc2VnbWVudHNGcm9tQm91bmRdKTtcblxuICAgIGNvbnN0IGNvbW1pdFBhcnNlZERhdGUgPSB1c2VDYWxsYmFjayhcbiAgICAgIChwYXJzZWQ6IERhdGUpID0+IHtcbiAgICAgICAgaWYgKCFpc1JhbmdlKSB7XG4gICAgICAgICAgY29udGV4dC5vblNlbGVjdGlvbihwYXJzZWQpO1xuICAgICAgICB9XG4gICAgICAgIGlmIChpc1JhbmdlICYmIHJhbmdlUGFydCkge1xuICAgICAgICAgIGhhbmRsZURhdGVTZWxlY3RSYW5nZSh7XG4gICAgICAgICAgICBkYXRlOiBwYXJzZWQsXG4gICAgICAgICAgICBhY3RpdmVSYW5nZVBhcnQ6IHJhbmdlUGFydCxcbiAgICAgICAgICAgIHN0YXJ0RGF0ZTogZGF0ZSxcbiAgICAgICAgICAgIGVuZERhdGUsXG4gICAgICAgICAgICBvblJhbmdlU2VsZWN0aW9uOiBjb250ZXh0Lm9uUmFuZ2VTZWxlY3Rpb24sXG4gICAgICAgICAgICBkaXNhYmxlRGF0ZSxcbiAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgIFtpc1JhbmdlLCByYW5nZVBhcnQsIGNvbnRleHQsIGVuZERhdGUsIGRhdGUsIGRpc2FibGVEYXRlXVxuICAgICk7XG5cbiAgICAvLyBSZXR1cm5zIHRoZSByYW5nZS1sZXZlbCBlcnJvciBtZXNzYWdlIGZvciB0aGUgcmFuZ2UgdGhhdCAqd291bGQqIGJlXG4gICAgLy8gY29tbWl0dGVkIGlmIGBwYXJzZWRgIHdlcmUgYXBwbGllZCB0byB0aGlzIGlucHV0LCBvciBudWxsLiBNaXJyb3JzIHRoZVxuICAgIC8vIGVuZHBvaW50IGNvbmRpdGlvbnMgaW4gaGFuZGxlRGF0ZVNlbGVjdFJhbmdlIHNvIHdlIG9ubHkgZmxhZyBzcGFucyB0aGF0XG4gICAgLy8gd291bGQgYWN0dWFsbHkgYmVjb21lIGEgY29tbWl0dGVkIHN0YXJ0K2VuZCByYW5nZSAoYm90aCBzZXQsIGluIG9yZGVyKS5cbiAgICBjb25zdCBnZXRSYW5nZUVycm9yID0gdXNlQ2FsbGJhY2soXG4gICAgICAocGFyc2VkOiBEYXRlKTogc3RyaW5nIHwgbnVsbCA9PiB7XG4gICAgICAgIGlmICghaXNSYW5nZSB8fCAhcmFuZ2VQYXJ0KSByZXR1cm4gbnVsbDtcbiAgICAgICAgY29uc3QgcmFuZ2VTdGFydCA9XG4gICAgICAgICAgcmFuZ2VQYXJ0ID09PSAnc3RhcnQnXG4gICAgICAgICAgICA/IHBhcnNlZFxuICAgICAgICAgICAgOiBkYXRlICYmIHBhcnNlZC5nZXRUaW1lKCkgPj0gZGF0ZS5nZXRUaW1lKClcbiAgICAgICAgICAgID8gZGF0ZVxuICAgICAgICAgICAgOiBudWxsO1xuICAgICAgICBjb25zdCByYW5nZUVuZCA9XG4gICAgICAgICAgcmFuZ2VQYXJ0ID09PSAnZW5kJ1xuICAgICAgICAgICAgPyBwYXJzZWRcbiAgICAgICAgICAgIDogZW5kRGF0ZSAmJiBwYXJzZWQuZ2V0VGltZSgpIDw9IGVuZERhdGUuZ2V0VGltZSgpXG4gICAgICAgICAgICA/IGVuZERhdGVcbiAgICAgICAgICAgIDogbnVsbDtcbiAgICAgICAgY29uc3QgcmFuZ2VSZXN1bHQgPSB2YWxpZGF0ZURhdGVSYW5nZShcbiAgICAgICAgICByYW5nZVN0YXJ0LFxuICAgICAgICAgIHJhbmdlRW5kLFxuICAgICAgICAgIHRyYW5zbGF0aW9ucyxcbiAgICAgICAgICBkaXNhYmxlRGF0ZVxuICAgICAgICApO1xuICAgICAgICByZXR1cm4gcmFuZ2VSZXN1bHQgJiYgIXJhbmdlUmVzdWx0LmlzVmFsaWRcbiAgICAgICAgICA/IHJhbmdlUmVzdWx0LmVycm9yTWVzc2FnZVxuICAgICAgICAgIDogbnVsbDtcbiAgICAgIH0sXG4gICAgICBbaXNSYW5nZSwgcmFuZ2VQYXJ0LCBkYXRlLCBlbmREYXRlLCB0cmFuc2xhdGlvbnMsIGRpc2FibGVEYXRlXVxuICAgICk7XG5cbiAgICBjb25zdCBjbGVhclNlbGVjdGlvbiA9IHVzZUNhbGxiYWNrKCgpID0+IHtcbiAgICAgIGlmICghaXNSYW5nZSkge1xuICAgICAgICBjb250ZXh0Lm9uU2VsZWN0aW9uKG51bGwpO1xuICAgICAgfVxuICAgICAgaWYgKGlzUmFuZ2UgJiYgcmFuZ2VQYXJ0KSB7XG4gICAgICAgIGlmIChyYW5nZVBhcnQgPT09ICdzdGFydCcpIGNvbnRleHQub25SYW5nZVNlbGVjdGlvbihudWxsLCBlbmREYXRlKTtcbiAgICAgICAgZWxzZSBjb250ZXh0Lm9uUmFuZ2VTZWxlY3Rpb24oZGF0ZSwgbnVsbCk7XG4gICAgICB9XG4gICAgfSwgW2lzUmFuZ2UsIHJhbmdlUGFydCwgY29udGV4dCwgZW5kRGF0ZSwgZGF0ZV0pO1xuXG4gICAgY29uc3Qgb25TZWdtZW50Q2hhbmdlID0gdXNlQ2FsbGJhY2soXG4gICAgICAobmV4dDogU2VnbWVudFZhbHVlcykgPT4ge1xuICAgICAgICBjb25zdCB2YWxpZGF0aW9uUmVzdWx0ID0gdmFsaWRhdGVTZWdtZW50cyhcbiAgICAgICAgICBuZXh0LFxuICAgICAgICAgIHRyYW5zbGF0aW9ucyxcbiAgICAgICAgICBkaXNhYmxlRGF0ZVxuICAgICAgICApO1xuICAgICAgICBpZiAodmFsaWRhdGlvblJlc3VsdC5pc1ZhbGlkKSB7XG4gICAgICAgICAgY29uc3QgcmFuZ2VFcnJvciA9IGdldFJhbmdlRXJyb3IodmFsaWRhdGlvblJlc3VsdC5kYXRlKTtcbiAgICAgICAgICBpZiAocmFuZ2VFcnJvcikge1xuICAgICAgICAgICAgc2V0SW5wdXRFcnJvcihyYW5nZUVycm9yKTtcbiAgICAgICAgICAgIHNldEhhc0Vycm9yKHRydWUpO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBzZXRJbnB1dEVycm9yKCcnKTtcbiAgICAgICAgICAgIHNldEhhc0Vycm9yKGZhbHNlKTtcbiAgICAgICAgICAgIGNvbW1pdFBhcnNlZERhdGUodmFsaWRhdGlvblJlc3VsdC5kYXRlKTtcbiAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSBpZiAoIW5leHQubW9udGggJiYgIW5leHQuZGF5ICYmICFuZXh0LnllYXIpIHtcbiAgICAgICAgICBzZXRJbnB1dEVycm9yKCcnKTtcbiAgICAgICAgICBzZXRIYXNFcnJvcihmYWxzZSk7XG4gICAgICAgICAgY2xlYXJTZWxlY3Rpb24oKTtcbiAgICAgICAgfSBlbHNlIGlmICh2YWxpZGF0aW9uUmVzdWx0LmVycm9yTWVzc2FnZSkge1xuICAgICAgICAgIHNldElucHV0RXJyb3IodmFsaWRhdGlvblJlc3VsdC5lcnJvck1lc3NhZ2UpO1xuICAgICAgICAgIHNldEhhc0Vycm9yKHRydWUpO1xuICAgICAgICB9XG4gICAgICB9LFxuICAgICAgW1xuICAgICAgICBjbGVhclNlbGVjdGlvbixcbiAgICAgICAgY29tbWl0UGFyc2VkRGF0ZSxcbiAgICAgICAgZ2V0UmFuZ2VFcnJvcixcbiAgICAgICAgdHJhbnNsYXRpb25zLFxuICAgICAgICBkaXNhYmxlRGF0ZSxcbiAgICAgICAgc2V0SGFzRXJyb3IsXG4gICAgICBdXG4gICAgKTtcblxuICAgIGNvbnN0IG9uQ29udGFpbmVyQmx1ciA9IHVzZUNhbGxiYWNrKFxuICAgICAgKGU6IEZvY3VzRXZlbnQ8SFRNTERpdkVsZW1lbnQ+KSA9PiB7XG4gICAgICAgIGlmIChjb250YWluZXJSZWYuY3VycmVudD8uY29udGFpbnMoZS5yZWxhdGVkVGFyZ2V0IGFzIE5vZGUpKSByZXR1cm47XG4gICAgICAgIGlzSW5wdXRGb2N1c2VkUmVmLmN1cnJlbnQgPSBmYWxzZTtcbiAgICAgICAgc2V0U2VnbWVudHMoKHByZXYpID0+IHtcbiAgICAgICAgICAvLyBDaGVjayBpZiBhbGwgc2VnbWVudHMgYXJlIGVtcHR5XG4gICAgICAgICAgaWYgKCFwcmV2Lm1vbnRoICYmICFwcmV2LmRheSAmJiAhcHJldi55ZWFyKSB7XG4gICAgICAgICAgICBzZXRJbnB1dEVycm9yKCcnKTtcbiAgICAgICAgICAgIHNldEhhc0Vycm9yKGZhbHNlKTtcbiAgICAgICAgICAgIHF1ZXVlTWljcm90YXNrKCgpID0+IHtcbiAgICAgICAgICAgICAgY2xlYXJTZWxlY3Rpb24oKTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgcmV0dXJuIGdldERhdGVTZWdtZW50c0Zyb21EYXRlKG51bGwpO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIC8vIFZhbGlkYXRlIHJhdyBpbnB1dCAod2l0aG91dCBub3JtYWxpemluZy9jbGFtcGluZylcbiAgICAgICAgICBjb25zdCB2YWxpZGF0aW9uUmVzdWx0ID0gdmFsaWRhdGVTZWdtZW50cyhcbiAgICAgICAgICAgIHByZXYsXG4gICAgICAgICAgICB0cmFuc2xhdGlvbnMsXG4gICAgICAgICAgICBkaXNhYmxlRGF0ZVxuICAgICAgICAgICk7XG5cbiAgICAgICAgICBpZiAodmFsaWRhdGlvblJlc3VsdC5pc1ZhbGlkKSB7XG4gICAgICAgICAgICAvLyBWYWxpZCBpbmRpdmlkdWFsIGRhdGUsIGJ1dCB0aGUgcmVzdWx0aW5nIHJhbmdlIG1heSBzcGFuIGFcbiAgICAgICAgICAgIC8vIGRpc2FibGVkIGRhdGUgLSBrZWVwIHRoZSByYXcgaW5wdXQgdmlzaWJsZSBhbmQgc3VyZmFjZSB0aGUgZXJyb3IuXG4gICAgICAgICAgICBjb25zdCByYW5nZUVycm9yID0gZ2V0UmFuZ2VFcnJvcih2YWxpZGF0aW9uUmVzdWx0LmRhdGUpO1xuICAgICAgICAgICAgaWYgKHJhbmdlRXJyb3IpIHtcbiAgICAgICAgICAgICAgc2V0SW5wdXRFcnJvcihyYW5nZUVycm9yKTtcbiAgICAgICAgICAgICAgc2V0SGFzRXJyb3IodHJ1ZSk7XG4gICAgICAgICAgICAgIHJldHVybiBwcmV2O1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gVmFsaWQgY29tcGxldGUgZGF0ZSAtIG5vcm1hbGl6ZSBhbmQgY29tbWl0XG4gICAgICAgICAgICBzZXRJbnB1dEVycm9yKCcnKTtcbiAgICAgICAgICAgIHNldEhhc0Vycm9yKGZhbHNlKTtcbiAgICAgICAgICAgIGNvbnN0IG5vcm1hbGl6ZWQgPSBub3JtYWxpemVTZWdtZW50VmFsdWVzKHByZXYpO1xuICAgICAgICAgICAgY29uc3Qgc2FtZUFzQm91bmQgPSBpc1NhbWVEYXkodmFsaWRhdGlvblJlc3VsdC5kYXRlLCBib3VuZERhdGUpO1xuICAgICAgICAgICAgaWYgKGlzQ2FsZW5kYXJPcGVuICYmICFzYW1lQXNCb3VuZCkge1xuICAgICAgICAgICAgICBxdWV1ZU1pY3JvdGFzaygoKSA9PiB7XG4gICAgICAgICAgICAgICAgY29tbWl0UGFyc2VkRGF0ZSh2YWxpZGF0aW9uUmVzdWx0LmRhdGUpO1xuICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybiBub3JtYWxpemVkO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIC8vIEluY29tcGxldGUgZW50cnkgb24gYmx1ciAtIHNob3cgdGhlIGluY29tcGxldGUtZGF0ZSBlcnJvclxuICAgICAgICAgIGlmICghdmFsaWRhdGlvblJlc3VsdC5lcnJvck1lc3NhZ2UpIHtcbiAgICAgICAgICAgIHNldElucHV0RXJyb3IodHJhbnNsYXRpb25zLmludmFsaWREYXRlSW5jb21wbGV0ZSk7XG4gICAgICAgICAgICBzZXRIYXNFcnJvcih0cnVlKTtcbiAgICAgICAgICAgIHJldHVybiBwcmV2OyAvLyBLZWVwIGluY29tcGxldGUgaW5wdXQgdmlzaWJsZSBzbyB1c2VyIGNhbiBjb3JyZWN0IGl0XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgLy8gSW52YWxpZCBjb21wbGV0ZSBlbnRyeSAtIHNob3cgZXJyb3IgYW5kIGtlZXAgaW52YWxpZCBpbnB1dCB2aXNpYmxlXG4gICAgICAgICAgc2V0SW5wdXRFcnJvcih2YWxpZGF0aW9uUmVzdWx0LmVycm9yTWVzc2FnZSk7XG4gICAgICAgICAgc2V0SGFzRXJyb3IodHJ1ZSk7XG4gICAgICAgICAgcmV0dXJuIHByZXY7XG4gICAgICAgIH0pO1xuICAgICAgfSxcbiAgICAgIFtcbiAgICAgICAgY29udGFpbmVyUmVmLFxuICAgICAgICBib3VuZERhdGUsXG4gICAgICAgIGNsZWFyU2VsZWN0aW9uLFxuICAgICAgICBjb21taXRQYXJzZWREYXRlLFxuICAgICAgICBnZXRSYW5nZUVycm9yLFxuICAgICAgICBpc0NhbGVuZGFyT3BlbixcbiAgICAgICAgdHJhbnNsYXRpb25zLFxuICAgICAgICBkaXNhYmxlRGF0ZSxcbiAgICAgICAgc2V0SGFzRXJyb3IsXG4gICAgICBdXG4gICAgKTtcblxuICAgIGNvbnN0IHNldEFjdGl2ZVJhbmdlUGFydEZvckZpZWxkID0gdXNlQ2FsbGJhY2soKCkgPT4ge1xuICAgICAgaWYgKGlzUmFuZ2UgJiYgcmFuZ2VQYXJ0KSBjb250ZXh0LnNldEFjdGl2ZVJhbmdlUGFydChyYW5nZVBhcnQpO1xuICAgIH0sIFtpc1JhbmdlLCByYW5nZVBhcnQsIGNvbnRleHRdKTtcblxuICAgIGNvbnN0IG9uU2VnbWVudEZvY3VzID0gdXNlQ2FsbGJhY2soKCkgPT4ge1xuICAgICAgaXNJbnB1dEZvY3VzZWRSZWYuY3VycmVudCA9IHRydWU7XG4gICAgICBzZXRBY3RpdmVSYW5nZVBhcnRGb3JGaWVsZCgpO1xuICAgIH0sIFtpc0lucHV0Rm9jdXNlZFJlZiwgc2V0QWN0aXZlUmFuZ2VQYXJ0Rm9yRmllbGRdKTtcblxuICAgIGNvbnN0IG9uU2hlbGxGb2N1cyA9IHVzZUNhbGxiYWNrKCgpID0+IHtcbiAgICAgIHNldEFjdGl2ZVJhbmdlUGFydEZvckZpZWxkKCk7XG4gICAgfSwgW3NldEFjdGl2ZVJhbmdlUGFydEZvckZpZWxkXSk7XG5cbiAgICBjb25zdCBvblNoZWxsQ2xpY2sgPSB1c2VDYWxsYmFjaygoKSA9PiB7XG4gICAgICBpZiAoZGlzYWJsZWQpIHJldHVybjtcbiAgICAgIHNldEFjdGl2ZVJhbmdlUGFydEZvckZpZWxkKCk7XG4gICAgICBvcGVuQ2FsZW5kYXIoKTtcbiAgICB9LCBbZGlzYWJsZWQsIHNldEFjdGl2ZVJhbmdlUGFydEZvckZpZWxkLCBvcGVuQ2FsZW5kYXJdKTtcblxuICAgIGNvbnN0IG9uU2VnbWVudEFsdEFycm93RG93biA9IHVzZUNhbGxiYWNrKCgpID0+IHtcbiAgICAgIGlmICghaXNDYWxlbmRhck9wZW4pIG9wZW5DYWxlbmRhcigpO1xuICAgICAgZm9jdXNDYWxlbmRhcigpO1xuICAgIH0sIFtpc0NhbGVuZGFyT3Blbiwgb3BlbkNhbGVuZGFyLCBmb2N1c0NhbGVuZGFyXSk7XG5cbiAgICByZXR1cm4gKFxuICAgICAgPEZvcm1Hcm91cFxuICAgICAgICBpZD17aW5wdXRJZH1cbiAgICAgICAgaXNTb2xvRmllbGRcbiAgICAgICAgbGFiZWw9e2xhYmVsID8/IGRlZmF1bHRMYWJlbH1cbiAgICAgICAgbWI9ezB9XG4gICAgICAgIHBiPXswfVxuICAgICAgICBzcGFjaW5nPVwidGlnaHRcIlxuICAgICAgICB3aWR0aD1cImZpdC1jb250ZW50XCJcbiAgICAgID5cbiAgICAgICAgPFNlZ21lbnRlZFNoZWxsXG4gICAgICAgICAgYXJpYS1sYWJlbGxlZGJ5PXtpbnB1dElkfVxuICAgICAgICAgIGlucHV0U2l6ZT17c2l6ZX1cbiAgICAgICAgICByZWY9e3NoZWxsUmVmfVxuICAgICAgICAgIHJvbGU9XCJncm91cFwiXG4gICAgICAgICAgdmFyaWFudD17aW5wdXRFcnJvciA/ICdlcnJvcicgOiAnZGVmYXVsdCd9XG4gICAgICAgICAgd2lkdGg9XCIxMTNweFwiXG4gICAgICAgICAgb25CbHVyPXtvbkNvbnRhaW5lckJsdXJ9XG4gICAgICAgICAgb25DbGljaz17b25TaGVsbENsaWNrfVxuICAgICAgICAgIG9uRm9jdXM9e29uU2hlbGxGb2N1c31cbiAgICAgICAgICB7Li4ucmVzdH1cbiAgICAgICAgPlxuICAgICAgICAgIDxGbGV4Qm94IGFsaWduSXRlbXM9XCJjZW50ZXJcIiBqdXN0aWZ5Q29udGVudD1cImNlbnRlclwiPlxuICAgICAgICAgICAge2xheW91dC5tYXAoKGl0ZW0sIGluZGV4KSA9PiB7XG4gICAgICAgICAgICAgIGlmIChpdGVtLmtpbmQgPT09ICdsaXRlcmFsJykge1xuICAgICAgICAgICAgICAgIHJldHVybiAoXG4gICAgICAgICAgICAgICAgICA8U2VnbWVudExpdGVyYWxcbiAgICAgICAgICAgICAgICAgICAgYXJpYS1oaWRkZW5cbiAgICAgICAgICAgICAgICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIHJlYWN0L25vLWFycmF5LWluZGV4LWtleVxuICAgICAgICAgICAgICAgICAgICBrZXk9e2BsaXRlcmFsLSR7aXRlbS50ZXh0fS0ke2luZGV4fWB9XG4gICAgICAgICAgICAgICAgICA+XG4gICAgICAgICAgICAgICAgICAgIHtgJHtpdGVtLnRleHR9YH1cbiAgICAgICAgICAgICAgICAgIDwvU2VnbWVudExpdGVyYWw+XG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBjb25zdCBpZHggPSBmaWVsZE9yZGVyLmluZGV4T2YoaXRlbS5maWVsZCk7XG4gICAgICAgICAgICAgIGNvbnN0IHByZXZGaWVsZCA9IGlkeCA+IDAgPyBmaWVsZE9yZGVyW2lkeCAtIDFdIDogbnVsbDtcbiAgICAgICAgICAgICAgY29uc3QgbmV4dEZpZWxkID1cbiAgICAgICAgICAgICAgICBpZHggPCBmaWVsZE9yZGVyLmxlbmd0aCAtIDEgPyBmaWVsZE9yZGVyW2lkeCArIDFdIDogbnVsbDtcblxuICAgICAgICAgICAgICByZXR1cm4gKFxuICAgICAgICAgICAgICAgIDxEYXRlUGlja2VySW5wdXRTZWdtZW50XG4gICAgICAgICAgICAgICAgICBhcHBseVNlZ21lbnRzPXtvblNlZ21lbnRDaGFuZ2V9XG4gICAgICAgICAgICAgICAgICBhc3NpZ25TZWdtZW50UmVmPXthc3NpZ25TZWdtZW50UmVmfVxuICAgICAgICAgICAgICAgICAgZGlzYWJsZWQ9eyEhZGlzYWJsZWR9XG4gICAgICAgICAgICAgICAgICBlcnJvcj17ISFpbnB1dEVycm9yfVxuICAgICAgICAgICAgICAgICAgZmllbGQ9e2l0ZW0uZmllbGR9XG4gICAgICAgICAgICAgICAgICBrZXk9e2l0ZW0uZmllbGR9XG4gICAgICAgICAgICAgICAgICBuZXh0RmllbGQ9e25leHRGaWVsZH1cbiAgICAgICAgICAgICAgICAgIHByZXZGaWVsZD17cHJldkZpZWxkfVxuICAgICAgICAgICAgICAgICAgc2VnbWVudHM9e3NlZ21lbnRzfVxuICAgICAgICAgICAgICAgICAgc2V0U2VnbWVudHM9e3NldFNlZ21lbnRzfVxuICAgICAgICAgICAgICAgICAgb25BbHRBcnJvd0Rvd249e29uU2VnbWVudEFsdEFycm93RG93bn1cbiAgICAgICAgICAgICAgICAgIG9uRm9jdXM9e29uU2VnbWVudEZvY3VzfVxuICAgICAgICAgICAgICAgICAgb25TaWJsaW5nRm9jdXM9e29uU2libGluZ1NlZ21lbnRGb2N1c31cbiAgICAgICAgICAgICAgICAvPlxuICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgfSl9XG4gICAgICAgICAgPC9GbGV4Qm94PlxuICAgICAgICAgIDxpbnB1dFxuICAgICAgICAgICAgYXJpYS1oaWRkZW5cbiAgICAgICAgICAgIGZvcm09e2Zvcm19XG4gICAgICAgICAgICBuYW1lPXtuYW1lID8/ICdkYXRlUGlja2VySW5wdXQnfVxuICAgICAgICAgICAgdGFiSW5kZXg9ey0xfVxuICAgICAgICAgICAgdHlwZT1cImhpZGRlblwiXG4gICAgICAgICAgICB2YWx1ZT17aGlkZGVuVmFsdWV9XG4gICAgICAgICAgLz5cbiAgICAgICAgICA8SWNvbkJ1dHRvblxuICAgICAgICAgICAgaWNvbj17TWluaUNhbGVuZGFySWNvbn1cbiAgICAgICAgICAgIG14PXs0fVxuICAgICAgICAgICAgcmVmPXtidXR0b25SZWZ9XG4gICAgICAgICAgICBzaXplPVwic21hbGxcIlxuICAgICAgICAgICAgdGlwPXt0cmFuc2xhdGlvbnMub3BlbkNhbGVuZGFyTGFiZWx9XG4gICAgICAgICAgICBvbkNsaWNrPXsoKSA9PiBidXR0b25SZWYuY3VycmVudD8uYmx1cigpfVxuICAgICAgICAgIC8+XG4gICAgICAgIDwvU2VnbWVudGVkU2hlbGw+XG4gICAgICAgIHtpbnB1dEVycm9yICYmIChcbiAgICAgICAgICA8QWJzb2x1dGVGb3JtRXJyb3IgYXJpYS1saXZlPVwicG9saXRlXCIgcm9sZT1cImFsZXJ0XCIgdmFyaWFudD1cImFic29sdXRlXCI+XG4gICAgICAgICAgICB7aW5wdXRFcnJvcn1cbiAgICAgICAgICA8L0Fic29sdXRlRm9ybUVycm9yPlxuICAgICAgICApfVxuICAgICAgPC9Gb3JtR3JvdXA+XG4gICAgKTtcbiAgfVxuKTtcbiJdfQ== */");
export const DatePickerInput = /*#__PURE__*/forwardRef(({
disabled,
- error,
form,
label,
name,
rangePart,
@@ -31,9 +50,10 @@
focusCalendar,
locale,
isCalendarOpen,
translations,
- disableDate
+ disableDate,
+ setHasError
} = context;
const isRange = mode === 'range';
const endDate = isRange ? context.endDate : null;
const date = isRange ? context.startDate : context.selectedDate;
@@ -52,12 +72,14 @@
const defaultLabel = !isRange ? translations.dateLabel : rangePart === 'end' ? translations.endDateLabel : translations.startDateLabel;
const boundDate = isRange && rangePart === 'end' ? endDate : date;
const segmentsFromBound = useMemo(() => getDateSegmentsFromDate(boundDate), [boundDate]);
const [segments, setSegments] = useState(segmentsFromBound);
+ const [inputError, setInputError] = useState('');
const parsedForHidden = parseSegmentsToDate(segments);
const hiddenValue = parsedForHidden ? formatDateISO8601DateOnly(parsedForHidden) : '';
const isInputFocusedRef = useRef(false);
const containerRef = useRef(null);
+ const buttonRef = useRef(null);
const segmentElRefs = useRef({});
const assignSegmentRef = useCallback((field, el) => {
segmentElRefs.current[field] = el;
}, [segmentElRefs]);
@@ -87,8 +109,20 @@
disableDate
});
}
}, [isRange, rangePart, context, endDate, date, disableDate]);
+
+ // Returns the range-level error message for the range that *would* be
+ // committed if `parsed` were applied to this input, or null. Mirrors the
+ // endpoint conditions in handleDateSelectRange so we only flag spans that
+ // would actually become a committed start+end range (both set, in order).
+ const getRangeError = useCallback(parsed => {
+ if (!isRange || !rangePart) return null;
+ const rangeStart = rangePart === 'start' ? parsed : date && parsed.getTime() >= date.getTime() ? date : null;
+ const rangeEnd = rangePart === 'end' ? parsed : endDate && parsed.getTime() <= endDate.getTime() ? endDate : null;
+ const rangeResult = validateDateRange(rangeStart, rangeEnd, translations, disableDate);
+ return rangeResult && !rangeResult.isValid ? rangeResult.errorMessage : null;
+ }, [isRange, rangePart, date, endDate, translations, disableDate]);
const clearSelection = useCallback(() => {
if (!isRange) {
context.onSelection(null);
}
@@ -96,35 +130,79 @@
if (rangePart === 'start') context.onRangeSelection(null, endDate);else context.onRangeSelection(date, null);
}
}, [isRange, rangePart, context, endDate, date]);
const onSegmentChange = useCallback(next => {
- const parsed = parseSegmentsToDate(next);
- if (parsed) commitParsedDate(parsed);else if (!next.month && !next.day && !next.year) clearSelection();
- }, [clearSelection, commitParsedDate]);
+ const validationResult = validateSegments(next, translations, disableDate);
+ if (validationResult.isValid) {
+ const rangeError = getRangeError(validationResult.date);
+ if (rangeError) {
+ setInputError(rangeError);
+ setHasError(true);
+ } else {
+ setInputError('');
+ setHasError(false);
+ commitParsedDate(validationResult.date);
+ }
+ } else if (!next.month && !next.day && !next.year) {
+ setInputError('');
+ setHasError(false);
+ clearSelection();
+ } else if (validationResult.errorMessage) {
+ setInputError(validationResult.errorMessage);
+ setHasError(true);
+ }
+ }, [clearSelection, commitParsedDate, getRangeError, translations, disableDate, setHasError]);
const onContainerBlur = useCallback(e => {
if (containerRef.current?.contains(e.relatedTarget)) return;
isInputFocusedRef.current = false;
setSegments(prev => {
- const normalized = normalizeSegmentValues(prev);
- const parsed = parseSegmentsToDate(normalized);
- if (parsed) {
- const sameAsBound = isSameDay(parsed, boundDate);
+ // Check if all segments are empty
+ if (!prev.month && !prev.day && !prev.year) {
+ setInputError('');
+ setHasError(false);
+ queueMicrotask(() => {
+ clearSelection();
+ });
+ return getDateSegmentsFromDate(null);
+ }
+
+ // Validate raw input (without normalizing/clamping)
+ const validationResult = validateSegments(prev, translations, disableDate);
+ if (validationResult.isValid) {
+ // Valid individual date, but the resulting range may span a
+ // disabled date - keep the raw input visible and surface the error.
+ const rangeError = getRangeError(validationResult.date);
+ if (rangeError) {
+ setInputError(rangeError);
+ setHasError(true);
+ return prev;
+ }
+ // Valid complete date - normalize and commit
+ setInputError('');
+ setHasError(false);
+ const normalized = normalizeSegmentValues(prev);
+ const sameAsBound = isSameDay(validationResult.date, boundDate);
if (isCalendarOpen && !sameAsBound) {
queueMicrotask(() => {
- commitParsedDate(parsed);
+ commitParsedDate(validationResult.date);
});
}
return normalized;
}
- if (!normalized.month && !normalized.day && !normalized.year) {
- queueMicrotask(() => {
- clearSelection();
- });
- return getDateSegmentsFromDate(null);
+
+ // Incomplete entry on blur - show the incomplete-date error
+ if (!validationResult.errorMessage) {
+ setInputError(translations.invalidDateIncomplete);
+ setHasError(true);
+ return prev; // Keep incomplete input visible so user can correct it
}
- return segmentsFromBound;
+
+ // Invalid complete entry - show error and keep invalid input visible
+ setInputError(validationResult.errorMessage);
+ setHasError(true);
+ return prev;
});
- }, [containerRef, boundDate, segmentsFromBound, clearSelection, commitParsedDate, isCalendarOpen]);
+ }, [containerRef, boundDate, clearSelection, commitParsedDate, getRangeError, isCalendarOpen, translations, disableDate, setHasError]);
const setActiveRangePartForField = useCallback(() => {
if (isRange && rangePart) context.setActiveRangePart(rangePart);
}, [isRange, rangePart, context]);
const onSegmentFocus = useCallback(() => {
@@ -142,22 +220,22 @@
const onSegmentAltArrowDown = useCallback(() => {
if (!isCalendarOpen) openCalendar();
focusCalendar();
}, [isCalendarOpen, openCalendar, focusCalendar]);
- return /*#__PURE__*/_jsx(FormGroup, {
- htmlFor: inputId,
+ return /*#__PURE__*/_jsxs(FormGroup, {
+ id: inputId,
isSoloField: true,
label: label ?? defaultLabel,
mb: 0,
pb: 0,
spacing: "tight",
width: "fit-content",
- children: /*#__PURE__*/_jsxs(SegmentedShell, {
- id: inputId,
+ children: [/*#__PURE__*/_jsxs(SegmentedShell, {
+ "aria-labelledby": inputId,
inputSize: size,
ref: shellRef,
role: "group",
- variant: error ? 'error' : 'default',
+ variant: inputError ? 'error' : 'default',
width: "113px",
onBlur: onContainerBlur,
onClick: onShellClick,
onFocus: onShellFocus,
@@ -180,9 +258,9 @@
return /*#__PURE__*/_jsx(DatePickerInputSegment, {
applySegments: onSegmentChange,
assignSegmentRef: assignSegmentRef,
disabled: !!disabled,
- error: !!error,
+ error: !!inputError,
field: item.field,
nextField: nextField,
prevField: prevField,
segments: segments,
@@ -198,18 +276,20 @@
name: name ?? 'datePickerInput',
tabIndex: -1,
type: "hidden",
value: hiddenValue
- }), /*#__PURE__*/_jsx(FlexBox, {
- alignItems: "center",
- justifyContent: "center",
- pl: 16,
- pr: 8,
- role: "presentation",
- children: /*#__PURE__*/_jsx(MiniCalendarIcon, {
- "aria-hidden": true,
- size: 16
- })
+ }), /*#__PURE__*/_jsx(IconButton, {
+ icon: MiniCalendarIcon,
+ mx: 4,
+ ref: buttonRef,
+ size: "small",
+ tip: translations.openCalendarLabel,
+ onClick: () => buttonRef.current?.blur()
})]
- })
+ }), inputError && /*#__PURE__*/_jsx(AbsoluteFormError, {
+ "aria-live": "polite",
+ role: "alert",
+ variant: "absolute",
+ children: inputError
+ })]
});
});
\ No newline at end of file Index: package/dist/DatePicker/utils/translations.js
===================================================================
--- package/dist/DatePicker/utils/translations.js
+++ package/dist/DatePicker/utils/translations.js
@@ -3,8 +3,15 @@
dateLabel: 'Date',
startDateLabel: 'Start date',
endDateLabel: 'End date',
calendarDialogAriaLabel: 'Choose date',
+ openCalendarLabel: 'Open calendar',
last7DaysDisplayText: 'Last 7 days',
last30DaysDisplayText: 'Last 30 days',
- last90DaysDisplayText: 'Last 90 days'
+ last90DaysDisplayText: 'Last 90 days',
+ invalidDateIncomplete: 'Incomplete date',
+ invalidDateInvalidMonth: 'Month must be between 1 and 12',
+ invalidDateInvalidDay: '{{month}} does not have {{day}} days',
+ invalidDateRollover: '{{month}} does not have {{day}} days',
+ invalidDateNotAvailable: 'This date is not available',
+ invalidDateRangeContainsDisabledDate: 'This date range contains unavailable dates'
};
\ No newline at end of file Index: package/package.json
===================================================================
--- package/package.json
+++ package/package.json
@@ -1,16 +1,16 @@
{
"name": "@codecademy/gamut",
"description": "Styleguide & Component library for Codecademy",
- "version": "72.5.0",
+ "version": "72.5.1-alpha.7ca70c.0",
"author": "Codecademy Engineering <[email protected]>",
"bin": "./bin/gamut.mjs",
"dependencies": {
- "@codecademy/gamut-icons": "9.57.11",
- "@codecademy/gamut-illustrations": "0.58.16",
- "@codecademy/gamut-patterns": "0.10.35",
- "@codecademy/gamut-styles": "20.1.0",
- "@codecademy/variance": "0.26.1",
+ "@codecademy/gamut-icons": "9.57.12-alpha.7ca70c.0",
+ "@codecademy/gamut-illustrations": "0.58.17-alpha.7ca70c.0",
+ "@codecademy/gamut-patterns": "0.10.36-alpha.7ca70c.0",
+ "@codecademy/gamut-styles": "20.1.1-alpha.7ca70c.0",
+ "@codecademy/variance": "0.26.2-alpha.7ca70c.0",
"@formatjs/intl-locale": "5.3.1",
"@react-aria/interactions": "3.25.0",
"@types/marked": "^4.0.8",
"@vidstack/react": "^1.12.12", Index: package/dist/DatePicker/DatePickerInput/index.d.ts
===================================================================
--- package/dist/DatePicker/DatePickerInput/index.d.ts
+++ package/dist/DatePicker/DatePickerInput/index.d.ts
@@ -1,9 +1,9 @@
import type { InputWrapperProps } from '../../Form/inputs/Input';
-export type DatePickerInputProps = Omit<InputWrapperProps, 'className' | 'type' | 'icon' | 'value' | 'onChange' | 'color'> & {
+export type DatePickerInputProps = Omit<InputWrapperProps, 'className' | 'type' | 'icon' | 'value' | 'onChange' | 'color' | 'error'> & {
/** In range mode: which part of the range this input edits. Omit for single-date or combined display. */
rangePart?: 'start' | 'end';
};
-export declare const DatePickerInput: import("react").ForwardRefExoticComponent<Omit<InputWrapperProps, "color" | "className" | "onChange" | "type" | "icon" | "value"> & {
+export declare const DatePickerInput: import("react").ForwardRefExoticComponent<Omit<InputWrapperProps, "color" | "error" | "className" | "onChange" | "type" | "icon" | "value"> & {
/** In range mode: which part of the range this input edits. Omit for single-date or combined display. */
rangePart?: "start" | "end";
} & import("react").RefAttributes<HTMLDivElement>>; Index: package/dist/DatePicker/utils/translations.d.ts
===================================================================
--- package/dist/DatePicker/utils/translations.d.ts
+++ package/dist/DatePicker/utils/translations.d.ts
@@ -8,12 +8,26 @@
/** Label for the end date input in range mode (default: "End date"). */
endDateLabel?: string;
/** aria-label for the calendar dialog (default: "Choose date"). */
calendarDialogAriaLabel?: string;
+ /** aria-label for the calendar icon trigger (default: "Open calendar"). */
+ openCalendarLabel?: string;
/** Label for the last 7 days quick action (default: "Last 7 days"). */
last7DaysDisplayText?: string;
/** Label for the last 30 days quick action (default: "Last 30 days"). */
last30DaysDisplayText?: string;
/** Label for the last 90 days quick action (default: "Last 90 days"). */
last90DaysDisplayText?: string;
+ /** Error message for incomplete date entry (default: "Incomplete date"). */
+ invalidDateIncomplete?: string;
+ /** Error message for invalid month (default: "Month must be between 1 and 12"). */
+ invalidDateInvalidMonth?: string;
+ /** Error message for invalid day (default: "{{month}} does not have {{day}} days"). Supports {{month}} and {{day}} template variables. */
+ invalidDateInvalidDay?: string;
+ /** Error message for date rollover (default: "{{month}} does not have {{day}} days"). Supports {{month}} and {{day}} template variables. */
+ invalidDateRollover?: string;
+ /** Error message for disabled/unavailable date (default: "This date is not available"). */
+ invalidDateNotAvailable?: string;
+ /** Error message for date range containing disabled dates (default: "This date range contains unavailable dates"). */
+ invalidDateRangeContainsDisabledDate?: string;
}
export declare const DEFAULT_DATE_PICKER_TRANSLATIONS: Required<DatePickerTranslations>; Index: package/dist/DatePicker/DatePickerContext/types.d.ts
===================================================================
--- package/dist/DatePicker/DatePickerContext/types.d.ts
+++ package/dist/DatePicker/DatePickerContext/types.d.ts
@@ -5,8 +5,17 @@
* Discriminator: same meaning as the `mode` prop on `DatePicker` (`"single"` or `"range"`).
*/
mode: Mode;
/**
+ * Whether there is an error in any input. Used by DatePicker to adjust popover position
+ * when calendar is open. Only tracks IF error exists, not the message.
+ */
+ hasError: boolean;
+ /**
+ * Callback to set whether error exists. Called by DatePickerInput when validation errors occur.
+ */
+ setHasError: (hasError: boolean) => void;
+ /**
* Resolved `Intl.Locale` for the `locale` prop (or the runtime default). The same object is
* passed to formatters and to APIs such as `getWeekInfo` where available.
*/
locale: Intl.Locale;