@moneyforward/mfui-components 4.5.0 → 4.6.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 (71) hide show
  1. package/dist/src/Badge/Badge.d.ts +1 -1
  2. package/dist/src/Badge/Badge.js +4 -4
  3. package/dist/src/Badge/Badge.types.d.ts +3 -1
  4. package/dist/src/Checkbox/Checkbox.js +7 -3
  5. package/dist/src/DataTable/DataTable.types.d.ts +2 -2
  6. package/dist/src/DataTable/DataTableCell/DataTableCell.js +21 -4
  7. package/dist/src/DataTable/DataTableCell/DataTableCell.types.d.ts +1 -1
  8. package/dist/src/DataTable/DataTableHeaderRow/DataTableHeaderRow.js +7 -5
  9. package/dist/src/DataTable/DataTableRow/DataTableRow.js +104 -32
  10. package/dist/src/DataTable/DataTableRow/DataTableRow.types.d.ts +44 -15
  11. package/dist/src/DataTable/DataTableRowActionContext.d.ts +34 -0
  12. package/dist/src/DataTable/DataTableRowActionContext.js +8 -0
  13. package/dist/src/DateTimeSelection/DatePicker/DatePickerCalendar/DatePickerCalendar.js +7 -3
  14. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerPopover/DateRangePickerCalendar/DateRangePickerCalendar.js +1 -1
  15. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerPopover/utilities/createDateRangePickerPopoverTestUtility.js +2 -2
  16. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerProvider/DateRangePickerProvider.js +13 -7
  17. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerTrigger/DateRangePickerTrigger.js +1 -1
  18. package/dist/src/DateTimeSelection/FilterDatePicker/FilterDatePicker.js +1 -1
  19. package/dist/src/DateTimeSelection/FilterMonthPicker/FilterMonthPicker.js +1 -1
  20. package/dist/src/DateTimeSelection/FilterMonthRangePicker/FilterMonthRangePicker.js +1 -1
  21. package/dist/src/DateTimeSelection/MonthPicker/MonthPickerPanel/MonthPickerPanel.js +1 -1
  22. package/dist/src/DateTimeSelection/MonthPicker/constants.d.ts +2 -2
  23. package/dist/src/DateTimeSelection/MonthPicker/constants.js +6 -6
  24. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerMonthCell/MonthRangePickerMonthCell.js +1 -1
  25. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerNavigation/MonthRangePickerNavigation.js +2 -2
  26. package/dist/src/DateTimeSelection/TimePicker/TimePicker.js +1 -1
  27. package/dist/src/DateTimeSelection/shared/BasePicker/constants.d.ts +2 -2
  28. package/dist/src/DateTimeSelection/shared/BasePicker/constants.js +10 -10
  29. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/BaseRangePickerCalendar/BaseRangePickerCalendar.js +1 -1
  30. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/constants.js +4 -4
  31. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerProvider/BaseRangePickerProvider.js +13 -7
  32. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerTrigger/BaseRangePickerTrigger.js +2 -2
  33. package/dist/src/DateTimeSelection/shared/CalendarGrid/CalendarGrid.js +1 -1
  34. package/dist/src/DateTimeSelection/shared/utilities/japaneseCalendar.js +2 -2
  35. package/dist/src/FileDropZone/FileDropZone.js +1 -1
  36. package/dist/src/FileDropZone/utils/fileHelpers.js +1 -1
  37. package/dist/src/MainNavigation/BaseMainNavigation.js +1 -1
  38. package/dist/src/MainNavigation/NarrowViewportMainNavigation.js +1 -1
  39. package/dist/src/MultipleSelectBox/MultipleSelectBoxTrigger/MultipleSelectBoxTrigger.js +1 -1
  40. package/dist/src/MultipleSelectBox/hooks/useCreateOption.js +8 -4
  41. package/dist/src/MultipleSelectBox/hooks/useOptionKeyboardNavigation.js +9 -4
  42. package/dist/src/MultipleSelectBox/hooks/useSelectedValues.js +16 -50
  43. package/dist/src/NumberBox/NumberBox.js +8 -4
  44. package/dist/src/PageHeader/PageHeader.types.d.ts +2 -0
  45. package/dist/src/Popover/Popover.js +11 -2
  46. package/dist/src/SelectBox/SelectBox.js +9 -10
  47. package/dist/src/SelectBox/hooks/useKeyboardNavigation.js +8 -2
  48. package/dist/src/SplitView/hooks/useSplitViewPanelVisibility.js +5 -2
  49. package/dist/src/SubNavigation/SubNavigation.js +80 -19
  50. package/dist/src/SubNavigation/SubNavigation.types.d.ts +18 -6
  51. package/dist/src/Tabs/Tabs.js +8 -4
  52. package/dist/src/Tabs/hooks/useCalculatedSelectedTabId.js +7 -5
  53. package/dist/src/Toast/ToastContainer/ToastContainer.js +11 -2
  54. package/dist/src/Tooltip/Tooltip.js +8 -3
  55. package/dist/src/Tooltip/hooks/useTooltipDisplayController.js +77 -88
  56. package/dist/src/utilities/dom/getFocusableNodes.js +1 -3
  57. package/dist/src/utilities/dom/useFixedColumns.js +6 -8
  58. package/dist/src/utilities/react/flattenChildren.d.ts +15 -0
  59. package/dist/src/utilities/react/flattenChildren.js +28 -0
  60. package/dist/src/utilities/state/useLatest.d.ts +1 -1
  61. package/dist/src/utilities/state/useLatest.js +8 -2
  62. package/dist/styled-system/recipes/badge-slot-recipe.d.ts +1 -1
  63. package/dist/styled-system/recipes/badge-slot-recipe.js +4 -0
  64. package/dist/styled-system/recipes/data-table-cell-slot-recipe.d.ts +1 -1
  65. package/dist/styled-system/recipes/data-table-cell-slot-recipe.js +4 -0
  66. package/dist/styled-system/recipes/data-table-row-slot-recipe.js +13 -6
  67. package/dist/styled-system/recipes/sub-navigation-slot-recipe.d.ts +1 -1
  68. package/dist/styled-system/recipes/sub-navigation-slot-recipe.js +12 -0
  69. package/dist/styles.css +155 -35
  70. package/dist/tsconfig.build.tsbuildinfo +1 -1
  71. package/package.json +12 -12
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
3
+ import { createContext, useCallback, useContext, useMemo, useState } from 'react';
4
4
  import { flushSync } from 'react-dom';
5
5
  import { dayjs } from '../../../utilities/date/dayjs';
6
6
  import { useUpdateEffect } from '../../../utilities/effect/useUpdateEffect';
