@moneyforward/mfui-components 3.34.0 → 3.36.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 (108) hide show
  1. package/dist/src/DataTable/DataTable.d.ts +50 -0
  2. package/dist/src/DataTable/DataTable.js +44 -0
  3. package/dist/src/DataTable/DataTableFilterButton/DataTableFilterButton.d.ts +6 -0
  4. package/dist/src/DataTable/DataTableFilterButton/DataTableFilterButton.js +63 -0
  5. package/dist/src/DataTable/DataTableFilterButton/DataTableFilterButton.types.d.ts +49 -0
  6. package/dist/src/DataTable/DataTableFilterButton/DataTableFilterButton.types.js +1 -0
  7. package/dist/src/DataTable/DataTableFilterButton/index.d.ts +2 -0
  8. package/dist/src/DataTable/DataTableFilterButton/index.js +1 -0
  9. package/dist/src/DataTable/DataTableHeaderCell/DataTableHeaderCell.js +6 -2
  10. package/dist/src/DataTable/DataTableHeaderCell/DataTableHeaderCell.types.d.ts +23 -1
  11. package/dist/src/DataTable/DataTableSortButton/DataTableSortButton.d.ts +4 -0
  12. package/dist/src/DataTable/DataTableSortButton/DataTableSortButton.js +17 -0
  13. package/dist/src/DataTable/DataTableSortButton/DataTableSortButton.types.d.ts +25 -0
  14. package/dist/src/DataTable/DataTableSortButton/DataTableSortButton.types.js +1 -0
  15. package/dist/src/DataTable/DataTableSortButton/index.d.ts +2 -0
  16. package/dist/src/DataTable/DataTableSortButton/index.js +1 -0
  17. package/dist/src/DataTable/index.d.ts +2 -0
  18. package/dist/src/DateTimeSelection/DatePicker/DatePickerDateCell/DatePickerDateCell.js +3 -0
  19. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerPopover/DateRangePickerCalendar/DateRangePickerDateCell.d.ts +2 -2
  20. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerPopover/DateRangePickerCalendar/DateRangePickerDateCell.js +5 -2
  21. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerTrigger/DateRangePickerTrigger.js +3 -2
  22. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerTrigger/DateRangePickerTrigger.types.d.ts +17 -0
  23. package/dist/src/DateTimeSelection/FilterDateRangePicker/DateRangePickerContent/DateRangePickerContent.js +3 -1
  24. package/dist/src/DateTimeSelection/FilterDateRangePicker/FilterDateRangePicker.types.d.ts +2 -1
  25. package/dist/src/DateTimeSelection/FilterMonthPicker/FilterMonthPicker.types.d.ts +2 -1
  26. package/dist/src/DateTimeSelection/FilterMonthPicker/MonthPickerPopover/MonthPickerPopover.js +2 -1
  27. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerMonthCell/MonthRangePickerMonthCell.d.ts +1 -0
  28. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerMonthCell/MonthRangePickerMonthCell.js +11 -8
  29. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerMonthCell/constants.d.ts +7 -15
  30. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerMonthCell/constants.js +26 -13
  31. package/dist/src/DateTimeSelection/TimePicker/TimePicker.d.ts +2 -0
  32. package/dist/src/DateTimeSelection/TimePicker/TimePicker.js +3 -2
  33. package/dist/src/DateTimeSelection/TimePicker/TimePicker.types.d.ts +27 -0
  34. package/dist/src/DateTimeSelection/shared/BasePicker/BasePicker.js +4 -1
  35. package/dist/src/DateTimeSelection/shared/BasePicker/constants.d.ts +1 -1
  36. package/dist/src/DateTimeSelection/shared/BasePicker/constants.js +2 -0
  37. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePicker.d.ts +1 -1
  38. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePicker.js +4 -4
  39. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePicker.types.d.ts +33 -1
  40. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/BaseRangePickerCalendar/BaseRangePickerDateCell.d.ts +2 -2
  41. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/BaseRangePickerCalendar/BaseRangePickerDateCell.js +15 -36
  42. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/BaseRangePickerCalendar/index.d.ts +0 -1
  43. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/BaseRangePickerCalendar/index.js +0 -1
  44. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/BaseRangePickerPopover.js +2 -1
  45. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerTrigger/BaseRangePickerTrigger.js +3 -2
  46. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerTrigger/BaseRangePickerTrigger.types.d.ts +17 -0
  47. package/dist/src/DateTimeSelection/shared/accessibility/constants.d.ts +7 -16
  48. package/dist/src/DateTimeSelection/shared/accessibility/constants.js +30 -14
  49. package/dist/src/DateTimeSelection/shared/accessibility/generateDateLabel.d.ts +6 -1
  50. package/dist/src/DateTimeSelection/shared/accessibility/generateDateLabel.js +14 -8
  51. package/dist/src/DeterminateProgressIndicator/DeterminateProgressIndicator.d.ts +18 -0
  52. package/dist/src/DeterminateProgressIndicator/DeterminateProgressIndicator.js +20 -0
  53. package/dist/src/DeterminateProgressIndicator/DeterminateProgressIndicator.types.d.ts +41 -0
  54. package/dist/src/DeterminateProgressIndicator/DeterminateProgressIndicator.types.js +1 -0
  55. package/dist/src/DeterminateProgressIndicator/index.d.ts +2 -0
  56. package/dist/src/DeterminateProgressIndicator/index.js +1 -0
  57. package/dist/src/FilterSelectBox/FilterSelectBox.types.d.ts +1 -1
  58. package/dist/src/IconButton/IconButton.d.ts +2 -0
  59. package/dist/src/IconButton/IconButton.js +2 -2
  60. package/dist/src/IconButton/IconButton.types.d.ts +7 -0
  61. package/dist/src/MultipleFilterSelectBox/MultipleFilterSelectBox.types.d.ts +1 -1
  62. package/dist/src/MultipleSelectBox/MultipleSelectBox.d.ts +1 -1
  63. package/dist/src/MultipleSelectBox/MultipleSelectBox.js +2 -2
  64. package/dist/src/MultipleSelectBox/MultipleSelectBox.types.d.ts +5 -1
  65. package/dist/src/MultipleSelectBox/MultipleSelectBoxTrigger/MultipleSelectBoxTrigger.js +2 -2
  66. package/dist/src/MultipleSelectBox/MultipleSelectBoxTrigger/MultipleSelectBoxTrigger.types.d.ts +39 -1
  67. package/dist/src/MultipleSelectBox/hooks/useSearchBox.js +14 -3
  68. package/dist/src/Popover/Popover.js +1 -1
  69. package/dist/src/Popover/Popover.types.d.ts +1 -1
  70. package/dist/src/SelectBox/SelectBox.d.ts +1 -1
  71. package/dist/src/SelectBox/SelectBox.js +11 -4
  72. package/dist/src/SelectBox/SelectBox.types.d.ts +38 -1
  73. package/dist/src/SelectBox/hooks/useSearchBox.d.ts +6 -1
  74. package/dist/src/SelectBox/hooks/useSearchBox.js +23 -4
  75. package/dist/src/SplitView/SplitView.js +16 -2
  76. package/dist/src/TextBox/TextBox.d.ts +2 -1
  77. package/dist/src/TextBox/TextBox.js +4 -3
  78. package/dist/src/TextBox/TextBox.types.d.ts +26 -0
  79. package/dist/src/Textarea/Textarea.d.ts +2 -1
  80. package/dist/src/Textarea/Textarea.js +11 -3
  81. package/dist/src/Textarea/Textarea.types.d.ts +26 -0
  82. package/dist/src/index.d.ts +1 -0
  83. package/dist/src/index.js +1 -0
  84. package/dist/styled-system/recipes/base-range-picker-trigger-slot-recipe.d.ts +1 -0
  85. package/dist/styled-system/recipes/base-range-picker-trigger-slot-recipe.js +5 -0
  86. package/dist/styled-system/recipes/data-table-filter-button-slot-recipe.d.ts +33 -0
  87. package/dist/styled-system/recipes/data-table-filter-button-slot-recipe.js +43 -0
  88. package/dist/styled-system/recipes/data-table-header-cell-slot-recipe.d.ts +3 -2
  89. package/dist/styled-system/recipes/data-table-header-cell-slot-recipe.js +69 -1
  90. package/dist/styled-system/recipes/data-table-sort-button-slot-recipe.d.ts +33 -0
  91. package/dist/styled-system/recipes/data-table-sort-button-slot-recipe.js +35 -0
  92. package/dist/styled-system/recipes/determinate-progress-indicator-slot-recipe.d.ts +36 -0
  93. package/dist/styled-system/recipes/determinate-progress-indicator-slot-recipe.js +61 -0
  94. package/dist/styled-system/recipes/icon-button-slot-recipe.d.ts +2 -1
  95. package/dist/styled-system/recipes/icon-button-slot-recipe.js +33 -2
  96. package/dist/styled-system/recipes/index.d.ts +3 -0
  97. package/dist/styled-system/recipes/index.js +3 -0
  98. package/dist/styled-system/recipes/text-box-slot-recipe.d.ts +4 -0
  99. package/dist/styled-system/recipes/text-box-slot-recipe.js +8 -2
  100. package/dist/styled-system/recipes/textarea-slot-recipe.d.ts +4 -0
  101. package/dist/styled-system/recipes/textarea-slot-recipe.js +6 -0
  102. package/dist/styled-system/recipes/time-picker-slot-recipe.d.ts +4 -0
  103. package/dist/styled-system/recipes/time-picker-slot-recipe.js +8 -2
  104. package/dist/styles.css +293 -14
  105. package/dist/tsconfig.build.tsbuildinfo +1 -1
  106. package/package.json +10 -10
  107. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/BaseRangePickerCalendar/constants.d.ts +0 -18
  108. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/BaseRangePickerCalendar/constants.js +0 -18
