@moneyforward/mfui-components 3.19.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 (113) hide show
  1. package/dist/src/CheckboxCard/CheckboxCard.js +1 -7
  2. package/dist/src/DateTimeSelection/DatePicker/DatePicker.js +2 -11
  3. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePicker.d.ts +1 -1
  4. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePicker.js +10 -2
  5. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePicker.types.d.ts +14 -0
  6. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerPopover/DateRangePickerPopover.js +13 -5
  7. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerPopover/utilities/createDateRangePickerPopoverTestUtility.js +2 -2
  8. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerProvider/DateRangePickerProvider.js +11 -12
  9. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerTrigger/hooks/useDateRangeTriggerValueController.js +4 -3
  10. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerTrigger/utilities/createDateRangePickerTriggerTestUtility.js +3 -1
  11. package/dist/src/DateTimeSelection/FilterDateRangePicker/DateRangePickerContent/DateRangePickerContent.d.ts +6 -0
  12. package/dist/src/DateTimeSelection/FilterDateRangePicker/DateRangePickerContent/DateRangePickerContent.js +33 -0
  13. package/dist/src/DateTimeSelection/FilterDateRangePicker/DateRangePickerContent/DateRangePickerContent.types.d.ts +17 -0
  14. package/dist/src/DateTimeSelection/FilterDateRangePicker/FilterDateRangePicker.d.ts +14 -0
  15. package/dist/src/DateTimeSelection/FilterDateRangePicker/FilterDateRangePicker.js +42 -0
  16. package/dist/src/DateTimeSelection/FilterDateRangePicker/FilterDateRangePicker.types.d.ts +78 -0
  17. package/dist/src/DateTimeSelection/FilterDateRangePicker/FilterDateRangePicker.types.js +1 -0
  18. package/dist/src/DateTimeSelection/FilterDateRangePicker/index.d.ts +2 -0
  19. package/dist/src/DateTimeSelection/FilterDateRangePicker/index.js +1 -0
  20. package/dist/src/DateTimeSelection/FilterMonthPicker/FilterMonthPicker.d.ts +14 -0
  21. package/dist/src/DateTimeSelection/FilterMonthPicker/FilterMonthPicker.js +75 -0
  22. package/dist/src/DateTimeSelection/FilterMonthPicker/FilterMonthPicker.types.d.ts +83 -0
  23. package/dist/src/DateTimeSelection/FilterMonthPicker/FilterMonthPicker.types.js +1 -0
  24. package/dist/src/DateTimeSelection/FilterMonthPicker/MonthPickerPopover/MonthPickerPopover.d.ts +6 -0
  25. package/dist/src/DateTimeSelection/FilterMonthPicker/MonthPickerPopover/MonthPickerPopover.js +45 -0
  26. package/dist/src/DateTimeSelection/FilterMonthPicker/MonthPickerPopover/MonthPickerPopover.types.d.ts +24 -0
  27. package/dist/src/DateTimeSelection/FilterMonthPicker/MonthPickerPopover/MonthPickerPopover.types.js +1 -0
  28. package/dist/src/DateTimeSelection/FilterMonthPicker/index.d.ts +2 -0
  29. package/dist/src/DateTimeSelection/FilterMonthPicker/index.js +1 -0
  30. package/dist/src/DateTimeSelection/FilterMonthRangePicker/FilterMonthRangePicker.d.ts +15 -0
  31. package/dist/src/DateTimeSelection/FilterMonthRangePicker/FilterMonthRangePicker.js +89 -0
  32. package/dist/src/DateTimeSelection/FilterMonthRangePicker/FilterMonthRangePicker.types.d.ts +75 -0
  33. package/dist/src/DateTimeSelection/FilterMonthRangePicker/FilterMonthRangePicker.types.js +1 -0
  34. package/dist/src/DateTimeSelection/FilterMonthRangePicker/MonthRangePickerPopover/MonthRangePickerPopover.d.ts +5 -0
  35. package/dist/src/DateTimeSelection/FilterMonthRangePicker/MonthRangePickerPopover/MonthRangePickerPopover.js +54 -0
  36. package/dist/src/DateTimeSelection/FilterMonthRangePicker/MonthRangePickerPopover/MonthRangePickerPopover.types.d.ts +19 -0
  37. package/dist/src/DateTimeSelection/FilterMonthRangePicker/MonthRangePickerPopover/MonthRangePickerPopover.types.js +1 -0
  38. package/dist/src/DateTimeSelection/FilterMonthRangePicker/index.d.ts +2 -0
  39. package/dist/src/DateTimeSelection/FilterMonthRangePicker/index.js +1 -0
  40. package/dist/src/DateTimeSelection/MonthPicker/MonthPicker.d.ts +1 -1
  41. package/dist/src/DateTimeSelection/MonthPicker/MonthPicker.js +10 -2
  42. package/dist/src/DateTimeSelection/MonthPicker/MonthPicker.types.d.ts +14 -0
  43. package/dist/src/DateTimeSelection/MonthPicker/MonthPickerPanel/MonthCell/MonthCell.js +2 -25
  44. package/dist/src/DateTimeSelection/MonthPicker/MonthPickerPanel/MonthPickerPanel.js +1 -1
  45. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePicker.d.ts +1 -1
  46. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePicker.js +14 -5
  47. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePicker.types.d.ts +21 -0
  48. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerMonthCell/MonthRangePickerMonthCell.d.ts +1 -0
  49. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerMonthCell/MonthRangePickerMonthCell.js +10 -3
  50. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerNavigation/MonthRangePickerNavigation.js +16 -3
  51. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerPanel/MonthRangePickerPanel.js +3 -1
  52. package/dist/src/DateTimeSelection/TimePicker/TimePicker.d.ts +43 -0
  53. package/dist/src/DateTimeSelection/TimePicker/TimePicker.js +85 -0
  54. package/dist/src/DateTimeSelection/TimePicker/TimePicker.types.d.ts +61 -0
  55. package/dist/src/DateTimeSelection/TimePicker/TimePicker.types.js +1 -0
  56. package/dist/src/DateTimeSelection/TimePicker/constants.d.ts +4 -0
  57. package/dist/src/DateTimeSelection/TimePicker/constants.js +12 -0
  58. package/dist/src/DateTimeSelection/TimePicker/index.d.ts +2 -0
  59. package/dist/src/DateTimeSelection/TimePicker/index.js +1 -0
  60. package/dist/src/DateTimeSelection/index.d.ts +4 -0
  61. package/dist/src/DateTimeSelection/index.js +4 -0
  62. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePicker.d.ts +1 -1
  63. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePicker.js +26 -10
  64. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePicker.types.d.ts +19 -0
  65. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerProvider/BaseRangePickerProvider.js +40 -19
  66. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerProvider/BaseRangePickerProvider.types.d.ts +19 -0
  67. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerTrigger/BaseRangePickerTrigger.js +4 -6
  68. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerTrigger/hooks/useDateRangeTriggerValueController.js +4 -3
  69. package/dist/src/DateTimeSelection/shared/CalendarLocale/CalendarLocaleContext.d.ts +1 -1
  70. package/dist/src/DateTimeSelection/shared/CalendarLocale/CalendarLocaleContext.js +1 -1
  71. package/dist/src/DateTimeSelection/shared/DayCell/DayCell.js +2 -3
  72. package/dist/src/DateTimeSelection/shared/MonthGrid/MonthGrid.js +3 -3
  73. package/dist/src/DateTimeSelection/shared/YearSelector/YearSelector.js +1 -1
  74. package/dist/src/DateTimeSelection/shared/utilities/dateParsing.js +27 -9
  75. package/dist/src/DateTimeSelection/shared/utilities/japaneseCalendar.d.ts +36 -8
  76. package/dist/src/DateTimeSelection/shared/utilities/japaneseCalendar.js +82 -15
  77. package/dist/src/DateTimeSelection/shared/utilities/monthCellMonthFormat.d.ts +14 -0
  78. package/dist/src/DateTimeSelection/shared/utilities/monthCellMonthFormat.js +35 -0
  79. package/dist/src/MultilineTextBox/index.d.ts +10 -2
  80. package/dist/src/MultilineTextBox/index.js +9 -1
  81. package/dist/src/MultipleSelectBox/MultipleSelectBoxTrigger/MultipleSelectBoxTrigger.js +1 -3
  82. package/dist/src/RadioButtonCard/RadioButtonCard.js +1 -7
  83. package/dist/src/TextBox/TextBox.js +2 -6
  84. package/dist/src/{MultilineTextBox/MultilineTextBox.d.ts → Textarea/Textarea.d.ts} +5 -2
  85. package/dist/src/{MultilineTextBox/MultilineTextBox.js → Textarea/Textarea.js} +9 -5
  86. package/dist/src/{MultilineTextBox/MultilineTextBox.types.d.ts → Textarea/Textarea.types.d.ts} +2 -2
  87. package/dist/src/Textarea/Textarea.types.js +1 -0
  88. package/dist/src/Textarea/index.d.ts +2 -0
  89. package/dist/src/Textarea/index.js +1 -0
  90. package/dist/src/Tooltip/Tooltip.js +12 -3
  91. package/dist/src/Typography/Typography.js +1 -3
  92. package/dist/src/index.d.ts +1 -0
  93. package/dist/src/index.js +1 -0
  94. package/dist/styled-system/recipes/filter-date-range-picker-slot-recipe.d.ts +33 -0
  95. package/dist/styled-system/recipes/filter-date-range-picker-slot-recipe.js +48 -0
  96. package/dist/styled-system/recipes/filter-month-picker-slot-recipe.d.ts +33 -0
  97. package/dist/styled-system/recipes/filter-month-picker-slot-recipe.js +44 -0
  98. package/dist/styled-system/recipes/filter-month-range-picker-slot-recipe.d.ts +33 -0
  99. package/dist/styled-system/recipes/filter-month-range-picker-slot-recipe.js +44 -0
  100. package/dist/styled-system/recipes/index.d.ts +5 -1
  101. package/dist/styled-system/recipes/index.js +5 -1
  102. package/dist/styled-system/recipes/textarea-slot-recipe.d.ts +52 -0
  103. package/dist/styled-system/recipes/textarea-slot-recipe.js +64 -0
  104. package/dist/styled-system/recipes/time-picker-slot-recipe.d.ts +44 -0
  105. package/dist/styled-system/recipes/time-picker-slot-recipe.js +62 -0
  106. package/dist/styles.css +601 -25
  107. package/dist/tsconfig.build.tsbuildinfo +1 -1
  108. package/package.json +13 -11
  109. package/dist/src/DateTimeSelection/shared/BasePicker/YearSelector/YearSelector.d.ts +0 -18
  110. package/dist/src/DateTimeSelection/shared/BasePicker/YearSelector/YearSelector.js +0 -36
  111. package/dist/styled-system/recipes/multiline-text-box-slot-recipe.d.ts +0 -52
  112. package/dist/styled-system/recipes/multiline-text-box-slot-recipe.js +0 -64
  113. /package/dist/src/{MultilineTextBox/MultilineTextBox.types.js → DateTimeSelection/FilterDateRangePicker/DateRangePickerContent/DateRangePickerContent.types.js} +0 -0
