@moneyforward/mfui-components 3.20.0 → 3.21.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 (53) hide show
  1. package/dist/src/DateTimeSelection/DatePicker/DatePicker.js +2 -11
  2. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePicker.d.ts +1 -1
  3. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePicker.js +10 -2
  4. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePicker.types.d.ts +14 -0
  5. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerPopover/DateRangePickerPopover.js +13 -5
  6. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerPopover/utilities/createDateRangePickerPopoverTestUtility.js +2 -2
  7. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerTrigger/utilities/createDateRangePickerTriggerTestUtility.js +3 -1
  8. package/dist/src/DateTimeSelection/MonthPicker/MonthPicker.d.ts +1 -1
  9. package/dist/src/DateTimeSelection/MonthPicker/MonthPicker.js +10 -2
  10. package/dist/src/DateTimeSelection/MonthPicker/MonthPicker.types.d.ts +14 -0
  11. package/dist/src/DateTimeSelection/MonthPicker/MonthPickerPanel/MonthCell/MonthCell.js +2 -25
  12. package/dist/src/DateTimeSelection/MonthPicker/MonthPickerPanel/MonthPickerPanel.js +1 -1
  13. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePicker.d.ts +1 -1
  14. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePicker.js +14 -5
  15. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePicker.types.d.ts +21 -0
  16. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerMonthCell/MonthRangePickerMonthCell.d.ts +1 -0
  17. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerMonthCell/MonthRangePickerMonthCell.js +10 -3
  18. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerNavigation/MonthRangePickerNavigation.js +16 -3
  19. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerPanel/MonthRangePickerPanel.js +3 -1
  20. package/dist/src/DateTimeSelection/TimePicker/TimePicker.d.ts +43 -0
  21. package/dist/src/DateTimeSelection/TimePicker/TimePicker.js +85 -0
  22. package/dist/src/DateTimeSelection/TimePicker/TimePicker.types.d.ts +61 -0
  23. package/dist/src/DateTimeSelection/TimePicker/TimePicker.types.js +1 -0
  24. package/dist/src/DateTimeSelection/TimePicker/constants.d.ts +4 -0
  25. package/dist/src/DateTimeSelection/TimePicker/constants.js +12 -0
  26. package/dist/src/DateTimeSelection/TimePicker/index.d.ts +2 -0
  27. package/dist/src/DateTimeSelection/TimePicker/index.js +1 -0
  28. package/dist/src/DateTimeSelection/index.d.ts +1 -0
  29. package/dist/src/DateTimeSelection/index.js +1 -0
  30. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePicker.d.ts +1 -1
  31. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePicker.js +2 -2
  32. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePicker.types.d.ts +10 -0
  33. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerProvider/BaseRangePickerProvider.js +24 -20
  34. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerProvider/BaseRangePickerProvider.types.d.ts +9 -0
  35. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerTrigger/hooks/useDateRangeTriggerValueController.js +4 -4
  36. package/dist/src/DateTimeSelection/shared/CalendarLocale/CalendarLocaleContext.d.ts +1 -1
  37. package/dist/src/DateTimeSelection/shared/CalendarLocale/CalendarLocaleContext.js +1 -1
  38. package/dist/src/DateTimeSelection/shared/DayCell/DayCell.js +2 -3
  39. package/dist/src/DateTimeSelection/shared/YearSelector/YearSelector.js +1 -1
  40. package/dist/src/DateTimeSelection/shared/utilities/dateParsing.js +16 -9
  41. package/dist/src/DateTimeSelection/shared/utilities/japaneseCalendar.d.ts +36 -8
  42. package/dist/src/DateTimeSelection/shared/utilities/japaneseCalendar.js +82 -15
  43. package/dist/src/DateTimeSelection/shared/utilities/monthCellMonthFormat.d.ts +14 -0
  44. package/dist/src/DateTimeSelection/shared/utilities/monthCellMonthFormat.js +35 -0
  45. package/dist/styled-system/recipes/index.d.ts +1 -0
  46. package/dist/styled-system/recipes/index.js +1 -0
  47. package/dist/styled-system/recipes/time-picker-slot-recipe.d.ts +44 -0
  48. package/dist/styled-system/recipes/time-picker-slot-recipe.js +62 -0
  49. package/dist/styles.css +168 -1
  50. package/dist/tsconfig.build.tsbuildinfo +1 -1
  51. package/package.json +5 -5
  52. package/dist/src/DateTimeSelection/shared/BasePicker/YearSelector/YearSelector.d.ts +0 -18
  53. package/dist/src/DateTimeSelection/shared/BasePicker/YearSelector/YearSelector.js +0 -36