@@ -0,0 +1,41 @@
1
+ import { type ComponentPropsWithoutRef } from 'react';
2
+ export type DeterminateProgressIndicatorProps = {
3
+ /**
4
+ * The current progress value expressed as a percentage (`0`–`100`).
5
+ *
6
+ * Values outside this range are clamped to `0` or `100`.
7
+ */
8
+ value: number;
9
+ /**
10
+ * Whether to display the value text (the progress percentage as a number).
11
+ *
12
+ * @default true
13
+ */
14
+ showValueText?: boolean;
15
+ /**
16
+ * Where to place the value text relative to the progress indicator.
17
+ *
18
+ * - `'right'`: to the right of the progress indicator, vertically centered.
19
+ * - `'top'`: in the upper-right corner, above the progress indicator.
20
+ *
21
+ * @default 'right'
22
+ */
23
+ valueTextPosition?: 'right' | 'top';
24
+ /**
25
+ * The unit shown after the value. Set to an empty string to hide the unit.
26
+ *
27
+ * @default '%'
28
+ */
29
+ unit?: string;
30
+ /**
31
+ * The accessible name describing what is loading.
32
+ *
33
+ * Provide this (or `aria-labelledby`) so assistive technologies can announce
34
+ * the purpose of the progress indicator.
35
+ */
36
+ 'aria-label'?: string;
37
+ /**
38
+ * The additional class name for the root element.
39
+ */
40
+ className?: string;
41
+ } & Omit<ComponentPropsWithoutRef<'div'>, 'role' | 'children'>;
@@ -0,0 +1,2 @@
1
+ export { DeterminateProgressIndicator } from './DeterminateProgressIndicator';
2
+ export type { DeterminateProgressIndicatorProps } from './DeterminateProgressIndicator.types';
@@ -0,0 +1 @@
1
+ export { DeterminateProgressIndicator } from './DeterminateProgressIndicator';
@@ -14,4 +14,4 @@ export type FilterSelectBoxProps = {
14
14
  * @param open - The new open state of the FilterSelectBox.
15
15
  */
16
16
  onOpenStateChanged?: (open: boolean) => void;
17
- } & Omit<SelectBoxProps, 'renderDisplayValue' | 'placeholder' | 'invalid' | 'triggerProps'>;
17
+ } & Omit<SelectBoxProps, 'renderDisplayValue' | 'placeholder' | 'invalid' | 'caution' | 'triggerProps'>;
@@ -1,5 +1,6 @@
1
1
  export declare const InternalIconButton: import("react").ForwardRefExoticComponent<{
2
2
  outlined?: import("../../styled-system/recipes").IconButtonSlotRecipeVariant['outlined'];
3
+ destructive?: import("../../styled-system/recipes").IconButtonSlotRecipeVariant['destructive'];
3
4
  children: React.ReactNode;
4
5
  disabled?: boolean;
5
6
  href?: string;
@@ -9,6 +10,7 @@ export declare const InternalIconButton: import("react").ForwardRefExoticCompone
9
10
  } & import("react").RefAttributes<HTMLAnchorElement | HTMLButtonElement>>;
