@moneyforward/mfui-components 3.36.1 → 3.37.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 (33) hide show
  1. package/dist/src/DataTable/DataTable.d.ts +4 -0
  2. package/dist/src/DataTable/DataTable.js +4 -0
  3. package/dist/src/DataTable/DataTableCell/DataTableCell.js +10 -2
  4. package/dist/src/DataTable/DataTableCell/DataTableCell.types.d.ts +32 -0
  5. package/dist/src/DataTable/DataTableHeaderCell/DataTableHeaderCell.js +5 -1
  6. package/dist/src/DataTable/DataTableRow/DataTableRow.types.d.ts +7 -0
  7. package/dist/src/MainNavigation/BaseMainNavigation.d.ts +1 -1
  8. package/dist/src/MainNavigation/BaseMainNavigation.js +23 -12
  9. package/dist/src/MainNavigation/MainNavigation.js +2 -2
  10. package/dist/src/MainNavigation/MainNavigation.types.d.ts +17 -1
  11. package/dist/src/MultipleSelectBox/MultipleSelectBox.js +54 -38
  12. package/dist/src/MultipleSelectBox/hooks/useVirtualizedMultipleSelectBoxOptions.d.ts +7 -6
  13. package/dist/src/MultipleSelectBox/hooks/useVirtualizedMultipleSelectBoxOptions.js +3 -3
  14. package/dist/src/SelectBox/SelectBox.js +67 -28
  15. package/dist/src/SelectBox/SelectBoxOption.d.ts +5 -0
  16. package/dist/src/SelectBox/SelectBoxOption.js +2 -2
  17. package/dist/src/SelectBox/SelectBoxOptionGroup.d.ts +9 -0
  18. package/dist/src/SelectBox/SelectBoxOptionGroup.js +6 -3
  19. package/dist/src/SelectBox/hooks/useVirtualizedOptions.d.ts +7 -6
  20. package/dist/src/SelectBox/hooks/useVirtualizedOptions.js +3 -3
  21. package/dist/src/shared/OptionGroupLabelDescriptions/OptionGroupLabelDescriptions.d.ts +37 -0
  22. package/dist/src/shared/OptionGroupLabelDescriptions/OptionGroupLabelDescriptions.js +32 -0
  23. package/dist/src/shared/OptionGroupLabelDescriptions/index.d.ts +1 -0
  24. package/dist/src/shared/OptionGroupLabelDescriptions/index.js +1 -0
  25. package/dist/src/utilities/dom/getGroupLabelId.d.ts +15 -0
  26. package/dist/src/utilities/dom/getGroupLabelId.js +15 -0
  27. package/dist/styled-system/recipes/base-main-navigation-slot-recipe.d.ts +1 -1
  28. package/dist/styled-system/recipes/base-main-navigation-slot-recipe.js +8 -0
  29. package/dist/styled-system/recipes/data-table-cell-slot-recipe.d.ts +1 -0
  30. package/dist/styled-system/recipes/data-table-cell-slot-recipe.js +4 -0
  31. package/dist/styles.css +15 -0
  32. package/dist/tsconfig.build.tsbuildinfo +1 -1
  33. package/package.json +9 -9
@@ -78,6 +78,8 @@ export declare const DataTable: import("react").ForwardRefExoticComponent<Omit<D
78
78
  * This component should be used inside the `DataTable` component.
79
79
  *
80
80
  * It renders and extends the props of `td` element.
81
+ * When the `isRowHeader` prop is provided, it renders `th scope="row"` instead,
82
+ * which exposes the cell as the row header to assistive technology.
81
83
  *
82
84
  * When the `onClickCell` prop is provided, the cell is rendered as an interactive cell.
83
85
  * Otherwise, the cell is rendered as a default cell.
@@ -92,6 +94,8 @@ export declare const DataTable: import("react").ForwardRefExoticComponent<Omit<D
92
94
  *
93
95
  * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td
94
96
  *
97
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th
98
+ *
95
99
  * @see https://react.dev/reference/rules/rules-of-hooks
96
100
  *
97
101
  * @example