@@ -0,0 +1,61 @@
1
+ import { type ComponentPropsWithoutRef, type MouseEvent, type SyntheticEvent } from 'react';
2
+ import { type IconButtonProps } from '../../IconButton';
3
+ type TimePickerOwnProps = {
4
+ /**
5
+ * Callback fired when the time value changes.
6
+ *
7
+ * @param value - The new time string (e.g., `"09:30"`) — empty string when cleared.
8
+ *
9
+ * @param event - The originating event. A `ChangeEvent<HTMLInputElement>` for
10
+ * input typing, a `MouseEvent` from the clear button when the value is cleared.
11
+ */
12
+ onChange?: (value: string, event: SyntheticEvent) => void;
13
+ /**
14
+ * The display and input format. Drives the granularity of the underlying
15
+ * `<input type="time">` by setting `step=1` for seconds.
16
+ *
17
+ * **iOS Safari limitation**: iOS Safari ignores the `step` attribute on
18
+ * `<input type="time">`, so the seconds segment is never rendered or
19
+ * editable there. Setting `format="HH:mm:ss"` on iOS Safari still produces
20
+ * an HH:mm-only input; consumers that need seconds on iOS must collect
21
+ * them through a separate control.
22
+ *
23
+ * @default 'HH:mm'
24
+ */
25
+ format?: 'HH:mm' | 'HH:mm:ss';
26
+ /**
27
+ * Size variant matching the Figma trigger: small (24px) / medium (32px) / large (48px).
28
+ *
29
+ * @default 'medium'
30
+ */
31
+ size?: 'small' | 'medium' | 'large';
32
+ /**
33
+ * Whether the input is invalid (applies error border + sets `aria-invalid`).
34
+ *
35
+ * @default false
36
+ */
37
+ invalid?: boolean;
38
+ /**
39
+ * Callback fired when the clear button is clicked.
40
+ */
41
+ onClear?: (event: MouseEvent<HTMLButtonElement>) => void;
42
+ /**
43
+ * Props for the clear IconButton. Use `aria-label` to override the default
44
+ * accessible name.
45
+ */
46
+ clearButtonProps?: Pick<IconButtonProps, 'aria-label'>;
47
+ /**
48
+ * Props for the picker indicator IconButton (the clock icon). Use `aria-label`
49
+ * to override the default accessible name.
50
+ */
51
+ pickerIndicatorIconButtonProps?: Pick<IconButtonProps, 'aria-label'>;
52
+ };
53
+ /**
54
+ * `TimePicker` is a wrapper around `<input type="time">`, so it inherits the
55
+ * native input's attributes (`name`, `id`, `min`, `max`, `step`, `required`,
56
+ * `disabled`, `aria-*`, `onBlur`, `onFocus`, `className`, …) plus the
57
+ * component-specific props above. `onChange`, `size`, `type` and `aria-invalid`
58
+ * are intentionally omitted — they are owned by the component.
59
+ */
60
+ export type TimePickerProps = Omit<ComponentPropsWithoutRef<'input'>, keyof TimePickerOwnProps | 'type' | 'aria-invalid'> & TimePickerOwnProps;
61
+ export {};
@@ -0,0 +1,4 @@
1
+ type TimePickerLabelKey = 'openPicker';
2
+ export declare const TIME_PICKER_LABELS: Record<'ja' | 'en', Record<TimePickerLabelKey, string>>;
3
+ export declare function getTimePickerLabel(locale: 'ja' | 'en', key: TimePickerLabelKey): string;
4
+ export {};
@@ -0,0 +1,12 @@
1
+ export const TIME_PICKER_LABELS = {
2
+ ja: {
3
+ openPicker: '時刻を開く',
4
+ },
5
+ en: {
6
+ openPicker: 'Open time picker',
7
+ },
8
+ };
9
+ export function getTimePickerLabel(locale, key) {
10
+ const safeLocale = locale in TIME_PICKER_LABELS ? locale : 'ja';
11
+ return TIME_PICKER_LABELS[safeLocale][key];
12
+ }
@@ -0,0 +1,2 @@
1
+ export { TimePicker } from './TimePicker';
2
+ export type { TimePickerProps } from './TimePicker.types';
@@ -0,0 +1 @@
1
+ export { TimePicker } from './TimePicker';
@@ -6,3 +6,4 @@ export * from './FilterMonthRangePicker';
6
6
  export * from './MonthPicker';
7
7
  export * from './DateRangePicker';
8
8
  export * from './MonthRangePicker';
9
+ export * from './TimePicker';
@@ -6,3 +6,4 @@ export * from './FilterMonthRangePicker';
6
6
  export * from './MonthPicker';
7
7
  export * from './DateRangePicker';
8
8
  export * from './MonthRangePicker';
9
+ export * from './TimePicker';
@@ -3,4 +3,4 @@ import { type BaseRangePickerProps } from './BaseRangePicker.types';
3
3
  * BaseRangePicker component
4
4
  * A generic component for selecting a range of dates with configurable format
5
5
  */