@@ -48,7 +48,7 @@ const useDateRangePickerContextValue = ({ value, defaultValue, disabled = false,
48
48
  const today = useMemo(() => new Date(), []);
49
49
  const [viewingMonth, setViewingMonth] = useTransformedState(value?.[0] ?? defaultValue?.[0] ?? new Date(today.getFullYear(), today.getMonth(), 1), normalizeToFirstOfMonth);
50
50
  // --- Selection/Preview State and Logic ---
51
- const [selecting, setSelecting] = useState('start');
51
+ const [selecting, setSelecting] = useState(() => (!startDate || endDate ? 'start' : 'end'));
52
52
  const [temporaryStart, setTemporaryStart] = useState(startDate);
53
53
  const [temporaryEnd, setTemporaryEnd] = useState(endDate);
54
54
  const [previewDate, setPreviewDate] = useState();
@@ -85,10 +85,10 @@ const useDateRangePickerContextValue = ({ value, defaultValue, disabled = false,
85
85
  onChange?.([newStartDate, newEndDate]);
86
86
  }, [onChange, format, setDates]);
87
87
  const handleStartDateChange = useCallback((date, nextEndDate) => {
88
- handleChange([date, nextEndDate !== undefined ? nextEndDate : endDate]);
88
+ handleChange([date, nextEndDate ?? endDate]);
89
89
  }, [endDate, handleChange]);
90
90
  const handleEndDateChange = useCallback((date, nextStartDate) => {
91
- handleChange([nextStartDate !== undefined ? nextStartDate : startDate, date]);
91
+ handleChange([nextStartDate ?? startDate, date]);
92
92
  }, [startDate, handleChange]);
93
93
  const handleStartDateStringChange = useCallback((value) => {
94
94
  setDateStrings([value, endDateString]);
@@ -131,12 +131,18 @@ const useDateRangePickerContextValue = ({ value, defaultValue, disabled = false,
131
131
  setTemporaryEnd(undefined);
132
132
  close();
133
133
  }, [handleChange, close]);
134
- useEffect(() => {
134
+ // Adjusted during render (React's documented pattern for resyncing state to changed
135
+ // props) rather than in an effect, so the UI never flashes the stale value for one frame.
136
+ const [prevStartDate, setPrevStartDate] = useState(startDate);
137
+ const [prevEndDate, setPrevEndDate] = useState(endDate);
138
+ if (startDate !== prevStartDate || endDate !== prevEndDate) {
139
+ setPrevStartDate(startDate);
140
+ setPrevEndDate(endDate);
135
141
  setTemporaryStart(startDate);
136
142
  setTemporaryEnd(endDate);
137
143
  setSelecting(!startDate || endDate ? 'start' : 'end');
138
144
  setPreviewDate(undefined);
139
- }, [startDate, endDate]);
145
+ }
140
146
  const handleDateClick = useCallback((date) => {
141
147
  // Prevent selection of disabled dates
142
148
  if (isDateOutsideConstraints(date, minDate, maxDate)) {
@@ -200,7 +206,7 @@ const useDateRangePickerContextValue = ({ value, defaultValue, disabled = false,
200
206
  }
201
207
  // handleChange already calls setDates and setDateStrings, so we don't need to call them again
202
208
  handleChange([newStart, newEnd]);
203
- // Do not setTemporaryStart/setTemporaryEnd here; let the effect sync them from startDate/endDate
209
+ // Do not setTemporaryStart/setTemporaryEnd here; the render-time adjustment above syncs them from startDate/endDate
204
210
  close();
205
211
  }, [handleChange, close, temporaryStart, temporaryEnd, minDate, maxDate]);
206
212
  const handleCancel = useCallback(() => {
@@ -34,7 +34,7 @@ export const DateRangePickerTrigger = forwardRef(({ enableClearValue = false, in
34
34
  });
35
35
  const { placeholder: startPlaceholder = LOCALE.START_DATE_PLACEHOLDER } = startInputProps;
36
36
  const { placeholder: endPlaceholder = LOCALE.END_DATE_PLACEHOLDER } = endInputProps;
37
- const shouldShowClearButton = enableClearValue && (enhancedStartInputProps.value || enhancedEndInputProps.value);
37
+ const shouldShowClearButton = enableClearValue && ((enhancedStartInputProps.value ?? '') || (enhancedEndInputProps.value ?? ''));
38
38
  // The error state takes precedence over the caution state
39
39
  const classes = baseRangePickerTriggerSlotRecipe({ invalid, caution: caution && !invalid, disabled, size });
40
40
  const handleClickClearButton = useCallback((event) => {
@@ -25,7 +25,7 @@ export function FilterDatePicker({ label, value, defaultValue, onChange, open, o
25
25
  const triggerRef = useRef(null);
26
26
  const dialogId = useId();
27
27
  const [internalValue, setInternalValue] = useState(defaultValue);
28
- const currentValue = value !== undefined ? value : internalValue;
28
+ const currentValue = value ?? internalValue;
29
29
  // When a controlled value transitions back to undefined, clear any stale internal value
30
30
  // so the component doesn't show a previously-selected uncontrolled date.
31
31
  const previousValueRef = useRef(value);
@@ -24,7 +24,7 @@ export function FilterMonthPicker({ label, value, defaultValue, onChange, open,
24
24
  const triggerRef = useRef(null);
25
25
  const dialogId = useId();
26
26
  const [internalValue, setInternalValue] = useState(defaultValue);
27
- const currentValue = value !== undefined ? value : internalValue;
27
+ const currentValue = value ?? internalValue;
28
28
  // When a controlled value transitions back to undefined, clear any stale internal value
29
29
  // so the component doesn't show a previously-selected uncontrolled month.
30
30
  const previousValueRef = useRef(value);
@@ -26,7 +26,7 @@ export function FilterMonthRangePicker({ label, value, defaultValue, onChange, o
26
26
  const triggerRef = useRef(null);
27
27
  const dialogId = useId();
28
28
  const [internalValue, setInternalValue] = useState(() => defaultValue ?? [undefined, undefined]);
29
- const currentValue = value !== undefined ? value : internalValue;
29
+ const currentValue = value ?? internalValue;
30
30
  const previousValueRef = useRef(value);
31
31
  useEffect(() => {
32
32
  const previousValue = previousValueRef.current;
@@ -88,7 +88,7 @@ export function MonthPickerPanel({ viewingValue, setViewingValue, value, onChang
88
88
  const handleYearSelect = useCallback((newYear) => {
89
89
  setViewingValue(new Date(newYear, 0, 1));
90
90
  }, [setViewingValue]);
91
- const shouldFilterYears = !!(minMonth || maxMonth || checkDisabledMonth);
91
+ const shouldFilterYears = !!(minMonth ?? maxMonth ?? checkDisabledMonth);
92
92
  const filterYear = useCallback((y) => {
93
93
  const normalizedMinMonth = minMonth ? new Date(minMonth) : undefined;
94
94
  const normalizedMaxMonth = maxMonth ? new Date(maxMonth) : undefined;
@@ -1,5 +1,5 @@
1
- export declare const LABEL_PREV_YEAR = "\u524D\u306E\u5E74";
2
- export declare const LABEL_NEXT_YEAR = "\u6B21\u306E\u5E74";
1
+ export declare const LABEL_PREV_YEAR = "\u524D\u306E\u5E74\u3092\u8868\u793A";
2
+ export declare const LABEL_NEXT_YEAR = "\u6B21\u306E\u5E74\u3092\u8868\u793A";
3
3
  export declare const LABEL_OPEN_CALENDAR = "\u30AB\u30EC\u30F3\u30C0\u30FC\u3092\u958B\u304F";
4
4
  export declare const LABEL_CLOSE_CALENDAR = "\u30AB\u30EC\u30F3\u30C0\u30FC\u3092\u9589\u3058\u308B";
5
5
  type MonthPickerLabelKey = 'prevYear' | 'nextYear';
@@ -1,5 +1,5 @@
1
- export const LABEL_PREV_YEAR = '前の年'; // Previous year
2
- export const LABEL_NEXT_YEAR = '次の年'; // Next year
1
+ export const LABEL_PREV_YEAR = '前の年を表示'; // Show previous year
2
+ export const LABEL_NEXT_YEAR = '次の年を表示'; // Show next year
3
3
  export const LABEL_OPEN_CALENDAR = 'カレンダーを開く'; // Open calendar
4
4
  export const LABEL_CLOSE_CALENDAR = 'カレンダーを閉じる'; // Close calendar
5
5
  /**
@@ -7,12 +7,12 @@ export const LABEL_CLOSE_CALENDAR = 'カレンダーを閉じる'; // Close cale
7
7
  */
8
8
  export const MONTH_PICKER_LABELS = {
9
9
  ja: {
10
- prevYear: '前の年',
11
- nextYear: '次の年',
10
+ prevYear: '前の年を表示',
11
+ nextYear: '次の年を表示',
12
12
  },
13
13
  en: {
14
- prevYear: 'Previous year',
15
- nextYear: 'Next year',
14
+ prevYear: 'Show previous year',
15
+ nextYear: 'Show next year',
16
16
  },
17
17
  };
18
18
  /**
@@ -23,7 +23,7 @@ import 'dayjs/locale/ja';
23
23
  export function MonthRangePickerMonthCell({ isInRange, isRangeStart, isRangeEnd, onMouseEnter, onMouseLeave, handleKeyboardNavigation, ...monthCellProps }) {
24
24
  const calendarLocale = useCalendarLocale();
25
25
  // Calculate if this month is selected (either start or end of range)
26
- const selected = isRangeStart || isRangeEnd || monthCellProps.selected || false;
26
+ const selected = !!isRangeStart || !!isRangeEnd || !!monthCellProps.selected;
27
27
  const monthFormat = useMemo(() => getMonthCellMonthFormat(calendarLocale, monthCellProps.date), [calendarLocale, monthCellProps.date]);
28
28
  // Create accessible label for range selection
29
29
  const accessibleLabel = useMemo(() => {
@@ -11,8 +11,8 @@ import { yearToEraLabel } from '../../shared/utilities/japaneseCalendar';
11
11
  // Constants for year navigation limits
12
12
  const MINIMUM_VIEWABLE_YEAR = 1900;
13
13
  const MAXIMUM_VIEWABLE_YEAR = 2100;
14
- const LABEL_PREV_YEAR = { ja: '前年', en: 'Previous year' };
15
- const LABEL_NEXT_YEAR = { ja: '翌年', en: 'Next year' };
14
+ const LABEL_PREV_YEAR = { ja: '前の年を表示', en: 'Show previous year' };
15
+ const LABEL_NEXT_YEAR = { ja: '次の年を表示', en: 'Show next year' };
16
16
  /**
17
17
  * Year navigation component for MonthRangePicker.
18
18
  *
@@ -41,7 +41,7 @@ export const TimePicker = forwardRef(({ value, defaultValue, onChange, format =
41
41
  // Track the live value internally so the clear button can read it whether
42
42
  // the consumer is using `value` (controlled) or `defaultValue` (uncontrolled).
43
43
  const [internalValue, setInternalValue] = useState(value ?? defaultValue ?? '');
44
- const effectiveValue = value === undefined ? internalValue : value;
44
+ const effectiveValue = value ?? internalValue;
45
45
  const hasValue = effectiveValue !== '';
46
46
  // The error state takes precedence over the caution state
47
47
  const classes = timePickerSlotRecipe({ size, disabled, invalid, caution: caution && !invalid });
@@ -2,8 +2,8 @@ import { type BasePickerProps } from './BasePicker.types';
2
2
  /**
3
3
  * Default labels for the DatePicker components with Japanese text
4
4
  */
5
- export declare const LABEL_GO_TO_PREV_MONTH = "\u524D\u306E\u6708";
6
- export declare const LABEL_GO_TO_NEXT_MONTH = "\u6B21\u306E\u6708";
5
+ export declare const LABEL_GO_TO_PREV_MONTH = "\u524D\u306E\u6708\u3092\u8868\u793A";
6
+ export declare const LABEL_GO_TO_NEXT_MONTH = "\u6B21\u306E\u6708\u3092\u8868\u793A";
7
7
  export declare const LABEL_SELECT_MONTH = "\u6708\u3092\u9078\u629E";
8
8
  export declare const LABEL_OPEN_CALENDAR = "\u30AB\u30EC\u30F3\u30C0\u30FC\u3092\u958B\u304F";
9
9
  export declare const LABEL_CLOSE_CALENDAR = "\u30AB\u30EC\u30F3\u30C0\u30FC\u3092\u9589\u3058\u308B";
@@ -1,18 +1,18 @@
1
1
  /**
2
2
  * Default labels for the DatePicker components with Japanese text
3
3
  */
4
- export const LABEL_GO_TO_PREV_MONTH = '前の月';
5
- export const LABEL_GO_TO_NEXT_MONTH = '次の月';
4
+ export const LABEL_GO_TO_PREV_MONTH = '前の月を表示';
5
+ export const LABEL_GO_TO_NEXT_MONTH = '次の月を表示';
6
6
  export const LABEL_SELECT_MONTH = '月を選択';
7
7
  export const LABEL_OPEN_CALENDAR = 'カレンダーを開く';
8
8
  export const LABEL_CLOSE_CALENDAR = 'カレンダーを閉じる';
9
9
  export const LABEL_TODAY = '今日';
10
10
  export const BASE_PICKER_LABELS = {
11
11
  ja: {
12
- GO_TO_PREV_MONTH: '前の月',
13
- GO_TO_NEXT_MONTH: '次の月',
14
- GO_TO_PREV_YEAR: '前の年',
15
- GO_TO_NEXT_YEAR: '次の年',
12
+ GO_TO_PREV_MONTH: '前の月を表示',
13
+ GO_TO_NEXT_MONTH: '次の月を表示',
14
+ GO_TO_PREV_YEAR: '前の年を表示',
15
+ GO_TO_NEXT_YEAR: '次の年を表示',
16
16
  SELECT_MONTH: '月を選択',
17
17
  SELECT_YEAR: '年を選択',
18
18
  OPEN_CALENDAR: 'カレンダーを開く',
@@ -21,10 +21,10 @@ export const BASE_PICKER_LABELS = {
21
21
  CLEAR_VALUE: '値をクリアする',
22
22
  },
23
23
  en: {
24
- GO_TO_PREV_MONTH: 'Previous month',
25
- GO_TO_NEXT_MONTH: 'Next month',
26
- GO_TO_PREV_YEAR: 'Previous year',
27
- GO_TO_NEXT_YEAR: 'Next year',
24
+ GO_TO_PREV_MONTH: 'Show previous month',
25
+ GO_TO_NEXT_MONTH: 'Show next month',
26
+ GO_TO_PREV_YEAR: 'Show previous year',
27
+ GO_TO_NEXT_YEAR: 'Show next year',
28
28
  SELECT_MONTH: 'Select month',
29
29
  SELECT_YEAR: 'Select year',
30
30
  OPEN_CALENDAR: 'Open calendar',
@@ -39,5 +39,5 @@ export function BaseRangePickerCalendar({ viewingMonth, handleKeyboardNavigation
39
39
  };
40
40
  return (_jsx(BaseRangePickerDateCell, { ...rest, date: date, selected: selected, inRange: inRange, disabled: disabled, isRangeStart: isStart, isRangeEnd: isEnd, onClick: handleClick, onMouseEnter: handleMouseEnter, onFocus: handleFocus, onMouseLeave: clearPreview, onBlur: clearPreview }, date.toISOString()));
41
41
  }, [isDateDisabled, isRangeStart, isRangeEnd, isInRange, handleDateClick, handlePreview, clearPreview]);
42
- return (_jsx("div", { className: cx(classNames.root, 'mfui-BaseRangePickerCalendar__root'), children: _jsx(CalendarGrid, { viewingValue: viewingMonth, renderDateCell: renderDateCell, pendingFocusDate: pendingFocusDate ?? undefined, value: temporaryStart || temporaryEnd, onDateClick: undefined, onPendingFocusDateChange: setPendingFocusDate, onKeyboardNavigation: handleKeyboardNavigation }) }));
42
+ return (_jsx("div", { className: cx(classNames.root, 'mfui-BaseRangePickerCalendar__root'), children: _jsx(CalendarGrid, { viewingValue: viewingMonth, renderDateCell: renderDateCell, pendingFocusDate: pendingFocusDate ?? undefined, value: temporaryStart ?? temporaryEnd, onDateClick: undefined, onPendingFocusDateChange: setPendingFocusDate, onKeyboardNavigation: handleKeyboardNavigation }) }));
43
43
  }
@@ -1,13 +1,13 @@
1
1
  export const BASE_RANGE_PICKER_POPOVER_LABELS = {
2
2
  ja: {
3
- GO_TO_PREV_MONTH: '前の月',
4
- GO_TO_NEXT_MONTH: '次の月',
3
+ GO_TO_PREV_MONTH: '前の月を表示',
4
+ GO_TO_NEXT_MONTH: '次の月を表示',
5
5
  APPLY: '適用',
6
6
  CANCEL: 'キャンセル',
7
7
  },
8
8
  en: {
9
- GO_TO_PREV_MONTH: 'Previous month',
10
- GO_TO_NEXT_MONTH: 'Next month',
9
+ GO_TO_PREV_MONTH: 'Show previous month',
10
+ GO_TO_NEXT_MONTH: 'Show next month',
11
11
  APPLY: 'Apply',
12
12
  CANCEL: 'Cancel',
13
13
  },
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
3
+ import { createContext, useCallback, useContext, useMemo, useState } from 'react';
4
4
  import { flushSync } from 'react-dom';
5
5
  import { dayjs } from '../../../../utilities/date/dayjs';
6
6
  import { useUpdateEffect } from '../../../../utilities/effect/useUpdateEffect';
@@ -117,7 +117,7 @@ const useBaseRangePickerContextValue = ({ value, defaultValue, disabled = false,
117
117
  today,
118
118
  }), normalizeToFirstOfMonth);
119
119
  // --- Selection/Preview State and Logic ---
120
- const [selecting, setSelecting] = useState('start');
120
+ const [selecting, setSelecting] = useState(() => (!startDate || endDate ? 'start' : 'end'));
121
121
  const [temporaryStart, setTemporaryStart] = useState(startDate);
122
122
  const [temporaryEnd, setTemporaryEnd] = useState(endDate);
123
123
  const [previewDate, setPreviewDate] = useState();
@@ -151,7 +151,7 @@ const useBaseRangePickerContextValue = ({ value, defaultValue, disabled = false,
151
151
  onChange?.([newStartDate, newEndDate]);
152
152
  }, [onChange, formatDate, setDates]);
153
153
  const handleStartDateChange = useCallback((date, nextEndDate) => {
154
- const proposedEndDate = nextEndDate !== undefined ? nextEndDate : endDate;
154
+ const proposedEndDate = nextEndDate ?? endDate;
155
155
  // Apply auto-completion behavior when start date is set and end date is missing
156
156
  // This follows design guidelines: if only one date is provided, both should be set to same value
157
157
  if (date && !proposedEndDate) {
@@ -162,7 +162,7 @@ const useBaseRangePickerContextValue = ({ value, defaultValue, disabled = false,
162
162
  }
163
163
  }, [endDate, handleChange]);
164
164
  const handleEndDateChange = useCallback((date, nextStartDate) => {
165
- const proposedStartDate = nextStartDate !== undefined ? nextStartDate : startDate;
165
+ const proposedStartDate = nextStartDate ?? startDate;
166
166
  // Apply auto-completion behavior when end date is set and start date is missing
167
167
  // This follows design guidelines: if only one date is provided, both should be set to same value
168
168
  if (date && !proposedStartDate) {
@@ -237,12 +237,18 @@ const useBaseRangePickerContextValue = ({ value, defaultValue, disabled = false,
237
237
  setTemporaryEnd(undefined);
238
238
  close();
239
239
  }, [handleChange, close]);
240
- useEffect(() => {
240
+ // Adjusted during render (React's documented pattern for resyncing state to changed
241
+ // props) rather than in an effect, so the UI never flashes the stale value for one frame.
242
+ const [prevStartDate, setPrevStartDate] = useState(startDate);
243
+ const [prevEndDate, setPrevEndDate] = useState(endDate);
244
+ if (startDate !== prevStartDate || endDate !== prevEndDate) {
245
+ setPrevStartDate(startDate);
246
+ setPrevEndDate(endDate);
241
247
  setTemporaryStart(startDate);
242
248
  setTemporaryEnd(endDate);
243
249
  setSelecting(!startDate || endDate ? 'start' : 'end');
244
250
  setPreviewDate(undefined);
245
- }, [startDate, endDate]);
251
+ }
246
252
  const handleDateClick = useCallback((date) => {
247
253
  // Prevent selection of disabled dates
248
254
  if (isDateOutsideConstraints(date, minDate, maxDate, format)) {
@@ -309,7 +315,7 @@ const useBaseRangePickerContextValue = ({ value, defaultValue, disabled = false,
309
315
  const autoCompletedRange = autoCompletePartialRange([newStart, newEnd]);
310
316
  // handleChange already calls setDates and setDateStrings, so we don't need to call them again
311
317
  handleChange(autoCompletedRange);
312
- // Do not setTemporaryStart/setTemporaryEnd here; let the effect sync them from startDate/endDate
318
+ // Do not setTemporaryStart/setTemporaryEnd here; the render-time adjustment above syncs them from startDate/endDate
313
319
  close();
314
320
  }, [handleChange, close, temporaryStart, temporaryEnd, minDate, maxDate, format]);
315
321
  const handleCancel = useCallback(() => {
@@ -39,7 +39,7 @@ export const BaseRangePickerTrigger = forwardRef(({ enableClearValue = false, in
39
39
  // Use input props placeholder, fallback to default
40
40
  const finalStartPlaceholder = startInputPlaceholder ?? getBaseRangePickerTriggerLabel('START_DATE_PLACEHOLDER', calendarLocale);
41
41
  const finalEndPlaceholder = endInputPlaceholder ?? getBaseRangePickerTriggerLabel('END_DATE_PLACEHOLDER', calendarLocale);
42
- const shouldShowClearButton = enableClearValue && (enhancedStartInputProps.value || enhancedEndInputProps.value);
42
+ const shouldShowClearButton = enableClearValue && ((enhancedStartInputProps.value ?? '') || (enhancedEndInputProps.value ?? ''));
43
43
  // The error state takes precedence over the caution state
44
44
  const classes = baseRangePickerTriggerSlotRecipe({ invalid, caution: caution && !invalid, disabled, size });
45
45
  // Popover open: mark calendar icon active. Use `undefined` when closed — `data-active="false"` is still truthy in CSS.
@@ -48,6 +48,6 @@ export const BaseRangePickerTrigger = forwardRef(({ enableClearValue = false, in
48
48
  handleClear();
49
49
  onClickClearButton?.(event);
50
50
  }, [handleClear, onClickClearButton]);
51
- 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, {}) })] }));
51
+ 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, {}) })] }));
52
52
  });
53
53
  BaseRangePickerTrigger.displayName = 'BaseRangePicker.Trigger';
@@ -32,7 +32,7 @@ export function CalendarGrid({ viewingValue, minDate, maxDate, checkDisabledDate
32
32
  if (!dateNumber)
33
33
  return null;
34
34
  const date = new Date(viewingValue.getFullYear(), viewingValue.getMonth(), dateNumber);
35
- const isDisabledByProps = (startOfMinDate && date < startOfMinDate) || (startOfMaxDate && date > startOfMaxDate);
35
+ const isDisabledByProps = !!(startOfMinDate && date < startOfMinDate) || !!(startOfMaxDate && date > startOfMaxDate);
36
36
  const isDisabledByCallback = checkDisabledDate?.(date);
37
37
  const disabled = isDisabledByProps || !!isDisabledByCallback;
38
38
  const isSelected = value ? checkIsSameDate(value, date) : false;
@@ -141,8 +141,8 @@ export function warekiToDate(wareki) {
141
141
  // Traditional format: groups 3,4 (month, day)
142
142
  // Western format: groups 5,6 (month, day)
143
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]);
144
+ const month = Number(matchedWareki[3] ?? matchedWareki[5] ?? matchedWareki[7]);
145
+ const day = matchedWareki[7] !== undefined ? 1 : Number(matchedWareki[4] ?? matchedWareki[6]);
146
146
  // Validation: ensure era year is reasonable (1-200 years per era)
147
147
  if (eraYear < 1 || eraYear > 200) {
148
148
  return {
@@ -231,7 +231,7 @@ fileSelectButtonLabel = 'ファイルを選択', instructionText, fileCountLabel
231
231
  return (_jsxs("div", { className: cx(classes.root, 'mfui-FileDropZone__root'), children: [_jsx("input", { ref: mergeRefs(ref, inputRef), id: id, type: "file", disabled: disabled, multiple: multiple, accept: accept, className: cx(classes.input, 'mfui-FileDropZone__input'), tabIndex: -1, "aria-hidden": "true", onChange: handleChange, onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDrop: handleDrop, ...props }, key), _jsxs("div", { "data-mfui-content": "file-drop-zone-container", className: cx(classes.container, 'mfui-FileDropZone__container', className), style: style, onClick: handleContainerClick, onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, children: [illustrationUrl ? (_jsx("div", { "data-mfui-content": "file-drop-zone-illustration", className: cx(classes.illustration, 'mfui-FileDropZone__illustration'), children: _jsx("img", { src: illustrationUrl, alt: "illustration" }) })) : null, _jsxs("div", { "data-mfui-content": "file-drop-zone-main-container", className: cx(classes.mainContainer, 'mfui-FileDropZone__mainContainer'), children: [_jsx("div", { "data-mfui-content": "file-drop-zone-instruction", className: cx(classes.instruction, 'mfui-FileDropZone__instruction'), style: fileBoxMaxWidthStyle, children: _jsx("p", { className: cx(classes.phrase, 'mfui-FileDropZone__phrase'), children: normalizedInstructionText }) }), _jsxs("div", { "data-mfui-content": "file-drop-zone-button-container", className: cx(classes.buttonContainer, 'mfui-FileDropZone__buttonContainer'), style: fileBoxMaxWidthStyle, children: [_jsx(Button, { ref: buttonRef, className: cx(classes.button, 'mfui-FileDropZone__button'), disabled: disabled, "aria-describedby": errorMessages.length > 0 || helperMessage || (displayValues && displayValues.length > 0)
232
232
  ? `${id ? `${id}-` : ''}file-drop-zone-messages`
233
233
  : undefined, onClick: handleFileInputTrigger, children: fileSelectButtonLabel }), displayValues !== undefined && displayValues.length > 0 && (_jsxs("div", { "data-mfui-content": "file-drop-zone-file-info", className: cx(classes.fileInfo, 'mfui-FileDropZone__fileInfo'), style: { maxWidth: `${(fileInfoWidth || 147).toString()}px` }, children: [renderDisplayValues, _jsx(ClearButton, { ...clearButtonProps, "aria-label": clearButtonProps?.['aria-label'] ?? 'ファイルをクリアする', className: cx(classes.clearButton, 'mfui-FileDropZone__clearButton'), disabled: disabled, onClick: handleClear })] }))] }), _jsxs("div", { id: id ? `${id}-file-drop-zone-messages` : 'file-drop-zone-messages', "data-mfui-content": "file-drop-zone-help-message", className: cx(classes.helpMessage, 'mfui-FileDropZone__helpMessage'), children: [errorMessages.length > 0 ? (_jsx("div", { role: "alert", "data-mfui-content": "file-drop-zone-error-messages", className: cx(classes.errorMessages, 'mfui-FileDropZone__errorMessages'), children: errorMessages.map((message, index) => (_jsx(HelpMessage, { role: undefined, messageType: "error", messageAlign: "center", children: message }, index))) })) : null, helperMessage ? _jsx(HelpMessage, { messageAlign: "center", children: helperMessage }) : null, displayValues && displayValues.length > 0 ? (_jsx("div", { className: "sr-only", children: displayValues.length === 1
234
- ? getScreenReaderText('single', displayValues[0] || '')
234
+ ? getScreenReaderText('single', displayValues[0] ?? '')
235
235
  : getScreenReaderText('multiple', displayValues.length) })) : null] })] }), extraElement ? (_jsx("div", { "data-mfui-content": "file-drop-zone-extra", className: cx(classes.extra, 'mfui-FileDropZone__extra'), children: extraElement })) : null] })] }));
236
236
  });
237
237
  FileDropZone.displayName = 'FileDropZone';
@@ -23,7 +23,7 @@ export function formatFileSize(bytes) {
23
23
  const i = Math.floor(Math.log(bytes) / Math.log(k));
24
24
  const unitIndex = Math.min(i, units.length - 1);
25
25
  const size = bytes / k ** unitIndex;
26
- return `${size.toFixed(1)} ${units[unitIndex] || 'B'}`;
26
+ return `${size.toFixed(1)} ${units[unitIndex] ?? 'B'}`;
27
27
  }
28
28
  /**
29
29
  * Get file extension from filename
@@ -70,7 +70,7 @@ function NestedNavigationItemRenderer({ navigation, originalIndex, navId, classe
70
70
  const hasCurrentChildren = navigation.children.some((child) => child.isCurrent);
71
71
  return (_jsx("li", { className: cx(classes.listItem, 'mfui-BaseMainNavigation__listItem'), "data-mfui-popover-portal-target-id": popoverPortalTargetId, children: isCollapsed || nestedNavigationDisplay === 'popover' ? (_jsx(PopoverNestedNavigationItem, { navigation: navigation, classes: classes, InternalLinkTag: InternalLinkTag, isCollapsed: isCollapsed, hasCurrentChildren: hasCurrentChildren, popoverPortalTargetId: popoverPortalTargetId, isOpen: openPopoverIndex === originalIndex, onOpenChange: (isOpen) => {
72
72
  setOpenPopoverIndex(isOpen ? originalIndex : null);
73
- } })) : (_jsxs("details", { open: ('isOpenByDefault' in navigation && navigation.isOpenByDefault) || hasCurrentChildren, className: cx(classes.parentDetails, 'mfui-BaseMainNavigation__parentDetails'), children: [_jsx(FocusIndicator, { position: "inside", children: _jsxs("summary", { className: cx(classes.parentSummary, 'mfui-BaseMainNavigation__parentSummary'), children: [_jsx("div", { "aria-hidden": "true", className: cx(classes.parentSummaryIcon, 'mfui-BaseMainNavigation__parentSummaryIcon'), children: navigation.icon }), _jsx(Typography, { variant: "controlLabel", children: navigation.label }), _jsx(DisclosureBasicCollapsed, { className: "mfui-BaseMainNavigation__icon_collapsed", "aria-hidden": "true", "data-mfui-content": "main-navigation-icon-collapsed" }), _jsx(DisclosureBasicExpanded, { className: "mfui-BaseMainNavigation__icon_expanded", "aria-hidden": "true", "data-mfui-content": "main-navigation-icon-expanded" })] }) }), _jsx("ul", { className: cx(classes.childrenList, 'mfui-BaseMainNavigation__childrenList'), children: navigation.children.map((child, childIndex) => (_jsx("li", { className: cx(classes.childrenListItem, 'mfui-BaseMainNavigation__childrenListItem'), children: _jsx(FocusIndicator, { position: "inside", children: _jsx(NavigationLink, { tag: InternalLinkTag, isExternal: child.isExternal, href: child.href, className: cx(classes.childrenListItemAnchor, 'mfui-BaseMainNavigation__childrenListItemAnchor'), "aria-current": child.isCurrent ? 'page' : undefined, children: _jsx(Typography, { variant: "controlLabel", children: child.label }) }) }) }, childIndex))) })] })) }));
73
+ } })) : (_jsxs("details", { open: !!('isOpenByDefault' in navigation && navigation.isOpenByDefault) || hasCurrentChildren, className: cx(classes.parentDetails, 'mfui-BaseMainNavigation__parentDetails'), children: [_jsx(FocusIndicator, { position: "inside", children: _jsxs("summary", { className: cx(classes.parentSummary, 'mfui-BaseMainNavigation__parentSummary'), children: [_jsx("div", { "aria-hidden": "true", className: cx(classes.parentSummaryIcon, 'mfui-BaseMainNavigation__parentSummaryIcon'), children: navigation.icon }), _jsx(Typography, { variant: "controlLabel", children: navigation.label }), _jsx(DisclosureBasicCollapsed, { className: "mfui-BaseMainNavigation__icon_collapsed", "aria-hidden": "true", "data-mfui-content": "main-navigation-icon-collapsed" }), _jsx(DisclosureBasicExpanded, { className: "mfui-BaseMainNavigation__icon_expanded", "aria-hidden": "true", "data-mfui-content": "main-navigation-icon-expanded" })] }) }), _jsx("ul", { className: cx(classes.childrenList, 'mfui-BaseMainNavigation__childrenList'), children: navigation.children.map((child, childIndex) => (_jsx("li", { className: cx(classes.childrenListItem, 'mfui-BaseMainNavigation__childrenListItem'), children: _jsx(FocusIndicator, { position: "inside", children: _jsx(NavigationLink, { tag: InternalLinkTag, isExternal: child.isExternal, href: child.href, className: cx(classes.childrenListItemAnchor, 'mfui-BaseMainNavigation__childrenListItemAnchor'), "aria-current": child.isCurrent ? 'page' : undefined, children: _jsx(Typography, { variant: "controlLabel", children: child.label }) }) }) }, childIndex))) })] })) }));
74
74
  }
75
75
  /**
76
76
  * The component for displaying nested navigation with a popover.
@@ -53,7 +53,7 @@ export const NarrowViewportMainNavigation = forwardRef(({ className, featureShor
53
53
  if (isGroupLabel) {
54
54
  return null;
55
55
  }
56
- return (_jsx("li", { className: cx(classes.listItem, 'mfui-NarrowViewportMainNavigation__listItem'), children: navigation.children ? (_jsxs("details", { open: ('isOpenByDefault' in navigation && navigation.isOpenByDefault) ||
56
+ return (_jsx("li", { className: cx(classes.listItem, 'mfui-NarrowViewportMainNavigation__listItem'), children: navigation.children ? (_jsxs("details", { open: !!('isOpenByDefault' in navigation && navigation.isOpenByDefault) ||
57
57
  hasCurrentNavigationInChildren(navigation.children), className: cx(classes.parentDetails, 'mfui-NarrowViewportMainNavigation__parentDetails'), children: [_jsx(FocusIndicator, { position: "inside", children: _jsxs("summary", { className: cx(classes.parentSummary, 'mfui-NarrowViewportMainNavigation__parentSummary'), children: [_jsx("div", { "aria-hidden": "true", className: cx(classes.parentSummaryIcon, 'mfui-NarrowViewportMainNavigation__parentSummaryIcon'), children: navigation.icon }), _jsx(Typography, { variant: "controlLabel", children: navigation.label }), _jsx(DisclosureBasicCollapsed, { className: "mfui-NarrowViewportMainNavigation__icon_collapsed", "aria-hidden": "true", "data-mfui-content": "main-navigation-icon-collapsed" }), _jsx(DisclosureBasicExpanded, { className: "mfui-NarrowViewportMainNavigation__icon_expanded", "aria-hidden": "true", "data-mfui-content": "main-navigation-icon-expanded" })] }) }), _jsx("ul", { className: cx(classes.childrenList, 'mfui-NarrowViewportMainNavigation__childrenList'), children: navigation.children.map((child, childIndex) => (_jsx("li", { className: cx(classes.childrenListItem, 'mfui-NarrowViewportMainNavigation__childrenListItem'), children: _jsx(FocusIndicator, { position: "inside", children: _jsx(NavigationLink, { tag: InternalLinkTag, isExternal: child.isExternal, href: child.href, className: cx(classes.childrenListItemAnchor, 'mfui-NarrowViewportMainNavigation__childrenListItemAnchor'), "aria-current": child.isCurrent ? 'page' : undefined, onClick: handleCloseMainNavigation, children: _jsx(Typography, { variant: "controlLabel", children: child.label }) }) }) }, childIndex))) })] })) : (_jsx(FocusIndicator, { position: "inside", children: _jsx(NavigationLink, { tag: InternalLinkTag, isExternal: navigation.isExternal, href: navigation.href, className: cx(classes.listItemAnchor, 'mfui-NarrowViewportMainNavigation__listItemAnchor'), "aria-current": navigation.isCurrent ? 'page' : undefined, onClick: handleCloseMainNavigation, children: _jsxs("div", { className: cx(classes.labelGroup, 'mfui-NarrowViewportMainNavigation__labelGroup'), children: [_jsxs("div", { className: cx(classes.iconAndLabel, 'mfui-NarrowViewportMainNavigation__iconAndLabel'), children: [_jsx("div", { "aria-hidden": "true", className: cx(classes.listItemAnchorIcon, 'mfui-NarrowViewportMainNavigation__listItemAnchorIcon'), children: navigation.icon }), _jsx(Typography, { variant: "controlLabel", children: navigation.label })] }), navigation.locked ? (_jsx(Lock, { className: cx(classes.lockIcon, 'mfui-NarrowViewportMainNavigation__lockIcon'), role: "img", "aria-label": navigation.lockIconProps?.['aria-label'] ?? 'ロックされています' })) : null] }) }) })) }, index));
58
58
  }) }), navigationItems.map((navigation, index) => {
59
59
  const isGroupLabel = navigation.children && 'isGroupLabel' in navigation && navigation.isGroupLabel;
@@ -48,6 +48,6 @@ placeholder, renderDisplayValue, updateSelectedValues, clearButtonProps, disable
48
48
  .map((option) => option.value)
49
49
  .filter((v) => v != null)
50
50
  .map(String);
51
- return (_jsx(FocusIndicator, { children: _jsxs("div", { ref: wrapperRef, className: cx(classes.wrapper, 'mfui-MultipleSelectBoxTrigger__wrapper', triggerWrapperProps?.className), children: [_jsx("button", { ref: mergeRefs(ref, triggerRef), type: "button", role: "combobox", "aria-controls": isOptionPanelOpen ? listBoxId : undefined, "aria-expanded": isOptionPanelOpen, "aria-haspopup": "listbox", "aria-invalid": invalid, "aria-required": required || undefined, "data-mfui-caution": caution && !invalid ? true : undefined, value: selectedValues, disabled: disabled, onKeyDown: onKeyDown, onBlur: onBlur, ...rest, ...triggerProps, className: cx(classes.trigger, 'mfui-MultipleSelectBoxTrigger__trigger', triggerProps?.className) }), _jsx("input", { type: "hidden", name: name, value: selectedValues }), _jsx("div", { className: cx(classes.displayValues, 'mfui-MultipleSelectBoxTrigger__displayValues'), "data-mfui-content": "multiple-select-box-trigger-display-values", children: renderer() }), selectedOptions.length > 0 && !disabled && !disableClearButton ? (_jsx("div", { className: cx(classes.clearButtonContainer, 'mfui-MultipleSelectBoxTrigger__clearButtonContainer'), children: _jsx(ClearButton, { size: size === 'small' ? 'small' : 'default', className: cx(classes.clearButton, 'mfui-MultipleSelectBoxTrigger__clearButton'), "aria-label": clearButtonProps?.['aria-label'] ?? '値をクリアする', "data-mfui-content": "multiple-select-box-clear-button", onClick: handleClearValue }) })) : null, _jsx("div", { className: cx(classes.dropdownIconContainer, 'mfui-MultipleSelectBoxTrigger__dropdownIconContainer'), children: _jsx(DropdownIcon, { "aria-hidden": "true", className: cx(classes.dropdownIcon, 'mfui-MultipleSelectBoxTrigger__dropdownIcon') }) })] }) }));
51
+ return (_jsx(FocusIndicator, { children: _jsxs("div", { ref: wrapperRef, className: cx(classes.wrapper, 'mfui-MultipleSelectBoxTrigger__wrapper', triggerWrapperProps?.className), children: [_jsx("button", { ref: mergeRefs(ref, triggerRef), type: "button", role: "combobox", "aria-controls": isOptionPanelOpen ? listBoxId : undefined, "aria-expanded": isOptionPanelOpen, "aria-haspopup": "listbox", "aria-invalid": invalid, "aria-required": required ? true : undefined, "data-mfui-caution": caution && !invalid ? true : undefined, value: selectedValues, disabled: disabled, onKeyDown: onKeyDown, onBlur: onBlur, ...rest, ...triggerProps, className: cx(classes.trigger, 'mfui-MultipleSelectBoxTrigger__trigger', triggerProps?.className) }), _jsx("input", { type: "hidden", name: name, value: selectedValues }), _jsx("div", { className: cx(classes.displayValues, 'mfui-MultipleSelectBoxTrigger__displayValues'), "data-mfui-content": "multiple-select-box-trigger-display-values", children: renderer() }), selectedOptions.length > 0 && !disabled && !disableClearButton ? (_jsx("div", { className: cx(classes.clearButtonContainer, 'mfui-MultipleSelectBoxTrigger__clearButtonContainer'), children: _jsx(ClearButton, { size: size === 'small' ? 'small' : 'default', className: cx(classes.clearButton, 'mfui-MultipleSelectBoxTrigger__clearButton'), "aria-label": clearButtonProps?.['aria-label'] ?? '値をクリアする', "data-mfui-content": "multiple-select-box-clear-button", onClick: handleClearValue }) })) : null, _jsx("div", { className: cx(classes.dropdownIconContainer, 'mfui-MultipleSelectBoxTrigger__dropdownIconContainer'), children: _jsx(DropdownIcon, { "aria-hidden": "true", className: cx(classes.dropdownIcon, 'mfui-MultipleSelectBoxTrigger__dropdownIcon') }) })] }) }));
52
52
  }
53
53
  export const MultipleSelectBoxTrigger = forwardRef(MultipleSelectBoxTriggerInner);
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { useCallback, useMemo, useState } from 'react';
3
3
  import { isSelectableOption } from '../utils/isSelectableOption';
4
4
  /**
5
5
  * Custom hook to manage the "Create New Option" feature for MultipleSelectBox.
@@ -20,10 +20,14 @@ export const useCreateOption = ({ enableCreateOption, enableSearchOptions, searc
20
20
  const exactMatchExists = filteredOptions.some((option) => isSelectableOption(option) && option.label === trimmedSearchText);
21
21
  return !exactMatchExists;
22
22
  }, [enableCreateOption, enableSearchOptions, trimmedSearchText, filteredOptions]);
23
- // Clear error state when search text changes
24
- useEffect(() => {
23
+ // Adjusted during render (React's documented pattern for resyncing state to a changed
24
+ // prop) rather than in an effect. Clears the error banner as soon as the search text
25
+ // changes, matching the comment above the effect this replaces.
26
+ const [prevSearchText, setPrevSearchText] = useState(searchText);
27
+ if (searchText !== prevSearchText) {
28
+ setPrevSearchText(searchText);
25
29
  setCreateError(false);
26
- }, [searchText]);
30
+ }
27
31
  const doCreate = useCallback(async () => {
28
32
  try {
29
33
  await onCreateOption?.(trimmedSearchText);
@@ -1,4 +1,4 @@
1
- import { useState, useCallback, useEffect } from 'react';
1
+ import { useState, useCallback } from 'react';
2
2
  import { findSiblingElements } from '../../utilities/dom/findSiblingElements';
3
3
  import { isSelectableOption, isOptionDisabled } from '../utils/isSelectableOption';
4
4
  /**
@@ -27,10 +27,15 @@ export function useOptionKeyboardNavigation({ filteredOptions, selectedValues, o
27
27
  }, [filteredOptions, selectedValues]);
28
28
  // State to track which option should be tabbable
29
29
  const [tabbableOptionIndex, setTabbableOptionIndex] = useState(getDefaultTabbableIndex());
30
- // Update tabbable index when filtered options or selected values change
31
- useEffect(() => {
30
+ // Adjusted during render (React's documented pattern for resyncing state to changed
31
+ // props) rather than in an effect, so the UI never flashes the stale index for one frame.
32
+ const [prevFilteredOptions, setPrevFilteredOptions] = useState(filteredOptions);
33
+ const [prevSelectedValues, setPrevSelectedValues] = useState(selectedValues);
34
+ if (filteredOptions !== prevFilteredOptions || selectedValues !== prevSelectedValues) {
35
+ setPrevFilteredOptions(filteredOptions);
36
+ setPrevSelectedValues(selectedValues);
32
37
  setTabbableOptionIndex(getDefaultTabbableIndex());
33
- }, [getDefaultTabbableIndex]);
38
+ }
34
39
  // Update tabbable index when selected values change
35
40
  const resetTabbableIndex = useCallback(() => {
36
41
  setTabbableOptionIndex(getDefaultTabbableIndex());
@@ -1,4 +1,4 @@
1
- import { useState, useEffect, useMemo } from 'react';
1
+ import { useState, useMemo } from 'react';
2
2
  /**
3
3
  * Helper function to convert defaultValue/value to options array and values set
4
4
  *
@@ -34,66 +34,32 @@ export function useSelectedValues({ options, defaultValue, value, onChange }) {
34
34
  // Store selected options with their full data (including labels)
35
35
  const [selectedOptionsMap, setSelectedOptionsMap] = useState(() => {
36
36
  // For initialization, use value if provided, otherwise use defaultValue
37
- const initialValue = value !== undefined ? value : defaultValue;
37
+ const initialValue = value ?? defaultValue;
38
38
  const { optionsMap } = initializeSelectedOptions(initialValue);
39
39
  return optionsMap;
40
40
  });
41
41
  // Store selected values as a Set for fast lookup
42
42
  const [localSelectedValuesSet, setLocalSelectedValuesSet] = useState(() => {
43
43
  // For initialization, use value if provided, otherwise use defaultValue
44
- const initialValue = value !== undefined ? value : defaultValue;
44
+ const initialValue = value ?? defaultValue;
45
45
  const { valuesSet } = initializeSelectedOptions(initialValue);
46
46
  return valuesSet;
47
47
  });
48
48
  // Derive local selected options array from the map
49
49
  const localSelectedOptions = useMemo(() => [...selectedOptionsMap.values()], [selectedOptionsMap]);
50
- // Update local state when controlled value changes
51
- useEffect(() => {
52
- if (!isControlled) {
53
- return;
54
- }
55
- const { optionsMap, valuesSet } = initializeSelectedOptions(value);
56
- // Preserve existing options from the map if they're not in the new options array
57
- setSelectedOptionsMap((previousMap) => {
58
- const newOptionsMap = new Map(optionsMap);
59
- for (const selectedValue of valuesSet) {
60
- if (!newOptionsMap.has(selectedValue) && previousMap.has(selectedValue)) {
61
- // Preserve the option from the existing map
62
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
63
- newOptionsMap.set(selectedValue, previousMap.get(selectedValue));
64
- }
65
- }
66
- return newOptionsMap;
67
- });
68
- setLocalSelectedValuesSet(valuesSet);
69
- }, [value, options, isControlled]);
70
- // Sync state for display-only mode (value provided but no onChange)
71
- // This handles scenarios like disabled/display-only components where
72
- // the parent provides values but doesn't expect interaction
73
- useEffect(() => {
74
- // Skip if this is controlled (already handled above) or uncontrolled (no value)
75
- if (isControlled || value === undefined) {
76
- return;
50
+ // Adjusted during render (React's documented pattern for resyncing state to a changed
51
+ // prop) rather than in an effect, so the UI never flashes the stale value for one
52
+ // frame. Covers both controlled (value + onChange) and display-only (value, no
53
+ // onChange) modes; uncontrolled mode (value === undefined) is left untouched.
54
+ const [prevValue, setPrevValue] = useState(value);
55
+ if (value !== prevValue) {
56
+ setPrevValue(value);
57
+ if (value !== undefined) {
58
+ const { optionsMap, valuesSet } = initializeSelectedOptions(value);
59
+ setSelectedOptionsMap(optionsMap);
60
+ setLocalSelectedValuesSet(valuesSet);
77
61
  }
78
- // This is display-only mode: value is provided but no onChange
79
- // Treat it like controlled for state sync purposes
80
- const { optionsMap, valuesSet } = initializeSelectedOptions(value);
81
- // Preserve existing options from the map if they're not in the new options array
82
- setSelectedOptionsMap((previousMap) => {
83
- const newOptionsMap = new Map(optionsMap);
84
- for (const selectedValue of valuesSet) {
85
- if (!newOptionsMap.has(selectedValue) && previousMap.has(selectedValue)) {
86
- // Preserve the option from the existing map
87
- const existingOption = previousMap.get(selectedValue);
88
- if (existingOption) {
89
- newOptionsMap.set(selectedValue, existingOption);
90
- }
91
- }
92
- }
93
- return newOptionsMap;
94
- });
95
- setLocalSelectedValuesSet(valuesSet);
96
- }, [value, options, isControlled]);
62
+ }
97
63
  // Helper function to update the selected values
98
64
  const updateSelectedValues = (newValues) => {
99
65
  const newValuesSet = new Set(newValues);
@@ -115,7 +81,7 @@ export function useSelectedValues({ options, defaultValue, value, onChange }) {
115
81
  }
116
82
  // Controlled component: Don't update local state directly
117
83
  // Let the parent component control the state via value prop
118
- // The useEffect will sync local state when value prop changes
84
+ // The render-time adjustment above will sync local state when value prop changes
119
85
  if (isControlled) {
120
86
  const selectedOptions = [...newOptionsMap.values()];
121
87
  onChange(selectedOptions);