10
11
  export declare const IconButton: import("react").ForwardRefExoticComponent<{
11
12
  outlined?: import("../../styled-system/recipes").IconButtonSlotRecipeVariant['outlined'];
13
+ destructive?: import("../../styled-system/recipes").IconButtonSlotRecipeVariant['destructive'];
12
14
  children: React.ReactNode;
13
15
  disabled?: boolean;
14
16
  href?: string;
@@ -20,8 +20,8 @@ import { FocusIndicator } from '../FocusIndicator';
20
20
  *
21
21
  * @param ref - Forwarded ref for the rendered button or anchor element.
22
22
  */
23
- function IconButtonBase({ children, outlined, size = 'default', className, href, customLinkComponent, disabled = false, type = 'button', ...rest }, ref) {
24
- const classes = iconButtonSlotRecipe({ outlined, size });
23
+ function IconButtonBase({ children, outlined, destructive, size = 'default', className, href, customLinkComponent, disabled = false, type = 'button', ...rest }, ref) {
24
+ const classes = iconButtonSlotRecipe({ outlined, size, destructive });
25
25
  const isButton = disabled || href === undefined;
26
26
  const Tag = useMemo(() => {
27
27
  // If disabled, always render as a button since the link component doesn't support disabled prop
@@ -7,6 +7,13 @@ type IconButtonCommonProps = {
7
7
  * @default false
8
8
  */
9
9
  outlined?: IconButtonSlotRecipeVariant['outlined'];
10
+ /**
11
+ * Whether the button is destructive.
12
+ * The style of the button will be changed to indicate that the action is destructive.
13
+ *
14
+ * @default false
15
+ */
16
+ destructive?: IconButtonSlotRecipeVariant['destructive'];
10
17
  /**
11
18
  * Icon element to be rendered inside the button.
12
19
  * Only one element (typically an SVG icon) is expected.
@@ -8,4 +8,4 @@ export type MultipleFilterSelectBoxProps<T extends AllowedValueTypes = string, A
8
8
  * The class name to apply to the component
9
9
  */
10
10
  className?: string;
11
- } & Omit<MultipleSelectBoxProps<T, AdditionalProps>, 'renderDisplayValue' | 'showDisplayValueAsTag' | 'placeholder' | 'invalid' | 'triggerWrapperProps' | 'triggerProps'>;
11
+ } & Omit<MultipleSelectBoxProps<T, AdditionalProps>, 'renderDisplayValue' | 'showDisplayValueAsTag' | 'placeholder' | 'invalid' | 'caution' | 'triggerWrapperProps' | 'triggerProps'>;
@@ -4,4 +4,4 @@ import { type MultipleSelectBoxProps, type AllowedValueTypes } from './MultipleS
4
4
  * This component is separated from the SelectBox component because it has a different behavior.
5
5
  * This component switches the variants of looks and behaviors depends on the props: size, invalid, disabled.
6
6
  */
7
- export declare function MultipleSelectBox<T extends AllowedValueTypes = string, AdditionalProps extends Record<string, unknown> = Record<string, never>>({ id, triggerProps, triggerWrapperProps, size, options, defaultValue, placeholder, emptyMessage, disabled, invalid, targetDOMNode, name, onChange, value, showDisplayValueAsTag, renderDisplayValue, enableSearchOptions, notFoundMessage, searchBoxProps, loading, onSearchOptions, clearButtonProps, disableClearButton, optionPanelProps, popoverWrapperProps, enableAllOptionsControls, enableApplyControls, showSelectedCount, selectAllButtonProps, clearAllButtonProps, selectedCountProps, cancelButtonProps, applyButtonProps, renderOption, renderPopoverHeader, renderOptionList, renderPopoverFooter, onOpenStateChanged, enableAutoUnmount, onBlur, showGroupOptionDivider, enableVirtualization, virtualizationOptions, infiniteScroll, enableCreateOption, onCreateOption, createOptionProps, }: MultipleSelectBoxProps<T, AdditionalProps>): import("react/jsx-runtime").JSX.Element;
7
+ export declare function MultipleSelectBox<T extends AllowedValueTypes = string, AdditionalProps extends Record<string, unknown> = Record<string, never>>({ id, triggerProps, triggerWrapperProps, size, options, defaultValue, placeholder, emptyMessage, disabled, invalid, required, caution, targetDOMNode, name, onChange, value, showDisplayValueAsTag, renderDisplayValue, enableSearchOptions, notFoundMessage, searchBoxProps, loading, onSearchOptions, clearButtonProps, disableClearButton, optionPanelProps, popoverWrapperProps, enableAllOptionsControls, enableApplyControls, showSelectedCount, selectAllButtonProps, clearAllButtonProps, selectedCountProps, cancelButtonProps, applyButtonProps, renderOption, renderPopoverHeader, renderOptionList, renderPopoverFooter, onOpenStateChanged, enableAutoUnmount, onBlur, showGroupOptionDivider, enableVirtualization, virtualizationOptions, infiniteScroll, enableCreateOption, onCreateOption, createOptionProps, }: MultipleSelectBoxProps<T, AdditionalProps>): import("react/jsx-runtime").JSX.Element;
@@ -36,7 +36,7 @@ const SKELETON_ITEM_COUNT = 4;
36
36
  */
37
37
  export function MultipleSelectBox({ id, triggerProps, triggerWrapperProps, size, options = [], defaultValue,
38
38
  // eslint-disable-next-line @typescript-eslint/no-deprecated
39
- placeholder, emptyMessage, disabled, invalid, 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, }) {
39
+ 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, }) {
40
40
  const classes = multipleSelectBoxSlotRecipe({ showGroupOptionDivider });
41
41
  const triggerRef = useRef(null);
42
42
  const listBoxId = useId();
@@ -350,5 +350,5 @@ placeholder, emptyMessage, disabled, invalid, targetDOMNode, name, onChange, val
350
350
  `${searchText.trim()}を追加できませんでした。` }) })) : null] })) : null;
351
351
  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,
352
352
  // eslint-disable-next-line @typescript-eslint/no-deprecated
353
- placeholder: placeholder, size: size, listBoxId: listBoxId, isOptionPanelOpen: isOptionPanelOpen, invalid: invalid, 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: toggleOptionPanel, onBlur: onBlur }));
353
+ 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: toggleOptionPanel, onBlur: onBlur }));
354
354
  }
@@ -414,7 +414,7 @@ export type MultipleSelectBoxProps<T extends AllowedValueTypes = string, Additio
414
414
  * Pass `aria-label` to override the search input's default
415
415
  * accessible name (`検索する`).
416
416
  */
417
- searchBoxProps?: Pick<SearchBoxProps, 'clearButtonProps' | 'placeholder' | 'invalid' | 'aria-label'>;
417
+ searchBoxProps?: Pick<SearchBoxProps, 'clearButtonProps' | 'placeholder' | 'invalid' | 'caution' | 'aria-label'>;
418
418
  /**
419
419
  * The properties for the option panel element.
420
420
  *
@@ -446,6 +446,10 @@ export type MultipleSelectBoxProps<T extends AllowedValueTypes = string, Additio
446
446
  * Handler executed when text is entered in the search box.
447
447
  * The handler is debounced by 300ms.
448
448
  *
449
+ * If the handler is provided after the user has already typed (e.g.
450
+ * `onSearchOptions={isReady ? handleSearch : undefined}`), it is called
451
+ * once with the current search text so that pending input is not lost.
452
+ *
449
453
  * When this prop is provided, client-side filtering is disabled as it's
450
454
  * assumed that filtering will be handled by the prop.
451
455
  */
@@ -14,7 +14,7 @@ import { cx } from '../../../styled-system/css';
14
14
  *