@@ -5,11 +5,14 @@ import { cx } from '../../../../styled-system/css';
5
5
  import { monthRangePickerNavigationSlotRecipe } from '../../../../styled-system/recipes';
6
6
  import { IconButton } from '../../../IconButton';
7
7
  import { Typography } from '../../../Typography';
8
+ import { useCalendarLocale } from '../../shared/CalendarLocale/CalendarLocaleContext';
9
+ import { useDisplayJapaneseCalendar } from '../../shared/CalendarLocale/DisplayJapaneseCalendarContext';
10
+ import { yearToEraLabel } from '../../shared/utilities/japaneseCalendar';
8
11
  // Constants for year navigation limits
9
12
  const MINIMUM_VIEWABLE_YEAR = 1900;
10
13
  const MAXIMUM_VIEWABLE_YEAR = 2100;
11
- const LABEL_PREV_YEAR = '前年';
12
- const LABEL_NEXT_YEAR = '翌年';
14
+ const LABEL_PREV_YEAR = { ja: '前年', en: 'Previous year' };
15
+ const LABEL_NEXT_YEAR = { ja: '翌年', en: 'Next year' };
13
16
  /**
14
17
  * Year navigation component for MonthRangePicker.
15
18
  *
@@ -21,8 +24,18 @@ const LABEL_NEXT_YEAR = '翌年';
21
24
  * @param props - MonthRangePickerNavigation props
22
25
  */
