@codecademy/gamut 68.2.3-alpha.e63333.0 → 68.2.3-alpha.f19d29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/DatePicker/Calendar/CalendarBody.d.ts +3 -0
  2. package/dist/DatePicker/Calendar/CalendarBody.js +145 -0
  3. package/dist/DatePicker/Calendar/CalendarFooter.d.ts +3 -0
  4. package/dist/DatePicker/Calendar/CalendarFooter.js +57 -0
  5. package/dist/DatePicker/Calendar/CalendarHeader.d.ts +3 -0
  6. package/dist/DatePicker/Calendar/CalendarHeader.js +46 -0
  7. package/dist/DatePicker/Calendar/CalendarNavLastMonth.d.ts +3 -0
  8. package/dist/DatePicker/Calendar/CalendarNavLastMonth.js +30 -0
  9. package/dist/DatePicker/Calendar/CalendarNavNextMonth.d.ts +3 -0
  10. package/dist/DatePicker/Calendar/CalendarNavNextMonth.js +30 -0
  11. package/dist/DatePicker/Calendar/CalendarWrapper.d.ts +8 -0
  12. package/dist/DatePicker/Calendar/CalendarWrapper.js +27 -0
  13. package/dist/DatePicker/Calendar/index.d.ts +6 -0
  14. package/dist/DatePicker/Calendar/index.js +6 -0
  15. package/dist/DatePicker/Calendar/types.d.ts +77 -0
  16. package/dist/DatePicker/Calendar/types.js +1 -0
  17. package/dist/DatePicker/Calendar/utils/dateGrid.d.ts +38 -0
  18. package/dist/DatePicker/Calendar/utils/dateGrid.js +103 -0
  19. package/dist/DatePicker/Calendar/utils/elements.d.ts +18 -0
  20. package/dist/DatePicker/Calendar/utils/elements.js +116 -0
  21. package/dist/DatePicker/Calendar/utils/format.d.ts +28 -0
  22. package/dist/DatePicker/Calendar/utils/format.js +72 -0
  23. package/dist/DatePicker/Calendar/utils/keyHandler.d.ts +12 -0
  24. package/dist/DatePicker/Calendar/utils/keyHandler.js +126 -0
  25. package/dist/DatePicker/DatePicker.d.ts +7 -0
  26. package/dist/DatePicker/DatePicker.js +157 -0
  27. package/dist/DatePicker/DatePickerCalendar.d.ts +11 -0
  28. package/dist/DatePicker/DatePickerCalendar.js +168 -0
  29. package/dist/DatePicker/DatePickerContext.d.ts +10 -0
  30. package/dist/DatePicker/DatePickerContext.js +18 -0
  31. package/dist/DatePicker/DatePickerInput/Segment/DatePickerInputSegment.d.ts +23 -0
  32. package/dist/DatePicker/DatePickerInput/Segment/DatePickerInputSegment.js +131 -0
  33. package/dist/DatePicker/DatePickerInput/Segment/elements.d.ts +16 -0
  34. package/dist/DatePicker/DatePickerInput/Segment/elements.js +36 -0
  35. package/dist/DatePicker/DatePickerInput/Segment/index.d.ts +4 -0
  36. package/dist/DatePicker/DatePickerInput/Segment/index.js +3 -0
  37. package/dist/DatePicker/DatePickerInput/Segment/segmentUtils.d.ts +50 -0
  38. package/dist/DatePicker/DatePickerInput/Segment/segmentUtils.js +201 -0
  39. package/dist/DatePicker/DatePickerInput/elements.d.ts +15 -0
  40. package/dist/DatePicker/DatePickerInput/elements.js +19 -0
  41. package/dist/DatePicker/DatePickerInput/index.d.ts +13 -0
  42. package/dist/DatePicker/DatePickerInput/index.js +206 -0
  43. package/dist/DatePicker/DatePickerInput/utils.d.ts +17 -0
  44. package/dist/DatePicker/DatePickerInput/utils.js +50 -0
  45. package/dist/DatePicker/index.d.ts +5 -0
  46. package/dist/DatePicker/index.js +5 -0
  47. package/dist/DatePicker/types.d.ts +86 -0
  48. package/dist/DatePicker/types.js +1 -0
  49. package/dist/DatePicker/utils/dateSelect.d.ts +29 -0
  50. package/dist/DatePicker/utils/dateSelect.js +145 -0
  51. package/dist/DatePicker/utils/locale.d.ts +38 -0
  52. package/dist/DatePicker/utils/locale.js +93 -0
  53. package/dist/DatePicker/utils/translations.d.ts +15 -0
  54. package/dist/DatePicker/utils/translations.js +10 -0
  55. package/dist/FocusTrap/index.d.ts +2 -2
  56. package/dist/PopoverContainer/PopoverContainer.js +3 -1
  57. package/dist/PopoverContainer/types.d.ts +5 -0
  58. package/dist/index.d.ts +2 -0
  59. package/dist/index.js +2 -0
  60. package/package.json +7 -6