15
15
  * @param ref - The forwarded ref to the trigger button element
16
16
  */
17
- function MultipleSelectBoxTriggerInner({ wrapperRef, triggerProps, triggerWrapperProps, size, selectedOptions, listBoxId, isOptionPanelOpen, invalid, showDisplayValueAsTag,
17
+ function MultipleSelectBoxTriggerInner({ wrapperRef, triggerProps, triggerWrapperProps, size, selectedOptions, listBoxId, isOptionPanelOpen, invalid, required, caution = false, showDisplayValueAsTag,
18
18
  // eslint-disable-next-line @typescript-eslint/no-deprecated
19
19
  placeholder, renderDisplayValue, updateSelectedValues, clearButtonProps, disableClearButton, onKeyDown, onBlur, name, disabled, ...rest }, ref) {
20
20
  const classes = multipleSelectBoxTriggerRecipe({ size });
@@ -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": listBoxId, "aria-expanded": isOptionPanelOpen, "aria-haspopup": "listbox", "aria-invalid": invalid, 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": listBoxId, "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') }) })] }) }));
52
52
  }
53
53
  export const MultipleSelectBoxTrigger = forwardRef(MultipleSelectBoxTriggerInner);
@@ -39,16 +39,54 @@ export type PassedProps<T extends AllowedValueTypes = string, AdditionalProps ex
39
39
  * @default false
40
40
  */
41
41
  invalid?: boolean;
42
+ /**
43
+ * Whether the trigger is required.
44
+ * When true, `aria-required="true"` is forwarded to the trigger so that
45
+ * assistive technologies announce the field as required.
46
+ *
47
+ * @default false
48
+ */
49
+ required?: boolean;
50
+ /**
51
+ * Whether the trigger element shows a caution state — the selection is valid but is
52
+ * worth double-checking.
53
+ *
54
+ * Unlike `invalid`, this does not set `aria-invalid` — the state is advisory, not an error.
55
+ * It changes the border color only, so you must also render an adjacent
56
+ * `<HelpMessage messageType="caution">` describing the concern — color alone does not
57
+ * convey the state (WCAG 2.2 1.4.1).
58
+ *
59
+ * Associate that `HelpMessage` with the trigger via `triggerProps['aria-describedby']` so
60
+ * screen readers announce it on focus, and give it `role="status"` so it is also announced
61
+ * the moment it appears while focus is already on the trigger.
62
+ *
63
+ * ```tsx
64
+ * const cautionMessageId = useId();
65
+ *
66
+ * <MultipleSelectBox caution triggerProps={{ 'aria-describedby': cautionMessageId }} />
67
+ * <HelpMessage id={cautionMessageId} role="status" messageType="caution">
68
+ * 通常より多くの項目が選択されています。
69
+ * </HelpMessage>
70
+ * ```
71
+ *
72
+ * Ignored while `invalid` is `true`, because the error state takes precedence.
73
+ *
74
+ * @default false
75
+ */
76
+ caution?: boolean;
42
77
  /**
43
78
  * The properties for the trigger element.
44
79
  *
45
80
  * @property aria-label - The alternative text for the trigger element. It is overridden by associated label using labelId prop.
46
81
  *
82
+ * @property aria-describedby - The id of the element describing the trigger element,
83
+ * such as an adjacent `HelpMessage`.
84
+ *
47
85
  * @property className - The class name for the trigger element.
48
86
  *
49
87
  * @default undefined
50
88
  */
51
- triggerProps?: Pick<ComponentPropsWithoutRef<'button'>, 'aria-label' | 'className'>;
89
+ triggerProps?: Pick<ComponentPropsWithoutRef<'button'>, 'aria-label' | 'aria-describedby' | 'className'>;
52
90
  /**
53
91
  * The properties for the wrapper element of the trigger element.
54
92
  *
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
  import { useEffect, useMemo, useRef, useState } from 'react';
3
+ import { useLatest } from '../../utilities/state/useLatest';
3
4
  import { normalizeFullWidth } from '../../utilities/string/normalizeFullWidth';
4
5
  /**
5
6
  * Custom hook to handle search functionality within a SelectBox component.
@@ -18,6 +19,10 @@ export const useSearchBox = (options, enableSearchOptions, onSearchOptions) => {
18
19
  const [searchText, setSearchText] = useState('');
19
20
  const debounceRef = useRef(null);
20
21
  const previousSearchTextRef = useRef('');
22
+ // Keep the latest handler in a ref so that a new handler identity (e.g. an
23
+ // inline function re-created on parent re-render) does not re-run the
24
+ // debounce effect and cancel a pending call.
25
+ const onSearchOptionsRef = useLatest(onSearchOptions);
21
26
  const filteredOptions = useMemo(() => {
22
27
  if (!enableSearchOptions || onSearchOptions) {
23
28
  return options;
@@ -29,8 +34,14 @@ export const useSearchBox = (options, enableSearchOptions, onSearchOptions) => {
29
34
  const onSearchTextChange = (event) => {
30
35
  setSearchText(event.target.value);
31
36
  };
37
+ // Track only the presence of the handler (not its identity) so that an
38
+ // undefined → defined transition re-runs the effect and dispatches the
39
+ // pending search text, while identity changes do not cancel pending calls.
40
+ // This preserves the behavior of the previous implementation, where the
41
+ // handler itself was an effect dependency and the transition dispatched.
42
+ const hasSearchHandler = Boolean(onSearchOptions);
32
43
  useEffect(() => {
33
- if (!enableSearchOptions || !onSearchOptions)
44
+ if (!enableSearchOptions || !hasSearchHandler)
34
45
  return;
35
46
  // Skip if searchText hasn't changed (prevents initial mount and unnecessary calls)
36
47
  if (searchText === previousSearchTextRef.current) {
@@ -41,14 +52,14 @@ export const useSearchBox = (options, enableSearchOptions, onSearchOptions) => {
41
52
  clearTimeout(debounceRef.current);
42
53
  }
43
54
  debounceRef.current = setTimeout(() => {
44
- onSearchOptions(searchText);
55
+ onSearchOptionsRef.current?.(searchText);
45
56
  }, 300);
46
57
  return () => {
47
58
  if (debounceRef.current) {
48
59
  clearTimeout(debounceRef.current);
49
60
  }
50
61
  };
51
- }, [searchText, onSearchOptions, enableSearchOptions]);
62
+ }, [searchText, enableSearchOptions, hasSearchHandler, onSearchOptionsRef]);
52
63
  return {
53
64
  onSearchTextChange,
54
65
  filteredOptions,
@@ -253,7 +253,7 @@ export function Popover({ renderTrigger, renderContent, open, onOpenStateChanged
253
253
  });
254
254
  };
255
255
  }, [isPopoverOpen, refs.reference, refs.floating]);
256
- return (_jsxs(_Fragment, { children: [renderTrigger(options), shouldRenderTheContent ? (_jsx(Portal, { targetDOMNode: targetElement, children: _jsx("div", { ref: refs.setFloating, tabIndex: contentProps.tabIndex ?? 0, "data-mfui-content": "popover-portal-content", "data-mfui-popover-depth": nestingDepth, className: cx(classes.root, 'mfui-Popover__root', contentProps.className), style: {
256
+ return (_jsxs(_Fragment, { children: [renderTrigger(options), shouldRenderTheContent ? (_jsx(Portal, { targetDOMNode: targetElement, children: _jsx("div", { ref: refs.setFloating, id: contentProps.id, tabIndex: contentProps.tabIndex ?? 0, "data-mfui-content": "popover-portal-content", "data-mfui-popover-depth": nestingDepth, className: cx(classes.root, 'mfui-Popover__root', contentProps.className), style: {
257
257
  ...floatingStyles,
258
258
  ...(!enableAutoUnmount && !isPopoverOpen && { display: 'none' }),
259
259
  }, onKeyDown: handleKeyDownPopover, children: _jsx(TargetDomNodeProvider, { targetDomNode: refs.floating.current, children: _jsx(PopoverContextProvider, { children: renderContent(options) }) }) }) })) : null] }));
@@ -126,7 +126,7 @@ export type PopoverProps = {
126
126
  *
127
127
  * @default {}
128
128
  */
129
- contentProps?: Pick<ComponentProps<'div'>, 'className'> & {
129
+ contentProps?: Pick<ComponentProps<'div'>, 'className' | 'id'> & {
130
130
  /**
131
131
  * The tab index of the popover content element.
132
132
  * Please note if you set this prop, it may cause the incorrect focus behavior when `enableAutoFocusOnPopover` is true.
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { type BasedAdditionalProps, type SelectBoxProps } from './SelectBox.types';
3
3
  /**
4
4
  * The general-purpose SelectBox component.
5
- * This component switches the variants of looks and behaviors depends on the props: size, invalid, disabled.
5
+ * This component switches the variants of looks and behaviors depends on the props: size, invalid, caution, disabled.
6
6
  */
7
7
  export declare const SelectBox: (<T extends string | number | undefined = string, AdditionalProps extends BasedAdditionalProps = BasedAdditionalProps>(props: SelectBoxProps<T, AdditionalProps> & {
8
8
  ref?: React.ForwardedRef<HTMLInputElement>;
@@ -33,12 +33,12 @@ const DEFAULT_SCROLL_OPTIONS = {
33
33
  const SKELETON_ITEM_COUNT = 4;
34
34
  /**
35
35
  * The general-purpose SelectBox component.
36
- * This component switches the variants of looks and behaviors depends on the props: size, invalid, disabled.
36
+ * This component switches the variants of looks and behaviors depends on the props: size, invalid, caution, disabled.
37
37
  */
38
38
  export const SelectBox = forwardRef((props, ref) => {
39
39
  const { id, triggerProps, clearButtonProps, enableClearButton = false, size, options = [], defaultValue,
40
40
  // eslint-disable-next-line @typescript-eslint/no-deprecated
41
- placeholder, emptyMessage, disabled, invalid, required, targetDOMNode, name, onChange, onBlur, value, enableSearchOptions = false, notFoundMessage, searchBoxProps, loading = false, onSearchOptions, renderDisplayValue, renderOption, renderPopoverContent, enableAutoScrollToSelectedOption, popoverContentProps, popoverWrapperProps, onOpenStateChanged, showGroupOptionDivider, triggerWrapperProps, enableAutoUnmount = true, enableConstrainedPopoverWidth = false, enableVirtualization = false, virtualizationOptions, infiniteScroll, } = props;
41
+ placeholder, emptyMessage, disabled, invalid, caution = false, required, targetDOMNode, name, onChange, onBlur, value, enableSearchOptions = false, notFoundMessage, searchBoxProps, loading = false, onSearchOptions, renderDisplayValue, renderOption, renderPopoverContent, enableAutoScrollToSelectedOption, popoverContentProps, popoverWrapperProps, onOpenStateChanged, showGroupOptionDivider, triggerWrapperProps, enableAutoUnmount = true, enableConstrainedPopoverWidth = false, enableVirtualization = false, virtualizationOptions, infiniteScroll, } = props;
42
42
  const classes = selectBoxSlotRecipe({ size, showGroupOptionDivider });
43
43
  const triggerRef = useRef(null);
44
44
  const listBoxId = useId();
@@ -53,7 +53,14 @@ export const SelectBox = forwardRef((props, ref) => {
53
53
  triggerRef.current?.focus();
54
54
  },
55
55
  });
56
- const { searchText, onSearchTextChange, filteredOptions } = useSearchBox(options, enableSearchOptions, onSearchOptions);
56
+ const { searchText, onSearchTextChange, filteredOptions, resetSearchText } = useSearchBox(options, enableSearchOptions, onSearchOptions);
57
+ // Clear the search text whenever the option panel closes so that
58
+ // reopening always starts from an unfiltered option list.
59
+ useUpdateEffect(() => {
60
+ if (!isOptionPanelOpen) {
61
+ resetSearchText();
62
+ }
63
+ }, [isOptionPanelOpen, resetSearchText]);
57
64
  const selectedOption = useMemo(() => {
58
65
  if (value === undefined)
59
66
  return null;
@@ -331,7 +338,7 @@ export const SelectBox = forwardRef((props, ref) => {
331
338
  const hasSelectedValue = value !== undefined || localSelectedOption !== null;
332
339
  const showClearButton = enableClearButton && hasSelectedValue;
333
340
  // Use Popover's centralized blur handling instead of custom logic
334
- return (_jsx(Popover, { open: isOptionPanelOpen, targetDOMNode: targetDOMNode, minWidth: popoverContentProps?.minWidth, maxHeight: popoverWrapperProps?.maxHeight, allowedPlacements: popoverContentProps?.allowedPlacements, enableConstrainedContentWidth: enableConstrainedPopoverWidth, renderContent: () => (_jsx("div", { ref: optionPanelRef, id: listBoxId, className: cx(classes.optionPanel, 'mfui-SelectBox__optionPanel', popoverContentProps?.className), tabIndex: -1, onKeyDown: handleKeyDownMenu, children: renderPopoverContent ? (renderPopoverContent({ searchNode, optionsNode })) : (_jsxs(_Fragment, { children: [searchNode, optionsNode] })) })), renderTrigger: ({ setTriggerRef, togglePopover, isPopoverOpen, handleTriggerKeyDown, handleTriggerBlur }) => (_jsxs("div", { ref: setTriggerRef, ...triggerWrapperProps, className: cx(classes.triggerWrapper, 'mfui-SelectBox__triggerWrapper', triggerWrapperProps?.className), children: [_jsx(FocusIndicator, { children: _jsxs("button", { ref: triggerRef, id: id, type: "button", role: "combobox", disabled: disabled, "aria-label": triggerProps?.['aria-label'], "aria-controls": listBoxId, "aria-expanded": isPopoverOpen, "aria-haspopup": "listbox", "aria-invalid": invalid, "aria-required": required, className: cx(classes.trigger, 'mfui-SelectBox__trigger', triggerProps?.className), "data-placeholder": !!placeholder && !localSelectedOption, "data-selected": !!localSelectedOption, "data-mfui-has-clear-button": showClearButton, onClick: togglePopover, onKeyDown: (event) => {
341
+ return (_jsx(Popover, { open: isOptionPanelOpen, targetDOMNode: targetDOMNode, minWidth: popoverContentProps?.minWidth, maxHeight: popoverWrapperProps?.maxHeight, allowedPlacements: popoverContentProps?.allowedPlacements, enableConstrainedContentWidth: enableConstrainedPopoverWidth, renderContent: () => (_jsx("div", { ref: optionPanelRef, id: listBoxId, className: cx(classes.optionPanel, 'mfui-SelectBox__optionPanel', popoverContentProps?.className), tabIndex: -1, onKeyDown: handleKeyDownMenu, children: renderPopoverContent ? (renderPopoverContent({ searchNode, optionsNode })) : (_jsxs(_Fragment, { children: [searchNode, optionsNode] })) })), renderTrigger: ({ setTriggerRef, togglePopover, isPopoverOpen, handleTriggerKeyDown, handleTriggerBlur }) => (_jsxs("div", { ref: setTriggerRef, ...triggerWrapperProps, className: cx(classes.triggerWrapper, 'mfui-SelectBox__triggerWrapper', triggerWrapperProps?.className), children: [_jsx(FocusIndicator, { children: _jsxs("button", { ref: triggerRef, id: id, type: "button", role: "combobox", disabled: disabled, "aria-label": triggerProps?.['aria-label'], "aria-controls": listBoxId, "aria-describedby": triggerProps?.['aria-describedby'], "aria-expanded": isPopoverOpen, "aria-haspopup": "listbox", "aria-invalid": invalid, "aria-required": required, className: cx(classes.trigger, 'mfui-SelectBox__trigger', triggerProps?.className), "data-placeholder": !!placeholder && !localSelectedOption, "data-selected": !!localSelectedOption, "data-mfui-has-clear-button": showClearButton, "data-mfui-caution": caution && !invalid ? true : undefined, onClick: togglePopover, onKeyDown: (event) => {
335
342
  handleTypeAhead(event.nativeEvent);
336
343
  handleTriggerKeyDown(event);
337
344
  }, onBlur: handleTriggerBlur, children: [_jsx("span", { "data-mfui-content": "select-box-trigger-display-value", children: renderTriggerLabel() }), _jsx(DropdownIcon, { "aria-hidden": "true", focusable: false }), _jsx("input", { ref: ref, type: "hidden", value: localSelectedOption?.value ?? '', name: name, disabled: disabled })] }) }), showClearButton ? (_jsx("div", { className: cx(classes.clearButtonWrapper, 'mfui-SelectBox__clearButtonWrapper'), children: _jsx(ClearButton, { size: size === 'small' ? 'small' : size === 'large' ? 'large' : 'default', "aria-label": clearButtonProps?.['aria-label'] ?? '値をクリアする', "data-mfui-content": "select-box-clear-button", onClick: (event) => {
@@ -157,9 +157,12 @@ export type SelectBoxProps<T extends AllowedValueTypes = string, AdditionalProps
157
157
  *
158
158
  * @property aria-label - The alternative text for the trigger element.
159
159
  *
160
+ * @property aria-describedby - The id of the element describing the trigger element,
161
+ * such as an adjacent `HelpMessage`.
162
+ *
160
163
  * @property className - The class name of the trigger element.
161
164
  */
162
- triggerProps?: Pick<ComponentPropsWithoutRef<'button'>, 'aria-label' | 'className'>;
165
+ triggerProps?: Pick<ComponentPropsWithoutRef<'button'>, 'aria-label' | 'aria-describedby' | 'className'>;
163
166
  /**
164
167
  * Whether you let user to clear the content or not.
165
168
  * This will add the Clear button if current SelectBox has value.
@@ -227,6 +230,33 @@ export type SelectBoxProps<T extends AllowedValueTypes = string, AdditionalProps
227
230
  * Whether the trigger is invalid.
228
231
  */
229
232
  invalid?: boolean;
233
+ /**
234
+ * Whether the trigger needs the user's attention while still being submittable.
235
+ * Use it when the selection is valid but is worth double-checking.
236
+ *
237
+ * Unlike `invalid`, this does not set `aria-invalid` — the state is advisory, not an error.
238
+ * It changes the border color only, so you must also render an adjacent
239
+ * `<HelpMessage messageType="caution">` describing the concern — color alone does not
240
+ * convey the state (WCAG 2.2 1.4.1).
241
+ *
242
+ * Associate that `HelpMessage` with the trigger via `triggerProps['aria-describedby']` so
243
+ * screen readers announce it on focus, and give it `role="status"` so it is also announced
244
+ * the moment it appears while focus is already on the trigger.
245
+ *
246
+ * ```tsx
247
+ * const cautionMessageId = useId();
248
+ *
249
+ * <SelectBox caution triggerProps={{ 'aria-describedby': cautionMessageId }} />
250
+ * <HelpMessage id={cautionMessageId} role="status" messageType="caution">
251
+ * 通常とは異なる勘定科目が選択されています。
252
+ * </HelpMessage>
253
+ * ```
254
+ *
255
+ * Ignored while `invalid` is `true`, because the error state takes precedence.
256
+ *
257
+ * @default false
258
+ */
259
+ caution?: boolean;
230
260
  /**
231
261
  * Whether the trigger is required.
232
262
  * When true, `aria-required="true"` is forwarded to the trigger so that
@@ -361,6 +391,13 @@ export type SelectBoxProps<T extends AllowedValueTypes = string, AdditionalProps
361
391
  * Handler executed when text is entered in the search box.
362
392
  * The handler is debounced by 300ms.
363
393
  *
394
+ * Also called with an empty string when the option panel closes, because the
395
+ * search text is reset on close so that reopening shows the full option list.
396
+ *
397
+ * If the handler is provided after the user has already typed (e.g.
398
+ * `onSearchOptions={isReady ? handleSearch : undefined}`), it is called
399
+ * once with the current search text so that pending input is not lost.
400
+ *
364
401
  * When this prop is provided, client-side filtering is disabled as it's
365
402
  * assumed that filtering will be handled by the prop.
366
403
  */
@@ -10,7 +10,8 @@ import { type BasedAdditionalProps, type SelectBoxOption } from '../SelectBox.ty
10
10
  * provided, client-side filtering is disabled as it's assumed that the caller
11
11
  * will handle the filtering.
12
12
  *
13
- * @returns Object containing the filtered options, search text, and handler to update search text.
13
+ * @returns Object containing the filtered options, search text, handler to update search text,
14
+ * and a callback to reset the search text.
14
15
  */
15
16
  export declare const useSearchBox: <T extends string | number | undefined = string, AdditionalProps extends BasedAdditionalProps = BasedAdditionalProps>(options: (SelectBoxOption<T, AdditionalProps> | null | undefined)[], enableSearchOptions: boolean, onSearchOptions?: (text: string) => void) => {
16
17
  /**
@@ -25,4 +26,8 @@ export declare const useSearchBox: <T extends string | number | undefined = stri
25
26
  * Current search text value.
26
27
  */
27
28
  searchText: string;
29
+ /**
30
+ * Resets the search text to an empty string.
31
+ */
32
+ resetSearchText: () => void;
28
33
  };
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
+ import { useLatest } from '../../utilities/state/useLatest';
3
4
  import { normalizeFullWidth } from '../../utilities/string/normalizeFullWidth';
4
5
  /**
5
6
  * Custom hook to handle search functionality within a SelectBox component.
@@ -12,12 +13,17 @@ import { normalizeFullWidth } from '../../utilities/string/normalizeFullWidth';
12
13
  * provided, client-side filtering is disabled as it's assumed that the caller
13
14
  * will handle the filtering.
14
15
  *
15
- * @returns Object containing the filtered options, search text, and handler to update search text.
16
+ * @returns Object containing the filtered options, search text, handler to update search text,
17
+ * and a callback to reset the search text.
16
18
  */
17
19
  export const useSearchBox = (options, enableSearchOptions, onSearchOptions) => {
18
20
  const [searchText, setSearchText] = useState('');
19
21
  const debounceRef = useRef(null);
20
22
  const previousSearchTextRef = useRef('');
23
+ // Keep the latest handler in a ref so that a new handler identity (e.g. an
24
+ // inline function re-created on parent re-render) does not re-run the
25
+ // debounce effect and cancel a pending call.
26
+ const onSearchOptionsRef = useLatest(onSearchOptions);
21
27
  const filteredOptions = useMemo(() => {
22
28
  // If not searchable or if onSearchOptions is provided, return all options without filtering
23
29
  if (!enableSearchOptions || onSearchOptions) {
@@ -49,8 +55,17 @@ export const useSearchBox = (options, enableSearchOptions, onSearchOptions) => {
49
55
  const handleSearchTextChange = useCallback((event) => {
50
56
  setSearchText(event.target.value);
51
57
  }, []);
58
+ const resetSearchText = useCallback(() => {
59
+ setSearchText('');
60
+ }, []);
61
+ // Track only the presence of the handler (not its identity) so that an
62
+ // undefined → defined transition re-runs the effect and dispatches the
63
+ // pending search text, while identity changes do not cancel pending calls.
64
+ // This preserves the behavior of the previous implementation, where the
65
+ // handler itself was an effect dependency and the transition dispatched.
66
+ const hasSearchHandler = Boolean(onSearchOptions);
52
67
  useEffect(() => {
53
- if (!enableSearchOptions || !onSearchOptions)
68
+ if (!enableSearchOptions || !hasSearchHandler)
54
69
  return;
55
70
  // Skip if searchText hasn't changed (prevents initial mount and unnecessary calls)
56
71
  if (searchText === previousSearchTextRef.current) {
@@ -61,14 +76,14 @@ export const useSearchBox = (options, enableSearchOptions, onSearchOptions) => {
61
76
  clearTimeout(debounceRef.current);
62
77
  }
63
78
  debounceRef.current = setTimeout(() => {
64
- onSearchOptions(searchText);
79
+ onSearchOptionsRef.current?.(searchText);
65
80
  }, 300);
66
81
  return () => {
67
82
  if (debounceRef.current) {
68
83
  clearTimeout(debounceRef.current);
69
84
  }
70
85
  };
71
- }, [searchText, onSearchOptions, enableSearchOptions]);
86
+ }, [searchText, enableSearchOptions, hasSearchHandler, onSearchOptionsRef]);
72
87
  return {
73
88
  /**
74
89
  * Handler for search input change events.
@@ -82,5 +97,9 @@ export const useSearchBox = (options, enableSearchOptions, onSearchOptions) => {
82
97
  * Current search text value.
83
98
  */
84
99
  searchText,
100
+ /**
101
+ * Resets the search text to an empty string.
102
+ */
103
+ resetSearchText,
85
104
  };
86
105
  };
@@ -195,6 +195,20 @@ export const SplitView = forwardRef(({ className, leftPanelSlot, rightPanelSlot,
195
195
  isDragging,
196
196
  dividerPropsStyle: dividerProps?.style,
197
197
  });
198
+ // `dividerProps` is spread after the built-in attributes so that consumers can override ARIA
199
+ // attributes (e.g. aria-label, aria-valuenow). These four entries must be excluded from that
200
+ // spread and re-applied afterwards, otherwise they would silently replace internal wiring:
201
+ // the styling classes/styles and the resize event handlers.
202
+ const { className: dividerClassName, style: _dividerPropsStyle, onPointerDown: dividerOnPointerDown, onKeyDown: dividerOnKeyDown, ...restDividerProps } = dividerProps ?? {};
203
+ // Run the internal resize handler first, then forward the event to the consumer's handler.
204
+ const handleDividerPointerDown = (event) => {
205
+ handleMouseDown(event);
206
+ dividerOnPointerDown?.(event);
207
+ };
208
+ const handleDividerKeyDown = (event) => {
209
+ handleKeyDown(event);
210
+ dividerOnKeyDown?.(event);
211
+ };
198
212
  // Apply controlled panel style to the correct panel based on targetPanel.
199
213
  // When a panel is the main panel (not controlled), flex: 1 ensures it fills remaining space.
200
214
  const computedLeftPanelStyle = resolvedTargetPanel === 'left' ? controlledPanelOuterStyle : leftPanelProps?.style;
@@ -209,7 +223,7 @@ export const SplitView = forwardRef(({ className, leftPanelSlot, rightPanelSlot,
209
223
  ref.current = node;
210
224
  }
211
225
  containerRef.current = node;
212
- }, className: cx(classes.root, 'mfui-SplitView__root', className), ...props, children: [resolvedTargetPanel === 'left' ? (shouldRenderControlledPanel || isSlideOutAnimating ? (_jsx("div", { ...leftPanelProps, className: cx(classes.leftPanel, 'mfui-SplitView__leftPanel', leftPanelProps?.className), style: computedLeftPanelStyle, children: _jsx("div", { style: controlledPanelInnerStyle, children: enableAutoUnmount ? (leftPanelSlot ?? lastRightPanelRef.current) : leftPanelSlot }) })) : null) : (_jsx("div", { ...leftPanelProps, className: cx(classes.leftPanel, 'mfui-SplitView__leftPanel', leftPanelProps?.className), style: computedLeftPanelStyle, children: leftPanelSlot })), isRightPanelVisible || isSlideOutAnimating ? (_jsx("div", { className: cx(classes.divider, 'mfui-SplitView__divider', dividerProps?.className), role: "separator", "aria-orientation": "vertical", "aria-label": dividerProps?.['aria-label'] ?? '左右のパネルを調整', "aria-valuenow": currentControlledPanelSize ?? initialPanelSizeNumber, "aria-valuemin": minPanelSizeNumber, "aria-valuemax": maxPanelSizeNumber,
226
+ }, className: cx(classes.root, 'mfui-SplitView__root', className), ...props, children: [resolvedTargetPanel === 'left' ? (shouldRenderControlledPanel || isSlideOutAnimating ? (_jsx("div", { ...leftPanelProps, className: cx(classes.leftPanel, 'mfui-SplitView__leftPanel', leftPanelProps?.className), style: computedLeftPanelStyle, children: _jsx("div", { style: controlledPanelInnerStyle, children: enableAutoUnmount ? (leftPanelSlot ?? lastRightPanelRef.current) : leftPanelSlot }) })) : null) : (_jsx("div", { ...leftPanelProps, className: cx(classes.leftPanel, 'mfui-SplitView__leftPanel', leftPanelProps?.className), style: computedLeftPanelStyle, children: leftPanelSlot })), isRightPanelVisible || isSlideOutAnimating ? (_jsx("div", { role: "separator", "aria-orientation": "vertical", "aria-label": dividerProps?.['aria-label'] ?? '左右のパネルを調整', "aria-valuenow": currentControlledPanelSize ?? initialPanelSizeNumber, "aria-valuemin": minPanelSizeNumber, "aria-valuemax": maxPanelSizeNumber,
213
227
  // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
214
- tabIndex: 0, style: dividerStyle, onPointerDown: handleMouseDown, onKeyDown: handleKeyDown, ...dividerProps })) : null, resolvedTargetPanel === 'right' ? (shouldRenderControlledPanel || isSlideOutAnimating ? (_jsx("div", { ...rightPanelProps, className: cx(classes.rightPanel, 'mfui-SplitView__rightPanel', rightPanelProps?.className), style: computedRightPanelStyle, children: _jsx("div", { style: controlledPanelInnerStyle, children: enableAutoUnmount ? (rightPanelSlot ?? lastRightPanelRef.current) : rightPanelSlot }) })) : null) : (_jsx("div", { ...rightPanelProps, className: cx(classes.rightPanel, 'mfui-SplitView__rightPanel', rightPanelProps?.className), style: computedRightPanelStyle, children: rightPanelSlot }))] }));
228
+ tabIndex: 0, ...restDividerProps, className: cx(classes.divider, 'mfui-SplitView__divider', dividerClassName), style: dividerStyle, onPointerDown: handleDividerPointerDown, onKeyDown: handleDividerKeyDown })) : null, resolvedTargetPanel === 'right' ? (shouldRenderControlledPanel || isSlideOutAnimating ? (_jsx("div", { ...rightPanelProps, className: cx(classes.rightPanel, 'mfui-SplitView__rightPanel', rightPanelProps?.className), style: computedRightPanelStyle, children: _jsx("div", { style: controlledPanelInnerStyle, children: enableAutoUnmount ? (rightPanelSlot ?? lastRightPanelRef.current) : rightPanelSlot }) })) : null) : (_jsx("div", { ...rightPanelProps, className: cx(classes.rightPanel, 'mfui-SplitView__rightPanel', rightPanelProps?.className), style: computedRightPanelStyle, children: rightPanelSlot }))] }));
215
229
  });
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  /**
3
3
  * The general-purpose TextBox component.
4
- * This component switches the variants of looks and behaviors depends on the props: size, invalid, disabled, enableClearButton.
4
+ * This component switches the variants of looks and behaviors depends on the props: size, invalid, caution, disabled, enableClearButton.
5
5
  *
6
6
  * Also extends the props of `<input>` element
7
7
  *
@@ -11,6 +11,7 @@ export declare const TextBox: React.ForwardRefExoticComponent<{
11
11
  enableClearButton?: boolean;
12
12
  onClear?: import("..").IconButtonProps['onClick'];
13
13
  invalid?: import("../../styled-system/recipes").TextBoxSlotRecipeVariant['invalid'];
14
+ caution?: import("../../styled-system/recipes").TextBoxSlotRecipeVariant['caution'];
14
15
  textBoxSize?: import("../../styled-system/recipes").TextBoxSlotRecipeVariant['size'];
15
16
  disabled?: import("../../styled-system/recipes").TextBoxSlotRecipeVariant['disabled'];
16
17
  wrapperProps?: React.ComponentPropsWithRef<'div'>;
@@ -11,13 +11,13 @@ import { useInputHasValue } from '../utilities/state/useInputHasValue';
11
11
  import { useIsTouchDevice } from '../utilities/dom/useIsTouchDevice';
12
12
  /**
13
13
  * The general-purpose TextBox component.
14
- * This component switches the variants of looks and behaviors depends on the props: size, invalid, disabled, enableClearButton.
14
+ * This component switches the variants of looks and behaviors depends on the props: size, invalid, caution, disabled, enableClearButton.
15
15
  *
16
16
  * Also extends the props of `<input>` element
17
17
  *
18
18
  * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input
19
19
  */
20
- export const TextBox = forwardRef(({ enableClearButton = false, onClear, invalid = false, textBoxSize = 'medium', disabled = false, wrapperProps = {}, prefixSlot, suffixSlot, clearButtonProps, touchDeviceReadOnly = false, ...inputProps }, ref) => {
20
+ export const TextBox = forwardRef(({ enableClearButton = false, onClear, invalid = false, caution = false, textBoxSize = 'medium', disabled = false, wrapperProps = {}, prefixSlot, suffixSlot, clearButtonProps, touchDeviceReadOnly = false, ...inputProps }, ref) => {
21
21
  const isTouchDevice = useIsTouchDevice();
22
22
  const shouldUseTouchDeviceMode = isTouchDevice && touchDeviceReadOnly;
23
23
  const inputRef = useRef(null);
@@ -28,7 +28,8 @@ export const TextBox = forwardRef(({ enableClearButton = false, onClear, invalid
28
28
  onInput: inputProps.onInput,
29
29
  });
30
30
  const shouldShowClearButton = enableClearButton && hasValue;
31
- const classes = textBoxSlotRecipe({ invalid, disabled, size: textBoxSize });
31
+ // The error state takes precedence over the caution state
32
+ const classes = textBoxSlotRecipe({ invalid, caution: caution && !invalid, disabled, size: textBoxSize });
32
33
  const mergedInputRef = mergeRefs(ref, inputRef);
33
34
  const onClickClear = useCallback((event) => {
34
35
  if (!inputRef.current)