@moneyforward/mfui-components 4.5.0 → 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/Badge/Badge.d.ts +1 -1
- package/dist/src/Badge/Badge.js +4 -4
- package/dist/src/Badge/Badge.types.d.ts +3 -1
- package/dist/src/Checkbox/Checkbox.js +7 -3
- package/dist/src/DataTable/DataTable.d.ts +1 -1
- package/dist/src/DataTable/DataTable.js +23 -3
- package/dist/src/DataTable/DataTable.types.d.ts +2 -2
- package/dist/src/DataTable/DataTableBody/DataTableBody.d.ts +2 -1
- package/dist/src/DataTable/DataTableBody/DataTableBody.js +72 -8
- package/dist/src/DataTable/DataTableBody/DataTableBody.types.d.ts +81 -1
- package/dist/src/DataTable/DataTableBody/hooks/useVirtualizedRows.d.ts +64 -0
- package/dist/src/DataTable/DataTableBody/hooks/useVirtualizedRows.js +53 -0
- package/dist/src/DataTable/DataTableBody/index.d.ts +1 -1
- package/dist/src/DataTable/DataTableCell/DataTableCell.js +21 -4
- package/dist/src/DataTable/DataTableCell/DataTableCell.types.d.ts +1 -1
- package/dist/src/DataTable/DataTableHeader/DataTableHeader.js +13 -2
- package/dist/src/DataTable/DataTableHeaderRow/DataTableHeaderRow.js +12 -7
- package/dist/src/DataTable/DataTableHeaderRow/DataTableHeaderRow.types.d.ts +9 -0
- package/dist/src/DataTable/DataTableProvider.d.ts +29 -2
- package/dist/src/DataTable/DataTableProvider.js +20 -1
- package/dist/src/DataTable/DataTableRow/DataTableRow.js +104 -32
- package/dist/src/DataTable/DataTableRow/DataTableRow.types.d.ts +44 -15
- package/dist/src/DataTable/DataTableRowActionContext.d.ts +34 -0
- package/dist/src/DataTable/DataTableRowActionContext.js +8 -0
- package/dist/src/DataTable/index.d.ts +1 -1
- package/dist/src/DateTimeSelection/DatePicker/DatePickerCalendar/DatePickerCalendar.js +7 -3
- package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerPopover/DateRangePickerCalendar/DateRangePickerCalendar.js +1 -1
- package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerPopover/utilities/createDateRangePickerPopoverTestUtility.js +2 -2
- package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerProvider/DateRangePickerProvider.js +13 -7
- package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerTrigger/DateRangePickerTrigger.js +1 -1
- package/dist/src/DateTimeSelection/FilterDatePicker/FilterDatePicker.js +1 -1
- package/dist/src/DateTimeSelection/FilterMonthPicker/FilterMonthPicker.js +1 -1
- package/dist/src/DateTimeSelection/FilterMonthRangePicker/FilterMonthRangePicker.js +1 -1
- package/dist/src/DateTimeSelection/MonthPicker/MonthPickerPanel/MonthPickerPanel.js +1 -1
- package/dist/src/DateTimeSelection/MonthPicker/constants.d.ts +2 -2
- package/dist/src/DateTimeSelection/MonthPicker/constants.js +6 -6
- package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerMonthCell/MonthRangePickerMonthCell.js +1 -1
- package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerNavigation/MonthRangePickerNavigation.js +2 -2
- package/dist/src/DateTimeSelection/TimePicker/TimePicker.js +1 -1
- package/dist/src/DateTimeSelection/shared/BasePicker/constants.d.ts +2 -2
- package/dist/src/DateTimeSelection/shared/BasePicker/constants.js +10 -10
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/BaseRangePickerCalendar/BaseRangePickerCalendar.js +1 -1
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/constants.js +4 -4
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerProvider/BaseRangePickerProvider.js +13 -7
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerTrigger/BaseRangePickerTrigger.js +2 -2
- package/dist/src/DateTimeSelection/shared/CalendarGrid/CalendarGrid.js +1 -1
- package/dist/src/DateTimeSelection/shared/utilities/japaneseCalendar.js +2 -2
- package/dist/src/FileDropZone/FileDropZone.js +1 -1
- package/dist/src/FileDropZone/utils/fileHelpers.js +1 -1
- package/dist/src/MainNavigation/BaseMainNavigation.js +1 -1
- package/dist/src/MainNavigation/NarrowViewportMainNavigation.js +1 -1
- package/dist/src/MultipleSelectBox/MultipleSelectBox.js +12 -3
- package/dist/src/MultipleSelectBox/MultipleSelectBoxTrigger/MultipleSelectBoxTrigger.js +1 -1
- package/dist/src/MultipleSelectBox/hooks/useCreateOption.js +8 -4
- package/dist/src/MultipleSelectBox/hooks/useOptionKeyboardNavigation.d.ts +1 -0
- package/dist/src/MultipleSelectBox/hooks/useOptionKeyboardNavigation.js +24 -12
- package/dist/src/MultipleSelectBox/hooks/useSelectedValues.js +16 -50
- package/dist/src/NumberBox/NumberBox.js +8 -4
- package/dist/src/PageHeader/PageHeader.types.d.ts +2 -0
- package/dist/src/Popover/Popover.js +11 -2
- package/dist/src/SelectBox/SelectBox.js +21 -13
- package/dist/src/SelectBox/hooks/useKeyboardNavigation.d.ts +1 -0
- package/dist/src/SelectBox/hooks/useKeyboardNavigation.js +29 -8
- package/dist/src/SplitView/hooks/useSplitViewPanelVisibility.js +5 -2
- package/dist/src/SubNavigation/SubNavigation.js +80 -19
- package/dist/src/SubNavigation/SubNavigation.types.d.ts +18 -6
- package/dist/src/Tabs/Tabs.js +8 -4
- package/dist/src/Tabs/hooks/useCalculatedSelectedTabId.js +7 -5
- package/dist/src/Toast/ToastContainer/ToastContainer.js +11 -2
- package/dist/src/Tooltip/Tooltip.d.ts +6 -0
- package/dist/src/Tooltip/Tooltip.js +14 -3
- package/dist/src/Tooltip/hooks/useTooltipDisplayController.js +77 -88
- package/dist/src/utilities/dom/getFocusableNodes.js +1 -3
- package/dist/src/utilities/dom/useFixedColumns.js +6 -8
- package/dist/src/utilities/react/flattenChildren.d.ts +15 -0
- package/dist/src/utilities/react/flattenChildren.js +28 -0
- package/dist/src/utilities/state/useLatest.d.ts +1 -1
- package/dist/src/utilities/state/useLatest.js +8 -2
- package/dist/styled-system/recipes/badge-slot-recipe.d.ts +1 -1
- package/dist/styled-system/recipes/badge-slot-recipe.js +4 -0
- package/dist/styled-system/recipes/data-table-body-slot-recipe.d.ts +1 -1
- package/dist/styled-system/recipes/data-table-body-slot-recipe.js +4 -0
- package/dist/styled-system/recipes/data-table-cell-slot-recipe.d.ts +1 -1
- package/dist/styled-system/recipes/data-table-cell-slot-recipe.js +4 -0
- package/dist/styled-system/recipes/data-table-row-slot-recipe.js +13 -6
- package/dist/styled-system/recipes/sub-navigation-slot-recipe.d.ts +1 -1
- package/dist/styled-system/recipes/sub-navigation-slot-recipe.js +12 -0
- package/dist/styled-system/tokens/index.js +4 -0
- package/dist/styled-system/tokens/tokens.d.ts +1 -1
- package/dist/styles.css +164 -39
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +12 -12
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export const LABEL_PREV_YEAR = '
|
|
2
|
-
export const LABEL_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: '
|
|
15
|
-
nextYear: '
|
|
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
|
|
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: '
|
|
15
|
-
const LABEL_NEXT_YEAR = { ja: '
|
|
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
|
|
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: '
|
|
25
|
-
GO_TO_NEXT_MONTH: '
|
|
26
|
-
GO_TO_PREV_YEAR: '
|
|
27
|
-
GO_TO_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
|
|
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
|
}
|
package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/constants.js
CHANGED
|
@@ -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: '
|
|
10
|
-
GO_TO_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,
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
}
|
|
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;
|
|
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]
|
|
145
|
-
const day = matchedWareki[7] !== undefined ? 1 : Number(matchedWareki[4]
|
|
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]
|
|
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;
|
|
@@ -31,12 +31,15 @@ import { useVirtualizedMultipleSelectBoxOptions } from './hooks/useVirtualizedMu
|
|
|
31
31
|
import { useInfiniteScroll } from '../utilities/dom/useInfiniteScroll';
|
|
32
32
|
// How many skeleton items to render while the component is loading.
|
|
33
33
|
const SKELETON_ITEM_COUNT = 4;
|
|
34
|
+
// The default for an omitted `options` prop. A default written inline (`options = []`) is a new
|
|
35
|
+
// array on every render, which invalidates every memoized value derived from the options.
|
|
36
|
+
const EMPTY_OPTIONS = [];
|
|
34
37
|
/**
|
|
35
38
|
* The MultipleSelectBox component.
|
|
36
39
|
* This component is separated from the SelectBox component because it has a different behavior.
|
|
37
40
|
* This component switches the variants of looks and behaviors depends on the props: size, invalid, disabled.
|
|
38
41
|
*/
|
|
39
|
-
export function MultipleSelectBox({ id, triggerProps, triggerWrapperProps, size, options =
|
|
42
|
+
export function MultipleSelectBox({ id, triggerProps, triggerWrapperProps, size, options = EMPTY_OPTIONS, defaultValue,
|
|
40
43
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
41
44
|
placeholder, emptyMessage, disabled, invalid, required, caution, targetDOMNode, name, onChange, value, showDisplayValueAsTag, renderDisplayValue, enableSearchOptions = false, notFoundMessage, searchBoxProps, loading = false, onSearchOptions, clearButtonProps, disableClearButton = false, optionPanelProps, popoverWrapperProps, enableAllOptionsControls = false, enableApplyControls = false, showSelectedCount = false, selectAllButtonProps, clearAllButtonProps, selectedCountProps, cancelButtonProps, applyButtonProps, renderOption, renderPopoverHeader, renderOptionList, renderPopoverFooter, onOpenStateChanged, enableAutoUnmount = true, onBlur, showGroupOptionDivider, enableVirtualization = false, virtualizationOptions, infiniteScroll, enableCreateOption = false, onCreateOption, createOptionProps, }) {
|
|
42
45
|
const classes = multipleSelectBoxSlotRecipe({ showGroupOptionDivider });
|
|
@@ -187,7 +190,7 @@ placeholder, emptyMessage, disabled, invalid, required, caution, targetDOMNode,
|
|
|
187
190
|
// Create an array of focusable (selectable and not disabled) options from filteredOptions
|
|
188
191
|
const focusableOptions = useMemo(() => filteredOptions.filter((option) => isSelectableOption(option) && option.value != null && !isOptionDisabled(option)), [filteredOptions]);
|
|
189
192
|
// Manage the keyboard navigation of options
|
|
190
|
-
const { handleOptionFocus, createKeyDownHandler, tabbableOptionIndex } = useOptionKeyboardNavigation({
|
|
193
|
+
const { handleOptionFocus, createKeyDownHandler, tabbableOptionIndex, resetTabbableIndex } = useOptionKeyboardNavigation({
|
|
191
194
|
filteredOptions: focusableOptions,
|
|
192
195
|
selectedValues: enableApplyControls ? temporaryValues : localSelectedValuesSet,
|
|
193
196
|
onSelectOption: handleSelectOption,
|
|
@@ -373,7 +376,13 @@ placeholder, emptyMessage, disabled, invalid, required, caution, targetDOMNode,
|
|
|
373
376
|
: undefined, children: renderListContent() }), isVirtualized ? renderInfiniteScrollLoading() : null, isVirtualized ? renderInfiniteScrollError('div') : null, _jsx(OptionGroupLabelDescriptions, { groups: rendersHiddenGroupLabels ? optionGroupLabels : [] })] }));
|
|
374
377
|
const createOptionAreaNode = isCreateOptionVisible ? (_jsxs("div", { className: cx(classes.createOptionArea, 'mfui-MultipleSelectBox__createOptionArea'), children: [_jsxs("button", { "data-mfui-content": "create-new-option", type: "button", className: cx(classes.createOptionButton, 'mfui-MultipleSelectBox__createOptionButton'), disabled: isCreating, onClick: handleCreateOption, children: [_jsx(Add, { "aria-hidden": true }), createOptionProps?.renderLabel?.(searchText.trim()) ?? (_jsxs(_Fragment, { children: [_jsx(Typography, { variant: "strongBody", children: searchText.trim() }), _jsx(Typography, { variant: "body", children: "\u3092\u8FFD\u52A0" })] }))] }), createError ? (_jsx("div", { className: cx(classes.createOptionErrorMessage, 'mfui-MultipleSelectBox__createOptionErrorMessage'), children: _jsx(HelpMessage, { messageType: "error", children: createOptionProps?.renderErrorMessage?.(searchText.trim()) ??
|
|
375
378
|
`${searchText.trim()}を追加できませんでした。` }) })) : null] })) : null;
|
|
379
|
+
const handleOpenStateChanged = (open) => {
|
|
380
|
+
// Every opening starts from the first selected option, not from the option focused last time.
|
|
381
|
+
if (open)
|
|
382
|
+
resetTabbableIndex();
|
|
383
|
+
toggleOptionPanel();
|
|
384
|
+
};
|
|
376
385
|
return (_jsx(Popover, { renderTrigger: ({ setTriggerRef, togglePopover, handleTriggerKeyDown, handleTriggerBlur }) => (_jsx(MultipleSelectBoxTrigger, { ref: triggerRef, wrapperRef: setTriggerRef, selectedOptions: localSelectedOptions, id: id, disabled: disabled, triggerProps: triggerProps, triggerWrapperProps: triggerWrapperProps, name: name,
|
|
377
386
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
378
|
-
placeholder: placeholder, size: size, listBoxId: listBoxId, isOptionPanelOpen: isOptionPanelOpen, invalid: invalid, required: required, caution: caution, showDisplayValueAsTag: showDisplayValueAsTag, renderDisplayValue: renderDisplayValue, clearButtonProps: clearButtonProps, disableClearButton: disableClearButton, updateSelectedValues: updateSelectedValues, onClick: togglePopover, onKeyDown: handleTriggerKeyDown, onBlur: handleTriggerBlur })), contentProps: { className: classes.popover }, minWidth: optionPanelProps?.minWidth, maxHeight: popoverWrapperProps?.maxHeight, allowedPlacements: optionPanelProps?.allowedPlacements, renderContent: () => (_jsxs("div", { ref: optionPanelRef, className: cx(classes.optionPanel, 'mfui-MultipleSelectBox__optionPanel', optionPanelProps?.className), tabIndex: -1, onKeyDown: handleKeyDownMenu, children: [enableSearchOptions || enableAllOptionsControls ? (_jsx(VStack, { className: cx(classes.menuHeader, 'mfui-MultipleSelectBox__menuHeader'), gap: "8px", children: renderPopoverHeader ? (renderPopoverHeader({ searchNode, allOptionsControlsNode })) : (_jsxs(_Fragment, { children: [searchNode, allOptionsControlsNode] })) })) : null, renderOptionList ? (renderOptionList({ optionsNode, createOptionAreaNode })) : (_jsxs(_Fragment, { children: [optionsNode, createOptionAreaNode] })), enableApplyControls || showSelectedCount ? (_jsx(HStack, { className: cx(classes.menuFooter, 'mfui-MultipleSelectBox__menuFooter'), justifyContent: "space-between", alignItems: "center", children: renderPopoverFooter ? (renderPopoverFooter({ selectedCountNode, applyControlsNode })) : (_jsxs(_Fragment, { children: [selectedCountNode, applyControlsNode] })) })) : null] })), open: isOptionPanelOpen, targetDOMNode: targetDOMNode, enableAutoUnmount: enableAutoUnmount, onOpenStateChanged:
|
|
387
|
+
placeholder: placeholder, size: size, listBoxId: listBoxId, isOptionPanelOpen: isOptionPanelOpen, invalid: invalid, required: required, caution: caution, showDisplayValueAsTag: showDisplayValueAsTag, renderDisplayValue: renderDisplayValue, clearButtonProps: clearButtonProps, disableClearButton: disableClearButton, updateSelectedValues: updateSelectedValues, onClick: togglePopover, onKeyDown: handleTriggerKeyDown, onBlur: handleTriggerBlur })), contentProps: { className: classes.popover }, minWidth: optionPanelProps?.minWidth, maxHeight: popoverWrapperProps?.maxHeight, allowedPlacements: optionPanelProps?.allowedPlacements, renderContent: () => (_jsxs("div", { ref: optionPanelRef, className: cx(classes.optionPanel, 'mfui-MultipleSelectBox__optionPanel', optionPanelProps?.className), tabIndex: -1, onKeyDown: handleKeyDownMenu, children: [enableSearchOptions || enableAllOptionsControls ? (_jsx(VStack, { className: cx(classes.menuHeader, 'mfui-MultipleSelectBox__menuHeader'), gap: "8px", children: renderPopoverHeader ? (renderPopoverHeader({ searchNode, allOptionsControlsNode })) : (_jsxs(_Fragment, { children: [searchNode, allOptionsControlsNode] })) })) : null, renderOptionList ? (renderOptionList({ optionsNode, createOptionAreaNode })) : (_jsxs(_Fragment, { children: [optionsNode, createOptionAreaNode] })), enableApplyControls || showSelectedCount ? (_jsx(HStack, { className: cx(classes.menuFooter, 'mfui-MultipleSelectBox__menuFooter'), justifyContent: "space-between", alignItems: "center", children: renderPopoverFooter ? (renderPopoverFooter({ selectedCountNode, applyControlsNode })) : (_jsxs(_Fragment, { children: [selectedCountNode, applyControlsNode] })) })) : null] })), open: isOptionPanelOpen, targetDOMNode: targetDOMNode, enableAutoUnmount: enableAutoUnmount, onOpenStateChanged: handleOpenStateChanged, onBlur: onBlur }));
|
|
379
388
|
}
|
|
@@ -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
|
|
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,
|
|
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
|
-
//
|
|
24
|
-
|
|
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
|
-
}
|
|
30
|
+
}
|
|
27
31
|
const doCreate = useCallback(async () => {
|
|
28
32
|
try {
|
|
29
33
|
await onCreateOption?.(trimmedSearchText);
|
|
@@ -34,5 +34,6 @@ export declare function useOptionKeyboardNavigation<T extends AllowedValueTypes
|
|
|
34
34
|
tabbableOptionIndex: number;
|
|
35
35
|
handleOptionFocus: (optionIndex: number) => void;
|
|
36
36
|
createKeyDownHandler: (option: MultipleSelectBoxOption<T, AdditionalProps>) => KeyboardEventHandler<HTMLLIElement>;
|
|
37
|
+
resetTabbableIndex: () => void;
|
|
37
38
|
};
|
|
38
39
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useCallback
|
|
1
|
+
import { useState, useCallback } from 'react';
|
|
2
2
|
import { findSiblingElements } from '../../utilities/dom/findSiblingElements';
|
|
3
3
|
import { isSelectableOption, isOptionDisabled } from '../utils/isSelectableOption';
|
|
4
4
|
/**
|
|
@@ -25,20 +25,31 @@ export function useOptionKeyboardNavigation({ filteredOptions, selectedValues, o
|
|
|
25
25
|
// Fallback to 0 if no enabled option is found
|
|
26
26
|
return Math.max(tabbableIndex, 0);
|
|
27
27
|
}, [filteredOptions, selectedValues]);
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
//
|
|
28
|
+
// The focused option is stored by its value, and the tabbable index is calculated during render.
|
|
29
|
+
// Storing the index and resetting it whenever `filteredOptions` is a different array would set
|
|
30
|
+
// state during render, which loops forever when `filteredOptions` is a new array on every render
|
|
31
|
+
// (e.g. `options` is omitted). The focused option stays tabbable while it is in `filteredOptions`,
|
|
32
|
+
// including after it is toggled, so the tabbable option is always the focused one. The caller
|
|
33
|
+
// forgets it with `resetTabbableIndex` whenever the option panel opens, so every opening starts
|
|
34
|
+
// from the first selected option, or the first option when nothing is selected.
|
|
35
|
+
const [focusedOption, setFocusedOption] = useState(null);
|
|
36
|
+
const focusedOptionIndex = focusedOption
|
|
37
|
+
? filteredOptions.findIndex((option) => isSelectableOption(option) && option.value === focusedOption.value)
|
|
38
|
+
: -1;
|
|
39
|
+
const tabbableOptionIndex = focusedOptionIndex !== -1 ? focusedOptionIndex : getDefaultTabbableIndex();
|
|
35
40
|
const resetTabbableIndex = useCallback(() => {
|
|
36
|
-
|
|
37
|
-
}, [
|
|
41
|
+
setFocusedOption(null);
|
|
42
|
+
}, []);
|
|
38
43
|
// Handler for when an option receives focus
|
|
39
44
|
const handleOptionFocus = useCallback((optionIndex) => {
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
const option = filteredOptions[optionIndex];
|
|
46
|
+
if (!option || !isSelectableOption(option)) {
|
|
47
|
+
setFocusedOption(null);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Keep the current object when the same option is focused again, so React can skip the re-render.
|
|
51
|
+
setFocusedOption((previous) => previous && previous.value === option.value ? previous : { value: option.value });
|
|
52
|
+
}, [filteredOptions]);
|
|
42
53
|
// Create keyboard event handler for an option
|
|
43
54
|
const createKeyDownHandler = useCallback((option) => (event) => {
|
|
44
55
|
const siblingElements = findSiblingElements(event.currentTarget, '[role="option"]:not([aria-disabled="true"])');
|
|
@@ -76,5 +87,6 @@ export function useOptionKeyboardNavigation({ filteredOptions, selectedValues, o
|
|
|
76
87
|
tabbableOptionIndex,
|
|
77
88
|
handleOptionFocus,
|
|
78
89
|
createKeyDownHandler,
|
|
90
|
+
resetTabbableIndex,
|
|
79
91
|
};
|
|
80
92
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState,
|
|
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
|
|
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
|
|
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
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
|
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);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { forwardRef, useCallback,
|
|
3
|
+
import { forwardRef, useCallback, useLayoutEffect, useReducer, useRef, useState } from 'react';
|
|
4
4
|
import { cx } from '../../styled-system/css';
|
|
5
5
|
import { mergeRefs } from '../utilities/dom/mergeRefs';
|
|
6
6
|
import { TextBox } from '../TextBox';
|
|
@@ -149,13 +149,17 @@ export const NumberBox = forwardRef(({ onChange, value, defaultValue, textAlign
|
|
|
149
149
|
const pendingCursorRef = useRef(null);
|
|
150
150
|
const [cursorRestoreToken, requestCursorRestore] = useReducer((count) => count + 1, 0);
|
|
151
151
|
const [displayValue, setDisplayValue] = useState(() => formatWithCommas(sanitizeInput(String(value ?? defaultValue ?? ''))));
|
|
152
|
-
//
|
|
153
|
-
|
|
152
|
+
// Adjusted during render (React's documented pattern for resyncing state to a
|
|
153
|
+
// changed prop) rather than in an effect, so the UI never flashes the stale value
|
|
154
|
+
// for one frame. Only fires when value prop is explicitly provided (controlled mode).
|
|
155
|
+
const [prevValue, setPrevValue] = useState(value);
|
|
156
|
+
if (value !== prevValue) {
|
|
157
|
+
setPrevValue(value);
|
|
154
158
|
if (value !== undefined) {
|
|
155
159
|
const sanitized = sanitizeInput(String(value));
|
|
156
160
|
setDisplayValue(formatWithCommas(sanitized));
|
|
157
161
|
}
|
|
158
|
-
}
|
|
162
|
+
}
|
|
159
163
|
// Restore cursor position after display value update
|
|
160
164
|
useLayoutEffect(() => {
|
|
161
165
|
if (pendingCursorRef.current !== null && inputRef.current) {
|