in packages/fluentui/react-northstar/src/components/Dropdown/Dropdown.tsx [335:544]
filteredItems: search(filteredItemsByValue, searchQuery),
filteredItemStrings,
};
}
return {
filteredItems: filteredItemsByValue.filter(
item => itemToString(item).toLowerCase().indexOf(searchQuery.toLowerCase()) !== -1,
),
filteredItemStrings,
};
}
return {
filteredItems: filteredItemsByValue,
filteredItemStrings,
};
}
const isEmpty = prop => {
return typeof prop === 'object' && !prop.props && !_.get(prop, 'children') && !_.get(prop, 'content');
};
/**
* A Dropdown allows user to select one or more values from a list of options.
* Can be created with search and multi-selection capabilities.
*
* @accessibility
* Implements [ARIA Combo Box](https://www.w3.org/TR/wai-aria-practices-1.1/#combobox) design pattern, uses aria-live to announce state changes.
* @accessibilityIssues
* [Issue 991203: VoiceOver doesn't narrate properly elements in the input/combobox](https://bugs.chromium.org/p/chromium/issues/detail?id=991203)
* [JAWS - ESC (ESCAPE) not closing collapsible listbox (dropdown) on first time #528](https://github.com/FreedomScientific/VFO-standards-support/issues/528)
*/
export const Dropdown = (React.forwardRef<HTMLDivElement, DropdownProps>((props, ref) => {
const context = useFluentContext();
const { setStart, setEnd } = useTelemetry(Dropdown.displayName, context.telemetry);
setStart();
const {
'aria-labelledby': ariaLabelledby,
'aria-invalid': ariaInvalid,
clearable,
clearIndicator,
checkable,
checkableIndicator,
className,
design,
disabled,
error,
fluid,
getA11ySelectionMessage,
a11ySelectedItemsMessage,
getA11yStatusMessage,
inline,
inverted,
itemToString,
itemToValue,
items,
highlightFirstItemOnOpen,
multiple,
headerMessage,
moveFocusOnTab,
noResultsMessage,
loading,
loadingMessage,
placeholder,
renderItem,
renderSelectedItem,
search,
searchInput,
styles,
toggleIndicator,
triggerButton,
variables,
} = props;
const {
align,
flipBoundary,
overflowBoundary,
popperRef,
position,
positionFixed,
offset,
unstable_disableTether,
unstable_pinned,
autoSize,
} = props; // PositioningProps passed directly to Dropdown
const [list, positioningProps] = partitionPopperPropsFromShorthand(props.list); // PositioningProps passed to Dropdown `list` prop's `popper` key
const buttonRef = React.useRef<HTMLElement>();
const inputRef = React.useRef<HTMLInputElement | undefined>() as React.MutableRefObject<HTMLInputElement | undefined>;
const listRef = React.useRef<HTMLElement>();
const selectedItemsRef = React.useRef<HTMLDivElement>();
const containerRef = React.useRef<HTMLDivElement>();
const defaultTriggerButtonId = React.useMemo(() => _.uniqueId('dropdown-trigger-button-'), []);
const ElementType = getElementType(props);
const unhandledProps = useUnhandledProps(Dropdown.handledProps, props);
const [activeSelectedIndex, setActiveSelectedIndex] = useAutoControlled<number | null | undefined>({
defaultValue: props.defaultActiveSelectedIndex,
initialValue: multiple ? null : undefined,
value: props.activeSelectedIndex,
});
const [highlightedIndex, setHighlightedIndex] = useAutoControlled<number | null>({
defaultValue: props.defaultHighlightedIndex,
initialValue: highlightFirstItemOnOpen ? 0 : null,
value: props.highlightedIndex,
});
const [open, setOpen] = useAutoControlled({
defaultValue: props.defaultOpen,
initialValue: false,
value: props.open,
});
const [searchQuery, setSearchQuery] = useAutoControlled<string | undefined>({
defaultValue: props.defaultSearchQuery,
initialValue: search ? '' : undefined,
value: props.searchQuery,
});
const [rawValue, setValue] = useAutoControlled({
defaultValue: props.defaultValue,
initialValue: [],
value: props.value,
});
const value = normalizeValue(multiple, rawValue);
const [a11ySelectionStatus, setA11ySelectionStatus] = React.useState('');
const [focused, setFocused] = React.useState(false);
const [isFromKeyboard, setIsFromKeyboard] = React.useState(false);
const [itemIsFromKeyboard, setItemIsFromKeyboard] = React.useState(false);
const [startingString, setStartingString] = React.useState<string | undefined>(search ? undefined : '');
const { filteredItems, filteredItemStrings } = getFilteredValues({
itemToString,
itemToValue,
items,
multiple,
search,
searchQuery,
value,
});
const { classes, styles: resolvedStyles } = useStyles<DropdownStylesProps>(Dropdown.displayName, {
className: dropdownClassName,
mapPropsToStyles: () => ({
disabled,
error,
fluid,
focused,
isEmptyClearIndicator: isEmpty(clearIndicator),
hasToggleIndicator: !!toggleIndicator,
inline,
inverted,
isFromKeyboard,
multiple,
open,
position: positioningProps?.position ?? position,
search: !!search,
hasItemsSelected: value.length > 0,
}),
mapPropsToInlineStyles: () => ({
className,
design,
styles,
variables,
}),
rtl: context.rtl,
});
const clearA11ySelectionMessage = React.useMemo(
() =>
_.debounce(() => {
setA11ySelectionStatus('');
}, a11yStatusCleanupTime),
[],
);
const clearStartingString = React.useMemo(
() =>
_.debounce(() => {
setStartingString('');
}, charKeyPressedCleanupTime),
[],
);
const handleChange = (e: React.SyntheticEvent) => {
// Dropdown component doesn't present any `input` component in markup, however all of our
// components should handle events transparently.
_.invoke(props, 'onChange', e, { ...props, value });
};
const handleOnBlur = (e: React.SyntheticEvent) => {
// Dropdown component doesn't present any `input` component in markup, however all of our
// components should handle events transparently.
if (e.target !== buttonRef.current) {
_.invoke(props, 'onBlur', e, props);
}
};
const renderTriggerButton = (getToggleButtonProps: (options?: GetToggleButtonPropsOptions) => any): JSX.Element => {
const content = getSelectedItemAsString(value[0]);
const triggerButtonId = triggerButton['id'] || defaultTriggerButtonId;
const triggerButtonProps = getToggleButtonProps({
disabled,
onFocus: handleTriggerButtonOrListFocus,
onBlur: handleTriggerButtonBlur,
onKeyDown: e => {