6
- export declare function BaseRangePicker({ value, defaultValue, disabled, invalid, targetDOMNode, onChange, format, onOpenStateChanged, disableAutoOpen, onBlur, allowedPlacements, enableViewportConstraint, enableClearButton, clearButtonProps, minDate, maxDate, enableAutoUnmount, minWidth, renderPopoverContent, startInputProps, endInputProps, calendarLocale, initialDisplayedMonths, initialViewingDate, pendingFocusDayjsFormat, }: BaseRangePickerProps): import("react/jsx-runtime").JSX.Element;
6
+ export declare function BaseRangePicker({ value, defaultValue, disabled, invalid, targetDOMNode, onChange, format, onOpenStateChanged, disableAutoOpen, onBlur, allowedPlacements, enableViewportConstraint, enableClearButton, clearButtonProps, minDate, maxDate, enableAutoUnmount, minWidth, renderPopoverContent, startInputProps, endInputProps, calendarLocale, initialDisplayedMonths, initialViewingDate, customFormatValue, pendingFocusDayjsFormat, }: BaseRangePickerProps): import("react/jsx-runtime").JSX.Element;
@@ -77,12 +77,12 @@ function InternalBaseRangePicker({ startInputRef, endInputRef, disabled, invalid
77
77
  * BaseRangePicker component
78
78
  * A generic component for selecting a range of dates with configurable format
79
79
  */
80
- export function BaseRangePicker({ value, defaultValue, disabled, invalid, targetDOMNode, onChange, format = 'YYYY/MM/DD', onOpenStateChanged, disableAutoOpen = false, onBlur, allowedPlacements, enableViewportConstraint, enableClearButton = false, clearButtonProps, minDate, maxDate, enableAutoUnmount = true, minWidth, renderPopoverContent, startInputProps, endInputProps, calendarLocale = 'ja', initialDisplayedMonths, initialViewingDate, pendingFocusDayjsFormat = 'YYYY-MM-DD', }) {
80
+ export function BaseRangePicker({ value, defaultValue, disabled, invalid, targetDOMNode, onChange, format = 'YYYY/MM/DD', onOpenStateChanged, disableAutoOpen = false, onBlur, allowedPlacements, enableViewportConstraint, enableClearButton = false, clearButtonProps, minDate, maxDate, enableAutoUnmount = true, minWidth, renderPopoverContent, startInputProps, endInputProps, calendarLocale = 'ja', initialDisplayedMonths, initialViewingDate, customFormatValue, pendingFocusDayjsFormat = 'YYYY-MM-DD', }) {
81
81
  const { isOpen, open, close } = useDisclosure({ value: false });
82
82
  const startInputRef = useRef(null);
83
83
  const endInputRef = useRef(null);
84
84
  // Default renderPopoverContent implementation for backward compatibility
85
85
  const defaultRenderPopoverContent = useCallback(() => _jsx(BaseRangePickerPopover, { calendarLocale: calendarLocale }), [calendarLocale]);
86
86
  const finalRenderPopoverContent = renderPopoverContent ?? defaultRenderPopoverContent;
87
- return (_jsx(BaseRangePickerProvider, { value: value, defaultValue: defaultValue, disabled: disabled, format: format, isOpen: isOpen, open: open, close: close, disableAutoOpen: disableAutoOpen, minDate: minDate, maxDate: maxDate, initialDisplayedMonths: initialDisplayedMonths, initialViewingDate: initialViewingDate, pendingFocusDayjsFormat: pendingFocusDayjsFormat, onChange: onChange, children: _jsx(InternalBaseRangePicker, { startInputRef: startInputRef, endInputRef: endInputRef, disabled: disabled, invalid: invalid, format: format, enableClearButton: enableClearButton, clearButtonProps: clearButtonProps, targetDOMNode: targetDOMNode, allowedPlacements: allowedPlacements, enableViewportConstraint: enableViewportConstraint, enableAutoUnmount: enableAutoUnmount, minWidth: minWidth, startInputProps: startInputProps, endInputProps: endInputProps, isOpen: isOpen, open: open, close: close, renderPopoverContent: finalRenderPopoverContent, calendarLocale: calendarLocale, pendingFocusDayjsFormat: pendingFocusDayjsFormat, onBlur: onBlur, onOpenStateChanged: onOpenStateChanged }) }));
87
+ return (_jsx(BaseRangePickerProvider, { value: value, defaultValue: defaultValue, disabled: disabled, format: format, isOpen: isOpen, open: open, close: close, disableAutoOpen: disableAutoOpen, minDate: minDate, maxDate: maxDate, initialDisplayedMonths: initialDisplayedMonths, initialViewingDate: initialViewingDate, customFormatValue: customFormatValue, pendingFocusDayjsFormat: pendingFocusDayjsFormat, onChange: onChange, children: _jsx(InternalBaseRangePicker, { startInputRef: startInputRef, endInputRef: endInputRef, disabled: disabled, invalid: invalid, format: format, enableClearButton: enableClearButton, clearButtonProps: clearButtonProps, targetDOMNode: targetDOMNode, allowedPlacements: allowedPlacements, enableViewportConstraint: enableViewportConstraint, enableAutoUnmount: enableAutoUnmount, minWidth: minWidth, startInputProps: startInputProps, endInputProps: endInputProps, isOpen: isOpen, open: open, close: close, renderPopoverContent: finalRenderPopoverContent, calendarLocale: calendarLocale, pendingFocusDayjsFormat: pendingFocusDayjsFormat, onBlur: onBlur, onOpenStateChanged: onOpenStateChanged }) }));
88
88
  }
@@ -270,6 +270,16 @@ export type BaseRangePickerProps = {
270
270
  * @default 'ja'
271
271
  */
272
272
  calendarLocale?: BasePickerProps['calendarLocale'];
273
+ /**
274
+ * Custom function to format a Date value for display in the input fields.
275
+ * When provided, overrides the default dayjs formatting.
276
+ * Use this with `displayJapaneseCalendar` to show era-format strings in the inputs.
277
+ *
278
+ * @param date - The date to format
279
+ *
280
+ * @returns The formatted string to display
281
+ */
282
+ customFormatValue?: (date: Date) => string;
273
283
  /**
274
284
  * Controls which months are initially displayed in the two-panel calendar view.
275
285
  * - `'currentAndNext'`: Shows the value's start month on the left and the next month on the right.
@@ -5,7 +5,7 @@ import { flushSync } from 'react-dom';
5
5
  import { dayjs } from '../../../../utilities/date/dayjs';
6
6
  import { useUpdateEffect } from '../../../../utilities/effect/useUpdateEffect';
7
7
  import { useTransformedState } from '../../../../utilities/state/useTransformedState';
8
- import { parseInputDateSimple } from '../../utilities/dateParsing';
8
+ import { parseInputDate } from '../../utilities/dateParsing';
9
9
  // eslint-disable-next-line @typescript-eslint/no-empty-function
10
10
  const noop = () => { };
11
11
  // Normalizes a date to the first of the month
@@ -98,11 +98,12 @@ function autoCompletePartialRange(dates) {
98
98
  }
99
99
  return dates;
100
100
  }
101
- const useBaseRangePickerContextValue = ({ value, defaultValue, disabled = false, onChange, format = 'YYYY/MM/DD', isOpen = false, open = noop, close = noop, disableAutoOpen, minDate, maxDate, initialDisplayedMonths = 'currentAndNext', initialViewingDate, pendingFocusDayjsFormat = 'YYYY-MM-DD', }) => {
101
+ const useBaseRangePickerContextValue = ({ value, defaultValue, disabled = false, onChange, format = 'YYYY/MM/DD', isOpen = false, open = noop, close = noop, disableAutoOpen, minDate, maxDate, initialDisplayedMonths = 'currentAndNext', initialViewingDate, customFormatValue, pendingFocusDayjsFormat = 'YYYY-MM-DD', }) => {
102
+ const formatDate = useMemo(() => customFormatValue ?? ((d) => dayjs(d).format(format)), [customFormatValue, format]);
102
103
  const [dates, setDates] = useTransformedState(defaultValue ?? [undefined, undefined], normalizeToStartOfDay);
103
104
  const [dateStrings, setDateStrings] = useState(() => {
104
105
  const [startDate, endDate] = value ?? defaultValue ?? [undefined, undefined];
105
- return [startDate ? dayjs(startDate).format(format) : '', endDate ? dayjs(endDate).format(format) : ''];
106
+ return [startDate ? formatDate(startDate) : '', endDate ? formatDate(endDate) : ''];
106
107
  });
107
108
  const [startDate, endDate] = value ?? dates;
108
109
  const [startDateString, endDateString] = dateStrings;
@@ -135,9 +136,9 @@ const useBaseRangePickerContextValue = ({ value, defaultValue, disabled = false,
135
136
  useUpdateEffect(() => {
136
137
  if (value) {
137
138
  setDates(value);
138
- setDateStrings([value[0] ? dayjs(value[0]).format(format) : '', value[1] ? dayjs(value[1]).format(format) : '']);
139
+ setDateStrings([value[0] ? formatDate(value[0]) : '', value[1] ? formatDate(value[1]) : '']);
139
140
  }
140
- }, [value, format, setDates]);
141
+ }, [value, formatDate, setDates]);
141
142
  const handleChange = useCallback((newDates) => {
142
143
  let [newStartDate, newEndDate] = normalizeToStartOfDay(newDates);
143
144
  // Swap dates if start date is after end date
@@ -146,12 +147,9 @@ const useBaseRangePickerContextValue = ({ value, defaultValue, disabled = false,
146
147
  }
147
148
  setDates([newStartDate, newEndDate]);
148
149
  // In uncontrolled mode, update dateStrings for input display
149
- setDateStrings([
150
- newStartDate ? dayjs(newStartDate).format(format) : '',
151
- newEndDate ? dayjs(newEndDate).format(format) : '',
152
- ]);
150
+ setDateStrings([newStartDate ? formatDate(newStartDate) : '', newEndDate ? formatDate(newEndDate) : '']);
153
151
  onChange?.([newStartDate, newEndDate]);
154
- }, [onChange, format, setDates]);
152
+ }, [onChange, formatDate, setDates]);
155
153
  const handleStartDateChange = useCallback((date, nextEndDate) => {
156
154
  const proposedEndDate = nextEndDate !== undefined ? nextEndDate : endDate;
157
155
  // Apply auto-completion behavior when start date is set and end date is missing
@@ -177,13 +175,16 @@ const useBaseRangePickerContextValue = ({ value, defaultValue, disabled = false,
177
175
  const handleStartDateStringChange = useCallback((value) => {
178
176
  setDateStrings([value, endDateString]);
179
177
  // Parse and update both viewing month and temporary selection for immediate visual feedback
180
- const parsedDate = parseInputDateSimple(value, format);
181
- if (parsedDate && !isDateOutsideConstraints(parsedDate, minDate, maxDate, format)) {
182
- setViewingMonth(parsedDate);
178
+ const parseResult = parseInputDate(value, format);
179
+ if (parseResult.isValid &&
180
+ parseResult.result &&
181
+ !isDateOutsideConstraints(parseResult.result, minDate, maxDate, format)) {
182
+ const newDate = parseResult.result;
183
+ setViewingMonth(newDate);
183
184
  // Update temporary selection for immediate calendar visual feedback
184
- setTemporaryStart(parsedDate);
185
+ setTemporaryStart(newDate);
185
186
  // Set pending focus date so when calendar opens, it focuses on this date
186
- setPendingFocusDate(dayjs(parsedDate).format(pendingFocusDayjsFormat));
187
+ setPendingFocusDate(dayjs(newDate).format(pendingFocusDayjsFormat));
187
188
  }
188
189
  else if (value === '') {
189
190
  // Clear temporary selection if input is empty
@@ -203,13 +204,16 @@ const useBaseRangePickerContextValue = ({ value, defaultValue, disabled = false,
203
204
  const handleEndDateStringChange = useCallback((value) => {
204
205
  setDateStrings([startDateString, value]);
205
206
  // Parse and update both viewing month and temporary selection for immediate visual feedback
206
- const parsedDate = parseInputDateSimple(value, format);
207
- if (parsedDate && !isDateOutsideConstraints(parsedDate, minDate, maxDate, format)) {
208
- setViewingMonth(parsedDate);
207
+ const parseResult = parseInputDate(value, format);
208
+ if (parseResult.isValid &&
209
+ parseResult.result &&
210
+ !isDateOutsideConstraints(parseResult.result, minDate, maxDate, format)) {
211
+ const newDate = parseResult.result;
212
+ setViewingMonth(newDate);
209
213
  // Update temporary selection for immediate calendar visual feedback
210
- setTemporaryEnd(parsedDate);
214
+ setTemporaryEnd(newDate);
211
215
  // Set pending focus date so when calendar opens, it focuses on this date
212
- setPendingFocusDate(dayjs(parsedDate).format(pendingFocusDayjsFormat));
216
+ setPendingFocusDate(dayjs(newDate).format(pendingFocusDayjsFormat));
213
217
  }
214
218
  else if (value === '') {
215
219
  // Clear temporary selection if input is empty
@@ -97,6 +97,15 @@ export type BaseRangePickerProviderProps = {
97
97
  * @internal
98
98
  */
99
99
  initialViewingDate?: Date;
100
+ /**
101
+ * Custom function to format a Date value for display in the input fields.
102
+ * When provided, overrides the default dayjs formatting.
103
+ *
104
+ * @param date - The date to format
105
+ *
106
+ * @returns The formatted string to display
107
+ */
108
+ customFormatValue?: (date: Date) => string;
100
109
  /**
101
110
  * dayjs format string for `pendingFocusDate` so it matches the focused grid cell's `data-mfui-*` attribute.
102
111
  *
@@ -1,7 +1,7 @@
1
1
  import { useMemo } from 'react';
2
2
  import { dayjs } from '../../../../../utilities/date/dayjs';
3
+ import { parseInputDate } from '../../../utilities/dateParsing';
3
4
  import { useBaseRangePickerContext } from '../../BaseRangePickerProvider';
4
- import { parseInputDateSimple } from '../../../utilities/dateParsing';
5
5
  const createInputProps = (baseProps, value, format, onStringChange, onDateChange, otherDate, isDateDisabled) => ({
6
6
  ...baseProps,
7
7
  value,
@@ -16,12 +16,12 @@ const createInputProps = (baseProps, value, format, onStringChange, onDateChange
16
16
  if (event.defaultPrevented)
17
17
  return;
18
18
  baseProps.onBlur?.(event);
19
- const parsedDate = parseInputDateSimple(value, format);
20
- if (!parsedDate) {
19
+ const parseResult = parseInputDate(value, format);
20
+ if (!parseResult.isValid || !parseResult.result) {
21
21
  onDateChange(undefined, otherDate);
22
22
  return;
23
23
  }
24
- const currentDate = dayjs(parsedDate).startOf('day').toDate();
24
+ const currentDate = dayjs(parseResult.result).startOf('day').toDate();
25
25
  // Check if the date is disabled due to constraints
26
26
  if (isDateDisabled?.(currentDate)) {
27
27
  // Clear the input if the date violates constraints
@@ -5,4 +5,4 @@ export declare function CalendarLocaleProvider({ value, children, }: {
5
5
  value: CalendarLocaleContextValue;
6
6
  children: React.ReactNode;
7
7
  }): import("react/jsx-runtime").JSX.Element;
8
- export declare function useCalendarLocale(): CalendarLocaleContextValue;
8
+ export declare function useCalendarLocale(): NonNullable<CalendarLocaleContextValue>;
@@ -6,5 +6,5 @@ export function CalendarLocaleProvider({ value, children, }) {
6
6
  return _jsx(CalendarLocaleContext.Provider, { value: value, children: children });
7
7
  }
8
8
  export function useCalendarLocale() {
9
- return useContext(CalendarLocaleContext);
9
+ return useContext(CalendarLocaleContext) ?? 'ja';
10
10
  }
@@ -10,8 +10,7 @@ import { useCalendarLocale } from '../CalendarLocale/CalendarLocaleContext';
10
10
  export function DayCell({ day }) {
11
11
  const calendarLocale = useCalendarLocale();
12
12
  const classNames = dayCellSlotRecipe();
13
- const safeLocale = calendarLocale ?? 'ja';
14
- const abbreviation = DAY_ABBREVIATIONS[safeLocale][day];
15
- const fullName = DAY_FULL_NAMES[safeLocale][day];
13
+ const abbreviation = DAY_ABBREVIATIONS[calendarLocale][day];
14
+ const fullName = DAY_FULL_NAMES[calendarLocale][day];
16
15
  return (_jsx("div", { className: cx(classNames.root, 'mfui-DayCell__root'), "aria-label": fullName, children: _jsx(Typography, { "aria-hidden": true, variant: "contentHeading", className: cx(classNames.label, 'mfui-DayCell__label'), children: abbreviation }) }));
17
16
  }
@@ -16,7 +16,7 @@ export function YearSelector({ value, onChange, filterYear, triggerProps, trigge
16
16
  const year = MINIMUM_VIEWABLE_YEAR + i;
17
17
  let label;
18
18
  if (displayJapaneseCalendar) {
19
- label = yearToEraLabel(year, calendarLocale ?? 'ja');
19
+ label = yearToEraLabel(year, calendarLocale);
20
20
  }
21
21
  else if (calendarLocale === 'ja') {
22
22
  label = `${String(year)}年`;
@@ -20,16 +20,23 @@ export function parseInputDate(inputString, format) {
20
20
  // Use the refined Japanese era detection from japaneseCalendar utility
21
21
  const hasJapaneseEra = isJapaneseEraFormat(inputString);
22
22
  if (hasJapaneseEra) {
23
- // Try Japanese calendar parsing for era formats
24
- try {
25
- const jpResult = warekiToDate(inputString);
26
- if (jpResult.isValid) {
27
- return { isValid: true, result: jpResult.result };
23
+ // Skip month-only wareki parsing when the format requires a day component.
24
+ // e.g. "令和7年1月" should not parse for YYYY/MM/DD — the user hasn't typed the day yet.
25
+ const isMonthOnlyEra = inputString.trimEnd().endsWith('月');
26
+ const formatRequiresDay = format.includes('D');
27
+ const shouldTryWareki = !(isMonthOnlyEra && formatRequiresDay);
28
+ if (shouldTryWareki) {
29
+ // Try Japanese calendar parsing for era formats
30
+ try {
31
+ const jpResult = warekiToDate(inputString);
32
+ if (jpResult.isValid) {
33
+ return { isValid: true, result: jpResult.result };
34
+ }
35
+ }
36
+ catch (error) {
37
+ // If Japanese calendar parsing fails, fall back to dayjs
38
+ console.warn('Japanese calendar parsing failed:', error);
28
39
  }
29
- }
30
- catch (error) {
31
- // If Japanese calendar parsing fails, fall back to dayjs
32
- console.warn('Japanese calendar parsing failed:', error);
33
40
  }
34
41
  }
35
42
  // Try dayjs parsing for Western calendar format first
@@ -1,8 +1,8 @@
1
- declare const REIWA: "\u4EE4\u548C";
2
- declare const HEISEI: "\u5E73\u6210";
3
- declare const SHOWA: "\u662D\u548C";
4
- declare const TAISHO: "\u5927\u6B63";
5
- declare const MEIJI: "\u660E\u6CBB";
1
+ declare const REIWA = "\u4EE4\u548C";
2
+ declare const HEISEI = "\u5E73\u6210";
3
+ declare const SHOWA = "\u662D\u548C";
4
+ declare const TAISHO = "\u5927\u6B63";
5
+ declare const MEIJI = "\u660E\u6CBB";
6
6
  type Geongo = typeof REIWA | typeof HEISEI | typeof SHOWA | typeof TAISHO | typeof MEIJI;
7
7
  declare const WAREKI_START_YEARS: {
8
8
  readonly r: 2019;
@@ -64,13 +64,41 @@ export declare function isJapaneseEraFormat(inputString: string): boolean;
64
64
  export declare function yearToEraLabel(year: number, locale: 'ja' | 'en'): string;
65
65
  /**
66
66
  * Formats a Date to a short English era string for display in the DatePicker trigger input.
67
- * Format: "{EraName} {eraYear}/{month}/{day}" (e.g., "Reiwa 7/12/25").
68
- * For pre-Meiji dates (year < 1868): falls back to the Gregorian year (e.g., "1867/12/25").
67
+ * Format: "{EraName} {eraYear}/{MM}/{DD}" (e.g., "Reiwa 7/03/06").
68
+ * For pre-Meiji dates (year < 1868): falls back to the Gregorian year (e.g., "1867/03/06").
69
69
  *
70
70
  * @param date - The date to format
71
71
  *
72
- * @returns Short English era string (e.g., "Reiwa 7/12/25")
72
+ * @returns Short English era string (e.g., "Reiwa 7/03/06")
73
73
  */
74
74
  export declare function dateToEnglishEraShort(date: Date): string;
75
+ /**
76
+ * Formats a Date to a short English era string for display in the MonthPicker trigger input.
77
+ * Format: "{EraName} {eraYear}/{MM}" (e.g., "Reiwa 7/03").
78
+ * For pre-Meiji dates (year < 1868): falls back to the Gregorian year (e.g., "1867/03").
79
+ *
80
+ * @param date - The date to format
81
+ *
82
+ * @returns Short English era month string (e.g., "Reiwa 7/03")
83
+ */
84
+ export declare function dateToEnglishEraMonthShort(date: Date): string;
85
+ /**
86
+ * Formats a Date to a Japanese-era string with day precision (e.g., "令和7年3月6日").
87
+ * Uses the platform's built-in `ja-JP-u-ca-japanese` calendar.
88
+ *
89
+ * @param date - The date to format
90
+ *
91
+ * @returns Japanese era string with day (e.g., "令和7年3月6日")
92
+ */
93
+ export declare function dateToJapaneseEra(date: Date): string;
94
+ /**
95
+ * Formats a Date to a Japanese-era string with month precision (e.g., "令和7年3月").
96
+ * Uses the platform's built-in `ja-JP-u-ca-japanese` calendar.
97
+ *
98
+ * @param date - The date to format
99
+ *
100
+ * @returns Japanese era month string (e.g., "令和7年3月")
101
+ */
102
+ export declare function dateToJapaneseEraMonth(date: Date): string;
75
103
  export { warekiReg, dateToWareki, fullWidthToHalfWidth, selectGengo, WAREKI_START_YEARS };
76
104
  export type { WarekiResult };
@@ -38,8 +38,8 @@ const WAREKI_START_YEARS = {
38
38
  const warekiReg = {
39
39
  // Western calendar: supports full-width numbers and flexible separators
40
40
  dateString: new RegExp(String.raw `^([0-90-9]{4})${WESTERN_SEPARATOR}([0-90-9]{1,2})${WESTERN_SEPARATOR}([0-90-9]{1,2})(?:[\s.]([0-90-9]{2}):([0-90-9]{2}))?$`),
41
- // Japanese era: supports both traditional (H12年2月12日) and Western-style (H12.2.12) formats
42
- wareki: new RegExp(String.raw `^(${Object.keys(WAREKI_START_YEARS).join('|')})(元|[0-90-9]{1,2})(?:年([0-90-9]{1,2})月([0-90-9]{1,2})日|[./-]([0-90-9]{1,2})[./-]([0-90-9]{1,2}))$`),
41
+ // Japanese era: supports traditional (H12年2月12日), Western-style (H12.2.12), and month-only (H12年2月) formats
42
+ wareki: new RegExp(String.raw `^(${Object.keys(WAREKI_START_YEARS).join('|')})(元|[0-90-9]{1,2})(?:年([0-90-9]{1,2})月([0-90-9]{1,2})日|[./-]([0-90-9]{1,2})[./-]([0-90-9]{1,2})|年([0-90-9]{1,2})月)$`),
43
43
  // Era detection for both traditional and Western-style formats
44
44
  eraDetection: /^(?:令和|平成|昭和|大正|明治|[rhstmRHSTM])(?:元|[0-90-9]{1,2})(?:年|[./-])/,
45
45
  };
@@ -137,11 +137,12 @@ export function warekiToDate(wareki) {
137
137
  const baseYear = WAREKI_START_YEARS[matchedWareki[1]];
138
138
  // Handle "元" (first year) and regular years
139
139
  const eraYear = matchedWareki[2] === '元' ? 1 : Number(matchedWareki[2]);
140
- // Handle both traditional (年月日) and Western-style (./- separators) formats
140
+ // Handle traditional (年月日), Western-style (./- separators), and month-only (年月) formats
141
141
  // Traditional format: groups 3,4 (month, day)
142
142
  // Western format: groups 5,6 (month, day)
143
- const month = Number(matchedWareki[3] || matchedWareki[5]);
144
- const day = Number(matchedWareki[4] || matchedWareki[6]);
143
+ // Month-only format: group 7 (month), day defaults to 1
144
+ const month = Number(matchedWareki[3] || matchedWareki[5] || matchedWareki[7]);
145
+ const day = matchedWareki[7] !== undefined ? 1 : Number(matchedWareki[4] || matchedWareki[6]);
145
146
  // Validation: ensure era year is reasonable (1-200 years per era)
146
147
  if (eraYear < 1 || eraYear > 200) {
147
148
  return {
@@ -240,24 +241,90 @@ export function yearToEraLabel(year, locale) {
240
241
  return `${gengo}${eraYear === 1 ? '元' : String(eraYear)}年`;
241
242
  }
242
243
  /**
243
- * Formats a Date to a short English era string for display in the DatePicker trigger input.
244
- * Format: "{EraName} {eraYear}/{month}/{day}" (e.g., "Reiwa 7/12/25").
245
- * For pre-Meiji dates (year < 1868): falls back to the Gregorian year (e.g., "1867/12/25").
244
+ * Internal helper used by `dateToEnglishEraShort` and `dateToEnglishEraMonthShort`.
245
+ * Pre-Meiji dates (year < 1868) fall back to the Gregorian year.
246
246
  *
247
247
  * @param date - The date to format
248
248
  *
249
- * @returns Short English era string (e.g., "Reiwa 7/12/25")
249
+ * @param options - Format options. `includeDay: true` produces "{Era} {Y}/{MM}/{DD}";
250
+ * `includeDay: false` produces "{Era} {Y}/{MM}" and resolves the era using day 1.
251
+ *
252
+ * @returns Formatted English era string
250
253
  */
251
- export function dateToEnglishEraShort(date) {
254
+ function dateToEnglishEra(date, { includeDay }) {
252
255
  const year = date.getFullYear();
253
- const month = date.getMonth() + 1;
254
- const day = date.getDate();
256
+ const monthNum = date.getMonth() + 1;
257
+ const dayNum = date.getDate();
258
+ const month = String(monthNum).padStart(2, '0');
259
+ const daySuffix = includeDay ? `/${String(dayNum).padStart(2, '0')}` : '';
255
260
  if (year < MEIJI_START_YEAR) {
256
- return `${String(year)}/${String(month)}/${String(day)}`;
261
+ return `${String(year)}/${month}${daySuffix}`;
257
262
  }
258
- const gengo = selectGengo(year, month, day);
263
+ // Month-only outputs resolve the era using day 1, which means the transition month
264
+ // keeps the older era label (e.g., Dec 1926 → Taisho 15/12, not Showa 1/12).
265
+ const gengo = selectGengo(year, monthNum, includeDay ? dayNum : 1);
259
266
  const eraYear = year - WAREKI_START_YEARS[gengo] + 1;
260
- return `${ERA_NAMES_EN[gengo]} ${String(eraYear)}/${String(month)}/${String(day)}`;
267
+ return `${ERA_NAMES_EN[gengo]} ${String(eraYear)}/${month}${daySuffix}`;
268
+ }
269
+ /**
270
+ * Formats a Date to a short English era string for display in the DatePicker trigger input.
271
+ * Format: "{EraName} {eraYear}/{MM}/{DD}" (e.g., "Reiwa 7/03/06").
272
+ * For pre-Meiji dates (year < 1868): falls back to the Gregorian year (e.g., "1867/03/06").
273
+ *
274
+ * @param date - The date to format
275
+ *
276
+ * @returns Short English era string (e.g., "Reiwa 7/03/06")
277
+ */
278
+ export function dateToEnglishEraShort(date) {
279
+ return dateToEnglishEra(date, { includeDay: true });
280
+ }
281
+ /**
282
+ * Formats a Date to a short English era string for display in the MonthPicker trigger input.
283
+ * Format: "{EraName} {eraYear}/{MM}" (e.g., "Reiwa 7/03").
284
+ * For pre-Meiji dates (year < 1868): falls back to the Gregorian year (e.g., "1867/03").
285
+ *
286
+ * @param date - The date to format
287
+ *
288
+ * @returns Short English era month string (e.g., "Reiwa 7/03")
289
+ */
290
+ export function dateToEnglishEraMonthShort(date) {
291
+ return dateToEnglishEra(date, { includeDay: false });
292
+ }
293
+ // Cached Intl formatters for the Japanese era (wareki) — module-level so the
294
+ // formatter instances are reused across components instead of being created
295
+ // inside each `useMemo`.
296
+ const japaneseEraDateFormatter = new Intl.DateTimeFormat('ja-JP-u-ca-japanese', {
297
+ era: 'long',
298
+ year: 'numeric',
299
+ month: 'narrow',
300
+ day: 'numeric',
301
+ });
302
+ const japaneseEraMonthFormatter = new Intl.DateTimeFormat('ja-JP-u-ca-japanese', {
303
+ era: 'long',
304
+ year: 'numeric',
305
+ month: 'narrow',
306
+ });
307
+ /**
308
+ * Formats a Date to a Japanese-era string with day precision (e.g., "令和7年3月6日").
309
+ * Uses the platform's built-in `ja-JP-u-ca-japanese` calendar.
310
+ *
311
+ * @param date - The date to format
312
+ *
313
+ * @returns Japanese era string with day (e.g., "令和7年3月6日")
314
+ */
315
+ export function dateToJapaneseEra(date) {
316
+ return japaneseEraDateFormatter.format(date);
317
+ }
318
+ /**
319
+ * Formats a Date to a Japanese-era string with month precision (e.g., "令和7年3月").
320
+ * Uses the platform's built-in `ja-JP-u-ca-japanese` calendar.
321
+ *
322
+ * @param date - The date to format
323
+ *
324
+ * @returns Japanese era month string (e.g., "令和7年3月")
325
+ */
326
+ export function dateToJapaneseEraMonth(date) {
327
+ return japaneseEraMonthFormatter.format(date);
261
328
  }
262
329
  // Export the utility functions and constants
263
330
  export { warekiReg, dateToWareki, fullWidthToHalfWidth, selectGengo, WAREKI_START_YEARS };
@@ -0,0 +1,14 @@
1
+ import { type BasePickerProps } from '../BasePicker/BasePicker.types';
2
+ /**
3
+ * dayjs format string for the visible month label in {@link MonthCell} (`monthLabel`),
4
+ * based on calendar locale (Japanese `M月` vs English three-letter abbreviations).
5
+ *
6
+ * @param calendarLocale - The calendar locale (`'ja'` or `'en'`) — selects between
7
+ * Japanese (`M月`) and English (`[Jan]`–`[Dec]`) abbreviations
8
+ *
9
+ * @param date - The date whose month index selects the English abbreviation.
10
+ * Only consulted when `calendarLocale === 'en'`.
11
+ *
12
+ * @returns A dayjs-compatible format string for the month label
13
+ */
14
+ export declare function getMonthCellMonthFormat(calendarLocale: BasePickerProps['calendarLocale'], date: Date): string;
@@ -0,0 +1,35 @@
1
+ // English month abbreviations wrapped in square brackets to escape them in dayjs format strings
2
+ const ENGLISH_MONTH_ABBREVIATIONS = [
3
+ '[Jan]',
4
+ '[Feb]',
5
+ '[Mar]',
6
+ '[Apr]',
7
+ '[May]',
8
+ '[Jun]',
9
+ '[Jul]',
10
+ '[Aug]',
11
+ '[Sep]',
12
+ '[Oct]',
13
+ '[Nov]',
14
+ '[Dec]',
15
+ ];
16
+ /**
17
+ * dayjs format string for the visible month label in {@link MonthCell} (`monthLabel`),
18
+ * based on calendar locale (Japanese `M月` vs English three-letter abbreviations).
19
+ *
20
+ * @param calendarLocale - The calendar locale (`'ja'` or `'en'`) — selects between
21
+ * Japanese (`M月`) and English (`[Jan]`–`[Dec]`) abbreviations
22
+ *
23
+ * @param date - The date whose month index selects the English abbreviation.
24
+ * Only consulted when `calendarLocale === 'en'`.
25
+ *
26
+ * @returns A dayjs-compatible format string for the month label
27
+ */
28
+ export function getMonthCellMonthFormat(calendarLocale, date) {
29
+ if (calendarLocale === 'en') {
30
+ // Date.getMonth() always returns 0–11, so the array access is safe.
31
+ const monthIndex = date.getMonth();
32
+ return ENGLISH_MONTH_ABBREVIATIONS[monthIndex];
33
+ }
34
+ return 'M月';
35
+ }
@@ -70,6 +70,7 @@ export * from './status-label-slot-recipe';
70
70
  export * from './month-cell-slot-recipe';
71
71
  export * from './month-grid-slot-recipe';
72
72
  export * from './month-picker-panel-slot-recipe';
73
+ export * from './time-picker-slot-recipe';
73
74
  export * from './skeleton-slot-recipe';
74
75
  export * from './checkbox-group-slot-recipe';
75
76
  export * from './multiple-filter-select-box-slot-recipe';
@@ -69,6 +69,7 @@ export * from './status-label-slot-recipe.js';
69
69
  export * from './month-cell-slot-recipe.js';
70
70
  export * from './month-grid-slot-recipe.js';
71
71
  export * from './month-picker-panel-slot-recipe.js';
72
+ export * from './time-picker-slot-recipe.js';
72
73
  export * from './skeleton-slot-recipe.js';
73
74
  export * from './checkbox-group-slot-recipe.js';
74
75
  export * from './multiple-filter-select-box-slot-recipe.js';