@@ -0,0 +1,15 @@
1
+ import { StyleProps } from '@codecademy/variance';
2
+ import { conditionalStyles, inputSizeStyles } from '../../Form/styles';
3
+ interface SegmentedShellProps extends StyleProps<typeof conditionalStyles>, StyleProps<typeof inputSizeStyles> {
4
+ }
5
+ /**
6
+ * Shell uses the same style stack as `Input`. `formFieldStyles` targets `&:focus`, but the host is a
7
+ * `div` — focus is on inner spinbuttons, so we mirror `Input` focus visuals with `&:focus-within`.
8
+ */
9
+ export declare const SegmentedShell: import("@emotion/styled").StyledComponent<{
10
+ theme?: import("@emotion/react").Theme;
11
+ as?: React.ElementType;
12
+ } & import("../../Box").FlexBoxProps & Pick<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "slot" | "style" | "title" | "dir" | "children" | "className" | "aria-hidden" | "onAnimationStart" | "onDragStart" | "onDragEnd" | "onDrag" | keyof import("react").ClassAttributes<HTMLDivElement> | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "accessKey" | "autoCapitalize" | "autoFocus" | "contentEditable" | "contextMenu" | "draggable" | "enterKeyHint" | "hidden" | "id" | "lang" | "nonce" | "spellCheck" | "tabIndex" | "translate" | "radioGroup" | "role" | "about" | "content" | "datatype" | "inlist" | "prefix" | "property" | "rel" | "resource" | "rev" | "typeof" | "vocab" | "autoCorrect" | "autoSave" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "results" | "security" | "unselectable" | "inputMode" | "is" | "exportparts" | "part" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-braillelabel" | "aria-brailleroledescription" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colindextext" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-description" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowindextext" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDragCapture" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture"> & {
13
+ theme?: import("@emotion/react").Theme;
14
+ } & SegmentedShellProps, {}, {}>;
15
+ export {};
@@ -0,0 +1,19 @@
1
+ import _styled from "@emotion/styled/base";
2
+ import { css, theme } from '@codecademy/gamut-styles';
3
+ import { FlexBox } from '../../Box';
4
+ import { conditionalStyles, formFieldFocusStyles, formFieldStyles, inputSizeStyles } from '../../Form/styles';
5
+ /**
6
+ * Shell uses the same style stack as `Input`. `formFieldStyles` targets `&:focus`, but the host is a
7
+ * `div` — focus is on inner spinbuttons, so we mirror `Input` focus visuals with `&:focus-within`.
8
+ */
9
+ export const SegmentedShell = /*#__PURE__*/_styled(FlexBox, {
10
+ target: "ex306dz0",
11
+ label: "SegmentedShell"
12
+ })(formFieldStyles, conditionalStyles, inputSizeStyles, ({
13
+ variant
14
+ }) => css({
15
+ '&:focus-within': variant === 'error' ? {
16
+ borderColor: 'feedback-error',
17
+ boxShadow: `inset 0 0 0 1px ${theme.colors['feedback-error']}`
18
+ } : formFieldFocusStyles
19
+ }), process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9EYXRlUGlja2VyL0RhdGVQaWNrZXJJbnB1dC9lbGVtZW50cy50c3giXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBb0I4QiIsImZpbGUiOiIuLi8uLi8uLi9zcmMvRGF0ZVBpY2tlci9EYXRlUGlja2VySW5wdXQvZWxlbWVudHMudHN4Iiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgY3NzLCB0aGVtZSB9IGZyb20gJ0Bjb2RlY2FkZW15L2dhbXV0LXN0eWxlcyc7XG5pbXBvcnQgeyBTdHlsZVByb3BzIH0gZnJvbSAnQGNvZGVjYWRlbXkvdmFyaWFuY2UnO1xuaW1wb3J0IHN0eWxlZCBmcm9tICdAZW1vdGlvbi9zdHlsZWQnO1xuXG5pbXBvcnQgeyBGbGV4Qm94IH0gZnJvbSAnLi4vLi4vQm94JztcbmltcG9ydCB7XG4gIGNvbmRpdGlvbmFsU3R5bGVzLFxuICBmb3JtRmllbGRGb2N1c1N0eWxlcyxcbiAgZm9ybUZpZWxkU3R5bGVzLFxuICBpbnB1dFNpemVTdHlsZXMsXG59IGZyb20gJy4uLy4uL0Zvcm0vc3R5bGVzJztcblxuaW50ZXJmYWNlIFNlZ21lbnRlZFNoZWxsUHJvcHNcbiAgZXh0ZW5kcyBTdHlsZVByb3BzPHR5cGVvZiBjb25kaXRpb25hbFN0eWxlcz4sXG4gICAgU3R5bGVQcm9wczx0eXBlb2YgaW5wdXRTaXplU3R5bGVzPiB7fVxuXG4vKipcbiAqIFNoZWxsIHVzZXMgdGhlIHNhbWUgc3R5bGUgc3RhY2sgYXMgYElucHV0YC4gYGZvcm1GaWVsZFN0eWxlc2AgdGFyZ2V0cyBgJjpmb2N1c2AsIGJ1dCB0aGUgaG9zdCBpcyBhXG4gKiBgZGl2YCDigJQgZm9jdXMgaXMgb24gaW5uZXIgc3BpbmJ1dHRvbnMsIHNvIHdlIG1pcnJvciBgSW5wdXRgIGZvY3VzIHZpc3VhbHMgd2l0aCBgJjpmb2N1cy13aXRoaW5gLlxuICovXG5leHBvcnQgY29uc3QgU2VnbWVudGVkU2hlbGwgPSBzdHlsZWQoRmxleEJveCk8U2VnbWVudGVkU2hlbGxQcm9wcz4oXG4gIGZvcm1GaWVsZFN0eWxlcyxcbiAgY29uZGl0aW9uYWxTdHlsZXMsXG4gIGlucHV0U2l6ZVN0eWxlcyxcbiAgKHsgdmFyaWFudCB9KSA9PlxuICAgIGNzcyh7XG4gICAgICAnJjpmb2N1cy13aXRoaW4nOlxuICAgICAgICB2YXJpYW50ID09PSAnZXJyb3InXG4gICAgICAgICAgPyB7XG4gICAgICAgICAgICAgIGJvcmRlckNvbG9yOiAnZmVlZGJhY2stZXJyb3InLFxuICAgICAgICAgICAgICBib3hTaGFkb3c6IGBpbnNldCAwIDAgMCAxcHggJHt0aGVtZS5jb2xvcnNbJ2ZlZWRiYWNrLWVycm9yJ119YCxcbiAgICAgICAgICAgIH1cbiAgICAgICAgICA6IGZvcm1GaWVsZEZvY3VzU3R5bGVzLFxuICAgIH0pXG4pO1xuIl19 */");
@@ -0,0 +1,13 @@
1
+ import type { InputWrapperProps } from '../../Form/inputs/Input';
2
+ /**
3
+ * Props for DatePickerInput. When used inside DatePicker, only overrides (e.g. placeholder, label).
4
+ * In range mode, use rangePart to bind to start or end date. When outside DatePicker, pass value, onChange, etc.
5
+ */
6
+ export type DatePickerInputProps = Omit<InputWrapperProps, 'className' | 'type' | 'icon' | 'value' | 'onChange' | 'color'> & {
7
+ /** In range mode: which part of the range this input edits. Omit for single-date or combined display. */
8
+ rangePart?: 'start' | 'end';
9
+ };
10
+ export declare const DatePickerInput: import("react").ForwardRefExoticComponent<Omit<InputWrapperProps, "color" | "className" | "onChange" | "type" | "icon" | "value"> & {
11
+ /** In range mode: which part of the range this input edits. Omit for single-date or combined display. */
12
+ rangePart?: "start" | "end";
13
+ } & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,206 @@
1
+ import { MiniCalendarIcon } from '@codecademy/gamut-icons';
2
+ import { forwardRef, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
3
+ import { FlexBox } from '../../Box';
4
+ import { FormGroup } from '../../Form/elements/FormGroup';
5
+ import { useDatePicker } from '../DatePickerContext';
6
+ import { SegmentedShell } from './elements';
7
+ import { DatePickerInputSegment, getDateSegmentsFromDate, normalizeSegmentValues, parseSegmentsToDate, SegmentLiteral } from './Segment';
8
+ import { formatDateISO8601DateOnly, getDateFieldOrder, getDateFormatLayout } from './utils';
9
+
10
+ /**
11
+ * Props for DatePickerInput. When used inside DatePicker, only overrides (e.g. placeholder, label).
12
+ * In range mode, use rangePart to bind to start or end date. When outside DatePicker, pass value, onChange, etc.
13
+ */
14
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
15
+ export const DatePickerInput = /*#__PURE__*/forwardRef(({
16
+ disabled,
17
+ error,
18
+ form,
19
+ label,
20
+ name,
21
+ rangePart,
22
+ size = 'base',
23
+ ...rest
24
+ }, ref) => {
25
+ const context = useDatePicker();
26
+ if (context == null) {
27
+ throw new Error('DatePickerInput must be used inside a DatePicker (it reads shared state from context).');
28
+ }
29
+ const {
30
+ mode,
31
+ startOrSelectedDate,
32
+ setSelection,
33
+ openCalendar,
34
+ focusCalendarGrid,
35
+ locale,
36
+ isCalendarOpen,
37
+ translations
38
+ } = context;
39
+ const isRange = mode === 'range';
40
+ const endDate = isRange ? context.endDate : null;
41
+ const inputID = useId();
42
+ const inputId = `datepicker-input-${inputID.replace(/:/g, '')}`;
43
+ const {
44
+ layout,
45
+ fieldOrder
46
+ } = useMemo(() => {
47
+ const layout = getDateFormatLayout(locale);
48
+ return {
49
+ layout,
50
+ fieldOrder: getDateFieldOrder(layout)
51
+ };
52
+ }, [locale]);
53
+ const firstField = fieldOrder[0];
54
+ const firstFieldId = `${inputId}-${firstField}`;
55
+ const defaultLabel = !isRange ? translations.dateLabel : rangePart === 'end' ? translations.endDateLabel : translations.startDateLabel;
56
+ const boundDate = isRange && rangePart === 'end' ? endDate : startOrSelectedDate;
57
+ const segmentsFromBound = useCallback(() => getDateSegmentsFromDate(boundDate), [boundDate]);
58
+ const [segments, setSegments] = useState(segmentsFromBound);
59
+ const parsedForHidden = parseSegmentsToDate(segments);
60
+ const hiddenValue = parsedForHidden ? formatDateISO8601DateOnly(parsedForHidden) : '';
61
+
62
+ /** True only while a segment spinbutton is focused — avoids clobbering partial typing. Icon/shell-only focus must not set this or calendar picks won't sync to segments. */
63
+ const isInputFocusedRef = useRef(false);
64
+ const containerRef = useRef(null);
65
+ const segmentElRefs = useRef({});
66
+ const assignSegmentRef = useCallback((field, el) => {
67
+ segmentElRefs.current[field] = el;
68
+ }, []);
69
+ const focusSegmentField = useCallback(field => {
70
+ segmentElRefs.current[field]?.focus();
71
+ }, []);
72
+ const setShellRef = useCallback(el => {
73
+ containerRef.current = el;
74
+ if (typeof ref === 'function') ref(el);else if (ref != null) ref.current = el;
75
+ }, [ref]);
76
+ useEffect(() => {
77
+ if (!isInputFocusedRef.current) {
78
+ setSegments(segmentsFromBound());
79
+ }
80
+ }, [segmentsFromBound]);
81
+ const commitParsedDate = useCallback(parsed => {
82
+ if (isRange && rangePart) {
83
+ if (rangePart === 'start') setSelection(parsed, endDate);else setSelection(startOrSelectedDate, parsed);
84
+ } else setSelection(parsed);
85
+ }, [isRange, rangePart, setSelection, endDate, startOrSelectedDate]);
86
+ const clearSelection = useCallback(() => {
87
+ if (isRange && rangePart) {
88
+ if (rangePart === 'start') setSelection(null, endDate);else setSelection(startOrSelectedDate, null);
89
+ } else setSelection(null);
90
+ }, [isRange, rangePart, setSelection, endDate, startOrSelectedDate]);
91
+ const applySegments = useCallback(next => {
92
+ const parsed = parseSegmentsToDate(next);
93
+ if (parsed) commitParsedDate(parsed);else if (!next.month && !next.day && !next.year) clearSelection();
94
+ }, [clearSelection, commitParsedDate]);
95
+ const handleContainerBlur = e => {
96
+ if (containerRef.current?.contains(e.relatedTarget)) return;
97
+ isInputFocusedRef.current = false;
98
+ setSegments(prev => {
99
+ const normalized = normalizeSegmentValues(prev);
100
+ const parsed = parseSegmentsToDate(normalized);
101
+ if (parsed) {
102
+ commitParsedDate(parsed);
103
+ return normalized;
104
+ }
105
+ if (!normalized.month && !normalized.day && !normalized.year) {
106
+ clearSelection();
107
+ return getDateSegmentsFromDate(null);
108
+ }
109
+ return segmentsFromBound();
110
+ });
111
+ };
112
+ const setActiveRangePartForField = () => {
113
+ if (isRange && rangePart) context.setActiveRangePart(rangePart);
114
+ };
115
+ const handleSegmentFocus = () => {
116
+ isInputFocusedRef.current = true;
117
+ setActiveRangePartForField();
118
+ };
119
+
120
+ /** Focus entered the shell (segment, icon, etc.). Range targeting only — does not mark segment editing. */
121
+ const handleShellFocus = () => {
122
+ setActiveRangePartForField();
123
+ };
124
+
125
+ /** Pointer activation on the shell (bubbles from segments/icon). Ensures range targeting even if focus order differs from click. */
126
+ const handleShellClick = () => {
127
+ if (disabled) return;
128
+ setActiveRangePartForField();
129
+ openCalendar({
130
+ moveFocusIntoCalendar: false
131
+ });
132
+ };
133
+ const focusOrOpenCalendarGrid = () => {
134
+ if (isCalendarOpen) focusCalendarGrid();else openCalendar({
135
+ moveFocusIntoCalendar: true
136
+ });
137
+ };
138
+ return /*#__PURE__*/_jsx(FormGroup, {
139
+ htmlFor: firstFieldId,
140
+ isSoloField: true,
141
+ label: label ?? defaultLabel,
142
+ mb: 0,
143
+ pb: 0,
144
+ spacing: "tight",
145
+ width: "fit-content",
146
+ children: /*#__PURE__*/_jsxs(SegmentedShell, {
147
+ inputSize: size === 'small' ? 'small' : 'base',
148
+ ref: setShellRef,
149
+ role: "group",
150
+ variant: error ? 'error' : undefined,
151
+ width: "113px",
152
+ onBlur: handleContainerBlur,
153
+ onClick: handleShellClick,
154
+ onFocus: handleShellFocus,
155
+ ...rest,
156
+ children: [/*#__PURE__*/_jsx(FlexBox, {
157
+ alignItems: "center",
158
+ justifyContent: "center",
159
+ children: layout.map((item, index) => {
160
+ if (item.kind === 'literal') {
161
+ return /*#__PURE__*/_jsx(SegmentLiteral, {
162
+ "aria-hidden": true
163
+ // eslint-disable-next-line react/no-array-index-key
164
+ ,
165
+ children: `${item.text}`
166
+ }, `literal-${item.text}-${index}`);
167
+ }
168
+ const idx = fieldOrder.indexOf(item.field);
169
+ const prevField = idx > 0 ? fieldOrder[idx - 1] : null;
170
+ const nextField = idx < fieldOrder.length - 1 ? fieldOrder[idx + 1] : null;
171
+ return /*#__PURE__*/_jsx(DatePickerInputSegment, {
172
+ applySegments: applySegments,
173
+ assignSegmentRef: assignSegmentRef,
174
+ disabled: !!disabled,
175
+ error: !!error,
176
+ field: item.field,
177
+ focusOrOpenCalendarGrid: focusOrOpenCalendarGrid,
178
+ focusSegmentField: focusSegmentField,
179
+ handleOnFocus: handleSegmentFocus,
180
+ nextField: nextField,
181
+ prevField: prevField,
182
+ segments: segments,
183
+ setSegments: setSegments
184
+ }, item.field);
185
+ })
186
+ }), /*#__PURE__*/_jsx("input", {
187
+ "aria-hidden": true,
188
+ form: form,
189
+ name: name,
190
+ tabIndex: -1,
191
+ type: "hidden",
192
+ value: hiddenValue
193
+ }), /*#__PURE__*/_jsx(FlexBox, {
194
+ alignItems: "center",
195
+ justifyContent: "center",
196
+ pl: 16,
197
+ pr: 8,
198
+ role: "presentation",
199
+ children: /*#__PURE__*/_jsx(MiniCalendarIcon, {
200
+ "aria-hidden": true,
201
+ size: 16
202
+ })
203
+ })]
204
+ })
205
+ });
206
+ });
@@ -0,0 +1,17 @@
1
+ /** Single date field in locale order (from `Intl.DateTimeFormat#formatToParts`). */
2
+ export type DatePartKind = 'month' | 'day' | 'year';
3
+ export type DateFormatLayoutItem = {
4
+ kind: 'field';
5
+ field: DatePartKind;
6
+ } | {
7
+ kind: 'literal';
8
+ text: string;
9
+ };
10
+ /**
11
+ * Month/day/year order and literal separators for the locale (e.g. MM/DD/YYYY vs DD/MM/YYYY).
12
+ */
13
+ export declare const getDateFormatLayout: (locale: Intl.Locale) => DateFormatLayoutItem[];
14
+ /** Focus / tab order for the three fields (locale order). */
15
+ export declare const getDateFieldOrder: (layout: DateFormatLayoutItem[]) => DatePartKind[];
16
+ /** ISO 8601 date-only string for hidden form fields. */
17
+ export declare const formatDateISO8601DateOnly: (date: Date) => string;
@@ -0,0 +1,50 @@
1
+ import { stringifyLocale } from '../utils/locale';
2
+
3
+ /** Single date field in locale order (from `Intl.DateTimeFormat#formatToParts`). */
4
+
5
+ /**
6
+ * Month/day/year order and literal separators for the locale (e.g. MM/DD/YYYY vs DD/MM/YYYY).
7
+ */
8
+ export const getDateFormatLayout = locale => {
9
+ const parts = new Intl.DateTimeFormat(stringifyLocale(locale), {
10
+ year: 'numeric',
11
+ month: '2-digit',
12
+ day: '2-digit'
13
+ }).formatToParts(new Date(2025, 10, 15));
14
+ const items = [];
15
+ for (const part of parts) {
16
+ if (part.type === 'month') items.push({
17
+ kind: 'field',
18
+ field: 'month'
19
+ });else if (part.type === 'day') items.push({
20
+ kind: 'field',
21
+ field: 'day'
22
+ });else if (part.type === 'year') items.push({
23
+ kind: 'field',
24
+ field: 'year'
25
+ });else if (part.type === 'literal') items.push({
26
+ kind: 'literal',
27
+ text: part.value
28
+ });
29
+ }
30
+ return items;
31
+ };
32
+
33
+ /** Focus / tab order for the three fields (locale order). */
34
+ export const getDateFieldOrder = layout => {
35
+ const order = [];
36
+ for (const item of layout) {
37
+ if (item.kind === 'field' && !order.includes(item.field)) {
38
+ order.push(item.field);
39
+ }
40
+ }
41
+ return order.length === 3 ? order : ['month', 'day', 'year'];
42
+ };
43
+
44
+ /** ISO 8601 date-only string for hidden form fields. */
45
+ export const formatDateISO8601DateOnly = date => {
46
+ const y = date.getFullYear();
47
+ const m = date.getMonth() + 1;
48
+ const d = date.getDate();
49
+ return `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
50
+ };
@@ -0,0 +1,5 @@
1
+ export * from './DatePicker';
2
+ export * from './DatePickerContext';
3
+ export * from './DatePickerCalendar';
4
+ export * from './DatePickerInput';
5
+ export type { IsoWeekday } from './utils/locale';
@@ -0,0 +1,5 @@
1
+ export * from './DatePicker';
2
+ export * from './DatePickerContext';
3
+ export * from './DatePickerCalendar';
4
+ export * from './DatePickerInput';
5
+ export {};
@@ -0,0 +1,86 @@
1
+ import { ComponentProps } from 'react';
2
+ import { Input } from '../Form/inputs/Input';
3
+ import { CalendarBaseProps } from './Calendar/types';
4
+ import { DatePickerTranslations } from './utils/translations';
5
+ export interface DatePickerBaseProps extends Pick<CalendarBaseProps, 'locale' | 'disabledDates'> {
6
+ /** When provided, only the provider is rendered and children compose Input + Calendar. */
7
+ children?: React.ReactNode;
8
+ /** Placeholder for the input. */
9
+ placeholder?: string;
10
+ /** Override UI strings (e.g. clear button). Merged with defaults. */
11
+ translations?: DatePickerTranslations;
12
+ inputSize?: ComponentProps<typeof Input>['size'];
13
+ }
14
+ export interface DatePickerSingleProps extends DatePickerBaseProps {
15
+ mode?: 'single';
16
+ /** Controlled selected date. */
17
+ selectedDate: Date | null;
18
+ /** Called when the user selects a date. */
19
+ setSelectedDate: (date: Date | null) => void;
20
+ /** Label for the input. */
21
+ label?: string;
22
+ }
23
+ export interface DatePickerRangeProps extends DatePickerBaseProps {
24
+ mode: 'range';
25
+ /** Controlled start date. */
26
+ startDate: Date | null;
27
+ /** Controlled end date. */
28
+ endDate: Date | null;
29
+ /** Called when the user changes the start date. */
30
+ setStartDate: (date: Date | null) => void;
31
+ /** Called when the user changes the end date. */
32
+ setEndDate: (date: Date | null) => void;
33
+ /** Label for the start date input. */
34
+ startLabel?: string;
35
+ /** Label for the end date input. */
36
+ endLabel?: string;
37
+ }
38
+ export type DatePickerProps = DatePickerSingleProps | DatePickerRangeProps;
39
+ export type OpenCalendarOptions = {
40
+ /**
41
+ * When true, move DOM focus into the date grid after open (keyboard / explicit request).
42
+ * When false (default), keep focus on the input so pointer users can type (WCAG 3.2.1).
43
+ */
44
+ moveFocusIntoCalendar?: boolean;
45
+ };
46
+ export interface DatePickerBaseContextValue extends Pick<CalendarBaseProps, 'disabledDates'> {
47
+ /**
48
+ * Resolved `Intl.Locale` from the `locale` prop (or runtime default). Same instance passed to
49
+ * formatters and available for `getWeekInfo()` etc.
50
+ */
51
+ locale: Intl.Locale;
52
+ isCalendarOpen: boolean;
53
+ openCalendar: (options?: OpenCalendarOptions) => void;
54
+ /** Move focus from the input into the grid when the calendar is already open (e.g. ArrowDown). */
55
+ focusCalendarGrid: () => void;
56
+ /**
57
+ * Flips on each grid focus request so `CalendarBody` effects re-run when `focusTarget` is unchanged.
58
+ * Not a semantic true/false — only the change matters; pair with `gridFocusRequested`.
59
+ */
60
+ focusGridSignal: boolean;
61
+ /** When true, `CalendarBody` runs a one-shot move of DOM focus into the grid if it is not already there. */
62
+ gridFocusRequested: boolean;
63
+ /** Clears `gridFocusRequested` after focus has moved into the grid (or call when closing). */
64
+ clearGridFocusRequest: () => void;
65
+ closeCalendar: () => void;
66
+ calendarDialogId: string;
67
+ /** UI string overrides (e.g. clear button). */
68
+ translations: Required<DatePickerTranslations>;
69
+ /** Start date (range) or selected date (single). */
70
+ startOrSelectedDate: Date | null;
71
+ /** Set selection. Single: (date). Range: (start, end). */
72
+ setSelection: (startOrSelectedDate: Date | null, endDate?: Date | null) => void;
73
+ }
74
+ export interface DatePickerSingleContextValue extends DatePickerBaseContextValue {
75
+ mode: 'single';
76
+ }
77
+ export type ActiveRangePart = 'start' | 'end' | null;
78
+ export interface DatePickerRangeContextValue extends DatePickerBaseContextValue {
79
+ mode: 'range';
80
+ endDate: Date | null;
81
+ /** Which input is active (start/end focused); null = selection mode. */
82
+ activeRangePart: ActiveRangePart;
83
+ /** Set which input is active (e.g. when input receives focus). */
84
+ setActiveRangePart: (part: ActiveRangePart) => void;
85
+ }
86
+ export type DatePickerContextValue = DatePickerSingleContextValue | DatePickerRangeContextValue;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,29 @@
1
+ import type { DatePickerBaseContextValue, DatePickerProps, DatePickerRangeContextValue, DatePickerRangeProps } from '../types';
2
+ export declare const isRangeProps: (props: DatePickerProps) => props is DatePickerRangeProps;
3
+ export type RangeContainsDisabledParams = {
4
+ start: Date;
5
+ end: Date;
6
+ disabledDates: Date[];
7
+ };
8
+ /** True if any disabled date falls within [start, end] (inclusive, by calendar day). */
9
+ export declare const rangeContainsDisabled: ({ start, end, disabledDates, }: RangeContainsDisabledParams) => boolean;
10
+ export type HandleDateSelectSingleParams = {
11
+ date: Date;
12
+ selectedDate: DatePickerBaseContextValue['startOrSelectedDate'];
13
+ } & Pick<DatePickerBaseContextValue, 'setSelection'>;
14
+ export declare const handleDateSelectSingle: ({ date, selectedDate, setSelection, }: HandleDateSelectSingleParams) => void;
15
+ type ApplyRangeOrNewStartParams = {
16
+ start: Date;
17
+ end: Date;
18
+ clickedDate: Date;
19
+ disabledDates: Date[];
20
+ } & Pick<DatePickerBaseContextValue, 'setSelection'>;
21
+ /** @returns whether a full start+end range was committed (calendar may close). */
22
+ export declare const applyRangeOrNewStart: ({ start, end, clickedDate, disabledDates, setSelection, }: ApplyRangeOrNewStartParams) => boolean;
23
+ export type HandleDateSelectRangeParams = {
24
+ date: Date;
25
+ startDate: DatePickerRangeContextValue['startOrSelectedDate'];
26
+ } & Pick<DatePickerRangeContextValue, 'activeRangePart' | 'endDate' | 'setSelection' | 'disabledDates'>;
27
+ /** @returns whether the calendar should close (full range selected and committed). */
28
+ export declare const handleDateSelectRange: ({ date, activeRangePart, startDate, endDate, setSelection, disabledDates, }: HandleDateSelectRangeParams) => boolean;
29
+ export {};
@@ -0,0 +1,145 @@
1
+ import { isDateInRange, isSameDay } from '../Calendar/utils/dateGrid';
2
+ export const isRangeProps = props => props.mode === 'range';
3
+ /** True if any disabled date falls within [start, end] (inclusive, by calendar day). */
4
+ export const rangeContainsDisabled = ({
5
+ start,
6
+ end,
7
+ disabledDates
8
+ }) => {
9
+ return disabledDates.some(date => isSameDay(date, start) || isSameDay(date, end) || isDateInRange(date, start, end));
10
+ };
11
+ export const handleDateSelectSingle = ({
12
+ date,
13
+ selectedDate,
14
+ setSelection
15
+ }) => {
16
+ // If clicked date is the same as Start Date: Clear Start Date
17
+ if (selectedDate && date.getTime() === selectedDate.getTime()) {
18
+ setSelection(null);
19
+ return;
20
+ }
21
+ // If clicked date is not the same as Start Date: Set Start Date to clicked date
22
+ setSelection(date);
23
+ };
24
+ /** @returns whether a full start+end range was committed (calendar may close). */
25
+ export const applyRangeOrNewStart = ({
26
+ start,
27
+ end,
28
+ clickedDate,
29
+ disabledDates,
30
+ setSelection
31
+ }) => {
32
+ // if range contains disabled dates, set start date to clicked date and end date to null
33
+ if (rangeContainsDisabled({
34
+ start,
35
+ end,
36
+ disabledDates
37
+ })) {
38
+ setSelection(clickedDate, null);
39
+ return false;
40
+ }
41
+ setSelection(start, end);
42
+ return true;
43
+ };
44
+ /** @returns whether the calendar should close (full range selected and committed). */
45
+ export const handleDateSelectRange = ({
46
+ date,
47
+ activeRangePart,
48
+ startDate,
49
+ endDate,
50
+ setSelection,
51
+ disabledDates = []
52
+ }) => {
53
+ // Range mode: field targeting (start or end input was focused)
54
+ if (activeRangePart === 'start') {
55
+ if (date.getTime() === startDate?.getTime()) {
56
+ setSelection(null, endDate);
57
+ return false;
58
+ }
59
+ const newEnd = endDate != null && date.getTime() <= endDate.getTime() ? endDate : null;
60
+ if (newEnd != null) {
61
+ return applyRangeOrNewStart({
62
+ start: date,
63
+ end: newEnd,
64
+ clickedDate: date,
65
+ disabledDates,
66
+ setSelection
67
+ });
68
+ }
69
+ setSelection(date, newEnd);
70
+ return false;
71
+ }
72
+ if (activeRangePart === 'end') {
73
+ if (date.getTime() === endDate?.getTime()) {
74
+ setSelection(startDate, null);
75
+ return false;
76
+ }
77
+ const newStart = startDate != null && date.getTime() >= startDate.getTime() ? startDate : null;
78
+ if (newStart != null) {
79
+ return applyRangeOrNewStart({
80
+ start: newStart,
81
+ end: date,
82
+ clickedDate: date,
83
+ disabledDates,
84
+ setSelection
85
+ });
86
+ }
87
+ setSelection(newStart, date);
88
+ return false;
89
+ }
90
+
91
+ // Range selection mode (no field focused: calendar drives both)
92
+ if (startDate && endDate) {
93
+ // if start date is end date and is clicked, clears everything
94
+ if (startDate.getTime() === endDate.getTime() && date.getTime() === startDate.getTime()) {
95
+ setSelection(null, null);
96
+ return false;
97
+ }
98
+ // if clicked on start date, end date becomes start date
99
+ if (date.getTime() === startDate.getTime()) {
100
+ setSelection(endDate, null);
101
+ return false;
102
+ }
103
+ // if clicked on end date, clears end date and start remains
104
+ if (date.getTime() === endDate.getTime()) {
105
+ setSelection(startDate, null);
106
+ return false;
107
+ }
108
+ // If clicked date > Start: Updates End Date to new date (Start remains)
109
+ if (date.getTime() > startDate.getTime()) {
110
+ return applyRangeOrNewStart({
111
+ start: startDate,
112
+ end: date,
113
+ clickedDate: date,
114
+ disabledDates,
115
+ setSelection
116
+ });
117
+ }
118
+ // If clicked date < Start: Updates Start Date to new date (End remains) - extends range to the left
119
+ return applyRangeOrNewStart({
120
+ start: date,
121
+ end: endDate,
122
+ clickedDate: date,
123
+ disabledDates,
124
+ setSelection
125
+ });
126
+ }
127
+ // Start is Set, End is Empty
128
+ if (startDate && !endDate) {
129
+ // If clicked date < Start: Restarts selection with clicked date as new Start
130
+ if (date.getTime() < startDate.getTime()) {
131
+ setSelection(date, null);
132
+ return false;
133
+ }
134
+ // If clicked date > Start: Sets it as End Date (if range valid)
135
+ return applyRangeOrNewStart({
136
+ start: startDate,
137
+ end: date,
138
+ clickedDate: date,
139
+ disabledDates,
140
+ setSelection
141
+ });
142
+ }
143
+ setSelection(date, null);
144
+ return false;
145
+ };
@@ -0,0 +1,38 @@
1
+ import '@formatjs/intl-locale/polyfill.js';
2
+ /**
3
+ * The runtime default locale string (user agent), matching what `Intl` uses when no locale is passed.
4
+ */
5
+ export declare const getDefaultLocaleTag: () => string;
6
+ /**
7
+ * Resolves `Intl.LocalesArgument` (or `undefined`) to a stable `Intl.Locale` instance for formatting
8
+ * and locale metadata (e.g. `getWeekInfo()` in supporting environments).
9
+ *
10
+ * - `undefined` → default runtime locale via {@link getDefaultLocaleTag}
11
+ * - `Intl.Locale` → returned as-is (no duplicate allocation)
12
+ * - `string` / `readonly string[]` → `new Intl.Locale(...)`
13
+ */
14
+ export declare const resolveLocale: (locales?: Intl.LocalesArgument) => Intl.Locale;
15
+ /**
16
+ * Memoized {@link resolveLocale} for calendar subcomponents. Pass the same `locale` prop you accept
17
+ * from `CalendarBaseProps` (optional `Intl.LocalesArgument`).
18
+ */
19
+ export declare const useResolvedLocale: (locale?: Intl.LocalesArgument) => Intl.Locale;
20
+ /**
21
+ * Convert an Intl.Locale to a string. This is necessary the Intl.DateTimeFormat constructor only accepts a string in some versions of TS.
22
+ * @param locale - The Intl.Locale to convert to a string.
23
+ * @returns The stringified locale.
24
+ */
25
+ export declare const stringifyLocale: (locale: Intl.Locale) => string;
26
+ /** ISO weekday: 1 = Monday … 7 = Sunday (matches `Intl.Locale#getWeekInfo().firstDay`). */
27
+ export type IsoWeekday = 1 | 2 | 3 | 4 | 5 | 6 | 7;
28
+ /**
29
+ * First calendar column weekday from `locale` (via `getWeekInfo()`), or explicit override.
30
+ * - `weekStartsOnOverride` — ISO weekday **1–7** (Monday … Sunday), same as `getWeekInfo().firstDay`
31
+ * - omitted → `locale.getWeekInfo().firstDay` when available, else **7** (Sunday)
32
+ */
33
+ export declare const getIsoFirstDayFromLocale: (locale: Intl.Locale, weekStartsOnOverride?: IsoWeekday) => IsoWeekday;
34
+ /**
35
+ * Hook: resolved first weekday for the calendar grid. Re-reads after mount so async polyfills
36
+ * (e.g. Firefox) can install `getWeekInfo` before the first paint in some bundles.
37
+ */
38
+ export declare const useIsoFirstWeekday: (locale: Intl.Locale, weekStartsOnOverride?: IsoWeekday) => IsoWeekday;