@patternfly/react-core
Version:
This library provides a set of common React components for use with the PatternFly reference implementation.
822 lines • 56 kB
JavaScript
import { __rest } from "tslib";
import * as React from 'react';
import styles from '@patternfly/react-styles/css/components/Select/select';
import badgeStyles from '@patternfly/react-styles/css/components/Badge/badge';
import formStyles from '@patternfly/react-styles/css/components/FormControl/form-control';
import buttonStyles from '@patternfly/react-styles/css/components/Button/button';
import { css } from '@patternfly/react-styles';
import TimesCircleIcon from '@patternfly/react-icons/dist/esm/icons/times-circle-icon';
import CheckCircleIcon from '@patternfly/react-icons/dist/esm/icons/check-circle-icon';
import ExclamationTriangleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon';
import ExclamationCircleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon';
import { SelectMenu } from './SelectMenu';
import { SelectOption } from './SelectOption';
import { SelectGroup } from './SelectGroup';
import { SelectToggle } from './SelectToggle';
import { SelectContext, SelectVariant, SelectPosition, SelectDirection, SelectFooterTabbableItems } from './selectConstants';
import { ChipGroup } from '../ChipGroup';
import { Chip } from '../Chip';
import { Spinner } from '../Spinner';
import { keyHandler, getNextIndex, getOUIAProps, getDefaultOUIAId, GenerateId } from '../../helpers';
import { KeyTypes } from '../../helpers/constants';
import { Divider } from '../Divider';
import { Popper } from '../../helpers/Popper/Popper';
import { createRenderableFavorites, extendItemsWithFavorite } from '../../helpers/favorites';
import { ValidatedOptions } from '../../helpers/constants';
import { findTabbableElements } from '../../helpers/util';
// seed for the aria-labelledby ID
let currentId = 0;
export class Select extends React.Component {
constructor() {
super(...arguments);
this.parentRef = React.createRef();
this.menuComponentRef = React.createRef();
this.filterRef = React.createRef();
this.clearRef = React.createRef();
this.inputRef = React.createRef();
this.refCollection = [[]];
this.optionContainerRefCollection = [];
this.footerRef = React.createRef();
this.state = {
focusFirstOption: false,
typeaheadInputValue: null,
typeaheadFilteredChildren: React.Children.toArray(this.props.children),
favoritesGroup: [],
typeaheadCurrIndex: -1,
typeaheadStoredIndex: -1,
creatableValue: '',
tabbedIntoFavoritesMenu: false,
ouiaStateId: getDefaultOUIAId(Select.displayName, this.props.variant),
viewMoreNextIndex: -1
};
this.getTypeaheadActiveChild = (typeaheadCurrIndex) => this.refCollection[typeaheadCurrIndex] ? this.refCollection[typeaheadCurrIndex][0] : null;
this.componentDidUpdate = (prevProps, prevState) => {
if (this.props.hasInlineFilter) {
this.refCollection[0][0] = this.filterRef.current;
}
// Move focus to top of the menu if state.focusFirstOption was updated to true and the menu does not have custom content
if (!prevState.focusFirstOption && this.state.focusFirstOption && !this.props.customContent) {
const firstRef = this.refCollection.find(ref => ref !== null);
if (firstRef && firstRef[0]) {
firstRef[0].focus();
}
}
else if (
// if viewMoreNextIndex is not -1, view more was clicked, set focus on first newly loaded item
this.state.viewMoreNextIndex !== -1 &&
this.refCollection.length > this.state.viewMoreNextIndex &&
this.props.loadingVariant !== 'spinner' &&
this.refCollection[this.state.viewMoreNextIndex][0] &&
this.props.variant !== 'typeahead' && // do not hard focus newly added items for typeahead variants
this.props.variant !== 'typeaheadmulti') {
this.refCollection[this.state.viewMoreNextIndex][0].focus();
}
const hasUpdatedChildren = prevProps.children.length !== this.props.children.length ||
prevProps.children.some((prevChild, index) => {
const prevChildProps = prevChild.props;
const currChild = this.props.children[index];
const { props: currChildProps } = currChild;
if (prevChildProps && currChildProps) {
return (prevChildProps.value !== currChildProps.value ||
prevChildProps.label !== currChildProps.label ||
prevChildProps.isDisabled !== currChildProps.isDisabled ||
prevChildProps.isPlaceholder !== currChildProps.isPlaceholder);
}
else {
return prevChild !== currChild;
}
});
if (hasUpdatedChildren) {
this.updateTypeAheadFilteredChildren(prevState.typeaheadInputValue || '', null);
}
// for menus with favorites,
// if the number of favorites or typeahead filtered children has changed, the generated
// list of favorites needs to be updated
if (this.props.onFavorite &&
(this.props.favorites.length !== prevProps.favorites.length ||
this.state.typeaheadFilteredChildren !== prevState.typeaheadFilteredChildren)) {
const tempRenderableChildren = this.props.variant === 'typeahead' || this.props.variant === 'typeaheadmulti'
? this.state.typeaheadFilteredChildren
: this.props.children;
const renderableFavorites = createRenderableFavorites(tempRenderableChildren, this.props.isGrouped, this.props.favorites);
const favoritesGroup = renderableFavorites.length
? [
React.createElement(SelectGroup, { key: "favorites", label: this.props.favoritesLabel }, renderableFavorites),
React.createElement(Divider, { key: "favorites-group-divider" })
]
: [];
this.setState({ favoritesGroup });
}
};
this.onEnter = () => {
this.setState({ focusFirstOption: true });
};
this.onToggle = (isExpanded, e) => {
const { isInputValuePersisted, onSelect, onToggle, hasInlineFilter } = this.props;
if (!isExpanded && isInputValuePersisted && onSelect) {
onSelect(undefined, this.inputRef.current ? this.inputRef.current.value : '');
}
if (isExpanded && hasInlineFilter) {
this.setState({
focusFirstOption: true
});
}
onToggle(isExpanded, e);
};
this.onClose = () => {
const { isInputFilterPersisted } = this.props;
this.setState(Object.assign(Object.assign({ focusFirstOption: false, typeaheadInputValue: null }, (!isInputFilterPersisted && {
typeaheadFilteredChildren: React.Children.toArray(this.props.children)
})), { typeaheadCurrIndex: -1, tabbedIntoFavoritesMenu: false, viewMoreNextIndex: -1 }));
};
this.onChange = (e) => {
if (e.target.value.toString() !== '' && !this.props.isOpen) {
this.onToggle(true, e);
}
if (this.props.onTypeaheadInputChanged) {
this.props.onTypeaheadInputChanged(e.target.value.toString());
}
this.setState({
typeaheadCurrIndex: -1,
typeaheadInputValue: e.target.value,
creatableValue: e.target.value
});
this.updateTypeAheadFilteredChildren(e.target.value.toString(), e);
this.refCollection = [[]];
};
this.updateTypeAheadFilteredChildren = (typeaheadInputValue, e) => {
let typeaheadFilteredChildren;
const { onFilter, isCreatable, onCreateOption, createText, noResultsFoundText, children, isGrouped, isCreateSelectOptionObject, loadingVariant } = this.props;
if (onFilter) {
/* The updateTypeAheadFilteredChildren callback is not only called on input changes but also when the children change.
* In this case the e is null but we can get the typeaheadInputValue from the state.
*/
typeaheadFilteredChildren = onFilter(e, e ? e.target.value : typeaheadInputValue) || children;
}
else {
let input;
try {
input = new RegExp(typeaheadInputValue.toString(), 'i');
}
catch (err) {
input = new RegExp(typeaheadInputValue.toString().replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
}
const childrenArray = React.Children.toArray(children);
if (isGrouped) {
const childFilter = (child) => child.props.value &&
child.props.value.toString &&
this.getDisplay(child.props.value.toString(), 'text').search(input) === 0;
typeaheadFilteredChildren =
typeaheadInputValue.toString() !== ''
? React.Children.map(children, group => {
if (React.isValidElement(group) && group.type === SelectGroup) {
const filteredGroupChildren = React.Children.toArray(group.props.children).filter(childFilter);
if (filteredGroupChildren.length > 0) {
return React.cloneElement(group, {
titleId: group.props.label && group.props.label.replace(/\W/g, '-'),
children: filteredGroupChildren
});
}
}
else {
return React.Children.toArray(group).filter(childFilter);
}
})
: childrenArray;
}
else {
typeaheadFilteredChildren =
typeaheadInputValue.toString() !== ''
? childrenArray.filter(child => {
const valueToCheck = child.props.value;
// Dividers don't have value and should not be filtered
if (!valueToCheck) {
return true;
}
const isSelectOptionObject = typeof valueToCheck !== 'string' &&
valueToCheck.toString &&
valueToCheck.compareTo;
// View more option should be returned as not a match
if (loadingVariant !== 'spinner' && (loadingVariant === null || loadingVariant === void 0 ? void 0 : loadingVariant.text) === valueToCheck) {
return true;
}
// spinner should be returned as not a match
if (loadingVariant === 'spinner' && valueToCheck === 'loading') {
return true;
}
if (isSelectOptionObject) {
return valueToCheck.compareTo(typeaheadInputValue);
}
else {
return this.getDisplay(child.props.value.toString(), 'text').search(input) === 0;
}
})
: childrenArray;
}
}
if (!typeaheadFilteredChildren) {
typeaheadFilteredChildren = [];
}
if (typeaheadFilteredChildren.length === 0) {
!isCreatable &&
typeaheadFilteredChildren.push(React.createElement(SelectOption, { isDisabled: true, key: "no-results", value: noResultsFoundText, isNoResultsOption: true }));
}
if (isCreatable && typeaheadInputValue !== '') {
const newValue = typeaheadInputValue;
if (!typeaheadFilteredChildren.find((i) => i.props.value && i.props.value.toString().toLowerCase() === newValue.toString().toLowerCase())) {
const newOptionValue = isCreateSelectOptionObject
? {
toString: () => newValue,
compareTo: value => this.toString()
.toLowerCase()
.includes(value.toString().toLowerCase())
}
: newValue;
typeaheadFilteredChildren.push(React.createElement(SelectOption, { key: `create ${newValue}`, value: newOptionValue, onClick: () => onCreateOption && onCreateOption(newValue) },
createText,
" \"",
newValue,
"\""));
}
}
this.setState({
typeaheadFilteredChildren
});
};
this.onClick = (e) => {
if (!this.props.isOpen) {
this.onToggle(true, e);
}
};
this.clearSelection = (_e) => {
this.setState({
typeaheadInputValue: null,
typeaheadFilteredChildren: React.Children.toArray(this.props.children),
typeaheadCurrIndex: -1
});
};
this.sendRef = (optionRef, favoriteRef, optionContainerRef, index) => {
this.refCollection[index] = [optionRef, favoriteRef];
this.optionContainerRefCollection[index] = optionContainerRef;
};
this.handleMenuKeys = (index, innerIndex, position) => {
keyHandler(index, innerIndex, position, this.refCollection, this.refCollection);
if (this.props.variant === SelectVariant.typeahead || this.props.variant === SelectVariant.typeaheadMulti) {
if (position !== 'tab') {
this.handleTypeaheadKeys(position);
}
}
};
this.moveFocus = (nextIndex, updateCurrentIndex = true) => {
const { isCreatable, createText } = this.props;
const hasDescriptionElm = Boolean(this.refCollection[nextIndex][0] && this.refCollection[nextIndex][0].classList.contains('pf-m-description'));
const isLoad = Boolean(this.refCollection[nextIndex][0] && this.refCollection[nextIndex][0].classList.contains('pf-m-load'));
const optionTextElm = hasDescriptionElm
? this.refCollection[nextIndex][0].firstElementChild
: this.refCollection[nextIndex][0];
let typeaheadInputValue = '';
if (isCreatable && optionTextElm.innerText.includes(createText)) {
typeaheadInputValue = this.state.creatableValue;
}
else if (optionTextElm && !isLoad) {
// !isLoad prevents the view more button text from appearing the typeahead input
typeaheadInputValue = optionTextElm.innerText;
}
this.setState(prevState => ({
typeaheadCurrIndex: updateCurrentIndex ? nextIndex : prevState.typeaheadCurrIndex,
typeaheadStoredIndex: nextIndex,
typeaheadInputValue
}));
};
this.switchFocusToFavoriteMenu = () => {
const { typeaheadCurrIndex, typeaheadStoredIndex } = this.state;
let indexForFocus = 0;
if (typeaheadCurrIndex !== -1) {
indexForFocus = typeaheadCurrIndex;
}
else if (typeaheadStoredIndex !== -1) {
indexForFocus = typeaheadStoredIndex;
}
if (this.refCollection[indexForFocus] !== null && this.refCollection[indexForFocus][0] !== null) {
this.refCollection[indexForFocus][0].focus();
}
else {
this.clearRef.current.focus();
}
this.setState({
tabbedIntoFavoritesMenu: true,
typeaheadCurrIndex: -1
});
};
this.moveFocusToLastMenuItem = () => {
const refCollectionLen = this.refCollection.length;
if (refCollectionLen > 0 &&
this.refCollection[refCollectionLen - 1] !== null &&
this.refCollection[refCollectionLen - 1][0] !== null) {
this.refCollection[refCollectionLen - 1][0].focus();
}
};
this.handleTypeaheadKeys = (position, shiftKey = false) => {
const { isOpen, onFavorite } = this.props;
const { typeaheadCurrIndex, tabbedIntoFavoritesMenu } = this.state;
const typeaheadActiveChild = this.getTypeaheadActiveChild(typeaheadCurrIndex);
if (isOpen) {
if (position === 'enter') {
if (typeaheadCurrIndex !== -1 && // do not allow selection without moving to an initial option
(typeaheadActiveChild || (this.refCollection[0] && this.refCollection[0][0]))) {
if (typeaheadActiveChild) {
if (!typeaheadActiveChild.classList.contains('pf-m-load')) {
const hasDescriptionElm = typeaheadActiveChild.childElementCount > 1;
const typeaheadActiveChildText = hasDescriptionElm
? typeaheadActiveChild.firstChild.innerText
: typeaheadActiveChild.innerText;
this.setState({
typeaheadInputValue: typeaheadActiveChildText
});
}
}
else if (this.refCollection[0] && this.refCollection[0][0]) {
this.setState({
typeaheadInputValue: this.refCollection[0][0].innerText
});
}
if (typeaheadActiveChild) {
typeaheadActiveChild.click();
}
else {
this.refCollection[0][0].click();
}
}
}
else if (position === 'tab') {
if (onFavorite) {
// if the input has focus, tab to the first item or the last item that was previously focused.
if (this.inputRef.current === document.activeElement) {
// If shift is also clicked and there is a footer, tab to the last item in tabbable footer
if (this.props.footer && shiftKey) {
const tabbableItems = findTabbableElements(this.footerRef, SelectFooterTabbableItems);
if (tabbableItems.length > 0) {
if (tabbableItems[tabbableItems.length - 1]) {
tabbableItems[tabbableItems.length - 1].focus();
}
}
}
else {
this.switchFocusToFavoriteMenu();
}
}
else {
// focus is on menu or footer
if (this.props.footer) {
let tabbedIntoMenu = false;
const tabbableItems = findTabbableElements(this.footerRef, SelectFooterTabbableItems);
if (tabbableItems.length > 0) {
// if current element is not in footer, tab to first tabbable element in footer,
// if shift was clicked, tab to input since focus is on menu
const currentElementIndex = tabbableItems.findIndex((item) => item === document.activeElement);
if (currentElementIndex === -1) {
if (shiftKey) {
// currently in menu, shift back to input
this.inputRef.current.focus();
}
else {
// currently in menu, tab to first tabbable item in footer
tabbableItems[0].focus();
}
}
else {
// already in footer
if (shiftKey) {
// shift to previous item
if (currentElementIndex === 0) {
// on first footer item, shift back to menu
this.switchFocusToFavoriteMenu();
tabbedIntoMenu = true;
}
else {
// shift to previous footer item
tabbableItems[currentElementIndex - 1].focus();
}
}
else {
// tab to next tabbable item in footer or to input.
if (tabbableItems[currentElementIndex + 1]) {
tabbableItems[currentElementIndex + 1].focus();
}
else {
this.inputRef.current.focus();
}
}
}
}
else {
// no tabbable items in footer, tab to input
this.inputRef.current.focus();
tabbedIntoMenu = false;
}
this.setState({ tabbedIntoFavoritesMenu: tabbedIntoMenu });
}
else {
this.inputRef.current.focus();
this.setState({ tabbedIntoFavoritesMenu: false });
}
}
}
else {
// Close if there is no footer
if (!this.props.footer) {
this.onToggle(false, null);
this.onClose();
}
else {
// has footer
const tabbableItems = findTabbableElements(this.footerRef, SelectFooterTabbableItems);
const currentElementIndex = tabbableItems.findIndex((item) => item === document.activeElement);
if (this.inputRef.current === document.activeElement) {
if (shiftKey) {
// close toggle if shift key and tab on input
this.onToggle(false, null);
this.onClose();
}
else {
// tab to first tabbable item in footer
if (tabbableItems[0]) {
tabbableItems[0].focus();
}
else {
this.onToggle(false, null);
this.onClose();
}
}
}
else {
// focus is in footer
if (shiftKey) {
if (currentElementIndex === 0) {
// shift tab back to input
this.inputRef.current.focus();
}
else {
// shift to previous footer item
tabbableItems[currentElementIndex - 1].focus();
}
}
else {
// tab to next footer item or close tab if last item
if (tabbableItems[currentElementIndex + 1]) {
tabbableItems[currentElementIndex + 1].focus();
}
else {
// no next item, close toggle
this.onToggle(false, null);
this.inputRef.current.focus();
this.onClose();
}
}
}
}
}
}
else if (!tabbedIntoFavoritesMenu) {
if (this.refCollection[0][0] === null) {
return;
}
let nextIndex;
if (typeaheadCurrIndex === -1 && position === 'down') {
nextIndex = 0;
}
else if (typeaheadCurrIndex === -1 && position === 'up') {
nextIndex = this.refCollection.length - 1;
}
else if (position !== 'left' && position !== 'right') {
nextIndex = getNextIndex(typeaheadCurrIndex, position, this.refCollection);
}
else {
nextIndex = typeaheadCurrIndex;
}
if (this.refCollection[nextIndex] === null) {
return;
}
this.moveFocus(nextIndex);
}
else {
const nextIndex = this.refCollection.findIndex(ref => ref !== undefined && (ref[0] === document.activeElement || ref[1] === document.activeElement));
this.moveFocus(nextIndex);
}
}
};
this.onClickTypeaheadToggleButton = () => {
if (this.inputRef && this.inputRef.current) {
this.inputRef.current.focus();
}
};
this.getDisplay = (value, type = 'node') => {
if (!value) {
return;
}
const item = this.props.isGrouped
? React.Children.toArray(this.props.children)
.reduce((acc, curr) => [...acc, ...React.Children.toArray(curr.props.children)], [])
.find(child => child.props.value.toString() === value.toString())
: React.Children.toArray(this.props.children).find(child => child.props.value &&
child.props.value.toString() === value.toString());
if (item) {
if (item && item.props.children) {
if (type === 'node') {
return item.props.children;
}
return this.findText(item);
}
return item.props.value.toString();
}
return value.toString();
};
this.findText = (item) => {
if (typeof item === 'string') {
return item;
}
else if (!React.isValidElement(item)) {
return '';
}
else {
const multi = [];
React.Children.toArray(item.props.children).forEach(child => multi.push(this.findText(child)));
return multi.join('');
}
};
this.generateSelectedBadge = () => {
const { customBadgeText, selections } = this.props;
if (customBadgeText !== null) {
return customBadgeText;
}
if (Array.isArray(selections) && selections.length > 0) {
return selections.length;
}
return null;
};
this.setVieMoreNextIndex = () => {
this.setState({ viewMoreNextIndex: this.refCollection.length - 1 });
};
this.isLastOptionBeforeFooter = (index) => this.props.footer && index === this.refCollection.length - 1 ? true : false;
}
extendTypeaheadChildren(typeaheadCurrIndex, favoritesGroup) {
const { isGrouped, onFavorite } = this.props;
const typeaheadChildren = favoritesGroup
? favoritesGroup.concat(this.state.typeaheadFilteredChildren)
: this.state.typeaheadFilteredChildren;
const activeElement = this.optionContainerRefCollection[typeaheadCurrIndex];
let typeaheadActiveChild = this.getTypeaheadActiveChild(typeaheadCurrIndex);
if (typeaheadActiveChild && typeaheadActiveChild.classList.contains('pf-m-description')) {
typeaheadActiveChild = typeaheadActiveChild.firstElementChild;
}
this.refCollection = [[]];
this.optionContainerRefCollection = [];
if (isGrouped) {
return React.Children.map(typeaheadChildren, (group) => {
if (group.type === Divider) {
return group;
}
else if (group.type === SelectGroup && onFavorite) {
return React.cloneElement(group, {
titleId: group.props.label && group.props.label.replace(/\W/g, '-'),
children: React.Children.map(group.props.children, (child) => child.type === Divider
? child
: React.cloneElement(child, {
isFocused: activeElement &&
(activeElement.id === child.props.id ||
(this.props.isCreatable &&
typeaheadActiveChild.innerText ===
`{createText} "${group.props.value}"`))
}))
});
}
else if (group.type === SelectGroup) {
return React.cloneElement(group, {
titleId: group.props.label && group.props.label.replace(/\W/g, '-'),
children: React.Children.map(group.props.children, (child) => child.type === Divider
? child
: React.cloneElement(child, {
isFocused: typeaheadActiveChild &&
(typeaheadActiveChild.innerText === child.props.value.toString() ||
(this.props.isCreatable &&
typeaheadActiveChild.innerText ===
`{createText} "${child.props.value}"`))
}))
});
}
else {
// group has been filtered down to SelectOption
return React.cloneElement(group, {
isFocused: typeaheadActiveChild &&
(typeaheadActiveChild.innerText === group.props.value.toString() ||
(this.props.isCreatable && typeaheadActiveChild.innerText === `{createText} "${group.props.value}"`))
});
}
});
}
return typeaheadChildren.map((child, index) => {
const childElement = child;
return childElement.type.displayName === 'Divider'
? child
: React.cloneElement(child, {
isFocused: typeaheadActiveChild
? typeaheadActiveChild.innerText === child.props.value.toString() ||
(this.props.isCreatable &&
typeaheadActiveChild.innerText === `{createText} "${child.props.value}"`)
: index === typeaheadCurrIndex // fallback for view more + typeahead use cases, when the new expanded list is loaded and refCollection hasn't be updated yet
});
});
}
render() {
const _a = this.props, { children, chipGroupProps, chipGroupComponent, className, customContent, variant, direction, onSelect, onClear, onBlur, toggleId, isOpen, isGrouped, isPlain, isDisabled, hasPlaceholderStyle, validated, selections: selectionsProp, typeAheadAriaLabel, clearSelectionsAriaLabel, toggleAriaLabel, removeSelectionAriaLabel, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedby, 'aria-invalid': ariaInvalid, placeholderText, width, maxHeight, toggleIcon, ouiaId, ouiaSafe, hasInlineFilter, isCheckboxSelectionBadgeHidden, inlineFilterPlaceholderText,
/* eslint-disable @typescript-eslint/no-unused-vars */
onFilter,
/* eslint-disable @typescript-eslint/no-unused-vars */
onTypeaheadInputChanged, onCreateOption, isCreatable, onToggle, createText, noResultsFoundText, customBadgeText, inputIdPrefix, inputAutoComplete,
/* eslint-disable @typescript-eslint/no-unused-vars */
isInputValuePersisted, isInputFilterPersisted,
/* eslint-enable @typescript-eslint/no-unused-vars */
menuAppendTo, favorites, onFavorite,
/* eslint-disable @typescript-eslint/no-unused-vars */
favoritesLabel, footer, loadingVariant, isCreateSelectOptionObject, shouldResetOnSelect } = _a, props = __rest(_a, ["children", "chipGroupProps", "chipGroupComponent", "className", "customContent", "variant", "direction", "onSelect", "onClear", "onBlur", "toggleId", "isOpen", "isGrouped", "isPlain", "isDisabled", "hasPlaceholderStyle", "validated", "selections", "typeAheadAriaLabel", "clearSelectionsAriaLabel", "toggleAriaLabel", "removeSelectionAriaLabel", 'aria-label', 'aria-labelledby', 'aria-describedby', 'aria-invalid', "placeholderText", "width", "maxHeight", "toggleIcon", "ouiaId", "ouiaSafe", "hasInlineFilter", "isCheckboxSelectionBadgeHidden", "inlineFilterPlaceholderText", "onFilter", "onTypeaheadInputChanged", "onCreateOption", "isCreatable", "onToggle", "createText", "noResultsFoundText", "customBadgeText", "inputIdPrefix", "inputAutoComplete", "isInputValuePersisted", "isInputFilterPersisted", "menuAppendTo", "favorites", "onFavorite", "favoritesLabel", "footer", "loadingVariant", "isCreateSelectOptionObject", "shouldResetOnSelect"]);
const { focusFirstOption: openedOnEnter, typeaheadCurrIndex, typeaheadInputValue, typeaheadFilteredChildren, favoritesGroup } = this.state;
const selectToggleId = toggleId || `pf-select-toggle-id-${currentId++}`;
const selections = Array.isArray(selectionsProp) ? selectionsProp : [selectionsProp];
// Find out if the selected option is a placeholder
const selectedOption = React.Children.toArray(children).find((option) => option.props.value === selections[0]);
const isSelectedPlaceholder = selectedOption && selectedOption.props.isPlaceholder;
const hasAnySelections = Boolean(selections[0] && selections[0] !== '');
const typeaheadActiveChild = this.getTypeaheadActiveChild(typeaheadCurrIndex);
let childPlaceholderText = null;
// If onFavorites is set, add isFavorite prop to children and add a Favorites group to the SelectMenu
let renderableItems = [];
if (onFavorite) {
// if variant is type-ahead call the extendTypeaheadChildren before adding favorites
let tempExtendedChildren = children;
if (variant === 'typeahead' || variant === 'typeaheadmulti') {
tempExtendedChildren = this.extendTypeaheadChildren(typeaheadCurrIndex, favoritesGroup);
}
else if (onFavorite) {
tempExtendedChildren = favoritesGroup.concat(children);
}
// mark items that are favorited with isFavorite
renderableItems = extendItemsWithFavorite(tempExtendedChildren, isGrouped, favorites);
}
else {
renderableItems = children;
}
if (!customContent) {
if (!hasAnySelections && !placeholderText) {
const childPlaceholder = React.Children.toArray(children).filter((child) => child.props.isPlaceholder === true);
childPlaceholderText =
(childPlaceholder[0] && this.getDisplay(childPlaceholder[0].props.value, 'node')) ||
(children[0] && this.getDisplay(children[0].props.value, 'node'));
}
}
if (isOpen) {
if (renderableItems.find(item => { var _a; return ((_a = item) === null || _a === void 0 ? void 0 : _a.key) === 'loading'; }) === undefined) {
if (loadingVariant === 'spinner') {
renderableItems.push(React.createElement(SelectOption, { isLoading: true, key: "loading", value: "loading" },
React.createElement(Spinner, { size: "lg" })));
}
else if (loadingVariant === null || loadingVariant === void 0 ? void 0 : loadingVariant.text) {
renderableItems.push(React.createElement(SelectOption, { isLoad: true, key: "loading", value: loadingVariant.text, setViewMoreNextIndex: this.setVieMoreNextIndex, onClick: loadingVariant === null || loadingVariant === void 0 ? void 0 : loadingVariant.onClick }));
}
}
}
const hasOnClear = onClear !== Select.defaultProps.onClear;
const clearBtn = (React.createElement("button", { className: css(buttonStyles.button, buttonStyles.modifiers.plain, styles.selectToggleClear), onClick: e => {
this.clearSelection(e);
onClear(e);
e.stopPropagation();
}, "aria-label": clearSelectionsAriaLabel, type: "button", disabled: isDisabled, ref: this.clearRef, onKeyDown: event => {
if (event.key === KeyTypes.Enter) {
this.clearRef.current.click();
}
} },
React.createElement(TimesCircleIcon, { "aria-hidden": true })));
let selectedChips = null;
if (variant === SelectVariant.typeaheadMulti) {
selectedChips = chipGroupComponent ? (chipGroupComponent) : (React.createElement(ChipGroup, Object.assign({}, chipGroupProps), selections &&
selections.map(item => (React.createElement(Chip, { key: item, onClick: (e) => onSelect(e, item), closeBtnAriaLabel: removeSelectionAriaLabel }, this.getDisplay(item, 'node'))))));
}
if (hasInlineFilter) {
const filterBox = (React.createElement(React.Fragment, null,
React.createElement("div", { key: "inline-filter", className: css(styles.selectMenuSearch) },
React.createElement("input", { key: "inline-filter-input", type: "search", className: css(formStyles.formControl, formStyles.modifiers.search), onChange: this.onChange, placeholder: inlineFilterPlaceholderText, onKeyDown: event => {
if (event.key === KeyTypes.ArrowUp) {
this.handleMenuKeys(0, 0, 'up');
event.preventDefault();
}
else if (event.key === KeyTypes.ArrowDown) {
this.handleMenuKeys(0, 0, 'down');
event.preventDefault();
}
else if (event.key === KeyTypes.ArrowLeft) {
this.handleMenuKeys(0, 0, 'left');
event.preventDefault();
}
else if (event.key === KeyTypes.ArrowRight) {
this.handleMenuKeys(0, 0, 'right');
event.preventDefault();
}
else if (event.key === KeyTypes.Tab && variant !== SelectVariant.checkbox && this.props.footer) {
// tab to footer or close menu if shift key
if (event.shiftKey) {
this.onToggle(false, event);
}
else {
const tabbableItems = findTabbableElements(this.footerRef, SelectFooterTabbableItems);
if (tabbableItems.length > 0) {
tabbableItems[0].focus();
event.stopPropagation();
event.preventDefault();
}
else {
this.onToggle(false, event);
}
}
}
else if (event.key === KeyTypes.Tab && variant === SelectVariant.checkbox) {
// More modal-like experience for checkboxes
// Let SelectOption handle this
if (event.shiftKey) {
this.handleMenuKeys(0, 0, 'up');
}
else {
this.handleMenuKeys(0, 0, 'down');
}
event.stopPropagation();
event.preventDefault();
}
}, ref: this.filterRef, autoComplete: inputAutoComplete })),
React.createElement(Divider, { key: "inline-filter-divider" })));
renderableItems = [filterBox, ...typeaheadFilteredChildren].map((option, index) => React.cloneElement(option, { key: index }));
}
let variantProps;
let variantChildren;
if (customContent) {
variantProps = {
selected: selections,
openedOnEnter,
isCustomContent: true
};
variantChildren = customContent;
}
else {
switch (variant) {
case 'single':
variantProps = {
selected: selections[0],
hasInlineFilter,
openedOnEnter
};
variantChildren = renderableItems;
break;
case 'checkbox':
variantProps = {
checked: selections,
isGrouped,
hasInlineFilter,
openedOnEnter
};
variantChildren = renderableItems;
break;
case 'typeahead':
variantProps = {
selected: selections[0],
openedOnEnter
};
variantChildren = onFavorite ? renderableItems : this.extendTypeaheadChildren(typeaheadCurrIndex);
if (variantChildren.length === 0) {
variantChildren.push(React.createElement(SelectOption, { isDisabled: true, key: 0, value: noResultsFoundText, isNoResultsOption: true }));
}
break;
case 'typeaheadmulti':
variantProps = {
selected: selections,
openedOnEnter
};
variantChildren = onFavorite ? renderableItems : this.extendTypeaheadChildren(typeaheadCurrIndex);
if (variantChildren.length === 0) {
variantChildren.push(React.createElement(SelectOption, { isDisabled: true, key: 0, value: noResultsFoundText, isNoResultsOption: true }));
}
break;
}
}
const innerMenu = (React.createElement(SelectMenu, Object.assign({}, props, { isGrouped: isGrouped, selected: selections }, variantProps, { openedOnEnter: openedOnEnter, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, sendRef: this.sendRef, keyHandler: this.handleMenuKeys, maxHeight: maxHeight, ref: this.menuComponentRef, footer: footer, footerRef: this.footerRef, isLastOptionBeforeFooter: this.isLastOptionBeforeFooter }), variantChildren));
const menuContainer = footer ? React.createElement("div", { className: css(styles.selectMenu) },
" ",
innerMenu,
" ") : innerMenu;
const popperContainer = (React.createElement("div", Object.assign({ className: css(styles.select, isOpen && styles.modifiers.expanded, validated === ValidatedOptions.success && styles.modifiers.success, validated === ValidatedOptions.warning && styles.modifiers.warning, validated === ValidatedOptions.error && styles.modifiers.invalid, direction === SelectDirection.up && styles.modifiers.top, className) }, (width && { style: { width } }), (validated !== ValidatedOptions.default && { 'aria-describedby': ariaDescribedby }), (validated !== ValidatedOptions.default && { 'aria-invalid': ariaInvalid })), isOpen && menuContainer));
const mainContainer = (React.createElement("div", Object.assign({ className: css(styles.select, isOpen && styles.modifiers.expanded, validated === ValidatedOptions.success && styles.modifiers.success, validated === ValidatedOptions.warning && styles.modifiers.warning, validated === ValidatedOptions.error && styles.modifiers.invalid, direction === SelectDirection.up && styles.modifiers.top, className), ref: this.parentRef }, getOUIAProps(Select.displayName, ouiaId !== undefined ? ouiaId : this.state.ouiaStateId, ouiaSafe), (width && { style: { width } }), (validated !== ValidatedOptions.default && { 'aria-describedby': ariaDescribedby }), (validated !== ValidatedOptions.default && { 'aria-invalid': ariaInvalid })),
React.createElement(SelectToggle, Object.assign({ id: selectToggleId, parentRef: this.parentRef, menuRef: this.menuComponentRef }, (footer && { footerRef: this.footerRef }), { isOpen: isOpen, isPlain: isPlain, hasPlaceholderStyle: hasPlaceholderStyle && (!selections.length || selections[0] === null || isSelectedPlaceholder), onToggle: this.onToggle, onEnter: this.onEnter, onClose: this.onClose, onBlur: onBlur, variant: variant, "aria-labelledby": `${ariaLabelledBy || ''} ${selectToggleId}`, "aria-label": toggleAriaLabel, handleTypeaheadKeys: this.handleTypeaheadKeys, moveFocusToLastMenuItem: this.moveFocusToLastMenuItem, isDisabled: isDisabled, hasClearButton: hasOnClear, hasFooter: footer !== undefined, onClickTypeaheadToggleButton: this.onClickTypeaheadToggleButton }),
customContent && (React.createElement("div", { className: css(styles.selectToggleWrapper) },
toggleIcon && React.createElement("span", { className: css(styles.selectToggleIcon) }, toggleIcon),
React.createElement("span", { className: css(styles.selectToggleText) }, placeholderText))),
variant === SelectVariant.single && !customContent && (React.createElement(React.Fragment, null,
React.createElement("div", { className: css(styles.selectToggleWrapper) },
toggleIcon && React.createElement("span", { className: css(styles.selectToggleIcon) }, toggleIcon),
React.createElement("span", { className: css(styles.selectToggleText) }, this.getDisplay(selections[0], 'node') || placeholderText || childPlaceholderText)),
hasOnClear &&