@itwin/itwinui-react
Version:
A react component library for iTwinUI
136 lines (135 loc) • 3.98 kB
JavaScript
import * as React from 'react';
import cx from 'classnames';
import {
useSafeContext,
useMergedRefs,
Portal,
Box,
useVirtualScroll,
ShadowRoot,
useLayoutEffect,
} from '../../utils/index.js';
import { ComboBoxStateContext, ComboBoxRefsContext } from './helpers.js';
import { List } from '../List/List.js';
let VirtualizedComboBoxMenu = (props) => {
let { children, ...rest } = props;
let { filteredOptions, getMenuItem, focusedIndex } =
useSafeContext(ComboBoxStateContext);
let { menuRef } = useSafeContext(ComboBoxRefsContext);
let mostlySubLabeled = React.useMemo(() => {
let numberOfSubLabels = 0;
for (let i = 0; i < Math.min(5, filteredOptions.length); i++)
if (filteredOptions[i].sublabel) numberOfSubLabels++;
return numberOfSubLabels >= Math.min(3, filteredOptions.length);
}, [filteredOptions]);
let focusedVisibleIndex = React.useMemo(() => {
let currentElement = menuRef.current?.querySelector(
`[data-iui-index="${focusedIndex}"]`,
);
if (!currentElement) return focusedIndex;
return Number(
currentElement.getAttribute('data-iui-filtered-index') ?? focusedIndex,
);
}, [focusedIndex, menuRef]);
let { virtualizer, css: virtualizerCss } = useVirtualScroll({
count: filteredOptions.length || 1,
getScrollElement: () => menuRef.current,
estimateSize: () => (mostlySubLabeled ? 48 : 36),
gap: -1,
});
useLayoutEffect(() => {
virtualizer.scrollToIndex(focusedVisibleIndex);
}, [virtualizer, focusedVisibleIndex]);
let virtualItemRenderer = React.useCallback(
(virtualItem) => {
let menuItem =
filteredOptions.length > 0
? getMenuItem(filteredOptions[virtualItem.index], virtualItem.index)
: children;
return React.cloneElement(menuItem, {
key: virtualItem.key,
ref: virtualizer.measureElement,
'data-iui-virtualizer': 'item',
style: {
width: '100%',
transform: `translateY(${virtualItem.start}px)`,
},
});
},
[filteredOptions, getMenuItem, children, virtualizer.measureElement],
);
return React.createElement(
'div',
{
style: {
display: 'contents',
},
},
React.createElement(
ShadowRoot,
{
css: virtualizerCss,
},
React.createElement(
Box,
{
as: 'div',
'data-iui-virtualizer': 'root',
...rest,
style: {
minBlockSize: virtualizer.getTotalSize(),
...props.style,
},
},
React.createElement('slot', null),
),
),
React.createElement(
React.Fragment,
null,
virtualizer
.getVirtualItems()
.map((virtualItem) => virtualItemRenderer(virtualItem)),
),
);
};
export const ComboBoxMenu = React.forwardRef((props, forwardedRef) => {
let { className, children, style, portal = true, ...rest } = props;
let { id, enableVirtualization, popover } =
useSafeContext(ComboBoxStateContext);
let { menuRef } = useSafeContext(ComboBoxRefsContext);
let refs = useMergedRefs(popover.refs.setFloating, forwardedRef, menuRef);
return (
popover.open &&
React.createElement(
Portal,
{
portal: portal,
},
React.createElement(
List,
{
as: 'div',
className: cx('iui-menu', className),
id: `${id}-list`,
role: 'listbox',
ref: refs,
...popover.getFloatingProps({
style: enableVirtualization
? {
maxInlineSize: 0,
...style,
}
: style,
...rest,
}),
},
enableVirtualization
? React.createElement(VirtualizedComboBoxMenu, null, children)
: children,
),
)
);
});
if ('development' === process.env.NODE_ENV)
ComboBoxMenu.displayName = 'ComboBoxMenu';