@codecademy/gamut 68.2.3-alpha.df22d8.0 → 68.2.3-alpha.e0ecfc.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.
- package/dist/DatePicker/Calendar/CalendarBody.d.ts +3 -0
- package/dist/DatePicker/Calendar/CalendarBody.js +143 -0
- package/dist/DatePicker/Calendar/CalendarFooter.d.ts +3 -0
- package/dist/DatePicker/Calendar/CalendarFooter.js +57 -0
- package/dist/DatePicker/Calendar/CalendarHeader.d.ts +3 -0
- package/dist/DatePicker/Calendar/CalendarHeader.js +46 -0
- package/dist/DatePicker/Calendar/CalendarNavLastMonth.d.ts +3 -0
- package/dist/DatePicker/Calendar/CalendarNavLastMonth.js +30 -0
- package/dist/DatePicker/Calendar/CalendarNavNextMonth.d.ts +3 -0
- package/dist/DatePicker/Calendar/CalendarNavNextMonth.js +30 -0
- package/dist/DatePicker/Calendar/CalendarWrapper.d.ts +8 -0
- package/dist/DatePicker/Calendar/CalendarWrapper.js +27 -0
- package/dist/DatePicker/Calendar/index.d.ts +6 -0
- package/dist/DatePicker/Calendar/index.js +6 -0
- package/dist/DatePicker/Calendar/types.d.ts +77 -0
- package/dist/DatePicker/Calendar/types.js +1 -0
- package/dist/DatePicker/Calendar/utils/dateGrid.d.ts +38 -0
- package/dist/DatePicker/Calendar/utils/dateGrid.js +103 -0
- package/dist/DatePicker/Calendar/utils/elements.d.ts +18 -0
- package/dist/DatePicker/Calendar/utils/elements.js +116 -0
- package/dist/DatePicker/Calendar/utils/format.d.ts +28 -0
- package/dist/DatePicker/Calendar/utils/format.js +72 -0
- package/dist/DatePicker/Calendar/utils/keyHandler.d.ts +12 -0
- package/dist/DatePicker/Calendar/utils/keyHandler.js +126 -0
- package/dist/DatePicker/Calendar/utils/validation.d.ts +13 -0
- package/dist/DatePicker/Calendar/utils/validation.js +23 -0
- package/dist/DatePicker/DatePicker.d.ts +7 -0
- package/dist/DatePicker/DatePicker.js +157 -0
- package/dist/DatePicker/DatePickerCalendar.d.ts +11 -0
- package/dist/DatePicker/DatePickerCalendar.js +163 -0
- package/dist/DatePicker/DatePickerContext.d.ts +10 -0
- package/dist/DatePicker/DatePickerContext.js +18 -0
- package/dist/DatePicker/DatePickerInput/Segment/DatePickerInputSegment.d.ts +20 -0
- package/dist/DatePicker/DatePickerInput/Segment/DatePickerInputSegment.js +138 -0
- package/dist/DatePicker/DatePickerInput/Segment/elements.d.ts +16 -0
- package/dist/DatePicker/DatePickerInput/Segment/elements.js +34 -0
- package/dist/DatePicker/DatePickerInput/Segment/index.d.ts +4 -0
- package/dist/DatePicker/DatePickerInput/Segment/index.js +3 -0
- package/dist/DatePicker/DatePickerInput/Segment/segmentUtils.d.ts +49 -0
- package/dist/DatePicker/DatePickerInput/Segment/segmentUtils.js +202 -0
- package/dist/DatePicker/DatePickerInput/elements.d.ts +15 -0
- package/dist/DatePicker/DatePickerInput/elements.js +19 -0
- package/dist/DatePicker/DatePickerInput/index.d.ts +13 -0
- package/dist/DatePicker/DatePickerInput/index.js +187 -0
- package/dist/DatePicker/DatePickerInput/utils.d.ts +17 -0
- package/dist/DatePicker/DatePickerInput/utils.js +50 -0
- package/dist/DatePicker/index.d.ts +5 -0
- package/dist/DatePicker/index.js +5 -0
- package/dist/DatePicker/types.d.ts +86 -0
- package/dist/DatePicker/types.js +1 -0
- package/dist/DatePicker/utils/dateSelect.d.ts +19 -0
- package/dist/DatePicker/utils/dateSelect.js +146 -0
- package/dist/DatePicker/utils/locale.d.ts +38 -0
- package/dist/DatePicker/utils/locale.js +93 -0
- package/dist/DatePicker/utils/translations.d.ts +15 -0
- package/dist/DatePicker/utils/translations.js +10 -0
- package/dist/FocusTrap/index.d.ts +2 -2
- package/dist/PopoverContainer/PopoverContainer.js +23 -3
- package/dist/PopoverContainer/types.d.ts +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- 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,187 @@
|
|
|
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
|
+
const isInputFocusedRef = useRef(false);
|
|
62
|
+
const containerRef = useRef(null);
|
|
63
|
+
const setShellRef = useCallback(el => {
|
|
64
|
+
containerRef.current = el;
|
|
65
|
+
if (typeof ref === 'function') ref(el);else if (ref != null) ref.current = el;
|
|
66
|
+
}, [ref]);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (!isInputFocusedRef.current) {
|
|
69
|
+
setSegments(segmentsFromBound());
|
|
70
|
+
}
|
|
71
|
+
}, [segmentsFromBound]);
|
|
72
|
+
const commitParsedDate = useCallback(parsed => {
|
|
73
|
+
if (isRange && rangePart) {
|
|
74
|
+
if (rangePart === 'start') setSelection(parsed, endDate);else setSelection(startOrSelectedDate, parsed);
|
|
75
|
+
} else setSelection(parsed);
|
|
76
|
+
}, [isRange, rangePart, setSelection, endDate, startOrSelectedDate]);
|
|
77
|
+
const clearSelection = useCallback(() => {
|
|
78
|
+
if (isRange && rangePart) {
|
|
79
|
+
if (rangePart === 'start') setSelection(null, endDate);else setSelection(startOrSelectedDate, null);
|
|
80
|
+
} else setSelection(null);
|
|
81
|
+
}, [isRange, rangePart, setSelection, endDate, startOrSelectedDate]);
|
|
82
|
+
const applySegments = useCallback(next => {
|
|
83
|
+
const parsed = parseSegmentsToDate(next);
|
|
84
|
+
if (parsed) commitParsedDate(parsed);else if (!next.month && !next.day && !next.year) clearSelection();
|
|
85
|
+
}, [clearSelection, commitParsedDate]);
|
|
86
|
+
const handleContainerBlur = e => {
|
|
87
|
+
if (containerRef.current?.contains(e.relatedTarget)) return;
|
|
88
|
+
isInputFocusedRef.current = false;
|
|
89
|
+
setSegments(prev => {
|
|
90
|
+
const normalized = normalizeSegmentValues(prev);
|
|
91
|
+
const parsed = parseSegmentsToDate(normalized);
|
|
92
|
+
if (parsed) {
|
|
93
|
+
commitParsedDate(parsed);
|
|
94
|
+
return normalized;
|
|
95
|
+
}
|
|
96
|
+
if (!normalized.month && !normalized.day && !normalized.year) {
|
|
97
|
+
clearSelection();
|
|
98
|
+
return getDateSegmentsFromDate(null);
|
|
99
|
+
}
|
|
100
|
+
return segmentsFromBound();
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
const handleContainerFocus = () => {
|
|
104
|
+
isInputFocusedRef.current = true;
|
|
105
|
+
};
|
|
106
|
+
const handleSegmentFocus = () => {
|
|
107
|
+
handleContainerFocus();
|
|
108
|
+
if (isRange && rangePart) context.setActiveRangePart(rangePart);
|
|
109
|
+
};
|
|
110
|
+
const handleOpenCalendar = () => {
|
|
111
|
+
if (!disabled) openCalendar({
|
|
112
|
+
moveFocusIntoCalendar: false
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
const focusOrOpenCalendarGrid = () => {
|
|
116
|
+
if (isCalendarOpen) focusCalendarGrid();else openCalendar({
|
|
117
|
+
moveFocusIntoCalendar: true
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
return /*#__PURE__*/_jsx(FormGroup, {
|
|
121
|
+
htmlFor: firstFieldId,
|
|
122
|
+
isSoloField: true,
|
|
123
|
+
label: label ?? defaultLabel,
|
|
124
|
+
mb: 0,
|
|
125
|
+
pb: 0,
|
|
126
|
+
spacing: "tight",
|
|
127
|
+
width: "fit-content",
|
|
128
|
+
children: /*#__PURE__*/_jsxs(SegmentedShell, {
|
|
129
|
+
inputSize: size === 'small' ? 'small' : 'base',
|
|
130
|
+
ref: setShellRef,
|
|
131
|
+
role: "group",
|
|
132
|
+
variant: error ? 'error' : undefined,
|
|
133
|
+
width: "113px",
|
|
134
|
+
onBlur: handleContainerBlur,
|
|
135
|
+
onFocus: handleContainerFocus,
|
|
136
|
+
...rest,
|
|
137
|
+
children: [/*#__PURE__*/_jsx(FlexBox, {
|
|
138
|
+
alignItems: "center",
|
|
139
|
+
justifyContent: "center",
|
|
140
|
+
children: layout.map((item, index) => {
|
|
141
|
+
if (item.kind === 'literal') {
|
|
142
|
+
return /*#__PURE__*/_jsx(SegmentLiteral, {
|
|
143
|
+
"aria-hidden": true
|
|
144
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
145
|
+
,
|
|
146
|
+
children: `${item.text}`
|
|
147
|
+
}, `literal-${item.text}-${index}`);
|
|
148
|
+
}
|
|
149
|
+
const idx = fieldOrder.indexOf(item.field);
|
|
150
|
+
const prevField = idx > 0 ? fieldOrder[idx - 1] : null;
|
|
151
|
+
const nextField = idx < fieldOrder.length - 1 ? fieldOrder[idx + 1] : null;
|
|
152
|
+
return /*#__PURE__*/_jsx(DatePickerInputSegment, {
|
|
153
|
+
applySegments: applySegments,
|
|
154
|
+
disabled: Boolean(disabled),
|
|
155
|
+
error: Boolean(error),
|
|
156
|
+
field: item.field,
|
|
157
|
+
focusOrOpenCalendarGrid: focusOrOpenCalendarGrid,
|
|
158
|
+
handleOnClick: handleOpenCalendar,
|
|
159
|
+
handleOnFocus: handleSegmentFocus,
|
|
160
|
+
nextField: nextField,
|
|
161
|
+
prevField: prevField,
|
|
162
|
+
segments: segments,
|
|
163
|
+
setSegments: setSegments
|
|
164
|
+
}, item.field);
|
|
165
|
+
})
|
|
166
|
+
}), /*#__PURE__*/_jsx("input", {
|
|
167
|
+
"aria-hidden": true,
|
|
168
|
+
form: form,
|
|
169
|
+
name: name,
|
|
170
|
+
tabIndex: -1,
|
|
171
|
+
type: "hidden",
|
|
172
|
+
value: hiddenValue
|
|
173
|
+
}), /*#__PURE__*/_jsx(FlexBox, {
|
|
174
|
+
alignItems: "center",
|
|
175
|
+
justifyContent: "center",
|
|
176
|
+
pl: 16,
|
|
177
|
+
pr: 8,
|
|
178
|
+
role: "presentation",
|
|
179
|
+
onClick: handleOpenCalendar,
|
|
180
|
+
children: /*#__PURE__*/_jsx(MiniCalendarIcon, {
|
|
181
|
+
"aria-hidden": true,
|
|
182
|
+
size: 16
|
|
183
|
+
})
|
|
184
|
+
})]
|
|
185
|
+
})
|
|
186
|
+
});
|
|
187
|
+
});
|
|
@@ -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,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,19 @@
|
|
|
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
|
+
export type HandleDateSelectRangeParams = {
|
|
16
|
+
date: Date;
|
|
17
|
+
startDate: DatePickerRangeContextValue['startOrSelectedDate'];
|
|
18
|
+
} & Pick<DatePickerRangeContextValue, 'activeRangePart' | 'endDate' | 'setSelection' | 'disabledDates'>;
|
|
19
|
+
export declare const handleDateSelectRange: ({ date, activeRangePart, startDate, endDate, setSelection, disabledDates, }: HandleDateSelectRangeParams) => void;
|
|
@@ -0,0 +1,146 @@
|
|
|
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
|
+
const applyRangeOrNewStart = ({
|
|
25
|
+
start,
|
|
26
|
+
end,
|
|
27
|
+
clickedDate,
|
|
28
|
+
disabledDates,
|
|
29
|
+
setSelection
|
|
30
|
+
}) => {
|
|
31
|
+
// if range contains disabled dates, set start date to clicked date and end date to null
|
|
32
|
+
if (rangeContainsDisabled({
|
|
33
|
+
start,
|
|
34
|
+
end,
|
|
35
|
+
disabledDates
|
|
36
|
+
})) {
|
|
37
|
+
setSelection(clickedDate, null);
|
|
38
|
+
} else {
|
|
39
|
+
setSelection(start, end);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
export const handleDateSelectRange = ({
|
|
43
|
+
date,
|
|
44
|
+
activeRangePart,
|
|
45
|
+
startDate,
|
|
46
|
+
endDate,
|
|
47
|
+
setSelection,
|
|
48
|
+
disabledDates = []
|
|
49
|
+
}) => {
|
|
50
|
+
// Range mode: field targeting (start or end input was focused)
|
|
51
|
+
if (activeRangePart === 'start') {
|
|
52
|
+
if (date.getTime() === startDate?.getTime()) {
|
|
53
|
+
setSelection(null, endDate);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const newEnd = endDate != null && date.getTime() <= endDate.getTime() ? endDate : null;
|
|
57
|
+
if (newEnd != null) {
|
|
58
|
+
applyRangeOrNewStart({
|
|
59
|
+
start: date,
|
|
60
|
+
end: newEnd,
|
|
61
|
+
clickedDate: date,
|
|
62
|
+
disabledDates,
|
|
63
|
+
setSelection
|
|
64
|
+
});
|
|
65
|
+
} else {
|
|
66
|
+
setSelection(date, newEnd);
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (activeRangePart === 'end') {
|
|
71
|
+
if (date.getTime() === endDate?.getTime()) {
|
|
72
|
+
setSelection(startDate, null);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const newStart = startDate != null && date.getTime() >= startDate.getTime() ? startDate : null;
|
|
76
|
+
if (newStart != null) {
|
|
77
|
+
applyRangeOrNewStart({
|
|
78
|
+
start: newStart,
|
|
79
|
+
end: date,
|
|
80
|
+
clickedDate: date,
|
|
81
|
+
disabledDates,
|
|
82
|
+
setSelection
|
|
83
|
+
});
|
|
84
|
+
} else {
|
|
85
|
+
setSelection(newStart, date);
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Range selection mode (no field focused: calendar drives both)
|
|
91
|
+
if (startDate && endDate) {
|
|
92
|
+
// if start date is end date and is clicked, clears everything
|
|
93
|
+
if (startDate.getTime() === endDate.getTime() && date.getTime() === startDate.getTime()) {
|
|
94
|
+
setSelection(null, null);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// if clicked on start date, end date becomes start date
|
|
98
|
+
if (date.getTime() === startDate.getTime()) {
|
|
99
|
+
setSelection(endDate, null);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// if clicked on end date, clears end date and start remains
|
|
103
|
+
if (date.getTime() === endDate.getTime()) {
|
|
104
|
+
setSelection(startDate, null);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
// If clicked date > Start: Updates End Date to new date (Start remains)
|
|
108
|
+
if (date.getTime() > startDate.getTime()) {
|
|
109
|
+
applyRangeOrNewStart({
|
|
110
|
+
start: startDate,
|
|
111
|
+
end: date,
|
|
112
|
+
clickedDate: date,
|
|
113
|
+
disabledDates,
|
|
114
|
+
setSelection
|
|
115
|
+
});
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
// If clicked date < Start: Updates Start Date to new date (End remains) - extends range to the left
|
|
119
|
+
applyRangeOrNewStart({
|
|
120
|
+
start: date,
|
|
121
|
+
end: endDate,
|
|
122
|
+
clickedDate: date,
|
|
123
|
+
disabledDates,
|
|
124
|
+
setSelection
|
|
125
|
+
});
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// Start is Set, End is Empty
|
|
129
|
+
if (startDate && !endDate) {
|
|
130
|
+
// If clicked date < Start: Restarts selection with clicked date as new Start
|
|
131
|
+
if (date.getTime() < startDate.getTime()) {
|
|
132
|
+
setSelection(date, null);
|
|
133
|
+
} else {
|
|
134
|
+
// If clicked date > Start: Sets it as End Date (if range valid)
|
|
135
|
+
applyRangeOrNewStart({
|
|
136
|
+
start: startDate,
|
|
137
|
+
end: date,
|
|
138
|
+
clickedDate: date,
|
|
139
|
+
disabledDates,
|
|
140
|
+
setSelection
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
setSelection(date, null);
|
|
146
|
+
};
|
|
@@ -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;
|