23
26
  export function MonthRangePickerNavigation({ viewingValue, setViewingValue, minMonth, maxMonth, }) {
27
+ const displayJapaneseCalendar = useDisplayJapaneseCalendar();
28
+ const calendarLocale = useCalendarLocale();
24
29
  const currentYear = viewingValue.getFullYear();
25
30
  const nextYear = currentYear + 1;
31
+ const formatYear = (year) => {
32
+ if (displayJapaneseCalendar) {
33
+ return yearToEraLabel(year, calendarLocale);
34
+ }
35
+ return calendarLocale === 'en' ? String(year) : `${String(year)}年`;
36
+ };
37
+ const currentYearLabel = formatYear(currentYear);
38
+ const nextYearLabel = formatYear(nextYear);
26
39
  // Calculate if year navigation should be disabled based on min/max constraints
27
40
  const { isPreviousYearDisabled, isNextYearDisabled } = useMemo(() => {
28
41
  const baseMinYearDisabled = currentYear <= MINIMUM_VIEWABLE_YEAR;
@@ -65,5 +78,5 @@ export function MonthRangePickerNavigation({ viewingValue, setViewingValue, minM
65
78
  }
66
79
  }, [isNextYearDisabled, viewingValue, nextYear, setViewingValue]);
67
80
  const classNames = monthRangePickerNavigationSlotRecipe();
68
- return (_jsxs("div", { className: cx(classNames.root, 'mfui-MonthRangePickerNavigation__root'), children: [_jsxs("div", { className: cx(classNames.column, 'mfui-MonthRangePickerNavigation__column'), children: [_jsx(IconButton, { "aria-label": LABEL_PREV_YEAR, disabled: isPreviousYearDisabled, onClick: handlePreviousYear, children: _jsx(PickerBefore, {}) }), _jsxs(Typography, { variant: "contentHeading", children: [currentYear, "\u5E74"] }), _jsx("div", { className: cx(classNames.blankIconButton, 'mfui-MonthRangePickerNavigation__blankIconButton') })] }), _jsxs("div", { className: cx(classNames.column, 'mfui-MonthRangePickerNavigation__column'), children: [_jsx("div", { className: cx(classNames.blankIconButton, 'mfui-MonthRangePickerNavigation__blankIconButton') }), _jsxs(Typography, { variant: "contentHeading", children: [nextYear, "\u5E74"] }), _jsx(IconButton, { "aria-label": LABEL_NEXT_YEAR, disabled: isNextYearDisabled, onClick: handleNextYear, children: _jsx(PickerAfter, {}) })] })] }));
81
+ return (_jsxs("div", { className: cx(classNames.root, 'mfui-MonthRangePickerNavigation__root'), children: [_jsxs("div", { className: cx(classNames.column, 'mfui-MonthRangePickerNavigation__column'), children: [_jsx(IconButton, { "aria-label": LABEL_PREV_YEAR[calendarLocale], disabled: isPreviousYearDisabled, onClick: handlePreviousYear, children: _jsx(PickerBefore, {}) }), _jsx(Typography, { variant: "contentHeading", children: currentYearLabel }), _jsx("div", { className: cx(classNames.blankIconButton, 'mfui-MonthRangePickerNavigation__blankIconButton') })] }), _jsxs("div", { className: cx(classNames.column, 'mfui-MonthRangePickerNavigation__column'), children: [_jsx("div", { className: cx(classNames.blankIconButton, 'mfui-MonthRangePickerNavigation__blankIconButton') }), _jsx(Typography, { variant: "contentHeading", children: nextYearLabel }), _jsx(IconButton, { "aria-label": LABEL_NEXT_YEAR[calendarLocale], disabled: isNextYearDisabled, onClick: handleNextYear, children: _jsx(PickerAfter, {}) })] })] }));
69
82
  }
@@ -7,6 +7,7 @@ import { BaseRangePickerFooter } from '../../shared/BaseRangePicker/BaseRangePic
7
7
  import { MonthRangePickerNavigation } from '../MonthRangePickerNavigation';
8
8
  import { MonthRangePickerContent } from '../MonthRangePickerContent';
9
9
  import { useBaseRangePickerContext } from '../../shared/BaseRangePicker/BaseRangePickerProvider';
10
+ import { useCalendarLocale } from '../../shared/CalendarLocale/CalendarLocaleContext';
10
11
  /**
11
12
  * MonthRangePickerPanel component for selecting month ranges.
12
13
  *
@@ -32,6 +33,7 @@ import { useBaseRangePickerContext } from '../../shared/BaseRangePicker/BaseRang
32
33
  */
33
34
  export function MonthRangePickerPanel({ viewingValue, setViewingValue, value, minMonth, maxMonth, }) {
34
35
  const { setPendingFocusDate, handlePreview } = useBaseRangePickerContext();
36
+ const calendarLocale = useCalendarLocale();
35
37
  // Cross-Panel Keyboard Navigation for MonthRangePicker
36
38
  const handleKeyboardNavigation = useCallback((event) => {
37
39
  const { key, currentTarget } = event;
@@ -85,5 +87,5 @@ export function MonthRangePickerPanel({ viewingValue, setViewingValue, value, mi
85
87
  }
86
88
  }, [viewingValue, setViewingValue, setPendingFocusDate, handlePreview]);
87
89
  const classNames = monthRangePickerPanelSlotRecipe();
88
- return (_jsxs("div", { className: cx(classNames.root, 'mfui-MonthRangePickerPanel__root'), children: [_jsx(MonthRangePickerNavigation, { viewingValue: viewingValue, setViewingValue: setViewingValue, minMonth: minMonth, maxMonth: maxMonth }), _jsx(MonthRangePickerContent, { viewingValue: viewingValue, value: value, minMonth: minMonth, maxMonth: maxMonth, handleKeyboardNavigation: handleKeyboardNavigation }), _jsx(BaseRangePickerFooter, {})] }));
90
+ return (_jsxs("div", { className: cx(classNames.root, 'mfui-MonthRangePickerPanel__root'), children: [_jsx(MonthRangePickerNavigation, { viewingValue: viewingValue, setViewingValue: setViewingValue, minMonth: minMonth, maxMonth: maxMonth }), _jsx(MonthRangePickerContent, { viewingValue: viewingValue, value: value, minMonth: minMonth, maxMonth: maxMonth, handleKeyboardNavigation: handleKeyboardNavigation }), _jsx(BaseRangePickerFooter, { calendarLocale: calendarLocale })] }));
89
91
  }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * TimePicker is a styled wrapper around the native `<input type="time">`.
3
+ *
4
+ * The browser owns the picker UI: typing into the segmented control, the
5
+ * UA-provided dropdown, and Escape-to-revert semantics all come from the
6
+ * platform. The trailing clock icon opens the native picker — on desktop via
7
+ * `HTMLInputElement.showPicker()`, on iOS Safari (where `showPicker` is not
8
+ * reliably implemented) by dispatching a click on the underlying input, which
9
+ * is what iOS Safari uses to open its native time picker.
10
+ *
11
+ * The `value` prop follows the same string format as the HTML `<input type="time">`
12
+ * element: `"HH:mm"` for hours and minutes, `"HH:mm:ss"` for hours, minutes, and seconds.
13
+ *
14
+ * The component does not render its own `<label>`. Consumers are expected to
15
+ * provide an accessible name via a parent `<label htmlFor>`, `aria-labelledby`,
16
+ * or `aria-label`.
17
+ *
18
+ * @example
19
+ * // Controlled
20
+ * const [time, setTime] = useState<string>('');
21
+ * <TimePicker value={time} onChange={setTime} />
22
+ *
23
+ * @example
24
+ * // With seconds
25
+ * <TimePicker format="HH:mm:ss" />
26
+ */
27
+ export declare const TimePicker: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").DetailedHTMLProps<import("react").InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "ref">, "type" | "aria-invalid" | keyof {
28
+ onChange?: (value: string, event: import("react").SyntheticEvent) => void;
29
+ format?: "HH:mm" | "HH:mm:ss";
30
+ size?: "small" | "medium" | "large";
31
+ invalid?: boolean;
32
+ onClear?: (event: import("react").MouseEvent<HTMLButtonElement>) => void;
33
+ clearButtonProps?: Pick<import("../..").IconButtonProps, "aria-label">;
34
+ pickerIndicatorIconButtonProps?: Pick<import("../..").IconButtonProps, "aria-label">;
35
+ }> & {
36
+ onChange?: (value: string, event: import("react").SyntheticEvent) => void;
37
+ format?: "HH:mm" | "HH:mm:ss";
38
+ size?: "small" | "medium" | "large";
39
+ invalid?: boolean;
40
+ onClear?: (event: import("react").MouseEvent<HTMLButtonElement>) => void;
41
+ clearButtonProps?: Pick<import("../..").IconButtonProps, "aria-label">;
42
+ pickerIndicatorIconButtonProps?: Pick<import("../..").IconButtonProps, "aria-label">;
43
+ } & import("react").RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,85 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { forwardRef, useCallback, useRef, useState } from 'react';
4
+ import { Time } from '@moneyforward/mfui-icons-react';
5
+ import { cx } from '../../../styled-system/css';
6
+ import { timePickerSlotRecipe } from '../../../styled-system/recipes';
7
+ import { FocusIndicator } from '../../FocusIndicator';
8
+ import { InternalIconButton as IconButton } from '../../IconButton/IconButton';
9
+ import { ClearButton } from '../../shared/ClearButton';
10
+ import { mergeRefs } from '../../utilities/dom/mergeRefs';
11
+ import { getTimePickerLabel } from './constants';
12
+ /**
13
+ * TimePicker is a styled wrapper around the native `<input type="time">`.
14
+ *
15
+ * The browser owns the picker UI: typing into the segmented control, the
16
+ * UA-provided dropdown, and Escape-to-revert semantics all come from the
17
+ * platform. The trailing clock icon opens the native picker — on desktop via
18
+ * `HTMLInputElement.showPicker()`, on iOS Safari (where `showPicker` is not
19
+ * reliably implemented) by dispatching a click on the underlying input, which
20
+ * is what iOS Safari uses to open its native time picker.
21
+ *
22
+ * The `value` prop follows the same string format as the HTML `<input type="time">`
23
+ * element: `"HH:mm"` for hours and minutes, `"HH:mm:ss"` for hours, minutes, and seconds.
24
+ *
25
+ * The component does not render its own `<label>`. Consumers are expected to
26
+ * provide an accessible name via a parent `<label htmlFor>`, `aria-labelledby`,
27
+ * or `aria-label`.
28
+ *
29
+ * @example
30
+ * // Controlled
31
+ * const [time, setTime] = useState<string>('');
32
+ * <TimePicker value={time} onChange={setTime} />
33
+ *
34
+ * @example
35
+ * // With seconds
36
+ * <TimePicker format="HH:mm:ss" />
37
+ */
38
+ export const TimePicker = forwardRef(({ value, defaultValue, onChange, format = 'HH:mm', size = 'medium', disabled = false, invalid = false, step, onClear, clearButtonProps, pickerIndicatorIconButtonProps, className, ...inputProps }, ref) => {
39
+ const inputRef = useRef(null);
40
+ const mergedInputRef = mergeRefs(ref, inputRef);
41
+ // Track the live value internally so the clear button can read it whether
42
+ // the consumer is using `value` (controlled) or `defaultValue` (uncontrolled).
43
+ const [internalValue, setInternalValue] = useState(value ?? defaultValue ?? '');
44
+ const effectiveValue = value === undefined ? internalValue : value;
45
+ const hasValue = effectiveValue !== '';
46
+ const classes = timePickerSlotRecipe({ size, disabled, invalid });
47
+ const pickerLabel = pickerIndicatorIconButtonProps?.['aria-label'] ?? getTimePickerLabel('ja', 'openPicker');
48
+ const resolvedStep = step ?? (format === 'HH:mm:ss' ? 1 : undefined);
49
+ const handleClear = useCallback((event) => {
50
+ if (!inputRef.current)
51
+ return;
52
+ // eslint-disable-next-line @typescript-eslint/unbound-method
53
+ const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
54
+ setter?.call(inputRef.current, '');
55
+ setInternalValue('');
56
+ onChange?.('', event);
57
+ inputRef.current.focus();
58
+ onClear?.(event);
59
+ }, [onChange, onClear]);
60
+ // Try the spec-compliant `showPicker()` API first. On iOS Safari (where
61
+ // `showPicker` is not reliably implemented and frequently throws), fall
62
+ // back to dispatching a click on the input element — iOS Safari opens its
63
+ // native time picker in response to a user-initiated click on the input.
64
+ const triggerPicker = useCallback(() => {
65
+ const input = inputRef.current;
66
+ if (!input || disabled)
67
+ return;
68
+ if (typeof input.showPicker === 'function') {
69
+ try {
70
+ input.showPicker();
71
+ return;
72
+ }
73
+ catch {
74
+ // Fall through to the click fallback below.
75
+ }
76
+ }
77
+ input.click();
78
+ }, [disabled]);
79
+ return (_jsx(FocusIndicator, { children: _jsxs("div", { className: cx(classes.root, 'mfui-TimePicker__root', className), children: [_jsx("input", { ...inputProps, ref: mergedInputRef, type: "time", className: cx(classes.input, 'mfui-TimePicker__input'), value: value, defaultValue: defaultValue, disabled: disabled, step: resolvedStep, "aria-invalid": invalid || undefined, onChange: (event) => {
80
+ setInternalValue(event.target.value);
81
+ onChange?.(event.target.value, event);
82
+ } }), hasValue && !disabled ? (_jsx(ClearButton, { size: size === 'small' ? 'small' : 'default', "aria-label": clearButtonProps?.['aria-label'] ?? '値をクリアする', className: cx(classes.clearButton, 'mfui-TimePicker__clearButton'), disabled: disabled, onClick: handleClear })) : null, _jsx(IconButton, { size: size === 'small' ? 'small' : 'default', "aria-label": pickerLabel, className: cx(classes.pickerButton, 'mfui-TimePicker__pickerButton'), disabled: disabled, onClick: () => {
83
+ triggerPicker();
84
+ }, children: _jsx(Time, {}) })] }) }));
85
+ });
@@ -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';
@@ -1,5 +1,9 @@
1
1
  export * from './DatePicker';
2
2
  export * from './FilterDatePicker';
3
+ export * from './FilterDateRangePicker';
4
+ export * from './FilterMonthPicker';
5
+ export * from './FilterMonthRangePicker';
3
6
  export * from './MonthPicker';
4
7
  export * from './DateRangePicker';
5
8
  export * from './MonthRangePicker';
9
+ export * from './TimePicker';
@@ -1,5 +1,9 @@
1
1
  export * from './DatePicker';
2
2
  export * from './FilterDatePicker';
3
+ export * from './FilterDateRangePicker';
4
+ export * from './FilterMonthPicker';
5
+ export * from './FilterMonthRangePicker';
3
6
  export * from './MonthPicker';
4
7
  export * from './DateRangePicker';
5
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, }: 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;
@@ -9,8 +9,8 @@ import { dayjs } from '../../../utilities/date/dayjs';
9
9
  /**
10
10
  * Internal component that has access to BaseRangePickerContext
11
11
  */
