@itwin/itwinui-react
Version:
A react component library for iTwinUI
178 lines (177 loc) • 5.79 kB
JavaScript
import * as React from 'react';
import { Input } from '../Input/Input.js';
import {
useSafeContext,
useMergedRefs,
useContainerWidth,
mergeEventHandlers,
useLatestRef,
} from '../../utils/index.js';
import { ComboBoxMultipleContainer } from './ComboBoxMultipleContainer.js';
import { ComboBoxStateContext, ComboBoxRefsContext } from './helpers.js';
export const ComboBoxInput = React.forwardRef((props, forwardedRef) => {
let { selectTags, size, style, ...rest } = props;
let {
isOpen,
id,
focusedIndex,
setFocusedIndex,
enableVirtualization,
multiple,
onClickHandler,
popover,
show,
hide,
} = useSafeContext(ComboBoxStateContext);
let { inputRef, menuRef, optionsExtraInfo } =
useSafeContext(ComboBoxRefsContext);
let refs = useMergedRefs(inputRef, popover.refs.setReference, forwardedRef);
let focusedIndexRef = useLatestRef(focusedIndex ?? -1);
let getIdFromIndex = (index) =>
menuRef.current?.querySelector(`[data-iui-index="${index}"]`)?.id ?? '';
let handleKeyDown = React.useCallback(
(event) => {
let length = Object.keys(optionsExtraInfo).length ?? 0;
if (event.altKey) return;
switch (event.key) {
case 'ArrowDown': {
event.preventDefault();
if (!isOpen) return show();
if (0 === length) return;
if (-1 === focusedIndexRef.current) {
let currentElement =
menuRef.current?.querySelector('[data-iui-index]');
return setFocusedIndex(
Number(currentElement?.getAttribute('data-iui-index') ?? 0),
);
}
if (
enableVirtualization &&
!menuRef.current?.querySelector(
`[data-iui-index="${focusedIndexRef.current}"]`,
)?.nextElementSibling
)
return;
let nextIndex = focusedIndexRef.current;
do {
let currentElement = menuRef.current?.querySelector(
`[data-iui-index="${nextIndex}"]`,
);
let nextElement =
currentElement?.nextElementSibling ??
menuRef.current?.querySelector('[data-iui-index]');
nextIndex = Number(nextElement?.getAttribute('data-iui-index'));
if (nextElement) return setFocusedIndex(nextIndex);
} while (nextIndex !== focusedIndexRef.current);
break;
}
case 'ArrowUp': {
event.preventDefault();
if (!isOpen) return show();
if (0 === length) return;
if (
enableVirtualization &&
!menuRef.current?.querySelector(
`[data-iui-index="${focusedIndexRef.current}"]`,
)?.previousElementSibling
)
return;
if (-1 === focusedIndexRef.current)
return setFocusedIndex(
Object.values(optionsExtraInfo)?.[length - 1].__originalIndex ??
-1,
);
let prevIndex = focusedIndexRef.current;
do {
let currentElement = menuRef.current?.querySelector(
`[data-iui-index="${prevIndex}"]`,
);
let prevElement =
currentElement?.previousElementSibling ??
menuRef.current?.querySelector('[data-iui-index]:last-of-type');
prevIndex = Number(prevElement?.getAttribute('data-iui-index'));
if (prevElement) return setFocusedIndex(prevIndex);
} while (prevIndex !== focusedIndexRef.current);
break;
}
case 'Enter':
event.preventDefault();
if (isOpen) {
if (focusedIndexRef.current > -1)
onClickHandler?.(focusedIndexRef.current);
} else show();
break;
case 'Escape':
event.preventDefault();
hide();
break;
case 'Tab':
hide();
break;
}
},
[
setFocusedIndex,
enableVirtualization,
focusedIndexRef,
isOpen,
menuRef,
onClickHandler,
optionsExtraInfo,
show,
hide,
],
);
let wasOpenBeforeClick = React.useRef(false);
let handlePointerDown = React.useCallback(() => {
wasOpenBeforeClick.current = isOpen;
}, [isOpen]);
let handleClick = React.useCallback(() => {
if (wasOpenBeforeClick.current) hide();
else show();
wasOpenBeforeClick.current = false;
}, [hide, show]);
let [tagContainerWidthRef, tagContainerWidth] = useContainerWidth();
return React.createElement(
React.Fragment,
null,
React.createElement(Input, {
ref: refs,
'aria-expanded': isOpen,
'aria-activedescendant':
isOpen && void 0 != focusedIndex && focusedIndex > -1
? getIdFromIndex(focusedIndex)
: void 0,
role: 'combobox',
'aria-controls': isOpen ? `${id}-list` : void 0,
'aria-autocomplete': 'list',
spellCheck: false,
autoCapitalize: 'none',
autoCorrect: 'off',
style: {
...(multiple && {
paddingInlineStart: tagContainerWidth + 18,
}),
...style,
},
size: size,
...popover.getReferenceProps({
...rest,
onPointerDown: mergeEventHandlers(
props.onPointerDown,
handlePointerDown,
),
onClick: mergeEventHandlers(props.onClick, handleClick),
onKeyDown: mergeEventHandlers(props.onKeyDown, handleKeyDown),
}),
}),
multiple && selectTags
? React.createElement(ComboBoxMultipleContainer, {
ref: tagContainerWidthRef,
selectedItems: selectTags,
})
: null,
);
});
if ('development' === process.env.NODE_ENV)
ComboBoxInput.displayName = 'ComboBoxInput';