UNPKG

@itwin/itwinui-react

Version:

A react component library for iTwinUI

136 lines (135 loc) 3.98 kB
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';