@@ -123,6 +123,8 @@ export const DataTable = Object.assign(Base, {
123
123
  * This component should be used inside the `DataTable` component.
124
124
  *
125
125
  * It renders and extends the props of `td` element.
126
+ * When the `isRowHeader` prop is provided, it renders `th scope="row"` instead,
127
+ * which exposes the cell as the row header to assistive technology.
126
128
  *
127
129
  * When the `onClickCell` prop is provided, the cell is rendered as an interactive cell.
128
130
  * Otherwise, the cell is rendered as a default cell.
@@ -137,6 +139,8 @@ export const DataTable = Object.assign(Base, {
137
139
  *
138
140
  * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td
139
141
  *
142
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th
143
+ *
140
144
  * @see https://react.dev/reference/rules/rules-of-hooks
141
145
  *
142
146
  * @example
@@ -10,7 +10,7 @@ import { cx } from '../../../styled-system/css';
10
10
  import { useFixedColumns } from '../../utilities/dom/useFixedColumns';
11
11
  import { handleCheckboxCellClick } from '../utils/handleCheckbox';
12
12
  import { mergeRefs } from '../../utilities/dom/mergeRefs';
13
- export const DataTableCell = forwardRef(({ onClickCell, children, type = 'text', className, isRowHighlighted, disabled = false, depth = 0, columnIndex, disclosureIconProps: nestingIconProps, style, ...props }, ref) => {
13
+ export const DataTableCell = forwardRef(({ onClickCell, children, type = 'text', className, isRowHighlighted, isRowHeader = false, disabled = false, depth = 0, columnIndex, disclosureIconProps: nestingIconProps, style, ...props }, ref) => {
14
14
  const { size, leftFixedColumnIndex, rightFixedColumnIndex, loading, enableRowSelection, showVerticalBorder, layout, } = useDataTableContext();
15
15
  const { cellRef, isFixedColumn, isEdgeFixedColumn, fixedPositionStyles } = useFixedColumns({
16
16
  enableRowSelection,
@@ -27,6 +27,11 @@ export const DataTableCell = forwardRef(({ onClickCell, children, type = 'text',
27
27
  isCheckboxCell: enableRowSelection && columnIndex === 0,
28
28
  verticalBorder: showVerticalBorder,
29
29
  layout,
30
+ // Passing `false` would emit a class for the unused `false` variant value, changing the
31
+ // class list of every existing cell. `undefined` keeps normal cells byte identical.
32
+ // The variants above deliberately keep passing `false`: retrofitting the same guard would
33
+ // itself change the class list of already shipped cells, which is what this guard avoids.
34
+ isRowHeader: isRowHeader || undefined,
30
35
  });
31
36
  const handleClick = (event) => {
32
37
  onClickCell?.(event);
@@ -48,7 +53,10 @@ export const DataTableCell = forwardRef(({ onClickCell, children, type = 'text',
48
53
  return (_jsx("div", { className: cx(classes.defaultCell, 'mfui-DataTableCell__defaultCell',
49
54
  /* TODO: Remove in v5.0.0 */ 'mfui-DataGridCell__defaultCell'), children: _jsx(CellContent, { depth: depth, disclosureIconProps: nestingIconProps, classes: classes, children: children }) }));
50
55
  })();
51
- return (_jsx("td", { ref: mergeRefs(ref, cellRef), className: cx(classes.root, 'mfui-DataTableCell__root',
56
+ // A row header identifies its row, so it must be a `th` for assistive technology.
57
+ // `scope` is only interpreted on `th`, which is why it cannot be passed to a plain cell.
58
+ const Tag = isRowHeader ? 'th' : 'td';
59
+ return (_jsx(Tag, { ref: mergeRefs(ref, cellRef), scope: isRowHeader ? 'row' : undefined, className: cx(classes.root, 'mfui-DataTableCell__root',
52
60
  /* TODO: Remove in v5.0.0 */ 'mfui-DataGridCell__root', className), style: { ...style, ...fixedPositionStyles, zIndex: isFixedColumn ? 1 : undefined }, ...props, children: cellContent }));
53
61
  });
54
62
  DataTableCell.displayName = 'DataTableCell';
@@ -34,6 +34,38 @@ export type DataTableCellProps = {
34
34
  * @default undefined
35
35
  */
36
36
  onClickCell?: (event?: React.MouseEvent<HTMLButtonElement>) => void;
37
+ /**
38
+ * Whether the cell is the row header, which identifies the row it belongs to.
39
+ *
40
+ * If `true`, the cell is rendered as `<th scope="row">` instead of `<td>`.
41
+ * This lets assistive technology announce the identifying cell of the row
42
+ * alongside every other cell in that row, so screen reader users navigating
43
+ * a wide table cell-by-cell can still tell which row they are in
44
+ * (e.g. 'Amount: ¥125,000, invoice_2026.pdf').
45
+ *
46
+ * Unlike `isRowHighlighted`, **this prop is intended to be passed by you.**
47
+ *
48
+ * The appearance is identical to a normal cell.
49
+ *
50
+ * Use it only when a column actually identifies the row, such as a file name or an
51
+ * invoice number. Set it on at most one cell per row. When every column holds an
52
+ * equally weighted attribute, there is no row header and marking one only makes
53
+ * the announcement verbose. The checkbox cell rendered by `enableRowSelection` is
54
+ * never the row header, so the row header is the second cell in that case.
55
+ *
56
+ * @default false
57
+ *
58
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th
59
+ *
60
+ * @example
61
+ * ```tsx
62
+ * <DataTable.Row>
63
+ * <DataTable.Cell isRowHeader>invoice_2026.pdf</DataTable.Cell>
64
+ * <DataTable.Cell type="amount">¥125,000</DataTable.Cell>
65
+ * </DataTable.Row>
66
+ * ```
67
+ */
68
+ isRowHeader?: boolean;
37
69
  /**
38
70
  * **Do not pass this prop manually.**
39
71
  *
@@ -57,7 +57,11 @@ export const DataTableHeaderCell = forwardRef(({ children, type = 'text', sortBu
57
57
  // Actions are rendered as siblings of the sort button (never inside it)
58
58
  // to avoid nesting interactive elements.
59
59
  const cellContentWithActions = actions ? (_jsxs("div", { className: cx(classes.container, 'mfui-DataTableHeaderCell__container'), children: [cellContent, _jsx("div", { className: cx(classes.actionsContainer, 'mfui-DataTableHeaderCell__actionsContainer'), children: actions })] })) : (cellContent);
60
- return (_jsx("th", { ref: mergeRefs(ref, cellRef), className: cx(classes.root, 'mfui-DataTableHeaderCell__root',
60
+ return (_jsx("th", { ref: mergeRefs(ref, cellRef),
61
+ // `th` also appears in the body now that `DataTable.Cell` supports `isRowHeader`, so the
62
+ // scope is stated explicitly rather than left to the auto-scope algorithm, whose result
63
+ // is harder to predict with `colSpan` / `rowSpan` and fixed columns.
64
+ scope: "col", className: cx(classes.root, 'mfui-DataTableHeaderCell__root',
61
65
  /* TODO: Remove in v5.0.0 */ 'mfui-DataGridHeaderCell__root', className), "aria-sort": sortOrder, style: fixedPositionStyles, ...props, children: cellContentWithActions }));
62
66
  });
63
67
  DataTableHeaderCell.displayName = 'DataTableHeaderCell';
@@ -47,6 +47,13 @@ export type DataTableRowProps = {
47
47
  * This prop will be passed to the `onClick` and `onKeyDown` of the `tr` element.
48
48
  * When the element is clicked, the event is passed to the `onClickRow` prop, but event isn't passed when the Enter or Space key is pressed.
49
49
  *
50
+ * Note that this callback is NOT called when the click originates from an interactive
51
+ * descendant inside the row — e.g. a button, link, input (including `Checkbox`), select,
52
+ * label, or an element with an interactive ARIA role such as `checkbox` or `menuitem`.
53
+ * Handle those interactions on the descendant itself (e.g. the `Checkbox`'s `onChange`).
54
+ * Likewise, Enter or Space triggers this callback only while the row itself is focused,
55
+ * not while a focusable descendant is focused.
56
+ *
50
57
  * @default undefined
51
58
  */
52
59
  onClickRow?: (event: React.MouseEvent<HTMLTableRowElement> | React.KeyboardEvent<HTMLTableRowElement>) => void;
@@ -3,4 +3,4 @@ import { type MainNavigationProps } from './MainNavigation.types';
3
3
  * The component for the main navigation on the normal viewport.
4
4
  * This component must only be used in MainNavigation.tsx, do not export it.
5
5
  */
6
- export declare function BaseMainNavigation({ className, featureShortcut, navigationItems, enableCollapsible, width, customLinkComponent, disableResponsive, isCollapsed: controlledIsCollapsed, onCollapsedChange, collapseButtonProps, expandButtonProps, }: MainNavigationProps): import("react/jsx-runtime").JSX.Element;
6
+ export declare function BaseMainNavigation({ className, featureShortcut, navigationItems, enableCollapsible, nestedNavigationDisplay, width, customLinkComponent, disableResponsive, isCollapsed: controlledIsCollapsed, onCollapsedChange, collapseButtonProps, expandButtonProps, }: MainNavigationProps): import("react/jsx-runtime").JSX.Element;
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { DisclosureBasicCollapsed, DisclosureBasicExpanded, Lock, PaneExpansionToLeft, PaneExpansionToRight, } from '@moneyforward/mfui-icons-react';
3
+ import { DisclosureBasicCollapsed, DisclosureBasicExpanded, DisclosurePopoverCollapsed, DisclosurePopoverExpanded, Lock, PaneExpansionToLeft, PaneExpansionToRight, } from '@moneyforward/mfui-icons-react';
4
4
  import { useId, useState, useRef } from 'react';
5
5
  import { cx } from '../../styled-system/css';
6
6
  import { FocusIndicator } from '../FocusIndicator';
@@ -13,7 +13,7 @@ import { Popover } from '../Popover';
13
13
  * The component for the main navigation on the normal viewport.
14
14
  * This component must only be used in MainNavigation.tsx, do not export it.
15
15
  */
16
- export function BaseMainNavigation({ className, featureShortcut, navigationItems, enableCollapsible = false, width = 'normal', customLinkComponent, disableResponsive, isCollapsed: controlledIsCollapsed, onCollapsedChange, collapseButtonProps, expandButtonProps, }) {
16
+ export function BaseMainNavigation({ className, featureShortcut, navigationItems, enableCollapsible = false, nestedNavigationDisplay = 'accordion', width = 'normal', customLinkComponent, disableResponsive, isCollapsed: controlledIsCollapsed, onCollapsedChange, collapseButtonProps, expandButtonProps, }) {
17
17
  const [localIsCollapsed, setLocalIsCollapsed] = useState(controlledIsCollapsed ?? false);
18
18
  const [openPopoverIndex, setOpenPopoverIndex] = useState(null);
19
19
  const isControlled = controlledIsCollapsed !== undefined;
@@ -38,7 +38,7 @@ export function BaseMainNavigation({ className, featureShortcut, navigationItems
38
38
  return null;
39
39
  }
40
40
  if (navigation.children) {
41
- return (_jsx(NestedNavigationItemRenderer, { navigation: navigation, originalIndex: index, classes: classes, InternalLinkTag: InternalLinkTag, isCollapsed: isCollapsed, openPopoverIndex: openPopoverIndex, setOpenPopoverIndex: setOpenPopoverIndex }, index));
41
+ return (_jsx(NestedNavigationItemRenderer, { navigation: navigation, originalIndex: index, navId: navId, classes: classes, InternalLinkTag: InternalLinkTag, isCollapsed: isCollapsed, nestedNavigationDisplay: nestedNavigationDisplay, openPopoverIndex: openPopoverIndex, setOpenPopoverIndex: setOpenPopoverIndex }, index));
42
42
  }
43
43
  return (_jsx("li", { className: cx(classes.listItem, 'mfui-BaseMainNavigation__listItem'), children: _jsx(Tooltip, { className: cx(classes.listItemTooltip, 'mfui-BaseMainNavigation__listItemTooltip'), content: navigation.label, disabled: !isCollapsed, placement: "right", children: _jsx(FocusIndicator, { position: "inside", children: _jsx(NavigationLink, { tag: InternalLinkTag, isExternal: navigation.isExternal, href: navigation.href, className: cx(classes.listItemAnchor, 'mfui-BaseMainNavigation__listItemAnchor'), "aria-label": isCollapsed ? navigation.label : undefined, "aria-current": navigation.isCurrent ? 'page' : undefined, children: _jsxs("div", { className: cx(classes.labelGroup, 'mfui-BaseMainNavigation__labelGroup'), children: [_jsxs("div", { className: cx(classes.iconAndLabel, 'mfui-BaseMainNavigation__iconAndLabel'), children: [_jsx("div", { "aria-hidden": "true", className: cx(classes.listItemAnchorIcon, 'mfui-BaseMainNavigation__listItemAnchorIcon'), children: navigation.icon }), isCollapsed ? null : _jsx(Typography, { variant: "controlLabel", children: navigation.label })] }), navigation.locked ? (_jsx(Lock, { className: cx(classes.lockIcon, 'mfui-BaseMainNavigation__lockIcon'), "aria-label": navigation.lockIconProps?.['aria-label'] ?? 'ロックされています' })) : null, !isCollapsed && navigation.statusSlot && navigation.locked !== true
44
44
  ? navigation.statusSlot
@@ -47,7 +47,7 @@ export function BaseMainNavigation({ className, featureShortcut, navigationItems
47
47
  if (!('isGroupMenu' in navigation && navigation.isGroupMenu)) {
48
48
  return null;
49
49
  }
50
- return (_jsx(NestedNavigationItemRenderer, { navigation: navigation, originalIndex: index, classes: classes, InternalLinkTag: InternalLinkTag, isCollapsed: isCollapsed, openPopoverIndex: openPopoverIndex, setOpenPopoverIndex: setOpenPopoverIndex }, index));
50
+ return (_jsx(NestedNavigationItemRenderer, { navigation: navigation, originalIndex: index, navId: navId, classes: classes, InternalLinkTag: InternalLinkTag, isCollapsed: isCollapsed, nestedNavigationDisplay: nestedNavigationDisplay, openPopoverIndex: openPopoverIndex, setOpenPopoverIndex: setOpenPopoverIndex }, index));
51
51
  }) })), !isCollapsed &&
52
52
  navigationItems.map((navigation, index) => {
53
53
  const isGroupLabel = navigation.children && 'isGroupLabel' in navigation && navigation.isGroupLabel;
@@ -60,21 +60,23 @@ export function BaseMainNavigation({ className, featureShortcut, navigationItems
60
60
  }
61
61
  /**
62
62
  * The component for rendering a parent navigation item with children.
63
- * Handles both collapsed (popover) and expanded (details/summary) states.
63
+ * Handles both popover and details/summary display modes.
64
64
  * This component must only be used in BaseMainNavigation.tsx, do not export it.
65
65
  */
66
- function NestedNavigationItemRenderer({ navigation, originalIndex, classes, InternalLinkTag, isCollapsed, openPopoverIndex, setOpenPopoverIndex, }) {
67
- const popoverPortalTargetId = `nested-popover-portal-target-${String(originalIndex)}`;
66
+ function NestedNavigationItemRenderer({ navigation, originalIndex, navId, classes, InternalLinkTag, isCollapsed, nestedNavigationDisplay, openPopoverIndex, setOpenPopoverIndex, }) {
67
+ // Scope the ID to this navigation instance so multiple MainNavigation components
68
+ // on the same page (e.g. the Storybook docs page) don't portal into each other.
69
+ const popoverPortalTargetId = `${navId}-nested-popover-portal-target-${String(originalIndex)}`;
68
70
  const hasCurrentChildren = navigation.children.some((child) => child.isCurrent);
69
- return (_jsx("li", { className: cx(classes.listItem, 'mfui-BaseMainNavigation__listItem'), "data-mfui-popover-portal-target-id": popoverPortalTargetId, children: isCollapsed ? (_jsx(CollapsedNestedNavigationItem, { navigation: navigation, classes: classes, InternalLinkTag: InternalLinkTag, hasCurrentChildren: hasCurrentChildren, popoverPortalTargetId: popoverPortalTargetId, isOpen: openPopoverIndex === originalIndex, onOpenChange: (isOpen) => {
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) => {
70
72
  setOpenPopoverIndex(isOpen ? originalIndex : null);
71
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))) })] })) }));
72
74
  }
73
75
  /**
74
- * The component for displaying nested navigation in collapsed state with popover.
76
+ * The component for displaying nested navigation with a popover.
75
77
  * This component must only be used in BaseMainNavigation.tsx, do not export it.
76
78
  */
77
- function CollapsedNestedNavigationItem({ navigation, classes, InternalLinkTag, hasCurrentChildren, popoverPortalTargetId, isOpen, onOpenChange, }) {
79
+ function PopoverNestedNavigationItem({ navigation, classes, InternalLinkTag, isCollapsed, hasCurrentChildren, popoverPortalTargetId, isOpen, onOpenChange, }) {
78
80
  const popoverContentRef = useRef(null);
79
81
  const handleKeyDown = (event) => {
80
82
  if (event.key === 'Enter' || event.key === ' ') {
@@ -87,9 +89,18 @@ function CollapsedNestedNavigationItem({ navigation, classes, InternalLinkTag, h
87
89
  const handleClick = () => {
88
90
  onOpenChange(!isOpen);
89
91
  };
90
- return (_jsx(Popover, { enableAutoFocusOnPopover: false, enableAutoFocusOnTrigger: false, open: isOpen, targetDOMNode: document.querySelector(`[data-mfui-popover-portal-target-id="${popoverPortalTargetId}"]`), allowedPlacements: ['right-start', 'right', 'right-end'], contentProps: {
92
+ // The expanded trigger shows the label and the disclosure chevron inline,
93
+ // while the collapsed trigger is icon-only and exposes the label via aria-label and a tooltip.
94
+ const isExpanded = !isCollapsed;
95
+ const DisclosureIcon = isOpen ? DisclosurePopoverExpanded : DisclosurePopoverCollapsed;
96
+ const popoverContentId = `${popoverPortalTargetId}-content`;
97
+ return (_jsx(Popover, { enableAutoFocusOnPopover: false, enableAutoFocusOnTrigger: false, open: isOpen,
98
+ // `document` is unavailable during server-side prerendering.
99
+ targetDOMNode: typeof document === 'undefined'
100
+ ? undefined
101
+ : document.querySelector(`[data-mfui-popover-portal-target-id="${popoverPortalTargetId}"]`), allowedPlacements: ['right-start', 'right', 'right-end'], contentProps: {
91
102
  tabIndex: -1,
92
- }, renderTrigger: ({ setTriggerRef }) => (_jsx(Tooltip, { className: cx(classes.listItemTooltip, 'mfui-BaseMainNavigation__listItemTooltip'), content: navigation.label, disabled: isOpen, placement: "right", children: _jsx(FocusIndicator, { position: "inside", children: _jsx("button", { ref: setTriggerRef, type: "button", className: cx(classes.listItemAnchor, 'mfui-BaseMainNavigation__listItemAnchor'), "data-mfui-current": hasCurrentChildren ? 'page' : undefined, "aria-current": hasCurrentChildren ? true : undefined, "aria-label": navigation.label, onClick: handleClick, onKeyDown: handleKeyDown, children: _jsx("div", { className: cx(classes.labelGroup, 'mfui-BaseMainNavigation__labelGroup'), children: _jsx("div", { className: cx(classes.iconAndLabel, 'mfui-BaseMainNavigation__iconAndLabel'), children: _jsx("div", { "aria-hidden": "true", className: cx(classes.listItemAnchorIcon, 'mfui-BaseMainNavigation__listItemAnchorIcon'), children: navigation.icon }) }) }) }) }) })), renderContent: () => (_jsx("div", { ref: popoverContentRef, className: cx(classes.popoverChildrenList, 'mfui-BaseMainNavigation__popoverChildrenList'), tabIndex: -1, children: navigation.children.map((child, childIndex) => (_jsx("div", { 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, onClick: () => {
103
+ }, renderTrigger: ({ setTriggerRef }) => (_jsx(Tooltip, { className: cx(classes.listItemTooltip, 'mfui-BaseMainNavigation__listItemTooltip'), content: navigation.label, disabled: isExpanded || isOpen, placement: "right", children: _jsx(FocusIndicator, { position: "inside", children: _jsx("button", { ref: setTriggerRef, type: "button", className: cx(classes.listItemAnchor, classes.popoverTrigger, 'mfui-BaseMainNavigation__listItemAnchor', 'mfui-BaseMainNavigation__popoverTrigger'), "data-mfui-current": hasCurrentChildren ? 'page' : undefined, "aria-current": hasCurrentChildren ? true : undefined, "aria-expanded": isOpen, "aria-controls": isOpen ? popoverContentId : undefined, "aria-label": isExpanded ? undefined : navigation.label, onClick: handleClick, onKeyDown: handleKeyDown, children: _jsxs("div", { className: cx(classes.labelGroup, 'mfui-BaseMainNavigation__labelGroup'), children: [_jsxs("div", { className: cx(classes.iconAndLabel, 'mfui-BaseMainNavigation__iconAndLabel'), children: [_jsx("div", { "aria-hidden": "true", className: cx(classes.listItemAnchorIcon, 'mfui-BaseMainNavigation__listItemAnchorIcon'), children: navigation.icon }), isExpanded ? _jsx(Typography, { variant: "controlLabel", children: navigation.label }) : null] }), isExpanded ? (_jsx(DisclosureIcon, { "aria-hidden": "true", className: cx(classes.popoverTriggerDisclosureIcon, 'mfui-BaseMainNavigation__popoverTriggerDisclosureIcon') })) : null] }) }) }) })), renderContent: () => (_jsx("div", { id: popoverContentId, ref: popoverContentRef, className: cx(classes.popoverChildrenList, 'mfui-BaseMainNavigation__popoverChildrenList'), tabIndex: -1, children: navigation.children.map((child, childIndex) => (_jsx("div", { 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, onClick: () => {
93
104
  onOpenChange(false);
94
105
  }, children: _jsx(Typography, { variant: "controlLabel", children: child.label }) }) }) }, childIndex))) })), onOpenStateChanged: onOpenChange }));
95
106
  }
@@ -9,7 +9,7 @@ import { NarrowViewportMainNavigation } from './NarrowViewportMainNavigation';
9
9
  * When the viewport is narrow, the navigation will be displayed as a drawer.
10
10
  * When the viewport is not narrow, the navigation will be displayed as a sidebar.
11
11
  */
12
- export const MainNavigation = forwardRef(({ className, featureShortcut, navigationItems, disableResponsive = false, width = 'normal', customLinkComponent, enableCollapsible, isCollapsed, onCollapsedChange, collapseButtonProps, expandButtonProps, }, ref) => {
12
+ export const MainNavigation = forwardRef(({ className, featureShortcut, navigationItems, disableResponsive = false, width = 'normal', customLinkComponent, enableCollapsible, nestedNavigationDisplay, isCollapsed, onCollapsedChange, collapseButtonProps, expandButtonProps, }, ref) => {
13
13
  const commonProps = {
14
14
  className,
15
15
  featureShortcut,
@@ -17,5 +17,5 @@ export const MainNavigation = forwardRef(({ className, featureShortcut, navigati
17
17
  width,
18
18
  customLinkComponent,
19
19
  };
20
- return (_jsxs(_Fragment, { children: [_jsx(BaseMainNavigation, { ...commonProps, enableCollapsible: enableCollapsible, disableResponsive: disableResponsive, isCollapsed: isCollapsed, onCollapsedChange: onCollapsedChange, collapseButtonProps: collapseButtonProps, expandButtonProps: expandButtonProps }), !disableResponsive && _jsx(NarrowViewportMainNavigation, { ...commonProps, ref: ref })] }));
20
+ return (_jsxs(_Fragment, { children: [_jsx(BaseMainNavigation, { ...commonProps, enableCollapsible: enableCollapsible, nestedNavigationDisplay: nestedNavigationDisplay, disableResponsive: disableResponsive, isCollapsed: isCollapsed, onCollapsedChange: onCollapsedChange, collapseButtonProps: collapseButtonProps, expandButtonProps: expandButtonProps }), !disableResponsive && _jsx(NarrowViewportMainNavigation, { ...commonProps, ref: ref })] }));
21
21
  });
@@ -85,6 +85,8 @@ type ParentNavigationItem = {
85
85
  * Whether the nested navigation is open by default.
86
86
  * When set to true, the parent navigation item is expanded on initial render,
87
87
  * regardless of whether any child has `isCurrent: true`.
88
+ * This property has no effect when the nested navigation is shown in a popover —
89
+ * i.e. when `MainNavigation` has `nestedNavigationDisplay="popover"`, or while the navigation is collapsed.
88
90
  *
89
91
  * @default false
90
92
  */
@@ -193,6 +195,20 @@ export type MainNavigationProps = {
193
195
  * @default false
194
196
  */
195
197
  enableCollapsible?: boolean;
198
+ /**
199
+ * How nested navigation items display their children in the normal viewport navigation.
200
+ *
201
+ * - `'accordion'`: Children expand below the parent item while the navigation is expanded.
202
+ * When the navigation is collapsed, children are always shown in a popover.
203
+ * - `'popover'`: Children are shown in a popover to the right in both expanded and collapsed states.
204
+ *
205
+ * Prefer `'accordion'` while the navigation is expanded unless the design requires a popover.
206
+ * When set to `'popover'`, `isOpenByDefault` on navigation items has no effect.
207
+ * The narrow viewport navigation always displays nested items as an accordion regardless of this property.
208
+ *
209
+ * @default 'accordion'
210
+ */
211
+ nestedNavigationDisplay?: 'accordion' | 'popover';
196
212
  /**
197
213
  * The width of the navigation.
198
214
  *
@@ -265,5 +281,5 @@ export type MainNavigationRef = {
265
281
  openMainNavigation: () => void;
266
282
  closeMainNavigation: () => void;
267
283
  };
268
- export type NarrowViewportMainNavigationProps = Omit<MainNavigationProps, 'enableCollapsible'>;
284
+ export type NarrowViewportMainNavigationProps = Omit<MainNavigationProps, 'enableCollapsible' | 'nestedNavigationDisplay'>;
269
285
  export {};
@@ -24,6 +24,8 @@ import { useApplyControls } from './hooks/useApplyControls';
24
24
  import { useCreateOption } from './hooks/useCreateOption';
25
25
  import { cx } from '../../styled-system/css';
26
26
  import { flattenOptions } from './utils/flattenOptions';
27
+ import { getGroupLabelId } from '../utilities/dom/getGroupLabelId';
28
+ import { OptionGroupLabelDescriptions } from '../shared/OptionGroupLabelDescriptions';
27
29
  import { isSelectableOption, isOptionDisabled, isOptionSelected } from './utils/isSelectableOption';
28
30
  import { useVirtualizedMultipleSelectBoxOptions } from './hooks/useVirtualizedMultipleSelectBoxOptions';
29
31
  import { useInfiniteScroll } from '../utilities/dom/useInfiniteScroll';
@@ -80,11 +82,50 @@ placeholder, emptyMessage, disabled, invalid, required, caution, targetDOMNode,
80
82
  });
81
83
  // Determine if virtualization should be enabled
82
84
  const shouldVirtualize = enableVirtualization && !loading && flattenedOptions.length > 0;
85
+ // The options reference their group label with `aria-describedby`. A virtualized option list can
86
+ // unmount the visible label row, so there the referenced element is rendered outside the list
87
+ // instead - see `OptionGroupLabelDescriptions`. Only one of the two ever carries the id.
88
+ const rendersHiddenGroupLabels = shouldVirtualize;
89
+ const optionGroupLabels = useMemo(() =>
90
+ // Same check as `isSelectableOption`, inlined because its type guard narrows the negative branch
91
+ // to `never` here. A group label is any entry without a value, including one that carries no
92
+ // nested options - `flattenOptions` keeps those.
93
+ filteredOptions.flatMap((option, optionIndex) => 'value' in option ? [] : [{ id: getGroupLabelId(listBoxId, optionIndex), label: option.label }]), [filteredOptions, listBoxId]);
94
+ // Create the row structure for virtualization: one row per entry of the already flat option list,
95
+ // group labels included. The nested options of a group entry are not expanded here - `filteredOptions`
96
+ // is the output of `flattenOptions`, so they are already present as their own entries.
97
+ const createVirtualizedItems = useCallback(() => {
98
+ if (!shouldVirtualize)
99
+ return [];
100
+ const items = [];
101
+ // An option is described by the group label it is rendered under, which is the last group label
102
+ // rendered before it.
103
+ let currentGroupLabelId;
104
+ filteredOptions.forEach((option, optionIndex) => {
105
+ if ('options' in option && Array.isArray(option.options)) {
106
+ currentGroupLabelId = getGroupLabelId(listBoxId, optionIndex);
107
+ items.push({
108
+ type: 'group',
109
+ option,
110
+ groupLabelId: currentGroupLabelId,
111
+ });
112
+ }
113
+ else {
114
+ items.push({
115
+ type: 'option',
116
+ option,
117
+ groupLabelId: currentGroupLabelId,
118
+ });
119
+ }
120
+ });
121
+ return items;
122
+ }, [filteredOptions, shouldVirtualize, listBoxId]);
123
+ const virtualizedItems = useMemo(() => createVirtualizedItems(), [createVirtualizedItems]);
83
124
  // Initialize virtualization
84
125
  const { virtualizer, virtualItems, totalSize, isVirtualized } = useVirtualizedMultipleSelectBoxOptions({
85
126
  enabled: shouldVirtualize,
86
127
  parentRef: scrollWrapperRef,
87
- flatOptions: filteredOptions.filter((option) => 'value' in option),
128
+ rowCount: virtualizedItems.length,
88
129
  estimateSize: virtualizationOptions?.estimateSize,
89
130
  overscan: virtualizationOptions?.overscan,
90
131
  });
@@ -152,37 +193,6 @@ placeholder, emptyMessage, disabled, invalid, required, caution, targetDOMNode,
152
193
  onSelectOption: handleSelectOption,
153
194
  onClosePanel: closeOptionPanel,
154
195
  });
155
- // Create a flat structure for virtualization that includes both group headers and options
156
- const createVirtualizedItems = useCallback(() => {
157
- if (!shouldVirtualize)
158
- return [];
159
- const items = [];
160
- filteredOptions.forEach((option) => {
161
- if ('options' in option && Array.isArray(option.options)) {
162
- // Add group header
163
- items.push({
164
- type: 'group',
165
- option,
166
- });
167
- // Add group options
168
- option.options.forEach((nestedOption) => {
169
- items.push({
170
- type: 'option',
171
- option: nestedOption,
172
- });
173
- });
174
- }
175
- else {
176
- // Regular option
177
- items.push({
178
- type: 'option',
179
- option,
180
- });
181
- }
182
- });
183
- return items;
184
- }, [filteredOptions, shouldVirtualize]);
185
- const virtualizedItems = useMemo(() => createVirtualizedItems(), [createVirtualizedItems]);
186
196
  // Render virtualized items - extracted for better maintainability
187
197
  const renderVirtualizedItems = useCallback(() => {
188
198
  let focusableIndex = 0;
@@ -190,7 +200,7 @@ placeholder, emptyMessage, disabled, invalid, required, caution, targetDOMNode,
190
200
  const virtualizedItem = virtualizedItems[virtualItem.index];
191
201
  if (!virtualizedItem)
192
202
  return null;
193
- const { type, option } = virtualizedItem;
203
+ const { type, option, groupLabelId } = virtualizedItem;
194
204
  if (type === 'group') {
195
205
  return (_jsx("div", { ref: virtualizer?.measureElement, "data-index": virtualItem.index, style: {
196
206
  position: 'absolute',
@@ -198,7 +208,7 @@ placeholder, emptyMessage, disabled, invalid, required, caution, targetDOMNode,
198
208
  left: 0,
199
209
  width: '100%',
200
210
  transform: `translateY(${String(virtualItem.start)}px)`,
201
- }, children: _jsx("li", { className: cx(classes.listItem, 'mfui-MultipleSelectBox__listItem'), "data-mfui-group-label": "true", "aria-hidden": "true", children: _jsx(Typography, { variant: "label", children: option.label }) }) }, virtualItem.key));
211
+ }, children: _jsx("li", { role: "presentation", className: cx(classes.listItem, 'mfui-MultipleSelectBox__listItem'), "data-mfui-group-label": "true", children: _jsx(Typography, { variant: "label", children: option.label }) }) }, virtualItem.key));
202
212
  }
203
213
  if (isSelectableOption(option) && option.value != null) {
204
214
  const { value, label } = option;
@@ -220,7 +230,7 @@ placeholder, emptyMessage, disabled, invalid, required, caution, targetDOMNode,
220
230
  left: 0,
221
231
  width: '100%',
222
232
  transform: `translateY(${String(virtualItem.start)}px)`,
223
- }, children: _jsx(FocusIndicator, { position: "inside", children: _jsx("li", { role: "option", "data-focusable": !isDisabled, "data-value": value, className: cx(classes.listItem, 'mfui-MultipleSelectBox__listItem'), "aria-selected": isSelected, "aria-disabled": isDisabled, tabIndex: isDisabled ? -1 : currentFocusableIndex === tabbableOptionIndex ? 0 : -1, onFocus: () => {
233
+ }, children: _jsx(FocusIndicator, { position: "inside", children: _jsx("li", { role: "option", "data-focusable": !isDisabled, "data-value": value, className: cx(classes.listItem, 'mfui-MultipleSelectBox__listItem'), "aria-selected": isSelected, "aria-disabled": isDisabled, "aria-describedby": groupLabelId, tabIndex: isDisabled ? -1 : currentFocusableIndex === tabbableOptionIndex ? 0 : -1, onFocus: () => {
224
234
  if (!isDisabled)
225
235
  handleOptionFocus(currentFocusableIndex);
226
236
  }, onKeyDown: createKeyDownHandler(option), onClick: () => {
@@ -251,6 +261,9 @@ placeholder, emptyMessage, disabled, invalid, required, caution, targetDOMNode,
251
261
  const renderNonVirtualizedItems = useCallback(() => {
252
262
  // Map from filteredOptions index to focusableOptions index
253
263
  let focusableIndex = 0;
264
+ // An option is described by the group label it is rendered under, which is the last group label
265
+ // rendered before it.
266
+ let currentGroupLabelId;
254
267
  return filteredOptions.map((option, optionIndex) => {
255
268
  if (isSelectableOption(option)) {
256
269
  // If selectable but value is null/undefined, skip rendering
@@ -268,7 +281,7 @@ placeholder, emptyMessage, disabled, invalid, required, caution, targetDOMNode,
268
281
  currentFocusableIndex = focusableIndex;
269
282
  focusableIndex += 1;
270
283
  }
271
- return (_jsx(FocusIndicator, { position: "inside", children: _jsx("li", { role: "option", "data-focusable": !isDisabled, className: cx(classes.listItem, 'mfui-MultipleSelectBox__listItem'), "aria-selected": isSelected, "aria-disabled": isDisabled, tabIndex: isDisabled ? -1 : currentFocusableIndex === tabbableOptionIndex ? 0 : -1, onFocus: () => {
284
+ return (_jsx(FocusIndicator, { position: "inside", children: _jsx("li", { role: "option", "data-focusable": !isDisabled, className: cx(classes.listItem, 'mfui-MultipleSelectBox__listItem'), "aria-selected": isSelected, "aria-disabled": isDisabled, "aria-describedby": currentGroupLabelId, tabIndex: isDisabled ? -1 : currentFocusableIndex === tabbableOptionIndex ? 0 : -1, onFocus: () => {
272
285
  if (!isDisabled)
273
286
  handleOptionFocus(currentFocusableIndex);
274
287
  }, onKeyDown: createKeyDownHandler(option), onClick: () => {
@@ -280,7 +293,8 @@ placeholder, emptyMessage, disabled, invalid, required, caution, targetDOMNode,
280
293
  }
281
294
  // Only render group label if truly a group label (no value property at all)
282
295
  const { label } = option;
283
- return (_jsx("li", { className: classes.listItem, "data-mfui-group-label": "true", "aria-hidden": "true", children: _jsx(Typography, { variant: "label", children: label }) }, `group-label-${String(optionIndex)}`));
296
+ currentGroupLabelId = getGroupLabelId(listBoxId, optionIndex);
297
+ return (_jsx("li", { id: rendersHiddenGroupLabels ? undefined : currentGroupLabelId, role: "presentation", className: classes.listItem, "data-mfui-group-label": "true", children: _jsx(Typography, { variant: "label", children: label }) }, `group-label-${String(optionIndex)}`));
284
298
  });
285
299
  }, [
286
300
  filteredOptions,
@@ -293,6 +307,8 @@ placeholder, emptyMessage, disabled, invalid, required, caution, targetDOMNode,
293
307
  createKeyDownHandler,
294
308
  handleOptionFocus,
295
309
  tabbableOptionIndex,
310
+ listBoxId,
311
+ rendersHiddenGroupLabels,
296
312
  ]);
297
313
  // Render infinite scroll error message with retry button
298
314
  const renderInfiniteScrollError = useCallback((as = 'li') => {
@@ -345,7 +361,7 @@ placeholder, emptyMessage, disabled, invalid, required, caution, targetDOMNode,
345
361
  ? resolvedNotFoundMessage
346
362
  : emptyMessage }) }, "empty"),
347
363
  !isVirtualized && renderInfiniteScrollError(),
348
- ].filter(Boolean) }), isVirtualized ? renderInfiniteScrollLoading() : null, isVirtualized ? renderInfiniteScrollError('div') : null] }));
364
+ ].filter(Boolean) }), isVirtualized ? renderInfiniteScrollLoading() : null, isVirtualized ? renderInfiniteScrollError('div') : null, _jsx(OptionGroupLabelDescriptions, { groups: rendersHiddenGroupLabels ? optionGroupLabels : [] })] }));
349
365
  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()) ??
350
366
  `${searchText.trim()}を追加できませんでした。` }) })) : null] })) : null;
351
367
  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,
@@ -1,7 +1,7 @@
1
1
  import { useVirtualizer } from '@tanstack/react-virtual';
2
2
  import { type RefObject } from 'react';
3
- import { type FlatMultipleSelectBoxOption, type VirtualOptionTypes } from '../MultipleSelectBox.types';
4
- export type UseVirtualizedMultipleSelectBoxOptionsParams<T extends string | number = string, AdditionalProps extends Record<string, unknown> = Record<string, never>> = {
3
+ import { type VirtualOptionTypes } from '../MultipleSelectBox.types';
4
+ export type UseVirtualizedMultipleSelectBoxOptionsParams = {
5
5
  /**
6
6
  * Enable virtualization for the option list
7
7
  */
@@ -11,9 +11,10 @@ export type UseVirtualizedMultipleSelectBoxOptionsParams<T extends string | numb
11
11
  */
12
12
  parentRef: RefObject<HTMLElement | null>;
13
13
  /**
14
- * Flat array of all options
14
+ * Number of rows the option list renders. Group labels are rows too, so this is not the number of
15
+ * options: sizing the scroll area by the option count alone makes the last rows unreachable.
15
16
  */
16
- flatOptions: FlatMultipleSelectBoxOption<T, AdditionalProps>[];
17
+ rowCount: number;
17
18
  } & Partial<VirtualOptionTypes>;
18
19
  export type UseVirtualizedMultipleSelectBoxOptionsReturn = {
19
20
  /**
@@ -54,10 +55,10 @@ export type UseVirtualizedMultipleSelectBoxOptionsReturn = {
54
55
  * const { virtualItems, totalSize, scrollToIndex, isVirtualized } = useVirtualizedMultipleSelectBoxOptions({
55
56
  * enabled: true,
56
57
  * parentRef: optionPanelRef,
57
- * flatOptions,
58
+ * rowCount: virtualizedItems.length,
58
59
  * estimateSize: () => 36, // or (index) => index < 10 ? 40 : 36 for dynamic sizing
59
60
  * overscan: 5,
60
61
  * });
61
62
  * ```
62
63
  */
63
- export declare function useVirtualizedMultipleSelectBoxOptions<T extends string | number = string, AdditionalProps extends Record<string, unknown> = Record<string, never>>({ enabled, parentRef, flatOptions, estimateSize, overscan, }: UseVirtualizedMultipleSelectBoxOptionsParams<T, AdditionalProps>): UseVirtualizedMultipleSelectBoxOptionsReturn;
64
+ export declare function useVirtualizedMultipleSelectBoxOptions({ enabled, parentRef, rowCount, estimateSize, overscan, }: UseVirtualizedMultipleSelectBoxOptionsParams): UseVirtualizedMultipleSelectBoxOptionsReturn;
@@ -13,17 +13,17 @@ import { useCallback } from 'react';
13
13
  * const { virtualItems, totalSize, scrollToIndex, isVirtualized } = useVirtualizedMultipleSelectBoxOptions({
14
14
  * enabled: true,
15
15
  * parentRef: optionPanelRef,
16
- * flatOptions,
16
+ * rowCount: virtualizedItems.length,
17
17
  * estimateSize: () => 36, // or (index) => index < 10 ? 40 : 36 for dynamic sizing
18
18
  * overscan: 5,
19
19
  * });
20
20
  * ```
21
21
  */
22
- export function useVirtualizedMultipleSelectBoxOptions({ enabled, parentRef, flatOptions, estimateSize = () => 36, overscan = 5, }) {
22
+ export function useVirtualizedMultipleSelectBoxOptions({ enabled, parentRef, rowCount, estimateSize = () => 36, overscan = 5, }) {
23
23
  const shouldVirtualize = enabled;
24
24
  // Always call useVirtualizer to maintain hook order, but with 0 count when disabled
25
25
  const virtualizer = useVirtualizer({
26
- count: shouldVirtualize ? flatOptions.length : 0,
26
+ count: shouldVirtualize ? rowCount : 0,
27
27
  getScrollElement: () =>
28
28
  // The parentRef itself is the scrollable element (option panel div)
29
29
  parentRef.current ?? null,