12
- function InternalBaseRangePicker({ startInputRef, endInputRef, disabled, invalid, format, enableClearButton, clearButtonProps, targetDOMNode, onBlur, isOpen, open, close, onOpenStateChanged, enableAutoUnmount, allowedPlacements, enableViewportConstraint, minWidth, renderPopoverContent, startInputProps = {}, endInputProps = {}, calendarLocale = 'ja', }) {
13
- const { setPendingFocusDate, temporaryStart, temporaryEnd, viewingMonth, setViewingMonth } = useBaseRangePickerContext();
12
+ function InternalBaseRangePicker({ startInputRef, endInputRef, disabled, invalid, format, enableClearButton, clearButtonProps, targetDOMNode, onBlur, isOpen, open, close, onOpenStateChanged, enableAutoUnmount, allowedPlacements, enableViewportConstraint, minWidth, renderPopoverContent, startInputProps = {}, endInputProps = {}, calendarLocale = 'ja', pendingFocusDayjsFormat = 'YYYY-MM-DD', }) {
13
+ const { setPendingFocusDate, temporaryStart, temporaryEnd, startDate, endDate, viewingMonth, setViewingMonth } = useBaseRangePickerContext();
14
14
  // Custom onOpen handler to prevent auto-focus when clicking on input elements
15
15
  // and implement smart focus management for calendar dates
16
16
  const handlePopoverOpen = useCallback((event) => {
@@ -31,7 +31,7 @@ function InternalBaseRangePicker({ startInputRef, endInputRef, disabled, invalid
31
31
  // 3. Selected start date (if exists)
32
32
  // 4. Selected end date (if exists and no start date)
33
33
  // 5. Current date (today)
34
- // 6. First non-disabled date (handled by CalendarGrid automatically)
34
+ // 6. First non-disabled cell (handled by CalendarGrid / MonthGrid automatically)
35
35
  let dateToFocus;
36
36
  if (temporaryStart) {
37
37
  dateToFocus = temporaryStart;
@@ -39,16 +39,32 @@ function InternalBaseRangePicker({ startInputRef, endInputRef, disabled, invalid
39
39
  else if (temporaryEnd) {
40
40
  dateToFocus = temporaryEnd;
41
41
  }
42
+ else if (startDate) {
43
+ dateToFocus = startDate;
44
+ }
45
+ else if (endDate) {
46
+ dateToFocus = endDate;
47
+ }
42
48
  else {
43
- // Default to today's date - CalendarGrid will handle finding first non-disabled date if today is disabled
49
+ // Default to today's date - the grid will focus the first non-disabled cell if needed
44
50
  dateToFocus = new Date();
45
51
  }
46
- // Set the pending focus date so CalendarGrid can focus on it when it renders
47
- // Use the format from the context to handle both date and month formats
48
- const formattedDate = dayjs(dateToFocus).format(format);
52
+ // Set the pending focus key so the popover grid can focus the matching cell when it renders.
53
+ // Must match DateCell `data-mfui-date` (YYYY-MM-DD) or MonthCell `data-mfui-month` (YYYY-MM).
54
+ const formattedDate = dayjs(dateToFocus).format(pendingFocusDayjsFormat);
49
55
  setPendingFocusDate(formattedDate);
50
56
  open();
51
- }, [startInputRef, endInputRef, temporaryStart, temporaryEnd, setPendingFocusDate, open, format]);
57
+ }, [
58
+ startInputRef,
59
+ endInputRef,
60
+ temporaryStart,
61
+ temporaryEnd,
62
+ startDate,
63
+ endDate,
64
+ setPendingFocusDate,
65
+ open,
66
+ pendingFocusDayjsFormat,
67
+ ]);
52
68
  return (_jsx(Popover, { enableAutomaticPortalTargetResolution: true, open: isOpen, allowedPlacements: allowedPlacements, enableViewportConstraint: enableViewportConstraint, minWidth: minWidth, renderTrigger: ({ setTriggerRef, handleTriggerBlur, handleTriggerKeyDown, openPopover }) => (_jsx(BaseRangePickerTrigger, { ref: setTriggerRef, disabled: disabled, invalid: invalid, format: format, enableClearValue: enableClearButton, clearButtonProps: clearButtonProps, startInputRef: startInputRef, endInputRef: endInputRef, startInputProps: startInputProps, endInputProps: endInputProps, popoverOpenFunction: openPopover, calendarLocale: calendarLocale, onBlur: handleTriggerBlur, onKeyDown: handleTriggerKeyDown })), renderContent: () => renderPopoverContent({
53
69
  viewingValue: viewingMonth,
54
70
  setViewingValue: setViewingMonth,
@@ -61,12 +77,12 @@ function InternalBaseRangePicker({ startInputRef, endInputRef, disabled, invalid
61
77
  * BaseRangePicker component
62
78
  * A generic component for selecting a range of dates with configurable format
63
79
  */
64
- 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, }) {
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', }) {
65
81
  const { isOpen, open, close } = useDisclosure({ value: false });
66
82
  const startInputRef = useRef(null);
67
83
  const endInputRef = useRef(null);
68
84
  // Default renderPopoverContent implementation for backward compatibility
69
85
  const defaultRenderPopoverContent = useCallback(() => _jsx(BaseRangePickerPopover, { calendarLocale: calendarLocale }), [calendarLocale]);
70
86
  const finalRenderPopoverContent = renderPopoverContent ?? defaultRenderPopoverContent;
71
- 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, 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, 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 }) }));
72
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.
@@ -307,4 +317,13 @@ export type BaseRangePickerProps = {
307
317
  * @internal
308
318
  */
309
319
  initialViewingDate?: Date;
320
+ /**
321
+ * dayjs format for pending grid focus strings (`pendingFocusDate` / `pendingFocusMonth`).
322
+ * Must match the grid: `YYYY-MM-DD` for day calendars, `YYYY-MM` for month grids.
323
+ *
324
+ * @internal
325
+ *
326
+ * @default 'YYYY-MM-DD'
327
+ */
328
+ pendingFocusDayjsFormat?: 'YYYY-MM-DD' | 'YYYY-MM';
310
329
  };
@@ -5,6 +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 { parseInputDate } from '../../utilities/dateParsing';
8
9
  // eslint-disable-next-line @typescript-eslint/no-empty-function
9
10
  const noop = () => { };
10
11
  // Normalizes a date to the first of the month
@@ -97,11 +98,12 @@ function autoCompletePartialRange(dates) {
97
98
  }
98
99
  return dates;
99
100
  }
100
- const useBaseRangePickerContextValue = ({ value, defaultValue, disabled = false, onChange, format = 'YYYY/MM/DD', isOpen = false, open = noop, close = noop, disableAutoOpen, minDate, maxDate, initialDisplayedMonths = 'currentAndNext', initialViewingDate, }) => {
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]);
101
103
  const [dates, setDates] = useTransformedState(defaultValue ?? [undefined, undefined], normalizeToStartOfDay);
102
104
  const [dateStrings, setDateStrings] = useState(() => {
103
105
  const [startDate, endDate] = value ?? defaultValue ?? [undefined, undefined];
104
- return [startDate ? dayjs(startDate).format(format) : '', endDate ? dayjs(endDate).format(format) : ''];
106
+ return [startDate ? formatDate(startDate) : '', endDate ? formatDate(endDate) : ''];
105
107
  });
106
108
  const [startDate, endDate] = value ?? dates;
107
109
  const [startDateString, endDateString] = dateStrings;
@@ -134,9 +136,9 @@ const useBaseRangePickerContextValue = ({ value, defaultValue, disabled = false,
134
136
  useUpdateEffect(() => {
135
137
  if (value) {
136
138
  setDates(value);
137
- 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]) : '']);
138
140
  }
139
- }, [value, format, setDates]);
141
+ }, [value, formatDate, setDates]);
140
142
  const handleChange = useCallback((newDates) => {
141
143
  let [newStartDate, newEndDate] = normalizeToStartOfDay(newDates);
142
144
  // Swap dates if start date is after end date
@@ -145,12 +147,9 @@ const useBaseRangePickerContextValue = ({ value, defaultValue, disabled = false,
145
147
  }
146
148
  setDates([newStartDate, newEndDate]);
147
149
  // In uncontrolled mode, update dateStrings for input display
148
- setDateStrings([
149
- newStartDate ? dayjs(newStartDate).format(format) : '',
150
- newEndDate ? dayjs(newEndDate).format(format) : '',
151
- ]);
150
+ setDateStrings([newStartDate ? formatDate(newStartDate) : '', newEndDate ? formatDate(newEndDate) : '']);
152
151
  onChange?.([newStartDate, newEndDate]);
153
- }, [onChange, format, setDates]);
152
+ }, [onChange, formatDate, setDates]);
154
153
  const handleStartDateChange = useCallback((date, nextEndDate) => {
155
154
  const proposedEndDate = nextEndDate !== undefined ? nextEndDate : endDate;
156
155
  // Apply auto-completion behavior when start date is set and end date is missing
@@ -176,39 +175,61 @@ const useBaseRangePickerContextValue = ({ value, defaultValue, disabled = false,
176
175
  const handleStartDateStringChange = useCallback((value) => {
177
176
  setDateStrings([value, endDateString]);
178
177
  // Parse and update both viewing month and temporary selection for immediate visual feedback
179
- const parsedDate = dayjs(value, format, true);
180
- if (parsedDate.isValid() && !isDateOutsideConstraints(parsedDate.toDate(), minDate, maxDate, format)) {
181
- const newDate = parsedDate.toDate();
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;
182
183
  setViewingMonth(newDate);
183
184
  // Update temporary selection for immediate calendar visual feedback
184
185
  setTemporaryStart(newDate);
185
186
  // Set pending focus date so when calendar opens, it focuses on this date
186
- setPendingFocusDate(parsedDate.format('YYYY-MM-DD'));
187
+ setPendingFocusDate(dayjs(newDate).format(pendingFocusDayjsFormat));
187
188
  }
188
189
  else if (value === '') {
189
190
  // Clear temporary selection if input is empty
190
191
  setTemporaryStart(undefined);
191
192
  setPendingFocusDate(null);
192
193
  }
193
- }, [endDateString, format, minDate, maxDate, setViewingMonth, setTemporaryStart, setPendingFocusDate]);
194
+ }, [
195
+ endDateString,
196
+ format,
197
+ minDate,
198
+ maxDate,
199
+ setViewingMonth,
200
+ setTemporaryStart,
201
+ setPendingFocusDate,
202
+ pendingFocusDayjsFormat,
203
+ ]);
194
204
  const handleEndDateStringChange = useCallback((value) => {
195
205
  setDateStrings([startDateString, value]);
196
206
  // Parse and update both viewing month and temporary selection for immediate visual feedback
197
- const parsedDate = dayjs(value, format, true);
198
- if (parsedDate.isValid() && !isDateOutsideConstraints(parsedDate.toDate(), minDate, maxDate, format)) {
199
- const newDate = parsedDate.toDate();
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;
200
212
  setViewingMonth(newDate);
201
213
  // Update temporary selection for immediate calendar visual feedback
202
214
  setTemporaryEnd(newDate);
203
215
  // Set pending focus date so when calendar opens, it focuses on this date
204
- setPendingFocusDate(parsedDate.format('YYYY-MM-DD'));
216
+ setPendingFocusDate(dayjs(newDate).format(pendingFocusDayjsFormat));
205
217
  }
206
218
  else if (value === '') {
207
219
  // Clear temporary selection if input is empty
208
220
  setTemporaryEnd(undefined);
209
221
  setPendingFocusDate(null);
210
222
  }
211
- }, [startDateString, format, minDate, maxDate, setViewingMonth, setTemporaryEnd, setPendingFocusDate]);
223
+ }, [
224
+ startDateString,
225
+ format,
226
+ minDate,
227
+ maxDate,
228
+ setViewingMonth,
229
+ setTemporaryEnd,
230
+ setPendingFocusDate,
231
+ pendingFocusDayjsFormat,
232
+ ]);
212
233
  const handleClear = useCallback(() => {
213
234
  // handleChange already calls setDates and setDateStrings, so we don't need to call them again
214
235
  handleChange([undefined, undefined]);
@@ -97,4 +97,23 @@ 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;
109
+ /**
110
+ * dayjs format string for `pendingFocusDate` so it matches the focused grid cell's `data-mfui-*` attribute.
111
+ *
112
+ * @internal
113
+ *
114
+ * @default 'YYYY-MM-DD' — matches {@link DateCell} `data-mfui-date`
115
+ *
116
+ * @example 'YYYY-MM' — matches {@link MonthCell} `data-mfui-month` (MonthRangePicker)
117
+ */
118
+ pendingFocusDayjsFormat?: 'YYYY-MM-DD' | 'YYYY-MM';
100
119
  };
@@ -19,7 +19,7 @@ import { useDateRangeTriggerInputClick } from './hooks/useDateRangeTriggerInputC
19
19
  */
20
20
  export const BaseRangePickerTrigger = forwardRef(({ enableClearValue = false, invalid = false, size = 'medium', disabled = false, wrapperProps = {}, clearButtonProps = {}, startInputProps = {}, endInputProps = {}, calendarButtonProps = {}, startInputRef, endInputRef, popoverOpenFunction, format = 'YYYY-MM-DD', calendarLocale = 'ja', ...props }, ref) => {
21
21
  const isTouchDevice = useIsTouchDevice();
22
- const { toggle, disableAutoOpen } = useBaseRangePickerContext();
22
+ const { toggle, disableAutoOpen, isOpen } = useBaseRangePickerContext();
23
23
  const { handleInputClick } = useDateRangeTriggerInputClick(disableAutoOpen ?? false, popoverOpenFunction);
24
24
  const { onClick: onClickClearButton, 'aria-label': clearAriaLabel } = clearButtonProps;
25
25
  const { onClick: onCalendarClick, 'aria-label': calendarAriaLabel } = calendarButtonProps;
@@ -41,14 +41,12 @@ export const BaseRangePickerTrigger = forwardRef(({ enableClearValue = false, in
41
41
  const finalEndPlaceholder = endInputPlaceholder ?? getBaseRangePickerTriggerLabel('END_DATE_PLACEHOLDER', calendarLocale);
42
42
  const shouldShowClearButton = enableClearValue && (enhancedStartInputProps.value || enhancedEndInputProps.value);
43
43
  const classes = baseRangePickerTriggerSlotRecipe({ invalid, disabled, size });
44
+ // Popover open: mark calendar icon active. Use `undefined` when closed — `data-active="false"` is still truthy in CSS.
45
+ const shouldCalendarIconActive = isOpen ? true : undefined;
44
46
  const handleClickClearButton = useCallback((event) => {
45
47
  handleClear();
46
48
  onClickClearButton?.(event);
47
49
  }, [handleClear, onClickClearButton]);
48
- return (_jsxs("div", { ...wrapperProps, ...props, ref: ref, className: cx(classes.root, 'mfui-BaseRangePickerTrigger__root', wrapperProps.className), children: [_jsx("div", { className: cx(classes.inputGroup, 'mfui-BaseRangePickerTrigger__inputGroup'), children: isTouchDevice ? (
49
- // Touch device: hidden inputs + display divs
50
- _jsxs(_Fragment, { children: [_jsx("input", { type: "hidden", value: enhancedStartInputProps.value, name: startInputProps.name }), _jsx("input", { type: "hidden", value: enhancedEndInputProps.value, name: endInputProps.name }), _jsx("div", { className: cx(classes.input, 'mfui-BaseRangePickerTrigger__input'), onClick: !disableAutoOpen && !disabled ? handleInputClick('start', startInputProps.onClick) : undefined, children: enhancedStartInputProps.value || finalStartPlaceholder }), _jsx(Typography, { variant: "controlLabel", className: cx(classes.separator, 'mfui-BaseRangePickerTrigger__separator'), children: SEPARATOR_CONTENT }), _jsx("div", { className: cx(classes.input, 'mfui-BaseRangePickerTrigger__input'), onClick: !disableAutoOpen && !disabled ? handleInputClick('end', endInputProps.onClick) : undefined, children: enhancedEndInputProps.value || finalEndPlaceholder })] })) : (
51
- // Desktop: Standard inputs with FocusIndicator
52
- _jsxs(_Fragment, { children: [_jsx(FocusIndicator, { position: "inside", children: _jsx("input", { ref: startInputRef, disabled: disabled, className: cx(classes.input, 'mfui-BaseRangePickerTrigger__input'), ...enhancedStartInputProps, placeholder: finalStartPlaceholder }) }), _jsx(Typography, { variant: "controlLabel", className: cx(classes.separator, 'mfui-BaseRangePickerTrigger__separator'), children: SEPARATOR_CONTENT }), _jsx(FocusIndicator, { position: "inside", children: _jsx("input", { ref: endInputRef, disabled: disabled, className: cx(classes.input, 'mfui-BaseRangePickerTrigger__input'), ...enhancedEndInputProps, placeholder: finalEndPlaceholder }) })] })) }), shouldShowClearButton ? (_jsx(ClearButton, { size: size === 'small' ? 'small' : 'default', "aria-label": clearAriaLabel ?? getBaseRangePickerTriggerLabel('CLEAR_BUTTON_LABEL', calendarLocale), className: cx(classes.actionIcon, 'mfui-BaseRangePickerTrigger__actionIcon'), disabled: disabled, onClick: handleClickClearButton })) : null, _jsx(IconButton, { size: size === 'small' ? 'small' : 'default', "aria-label": calendarAriaLabel ?? getBaseRangePickerTriggerLabel('CALENDAR_BUTTON_LABEL', calendarLocale), className: cx(classes.actionIcon, 'mfui-BaseRangePickerTrigger__actionIcon'), disabled: disabled, onClick: onCalendarClick ?? toggle, children: _jsx(Calendar, {}) })] }));
50
+ return (_jsxs("div", { ...wrapperProps, ...props, ref: ref, className: cx(classes.root, 'mfui-BaseRangePickerTrigger__root', wrapperProps.className), children: [_jsx("div", { className: cx(classes.inputGroup, 'mfui-BaseRangePickerTrigger__inputGroup'), children: isTouchDevice ? (_jsxs(_Fragment, { children: [_jsx("input", { type: "hidden", value: enhancedStartInputProps.value, name: startInputProps.name }), _jsx("input", { type: "hidden", value: enhancedEndInputProps.value, name: endInputProps.name }), _jsx("div", { className: cx(classes.input, 'mfui-BaseRangePickerTrigger__input'), onClick: !disableAutoOpen && !disabled ? handleInputClick('start', startInputProps.onClick) : undefined, children: enhancedStartInputProps.value || finalStartPlaceholder }), _jsx(Typography, { variant: "controlLabel", className: cx(classes.separator, 'mfui-BaseRangePickerTrigger__separator'), children: SEPARATOR_CONTENT }), _jsx("div", { className: cx(classes.input, 'mfui-BaseRangePickerTrigger__input'), onClick: !disableAutoOpen && !disabled ? handleInputClick('end', endInputProps.onClick) : undefined, children: enhancedEndInputProps.value || finalEndPlaceholder })] })) : (_jsxs(_Fragment, { children: [_jsx(FocusIndicator, { position: "inside", children: _jsx("input", { ref: startInputRef, disabled: disabled, className: cx(classes.input, 'mfui-BaseRangePickerTrigger__input'), ...enhancedStartInputProps, placeholder: finalStartPlaceholder }) }), _jsx(Typography, { variant: "controlLabel", className: cx(classes.separator, 'mfui-BaseRangePickerTrigger__separator'), children: SEPARATOR_CONTENT }), _jsx(FocusIndicator, { position: "inside", children: _jsx("input", { ref: endInputRef, disabled: disabled, className: cx(classes.input, 'mfui-BaseRangePickerTrigger__input'), ...enhancedEndInputProps, placeholder: finalEndPlaceholder }) })] })) }), shouldShowClearButton ? (_jsx(ClearButton, { size: size === 'small' ? 'small' : 'default', "aria-label": clearAriaLabel ?? getBaseRangePickerTriggerLabel('CLEAR_BUTTON_LABEL', calendarLocale), className: cx(classes.actionIcon, 'mfui-BaseRangePickerTrigger__actionIcon'), disabled: disabled, onClick: handleClickClearButton })) : null, _jsx(IconButton, { size: size === 'small' ? 'small' : 'default', "aria-label": calendarAriaLabel ?? getBaseRangePickerTriggerLabel('CALENDAR_BUTTON_LABEL', calendarLocale), className: cx(classes.actionIcon, 'mfui-BaseRangePickerTrigger__actionIcon'), "data-active": shouldCalendarIconActive, disabled: disabled, onClick: onCalendarClick ?? toggle, children: _jsx(Calendar, {}) })] }));
53
51
  });
54
52
  BaseRangePickerTrigger.displayName = 'BaseRangePicker.Trigger';