@patternfly/react-core 5.0.0-alpha.62 → 5.0.0-alpha.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/esm/components/CalendarMonth/CalendarMonth.d.ts.map +1 -1
- package/dist/esm/components/CalendarMonth/CalendarMonth.js +9 -11
- package/dist/esm/components/CalendarMonth/CalendarMonth.js.map +1 -1
- package/dist/esm/components/DualListSelector/DualListSelectorControl.d.ts +1 -1
- package/dist/esm/components/Menu/MenuItem.d.ts +1 -1
- package/dist/esm/components/MenuToggle/MenuToggle.d.ts.map +1 -1
- package/dist/esm/components/MenuToggle/MenuToggle.js +3 -1
- package/dist/esm/components/MenuToggle/MenuToggle.js.map +1 -1
- package/dist/esm/components/Select/Select.d.ts +26 -192
- package/dist/esm/components/Select/Select.d.ts.map +1 -1
- package/dist/esm/components/Select/Select.js +51 -935
- package/dist/esm/components/Select/Select.js.map +1 -1
- package/dist/esm/components/Select/SelectGroup.d.ts +7 -8
- package/dist/esm/components/Select/SelectGroup.d.ts.map +1 -1
- package/dist/esm/components/Select/SelectGroup.js +4 -7
- package/dist/esm/components/Select/SelectGroup.js.map +1 -1
- package/dist/{js/next → esm}/components/Select/SelectList.d.ts +1 -1
- package/dist/esm/components/Select/SelectList.d.ts.map +1 -0
- package/dist/esm/{next/components → components}/Select/SelectList.js +1 -1
- package/dist/esm/components/Select/SelectList.js.map +1 -0
- package/dist/esm/components/Select/SelectOption.d.ts +13 -66
- package/dist/esm/components/Select/SelectOption.d.ts.map +1 -1
- package/dist/esm/components/Select/SelectOption.js +6 -192
- package/dist/esm/components/Select/SelectOption.js.map +1 -1
- package/dist/esm/components/Select/index.d.ts +1 -1
- package/dist/esm/components/Select/index.d.ts.map +1 -1
- package/dist/esm/components/Select/index.js +1 -1
- package/dist/esm/components/Select/index.js.map +1 -1
- package/dist/esm/deprecated/components/Select/Select.d.ts +199 -0
- package/dist/esm/deprecated/components/Select/Select.d.ts.map +1 -0
- package/dist/esm/deprecated/components/Select/Select.js +940 -0
- package/dist/esm/deprecated/components/Select/Select.js.map +1 -0
- package/dist/esm/deprecated/components/Select/SelectGroup.d.ts +13 -0
- package/dist/esm/deprecated/components/Select/SelectGroup.d.ts.map +1 -0
- package/dist/esm/deprecated/components/Select/SelectGroup.js +13 -0
- package/dist/esm/deprecated/components/Select/SelectGroup.js.map +1 -0
- package/dist/esm/deprecated/components/Select/SelectMenu.d.ts.map +1 -0
- package/dist/esm/{components → deprecated/components}/Select/SelectMenu.js +1 -1
- package/dist/esm/deprecated/components/Select/SelectMenu.js.map +1 -0
- package/dist/esm/deprecated/components/Select/SelectOption.d.ts +73 -0
- package/dist/esm/deprecated/components/Select/SelectOption.d.ts.map +1 -0
- package/dist/esm/deprecated/components/Select/SelectOption.js +196 -0
- package/dist/esm/deprecated/components/Select/SelectOption.js.map +1 -0
- package/dist/esm/deprecated/components/Select/SelectToggle.d.ts.map +1 -0
- package/dist/esm/{components → deprecated/components}/Select/SelectToggle.js +2 -2
- package/dist/esm/deprecated/components/Select/SelectToggle.js.map +1 -0
- package/dist/esm/{next → deprecated}/components/Select/index.d.ts +1 -1
- package/dist/esm/deprecated/components/Select/index.d.ts.map +1 -0
- package/dist/esm/{next → deprecated}/components/Select/index.js +1 -1
- package/dist/esm/deprecated/components/Select/index.js.map +1 -0
- package/dist/esm/deprecated/components/Select/selectConstants.d.ts.map +1 -0
- package/dist/esm/deprecated/components/Select/selectConstants.js.map +1 -0
- package/dist/esm/deprecated/components/index.d.ts +1 -0
- package/dist/esm/deprecated/components/index.d.ts.map +1 -1
- package/dist/esm/deprecated/components/index.js +1 -0
- package/dist/esm/deprecated/components/index.js.map +1 -1
- package/dist/esm/next/components/index.d.ts +0 -1
- package/dist/esm/next/components/index.d.ts.map +1 -1
- package/dist/esm/next/components/index.js +0 -1
- package/dist/esm/next/components/index.js.map +1 -1
- package/dist/js/components/CalendarMonth/CalendarMonth.d.ts.map +1 -1
- package/dist/js/components/CalendarMonth/CalendarMonth.js +8 -10
- package/dist/js/components/CalendarMonth/CalendarMonth.js.map +1 -1
- package/dist/js/components/DualListSelector/DualListSelectorControl.d.ts +1 -1
- package/dist/js/components/Menu/MenuItem.d.ts +1 -1
- package/dist/js/components/MenuToggle/MenuToggle.d.ts.map +1 -1
- package/dist/js/components/MenuToggle/MenuToggle.js +3 -1
- package/dist/js/components/MenuToggle/MenuToggle.js.map +1 -1
- package/dist/js/components/Select/Select.d.ts +26 -192
- package/dist/js/components/Select/Select.d.ts.map +1 -1
- package/dist/js/components/Select/Select.js +51 -936
- package/dist/js/components/Select/Select.js.map +1 -1
- package/dist/js/components/Select/SelectGroup.d.ts +7 -8
- package/dist/js/components/Select/SelectGroup.d.ts.map +1 -1
- package/dist/js/components/Select/SelectGroup.js +4 -7
- package/dist/js/components/Select/SelectGroup.js.map +1 -1
- package/dist/{esm/next → js}/components/Select/SelectList.d.ts +1 -1
- package/dist/js/components/Select/SelectList.d.ts.map +1 -0
- package/dist/js/{next/components → components}/Select/SelectList.js +1 -1
- package/dist/js/components/Select/SelectList.js.map +1 -0
- package/dist/js/components/Select/SelectOption.d.ts +13 -66
- package/dist/js/components/Select/SelectOption.d.ts.map +1 -1
- package/dist/js/components/Select/SelectOption.js +7 -193
- package/dist/js/components/Select/SelectOption.js.map +1 -1
- package/dist/js/components/Select/index.d.ts +1 -1
- package/dist/js/components/Select/index.d.ts.map +1 -1
- package/dist/js/components/Select/index.js +1 -1
- package/dist/js/components/Select/index.js.map +1 -1
- package/dist/js/deprecated/components/Select/Select.d.ts +199 -0
- package/dist/js/deprecated/components/Select/Select.d.ts.map +1 -0
- package/dist/js/deprecated/components/Select/Select.js +944 -0
- package/dist/js/deprecated/components/Select/Select.js.map +1 -0
- package/dist/js/deprecated/components/Select/SelectGroup.d.ts +13 -0
- package/dist/js/deprecated/components/Select/SelectGroup.d.ts.map +1 -0
- package/dist/js/deprecated/components/Select/SelectGroup.js +17 -0
- package/dist/js/deprecated/components/Select/SelectGroup.js.map +1 -0
- package/dist/js/deprecated/components/Select/SelectMenu.d.ts.map +1 -0
- package/dist/js/{components → deprecated/components}/Select/SelectMenu.js +1 -1
- package/dist/js/deprecated/components/Select/SelectMenu.js.map +1 -0
- package/dist/js/deprecated/components/Select/SelectOption.d.ts +73 -0
- package/dist/js/deprecated/components/Select/SelectOption.d.ts.map +1 -0
- package/dist/js/deprecated/components/Select/SelectOption.js +200 -0
- package/dist/js/deprecated/components/Select/SelectOption.js.map +1 -0
- package/dist/js/deprecated/components/Select/SelectToggle.d.ts.map +1 -0
- package/dist/js/{components → deprecated/components}/Select/SelectToggle.js +2 -2
- package/dist/js/deprecated/components/Select/SelectToggle.js.map +1 -0
- package/dist/js/{next → deprecated}/components/Select/index.d.ts +1 -1
- package/dist/js/deprecated/components/Select/index.d.ts.map +1 -0
- package/dist/js/{next → deprecated}/components/Select/index.js +1 -1
- package/dist/js/deprecated/components/Select/index.js.map +1 -0
- package/dist/js/deprecated/components/Select/selectConstants.d.ts.map +1 -0
- package/dist/js/deprecated/components/Select/selectConstants.js.map +1 -0
- package/dist/js/deprecated/components/index.d.ts +1 -0
- package/dist/js/deprecated/components/index.d.ts.map +1 -1
- package/dist/js/deprecated/components/index.js +1 -0
- package/dist/js/deprecated/components/index.js.map +1 -1
- package/dist/js/next/components/index.d.ts +0 -1
- package/dist/js/next/components/index.d.ts.map +1 -1
- package/dist/js/next/components/index.js +0 -1
- package/dist/js/next/components/index.js.map +1 -1
- package/dist/umd/react-core.min.js +3 -3
- package/package.json +2 -2
- package/src/components/CalendarMonth/CalendarMonth.tsx +25 -15
- package/src/components/DatePicker/__tests__/__snapshots__/DatePicker.test.tsx.snap +22 -31
- package/src/components/LoginPage/examples/LoginPageLanguageSelect.tsx +28 -19
- package/src/components/MenuToggle/MenuToggle.tsx +2 -1
- package/src/components/MenuToggle/__tests__/__snapshots__/MenuToggle.test.tsx.snap +18 -10
- package/src/components/MenuToggle/examples/MenuToggle.md +85 -78
- package/src/components/MenuToggle/examples/MenuToggleSplitButtonAction.tsx +10 -11
- package/src/components/MenuToggle/examples/MenuToggleSplitButtonCheckbox.tsx +4 -17
- package/src/components/MenuToggle/examples/MenuToggleSplitButtonCheckboxWithText.tsx +10 -11
- package/src/components/MenuToggle/examples/MenuToggleTypeahead.tsx +35 -0
- package/src/components/Select/Select.tsx +113 -1475
- package/src/components/Select/SelectGroup.tsx +13 -25
- package/src/{next/components → components}/Select/SelectList.tsx +1 -1
- package/src/components/Select/SelectOption.tsx +22 -452
- package/src/components/Select/examples/Select.md +11 -2897
- package/src/{next/components → components}/Select/examples/SelectBasic.tsx +1 -2
- package/src/{next/components → components}/Select/examples/SelectCheckbox.tsx +1 -2
- package/src/{next/components → components}/Select/examples/SelectGrouped.tsx +1 -2
- package/src/{next/components → components}/Select/examples/SelectMultiTypeahead.tsx +4 -1
- package/src/{next/components → components}/Select/examples/SelectTypeahead.tsx +4 -1
- package/src/components/Select/index.ts +1 -1
- package/src/components/Toolbar/__tests__/Toolbar.test.tsx +3 -21
- package/src/components/Toolbar/__tests__/__snapshots__/Toolbar.test.tsx.snap +12 -124
- package/src/components/Toolbar/examples/Toolbar.md +0 -1
- package/src/components/Toolbar/examples/ToolbarComponentManagedToggleGroups.tsx +59 -63
- package/src/components/Toolbar/examples/ToolbarConsumerManagedToggleGroups.tsx +58 -62
- package/src/components/Toolbar/examples/ToolbarCustomChipGroupContent.tsx +107 -23
- package/src/components/Toolbar/examples/ToolbarGroups.tsx +96 -59
- package/src/components/Toolbar/examples/ToolbarStacked.tsx +56 -39
- package/src/components/Toolbar/examples/ToolbarWithFilters.tsx +115 -28
- package/src/components/Tooltip/examples/TooltipOptions.tsx +53 -23
- package/src/demos/Card/Card.md +39 -23
- package/src/demos/CardDemos.md +99 -46
- package/src/demos/Toolbar.md +51 -21
- package/src/demos/examples/Tabs/TabsAndTable.tsx +5 -22
- package/src/deprecated/components/Select/Select.tsx +1495 -0
- package/src/deprecated/components/Select/SelectGroup.tsx +36 -0
- package/src/{components → deprecated/components}/Select/SelectMenu.tsx +2 -2
- package/src/deprecated/components/Select/SelectOption.tsx +461 -0
- package/src/{components → deprecated/components}/Select/SelectToggle.tsx +3 -3
- package/src/{components → deprecated/components}/Select/__tests__/Select.test.tsx +1 -1
- package/src/deprecated/components/Select/examples/Select.md +2937 -0
- package/src/{next → deprecated}/components/Select/index.ts +1 -1
- package/src/deprecated/components/index.ts +1 -0
- package/src/next/components/index.ts +0 -1
- package/dist/esm/components/Select/SelectMenu.d.ts.map +0 -1
- package/dist/esm/components/Select/SelectMenu.js.map +0 -1
- package/dist/esm/components/Select/SelectToggle.d.ts.map +0 -1
- package/dist/esm/components/Select/SelectToggle.js.map +0 -1
- package/dist/esm/components/Select/selectConstants.d.ts.map +0 -1
- package/dist/esm/components/Select/selectConstants.js.map +0 -1
- package/dist/esm/next/components/Select/Select.d.ts +0 -33
- package/dist/esm/next/components/Select/Select.d.ts.map +0 -1
- package/dist/esm/next/components/Select/Select.js +0 -56
- package/dist/esm/next/components/Select/Select.js.map +0 -1
- package/dist/esm/next/components/Select/SelectGroup.d.ts +0 -12
- package/dist/esm/next/components/Select/SelectGroup.d.ts.map +0 -1
- package/dist/esm/next/components/Select/SelectGroup.js +0 -10
- package/dist/esm/next/components/Select/SelectGroup.js.map +0 -1
- package/dist/esm/next/components/Select/SelectList.d.ts.map +0 -1
- package/dist/esm/next/components/Select/SelectList.js.map +0 -1
- package/dist/esm/next/components/Select/SelectOption.d.ts +0 -20
- package/dist/esm/next/components/Select/SelectOption.d.ts.map +0 -1
- package/dist/esm/next/components/Select/SelectOption.js +0 -10
- package/dist/esm/next/components/Select/SelectOption.js.map +0 -1
- package/dist/esm/next/components/Select/index.d.ts.map +0 -1
- package/dist/esm/next/components/Select/index.js.map +0 -1
- package/dist/js/components/Select/SelectMenu.d.ts.map +0 -1
- package/dist/js/components/Select/SelectMenu.js.map +0 -1
- package/dist/js/components/Select/SelectToggle.d.ts.map +0 -1
- package/dist/js/components/Select/SelectToggle.js.map +0 -1
- package/dist/js/components/Select/selectConstants.d.ts.map +0 -1
- package/dist/js/components/Select/selectConstants.js.map +0 -1
- package/dist/js/next/components/Select/Select.d.ts +0 -33
- package/dist/js/next/components/Select/Select.d.ts.map +0 -1
- package/dist/js/next/components/Select/Select.js +0 -59
- package/dist/js/next/components/Select/Select.js.map +0 -1
- package/dist/js/next/components/Select/SelectGroup.d.ts +0 -12
- package/dist/js/next/components/Select/SelectGroup.d.ts.map +0 -1
- package/dist/js/next/components/Select/SelectGroup.js +0 -14
- package/dist/js/next/components/Select/SelectGroup.js.map +0 -1
- package/dist/js/next/components/Select/SelectList.d.ts.map +0 -1
- package/dist/js/next/components/Select/SelectList.js.map +0 -1
- package/dist/js/next/components/Select/SelectOption.d.ts +0 -20
- package/dist/js/next/components/Select/SelectOption.d.ts.map +0 -1
- package/dist/js/next/components/Select/SelectOption.js +0 -14
- package/dist/js/next/components/Select/SelectOption.js.map +0 -1
- package/dist/js/next/components/Select/index.d.ts.map +0 -1
- package/dist/js/next/components/Select/index.js.map +0 -1
- package/src/components/MenuToggle/examples/MenuToggleSplitButtonActionPrimary.tsx +0 -58
- package/src/components/MenuToggle/examples/MenuToggleSplitButtonActionSecondary.tsx +0 -58
- package/src/components/MenuToggle/examples/MenuToggleSplitButtonCheckboxPrimary.tsx +0 -55
- package/src/components/MenuToggle/examples/MenuToggleSplitButtonCheckboxSecondary.tsx +0 -55
- package/src/next/components/Select/Select.tsx +0 -133
- package/src/next/components/Select/SelectGroup.tsx +0 -24
- package/src/next/components/Select/SelectOption.tsx +0 -31
- package/src/next/components/Select/examples/Select.md +0 -38
- /package/dist/esm/{components → deprecated/components}/Select/SelectMenu.d.ts +0 -0
- /package/dist/esm/{components → deprecated/components}/Select/SelectToggle.d.ts +0 -0
- /package/dist/esm/{components → deprecated/components}/Select/selectConstants.d.ts +0 -0
- /package/dist/esm/{components → deprecated/components}/Select/selectConstants.js +0 -0
- /package/dist/js/{components → deprecated/components}/Select/SelectMenu.d.ts +0 -0
- /package/dist/js/{components → deprecated/components}/Select/SelectToggle.d.ts +0 -0
- /package/dist/js/{components → deprecated/components}/Select/selectConstants.d.ts +0 -0
- /package/dist/js/{components → deprecated/components}/Select/selectConstants.js +0 -0
- /package/src/{components → deprecated/components}/Select/__tests__/SelectGroup.test.tsx +0 -0
- /package/src/{components → deprecated/components}/Select/__tests__/SelectOption.test.tsx +0 -0
- /package/src/{components → deprecated/components}/Select/__tests__/SelectToggle.test.tsx +0 -0
- /package/src/{components → deprecated/components}/Select/__tests__/__snapshots__/Select.test.tsx.snap +0 -0
- /package/src/{components → deprecated/components}/Select/__tests__/__snapshots__/SelectGroup.test.tsx.snap +0 -0
- /package/src/{components → deprecated/components}/Select/__tests__/__snapshots__/SelectOption.test.tsx.snap +0 -0
- /package/src/{components → deprecated/components}/Select/__tests__/__snapshots__/SelectToggle.test.tsx.snap +0 -0
- /package/src/{components → deprecated/components}/Select/selectConstants.tsx +0 -0
|
@@ -0,0 +1,1495 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import styles from '@patternfly/react-styles/css/components/Select/select';
|
|
3
|
+
import badgeStyles from '@patternfly/react-styles/css/components/Badge/badge';
|
|
4
|
+
import formStyles from '@patternfly/react-styles/css/components/FormControl/form-control';
|
|
5
|
+
import buttonStyles from '@patternfly/react-styles/css/components/Button/button';
|
|
6
|
+
import { css } from '@patternfly/react-styles';
|
|
7
|
+
import TimesCircleIcon from '@patternfly/react-icons/dist/esm/icons/times-circle-icon';
|
|
8
|
+
import CheckCircleIcon from '@patternfly/react-icons/dist/esm/icons/check-circle-icon';
|
|
9
|
+
import ExclamationTriangleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon';
|
|
10
|
+
import ExclamationCircleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon';
|
|
11
|
+
import { SelectMenu } from './SelectMenu';
|
|
12
|
+
import { SelectOption, SelectOptionObject } from './SelectOption';
|
|
13
|
+
import { SelectGroup, SelectGroupProps } from './SelectGroup';
|
|
14
|
+
import { SelectToggle } from './SelectToggle';
|
|
15
|
+
import {
|
|
16
|
+
SelectContext,
|
|
17
|
+
SelectVariant,
|
|
18
|
+
SelectPosition,
|
|
19
|
+
SelectDirection,
|
|
20
|
+
SelectFooterTabbableItems
|
|
21
|
+
} from './selectConstants';
|
|
22
|
+
import { ChipGroup, ChipGroupProps } from '../../../components/ChipGroup';
|
|
23
|
+
import { Chip } from '../../../components/Chip';
|
|
24
|
+
import { Spinner } from '../../../components/Spinner';
|
|
25
|
+
import {
|
|
26
|
+
keyHandler,
|
|
27
|
+
getNextIndex,
|
|
28
|
+
getOUIAProps,
|
|
29
|
+
OUIAProps,
|
|
30
|
+
getDefaultOUIAId,
|
|
31
|
+
PickOptional,
|
|
32
|
+
GenerateId
|
|
33
|
+
} from '../../../helpers';
|
|
34
|
+
import { KeyTypes } from '../../../helpers/constants';
|
|
35
|
+
import { Divider } from '../../../components/Divider';
|
|
36
|
+
import { Popper } from '../../../helpers/Popper/Popper';
|
|
37
|
+
import { createRenderableFavorites, extendItemsWithFavorite } from '../../../helpers/favorites';
|
|
38
|
+
import { ValidatedOptions } from '../../../helpers/constants';
|
|
39
|
+
import { findTabbableElements } from '../../../helpers/util';
|
|
40
|
+
|
|
41
|
+
// seed for the aria-labelledby ID
|
|
42
|
+
let currentId = 0;
|
|
43
|
+
|
|
44
|
+
export interface SelectViewMoreObject {
|
|
45
|
+
/** View more text */
|
|
46
|
+
text: string;
|
|
47
|
+
/** Callback for when the view more button is clicked */
|
|
48
|
+
onClick: (event: React.MouseEvent | React.ChangeEvent) => void;
|
|
49
|
+
}
|
|
50
|
+
export interface SelectProps
|
|
51
|
+
extends Omit<React.HTMLProps<HTMLDivElement>, 'onSelect' | 'ref' | 'checked' | 'selected'>,
|
|
52
|
+
OUIAProps {
|
|
53
|
+
/** Content rendered inside the Select. Must be React.ReactElement<SelectGroupProps>[] */
|
|
54
|
+
children?: React.ReactElement[];
|
|
55
|
+
/** Classes applied to the root of the Select */
|
|
56
|
+
className?: string;
|
|
57
|
+
/** Indicates where menu will be aligned horizontally */
|
|
58
|
+
position?: SelectPosition | 'right' | 'left';
|
|
59
|
+
/** Flag specifying which direction the Select menu expands */
|
|
60
|
+
direction?: 'up' | 'down';
|
|
61
|
+
/** Flag to indicate if select is open */
|
|
62
|
+
isOpen?: boolean;
|
|
63
|
+
/** Flag to indicate if select options are grouped */
|
|
64
|
+
isGrouped?: boolean;
|
|
65
|
+
/** Display the toggle with no border or background */
|
|
66
|
+
isPlain?: boolean;
|
|
67
|
+
/** Flag to indicate if select is disabled */
|
|
68
|
+
isDisabled?: boolean;
|
|
69
|
+
/** Flag to indicate if the typeahead select allows new items */
|
|
70
|
+
isCreatable?: boolean;
|
|
71
|
+
/** Flag to indicate if create option should be at top of typeahead */
|
|
72
|
+
isCreateOptionOnTop?: boolean;
|
|
73
|
+
/** Flag indicating if placeholder styles should be applied */
|
|
74
|
+
hasPlaceholderStyle?: boolean;
|
|
75
|
+
/** Flag indicating if the creatable option should set its value as a SelectOptionObject */
|
|
76
|
+
isCreateSelectOptionObject?: boolean;
|
|
77
|
+
/** Value to indicate if the select is modified to show that validation state.
|
|
78
|
+
* If set to success, select will be modified to indicate valid state.
|
|
79
|
+
* If set to error, select will be modified to indicate error state.
|
|
80
|
+
* If set to warning, select will be modified to indicate warning state.
|
|
81
|
+
*/
|
|
82
|
+
validated?: 'success' | 'warning' | 'error' | 'default';
|
|
83
|
+
/** Loading variant to display either the spinner or the view more text button */
|
|
84
|
+
loadingVariant?: 'spinner' | SelectViewMoreObject;
|
|
85
|
+
/** Text displayed in typeahead select to prompt the user to create an item */
|
|
86
|
+
createText?: string;
|
|
87
|
+
/** Title text of Select */
|
|
88
|
+
placeholderText?: string | React.ReactNode;
|
|
89
|
+
/** Text to display in typeahead select when no results are found */
|
|
90
|
+
noResultsFoundText?: string;
|
|
91
|
+
/** Array of selected items for multi select variants. */
|
|
92
|
+
selections?: string | SelectOptionObject | (string | SelectOptionObject)[];
|
|
93
|
+
/** Flag indicating if selection badge should be hidden for checkbox variant,default false */
|
|
94
|
+
isCheckboxSelectionBadgeHidden?: boolean;
|
|
95
|
+
/** Id for select toggle element */
|
|
96
|
+
toggleId?: string;
|
|
97
|
+
/** Ref for the select toggle element */
|
|
98
|
+
toggleRef?: React.Ref<HTMLButtonElement> | React.Ref<HTMLDivElement>;
|
|
99
|
+
/** Adds accessible text to Select */
|
|
100
|
+
'aria-label'?: string;
|
|
101
|
+
/** Id of label for the Select aria-labelledby */
|
|
102
|
+
'aria-labelledby'?: string;
|
|
103
|
+
/** Id of div for the select aria-labelledby */
|
|
104
|
+
'aria-describedby'?: string;
|
|
105
|
+
/** Flag indicating if the select is an invalid state */
|
|
106
|
+
'aria-invalid'?: boolean;
|
|
107
|
+
/** Label for input field of type ahead select variants */
|
|
108
|
+
typeAheadAriaLabel?: string;
|
|
109
|
+
/** Id of div for input field of type ahead select variants */
|
|
110
|
+
typeAheadAriaDescribedby?: string;
|
|
111
|
+
/** Label for clear selection button of type ahead select variants */
|
|
112
|
+
clearSelectionsAriaLabel?: string;
|
|
113
|
+
/** Label for toggle of type ahead select variants */
|
|
114
|
+
toggleAriaLabel?: string;
|
|
115
|
+
/** Label for remove chip button of multiple type ahead select variant */
|
|
116
|
+
removeSelectionAriaLabel?: string;
|
|
117
|
+
/** ID list of favorited select items */
|
|
118
|
+
favorites?: string[];
|
|
119
|
+
/** Label for the favorites group */
|
|
120
|
+
favoritesLabel?: string;
|
|
121
|
+
/** Enables favorites. Callback called when a select options's favorite button is clicked */
|
|
122
|
+
onFavorite?: (itemId: string, isFavorite: boolean) => void;
|
|
123
|
+
/** Callback for selection behavior */
|
|
124
|
+
onSelect?: (
|
|
125
|
+
event: React.MouseEvent | React.ChangeEvent,
|
|
126
|
+
value: string | SelectOptionObject,
|
|
127
|
+
isPlaceholder?: boolean
|
|
128
|
+
) => void;
|
|
129
|
+
/** Callback for toggle button behavior */
|
|
130
|
+
onToggle: (event: React.MouseEvent | React.ChangeEvent | React.KeyboardEvent | Event, isExpanded: boolean) => void;
|
|
131
|
+
/** Callback for toggle blur */
|
|
132
|
+
onBlur?: (event?: any) => void;
|
|
133
|
+
/** Callback for typeahead clear button */
|
|
134
|
+
onClear?: (event: React.MouseEvent) => void;
|
|
135
|
+
/** Optional callback for custom filtering */
|
|
136
|
+
onFilter?: (e: React.ChangeEvent<HTMLInputElement> | null, value: string) => React.ReactElement[] | undefined;
|
|
137
|
+
/** Optional callback for newly created options */
|
|
138
|
+
onCreateOption?: (newOptionValue: string) => void;
|
|
139
|
+
/** Optional event handler called each time the value in the typeahead input changes. */
|
|
140
|
+
onTypeaheadInputChanged?: (value: string) => void;
|
|
141
|
+
/** Variant of rendered Select */
|
|
142
|
+
variant?: 'single' | 'checkbox' | 'typeahead' | 'typeaheadmulti';
|
|
143
|
+
/** Width of the select container as a number of px or string percentage */
|
|
144
|
+
width?: string | number;
|
|
145
|
+
/** Max height of the select container as a number of px or string percentage */
|
|
146
|
+
maxHeight?: string | number;
|
|
147
|
+
/** Icon element to render inside the select toggle */
|
|
148
|
+
toggleIcon?: React.ReactElement;
|
|
149
|
+
/** Custom icon for the dropdown replacing the CaretDownIcon */
|
|
150
|
+
toggleIndicator?: React.ReactElement;
|
|
151
|
+
/** Custom content to render in the select menu. If this prop is defined, the variant prop will be ignored and the select will render with a single select toggle */
|
|
152
|
+
customContent?: React.ReactNode;
|
|
153
|
+
/** Flag indicating if select should have an inline text input for filtering */
|
|
154
|
+
hasInlineFilter?: boolean;
|
|
155
|
+
/** Placeholder text for inline filter */
|
|
156
|
+
inlineFilterPlaceholderText?: string;
|
|
157
|
+
/** Custom text for select badge */
|
|
158
|
+
customBadgeText?: string | number;
|
|
159
|
+
/** Prefix for the id of the input in the checkbox select variant*/
|
|
160
|
+
inputIdPrefix?: string;
|
|
161
|
+
/** Value for the typeahead and inline filtering input autocomplete attribute. When targeting Chrome this property should be a random string. */
|
|
162
|
+
inputAutoComplete?: string;
|
|
163
|
+
/** Optional props to pass to the chip group in the typeaheadmulti variant */
|
|
164
|
+
chipGroupProps?: Omit<ChipGroupProps, 'children' | 'ref'>;
|
|
165
|
+
/** Optional props to render custom chip group in the typeaheadmulti variant */
|
|
166
|
+
chipGroupComponent?: React.ReactNode;
|
|
167
|
+
/** Flag for retaining keyboard-entered value in typeahead text field when focus leaves input away */
|
|
168
|
+
isInputValuePersisted?: boolean;
|
|
169
|
+
/** Flag for retaining filter results on blur from keyboard-entered typeahead text */
|
|
170
|
+
isInputFilterPersisted?: boolean;
|
|
171
|
+
/** Flag indicating the typeahead input value should reset upon selection */
|
|
172
|
+
shouldResetOnSelect?: boolean;
|
|
173
|
+
/** Content rendered in the footer of the select menu */
|
|
174
|
+
footer?: React.ReactNode;
|
|
175
|
+
/** The container to append the menu to. Defaults to 'inline'.
|
|
176
|
+
* If your menu is being cut off you can append it to an element higher up the DOM tree.
|
|
177
|
+
* Some examples:
|
|
178
|
+
* menuAppendTo="parent"
|
|
179
|
+
* menuAppendTo={() => document.body}
|
|
180
|
+
* menuAppendTo={document.getElementById('target')}
|
|
181
|
+
*/
|
|
182
|
+
menuAppendTo?: HTMLElement | (() => HTMLElement) | 'inline' | 'parent';
|
|
183
|
+
/** Flag for indicating that the select menu should automatically flip vertically when
|
|
184
|
+
* it reaches the boundary. This prop can only be used when the select component is not
|
|
185
|
+
* appended inline, e.g. `menuAppendTo="parent"`
|
|
186
|
+
*/
|
|
187
|
+
isFlipEnabled?: boolean;
|
|
188
|
+
/** z-index of the select menu when menuAppendTo is not inline. */
|
|
189
|
+
zIndex?: number;
|
|
190
|
+
/** Value to overwrite the randomly generated data-ouia-component-id.*/
|
|
191
|
+
ouiaId?: number | string;
|
|
192
|
+
/** Set the value of data-ouia-safe. Only set to true when the component is in a static state, i.e. no animations are occurring. At all other times, this value must be false. */
|
|
193
|
+
ouiaSafe?: boolean;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface SelectState {
|
|
197
|
+
focusFirstOption: boolean;
|
|
198
|
+
typeaheadInputValue: string | null;
|
|
199
|
+
typeaheadFilteredChildren: React.ReactNode[];
|
|
200
|
+
favoritesGroup: React.ReactNode[];
|
|
201
|
+
typeaheadCurrIndex: number;
|
|
202
|
+
creatableValue: string;
|
|
203
|
+
tabbedIntoFavoritesMenu: boolean;
|
|
204
|
+
typeaheadStoredIndex: number;
|
|
205
|
+
ouiaStateId: string;
|
|
206
|
+
viewMoreNextIndex: number;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export class Select extends React.Component<SelectProps & OUIAProps, SelectState> {
|
|
210
|
+
static displayName = 'Select';
|
|
211
|
+
private parentRef = React.createRef<HTMLDivElement>();
|
|
212
|
+
private menuComponentRef = React.createRef<HTMLElement>();
|
|
213
|
+
private filterRef = React.createRef<HTMLInputElement>();
|
|
214
|
+
private clearRef = React.createRef<HTMLButtonElement>();
|
|
215
|
+
private inputRef = React.createRef<HTMLInputElement>();
|
|
216
|
+
private refCollection: HTMLElement[][] = [[]];
|
|
217
|
+
private optionContainerRefCollection: HTMLElement[] = [];
|
|
218
|
+
private footerRef = React.createRef<HTMLDivElement>();
|
|
219
|
+
|
|
220
|
+
static defaultProps: PickOptional<SelectProps> = {
|
|
221
|
+
children: [] as React.ReactElement[],
|
|
222
|
+
className: '',
|
|
223
|
+
position: SelectPosition.left,
|
|
224
|
+
direction: SelectDirection.down,
|
|
225
|
+
toggleId: null as string,
|
|
226
|
+
isOpen: false,
|
|
227
|
+
isGrouped: false,
|
|
228
|
+
isPlain: false,
|
|
229
|
+
isDisabled: false,
|
|
230
|
+
hasPlaceholderStyle: false,
|
|
231
|
+
isCreatable: false,
|
|
232
|
+
isCreateOptionOnTop: false,
|
|
233
|
+
validated: 'default',
|
|
234
|
+
'aria-label': '',
|
|
235
|
+
'aria-labelledby': '',
|
|
236
|
+
'aria-describedby': '',
|
|
237
|
+
'aria-invalid': false,
|
|
238
|
+
typeAheadAriaLabel: '',
|
|
239
|
+
typeAheadAriaDescribedby: '',
|
|
240
|
+
clearSelectionsAriaLabel: 'Clear all',
|
|
241
|
+
toggleAriaLabel: 'Options menu',
|
|
242
|
+
removeSelectionAriaLabel: 'Remove',
|
|
243
|
+
selections: [],
|
|
244
|
+
createText: 'Create',
|
|
245
|
+
placeholderText: '',
|
|
246
|
+
noResultsFoundText: 'No results found',
|
|
247
|
+
variant: SelectVariant.single,
|
|
248
|
+
width: '',
|
|
249
|
+
onClear: () => undefined as void,
|
|
250
|
+
onCreateOption: () => undefined as void,
|
|
251
|
+
toggleIcon: null as React.ReactElement,
|
|
252
|
+
toggleIndicator: null as React.ReactElement,
|
|
253
|
+
onFilter: null,
|
|
254
|
+
onTypeaheadInputChanged: null,
|
|
255
|
+
customContent: null,
|
|
256
|
+
hasInlineFilter: false,
|
|
257
|
+
inlineFilterPlaceholderText: null,
|
|
258
|
+
customBadgeText: null,
|
|
259
|
+
inputIdPrefix: '',
|
|
260
|
+
inputAutoComplete: 'off',
|
|
261
|
+
menuAppendTo: 'inline',
|
|
262
|
+
favorites: [] as string[],
|
|
263
|
+
favoritesLabel: 'Favorites',
|
|
264
|
+
ouiaSafe: true,
|
|
265
|
+
chipGroupComponent: null,
|
|
266
|
+
isInputValuePersisted: false,
|
|
267
|
+
isInputFilterPersisted: false,
|
|
268
|
+
isCreateSelectOptionObject: false,
|
|
269
|
+
shouldResetOnSelect: true,
|
|
270
|
+
isFlipEnabled: true,
|
|
271
|
+
zIndex: 9999
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
state: SelectState = {
|
|
275
|
+
focusFirstOption: false,
|
|
276
|
+
typeaheadInputValue: null,
|
|
277
|
+
typeaheadFilteredChildren: React.Children.toArray(this.props.children),
|
|
278
|
+
favoritesGroup: [] as React.ReactNode[],
|
|
279
|
+
typeaheadCurrIndex: -1,
|
|
280
|
+
typeaheadStoredIndex: -1,
|
|
281
|
+
creatableValue: '',
|
|
282
|
+
tabbedIntoFavoritesMenu: false,
|
|
283
|
+
ouiaStateId: getDefaultOUIAId(Select.displayName, this.props.variant),
|
|
284
|
+
viewMoreNextIndex: -1
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
getTypeaheadActiveChild = (typeaheadCurrIndex: number) =>
|
|
288
|
+
this.refCollection[typeaheadCurrIndex] ? this.refCollection[typeaheadCurrIndex][0] : null;
|
|
289
|
+
|
|
290
|
+
componentDidUpdate = (prevProps: SelectProps, prevState: SelectState) => {
|
|
291
|
+
if (this.props.hasInlineFilter) {
|
|
292
|
+
this.refCollection[0][0] = this.filterRef.current;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Move focus to top of the menu if state.focusFirstOption was updated to true and the menu does not have custom content
|
|
296
|
+
if (!prevState.focusFirstOption && this.state.focusFirstOption && !this.props.customContent) {
|
|
297
|
+
const firstRef = this.refCollection.find(
|
|
298
|
+
ref =>
|
|
299
|
+
// If a select option is disabled then ref[0] will be undefined, so we want to return
|
|
300
|
+
// the first ref that both a) is not null and b) is not disabled.
|
|
301
|
+
ref !== null && ref[0]
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
if (firstRef && firstRef[0]) {
|
|
305
|
+
firstRef[0].focus();
|
|
306
|
+
}
|
|
307
|
+
} else if (
|
|
308
|
+
// if viewMoreNextIndex is not -1, view more was clicked, set focus on first newly loaded item
|
|
309
|
+
this.state.viewMoreNextIndex !== -1 &&
|
|
310
|
+
this.refCollection.length > this.state.viewMoreNextIndex &&
|
|
311
|
+
this.props.loadingVariant !== 'spinner' &&
|
|
312
|
+
this.refCollection[this.state.viewMoreNextIndex][0] &&
|
|
313
|
+
this.props.variant !== 'typeahead' && // do not hard focus newly added items for typeahead variants
|
|
314
|
+
this.props.variant !== 'typeaheadmulti'
|
|
315
|
+
) {
|
|
316
|
+
this.refCollection[this.state.viewMoreNextIndex][0].focus();
|
|
317
|
+
this.setState({ viewMoreNextIndex: -1 });
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const checkUpdatedChildren = (prevChildren: React.ReactElement[], currChildren: React.ReactElement[]) =>
|
|
321
|
+
Array.from(prevChildren).some((prevChild: React.ReactElement, index: number) => {
|
|
322
|
+
const prevChildProps = prevChild.props;
|
|
323
|
+
const currChild = currChildren[index];
|
|
324
|
+
const { props: currChildProps } = currChild;
|
|
325
|
+
|
|
326
|
+
if (prevChildProps && currChildProps) {
|
|
327
|
+
return (
|
|
328
|
+
prevChildProps.value !== currChildProps.value ||
|
|
329
|
+
prevChildProps.label !== currChildProps.label ||
|
|
330
|
+
prevChildProps.isDisabled !== currChildProps.isDisabled ||
|
|
331
|
+
prevChildProps.isPlaceholder !== currChildProps.isPlaceholder
|
|
332
|
+
);
|
|
333
|
+
} else {
|
|
334
|
+
return prevChild !== currChild;
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const hasUpdatedChildren =
|
|
339
|
+
prevProps.children.length !== this.props.children.length ||
|
|
340
|
+
checkUpdatedChildren(prevProps.children, this.props.children) ||
|
|
341
|
+
(this.props.isGrouped &&
|
|
342
|
+
Array.from(prevProps.children).some(
|
|
343
|
+
(prevChild: React.ReactElement, index: number) =>
|
|
344
|
+
prevChild.type === SelectGroup &&
|
|
345
|
+
prevChild.props.children &&
|
|
346
|
+
this.props.children[index].props.children &&
|
|
347
|
+
(prevChild.props.children.length !== this.props.children[index].props.children.length ||
|
|
348
|
+
checkUpdatedChildren(prevChild.props.children, this.props.children[index].props.children))
|
|
349
|
+
));
|
|
350
|
+
|
|
351
|
+
if (hasUpdatedChildren) {
|
|
352
|
+
this.updateTypeAheadFilteredChildren(prevState.typeaheadInputValue || '', null);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// for menus with favorites,
|
|
356
|
+
// if the number of favorites or typeahead filtered children has changed, the generated
|
|
357
|
+
// list of favorites needs to be updated
|
|
358
|
+
if (
|
|
359
|
+
this.props.onFavorite &&
|
|
360
|
+
(this.props.favorites.length !== prevProps.favorites.length ||
|
|
361
|
+
this.state.typeaheadFilteredChildren !== prevState.typeaheadFilteredChildren)
|
|
362
|
+
) {
|
|
363
|
+
const tempRenderableChildren =
|
|
364
|
+
this.props.variant === 'typeahead' || this.props.variant === 'typeaheadmulti'
|
|
365
|
+
? this.state.typeaheadFilteredChildren
|
|
366
|
+
: this.props.children;
|
|
367
|
+
const renderableFavorites = createRenderableFavorites(
|
|
368
|
+
tempRenderableChildren,
|
|
369
|
+
this.props.isGrouped,
|
|
370
|
+
this.props.favorites
|
|
371
|
+
);
|
|
372
|
+
const favoritesGroup = renderableFavorites.length
|
|
373
|
+
? [
|
|
374
|
+
<SelectGroup key="favorites" label={this.props.favoritesLabel}>
|
|
375
|
+
{renderableFavorites}
|
|
376
|
+
</SelectGroup>,
|
|
377
|
+
<Divider key="favorites-group-divider" />
|
|
378
|
+
]
|
|
379
|
+
: [];
|
|
380
|
+
this.setState({ favoritesGroup });
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
onEnter = () => {
|
|
385
|
+
this.setState({ focusFirstOption: true });
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
onToggle = (e: React.MouseEvent | React.ChangeEvent | React.KeyboardEvent | Event, isExpanded: boolean) => {
|
|
389
|
+
const { isInputValuePersisted, onSelect, onToggle, hasInlineFilter } = this.props;
|
|
390
|
+
if (!isExpanded && isInputValuePersisted && onSelect) {
|
|
391
|
+
onSelect(undefined, this.inputRef.current ? this.inputRef.current.value : '');
|
|
392
|
+
}
|
|
393
|
+
if (isExpanded && hasInlineFilter) {
|
|
394
|
+
this.setState({
|
|
395
|
+
focusFirstOption: true
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
onToggle(e, isExpanded);
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
onClose = () => {
|
|
402
|
+
const { isInputFilterPersisted } = this.props;
|
|
403
|
+
|
|
404
|
+
this.setState({
|
|
405
|
+
focusFirstOption: false,
|
|
406
|
+
typeaheadInputValue: null,
|
|
407
|
+
...(!isInputFilterPersisted && {
|
|
408
|
+
typeaheadFilteredChildren: React.Children.toArray(this.props.children)
|
|
409
|
+
}),
|
|
410
|
+
typeaheadCurrIndex: -1,
|
|
411
|
+
tabbedIntoFavoritesMenu: false,
|
|
412
|
+
viewMoreNextIndex: -1
|
|
413
|
+
});
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
417
|
+
if (e.target.value.toString() !== '' && !this.props.isOpen) {
|
|
418
|
+
this.onToggle(e, true);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (this.props.onTypeaheadInputChanged) {
|
|
422
|
+
this.props.onTypeaheadInputChanged(e.target.value.toString());
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
this.setState({
|
|
426
|
+
typeaheadCurrIndex: -1,
|
|
427
|
+
typeaheadInputValue: e.target.value,
|
|
428
|
+
creatableValue: e.target.value
|
|
429
|
+
});
|
|
430
|
+
this.updateTypeAheadFilteredChildren(e.target.value.toString(), e);
|
|
431
|
+
this.refCollection = [[]];
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
updateTypeAheadFilteredChildren = (typeaheadInputValue: string, e: React.ChangeEvent<HTMLInputElement> | null) => {
|
|
435
|
+
let typeaheadFilteredChildren: any;
|
|
436
|
+
|
|
437
|
+
const {
|
|
438
|
+
onFilter,
|
|
439
|
+
isCreatable,
|
|
440
|
+
isCreateOptionOnTop,
|
|
441
|
+
onCreateOption,
|
|
442
|
+
createText,
|
|
443
|
+
noResultsFoundText,
|
|
444
|
+
children,
|
|
445
|
+
isGrouped,
|
|
446
|
+
isCreateSelectOptionObject,
|
|
447
|
+
loadingVariant
|
|
448
|
+
} = this.props;
|
|
449
|
+
|
|
450
|
+
if (onFilter) {
|
|
451
|
+
/* The updateTypeAheadFilteredChildren callback is not only called on input changes but also when the children change.
|
|
452
|
+
* In this case the e is null but we can get the typeaheadInputValue from the state.
|
|
453
|
+
*/
|
|
454
|
+
typeaheadFilteredChildren = onFilter(e, e ? e.target.value : typeaheadInputValue) || children;
|
|
455
|
+
} else {
|
|
456
|
+
let input: RegExp;
|
|
457
|
+
try {
|
|
458
|
+
input = new RegExp(typeaheadInputValue.toString(), 'i');
|
|
459
|
+
} catch (err) {
|
|
460
|
+
input = new RegExp(typeaheadInputValue.toString().replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
|
|
461
|
+
}
|
|
462
|
+
const childrenArray = React.Children.toArray(children) as React.ReactElement<SelectGroupProps>[];
|
|
463
|
+
if (isGrouped) {
|
|
464
|
+
const childFilter = (child: React.ReactElement<SelectGroupProps>) =>
|
|
465
|
+
child.props.value &&
|
|
466
|
+
child.props.value.toString &&
|
|
467
|
+
this.getDisplay(child.props.value.toString(), 'text').search(input) === 0;
|
|
468
|
+
typeaheadFilteredChildren =
|
|
469
|
+
typeaheadInputValue.toString() !== ''
|
|
470
|
+
? React.Children.map(children, group => {
|
|
471
|
+
if (
|
|
472
|
+
React.isValidElement<React.ComponentProps<typeof SelectGroup>>(group) &&
|
|
473
|
+
group.type === SelectGroup
|
|
474
|
+
) {
|
|
475
|
+
const filteredGroupChildren = (React.Children.toArray(group.props.children) as React.ReactElement<
|
|
476
|
+
SelectGroupProps
|
|
477
|
+
>[]).filter(childFilter);
|
|
478
|
+
if (filteredGroupChildren.length > 0) {
|
|
479
|
+
return React.cloneElement(group, {
|
|
480
|
+
titleId: group.props.label && group.props.label.replace(/\W/g, '-'),
|
|
481
|
+
children: filteredGroupChildren as any
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
} else {
|
|
485
|
+
return (React.Children.toArray(group) as React.ReactElement<SelectGroupProps>[]).filter(childFilter);
|
|
486
|
+
}
|
|
487
|
+
})
|
|
488
|
+
: childrenArray;
|
|
489
|
+
} else {
|
|
490
|
+
typeaheadFilteredChildren =
|
|
491
|
+
typeaheadInputValue.toString() !== ''
|
|
492
|
+
? childrenArray.filter(child => {
|
|
493
|
+
const valueToCheck = child.props.value;
|
|
494
|
+
// Dividers don't have value and should not be filtered
|
|
495
|
+
if (!valueToCheck) {
|
|
496
|
+
return true;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const isSelectOptionObject =
|
|
500
|
+
typeof valueToCheck !== 'string' &&
|
|
501
|
+
(valueToCheck as SelectOptionObject).toString &&
|
|
502
|
+
(valueToCheck as SelectOptionObject).compareTo;
|
|
503
|
+
|
|
504
|
+
// View more option should be returned as not a match
|
|
505
|
+
if (loadingVariant !== 'spinner' && loadingVariant?.text === valueToCheck) {
|
|
506
|
+
return true;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// spinner should be returned as not a match
|
|
510
|
+
if (loadingVariant === 'spinner' && valueToCheck === 'loading') {
|
|
511
|
+
return true;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (isSelectOptionObject) {
|
|
515
|
+
return (valueToCheck as SelectOptionObject).compareTo(typeaheadInputValue);
|
|
516
|
+
} else {
|
|
517
|
+
return this.getDisplay(child.props.value.toString(), 'text').search(input) === 0;
|
|
518
|
+
}
|
|
519
|
+
})
|
|
520
|
+
: childrenArray;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (!typeaheadFilteredChildren) {
|
|
524
|
+
typeaheadFilteredChildren = [];
|
|
525
|
+
}
|
|
526
|
+
if (typeaheadFilteredChildren.length === 0) {
|
|
527
|
+
!isCreatable &&
|
|
528
|
+
typeaheadFilteredChildren.push(
|
|
529
|
+
<SelectOption isDisabled key="no-results" value={noResultsFoundText} isNoResultsOption />
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
if (isCreatable && typeaheadInputValue !== '') {
|
|
533
|
+
const newValue = typeaheadInputValue;
|
|
534
|
+
if (
|
|
535
|
+
!typeaheadFilteredChildren.find(
|
|
536
|
+
(i: React.ReactElement) =>
|
|
537
|
+
i.props.value && i.props.value.toString().toLowerCase() === newValue.toString().toLowerCase()
|
|
538
|
+
)
|
|
539
|
+
) {
|
|
540
|
+
const newOptionValue = isCreateSelectOptionObject
|
|
541
|
+
? ({
|
|
542
|
+
toString: () => newValue,
|
|
543
|
+
compareTo: value =>
|
|
544
|
+
this.toString()
|
|
545
|
+
.toLowerCase()
|
|
546
|
+
.includes(value.toString().toLowerCase())
|
|
547
|
+
} as SelectOptionObject)
|
|
548
|
+
: newValue;
|
|
549
|
+
|
|
550
|
+
const createSelectOption = (
|
|
551
|
+
<SelectOption
|
|
552
|
+
key={`create ${newValue}`}
|
|
553
|
+
value={newOptionValue}
|
|
554
|
+
onClick={() => onCreateOption && onCreateOption(newValue)}
|
|
555
|
+
>
|
|
556
|
+
{createText} "{newValue}"
|
|
557
|
+
</SelectOption>
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
if (isCreateOptionOnTop) {
|
|
561
|
+
typeaheadFilteredChildren.unshift(createSelectOption);
|
|
562
|
+
} else {
|
|
563
|
+
typeaheadFilteredChildren.push(createSelectOption);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
this.setState({
|
|
569
|
+
typeaheadFilteredChildren
|
|
570
|
+
});
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
onClick = (e: React.MouseEvent) => {
|
|
574
|
+
if (!this.props.isOpen) {
|
|
575
|
+
this.onToggle(e, true);
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
clearSelection = (_e: React.MouseEvent) => {
|
|
580
|
+
this.setState({
|
|
581
|
+
typeaheadInputValue: null,
|
|
582
|
+
typeaheadFilteredChildren: React.Children.toArray(this.props.children),
|
|
583
|
+
typeaheadCurrIndex: -1
|
|
584
|
+
});
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
extendTypeaheadChildren(typeaheadCurrIndex: number, favoritesGroup?: React.ReactNode[]) {
|
|
588
|
+
const { isGrouped, onFavorite, createText } = this.props;
|
|
589
|
+
const typeaheadChildren = favoritesGroup
|
|
590
|
+
? favoritesGroup.concat(this.state.typeaheadFilteredChildren)
|
|
591
|
+
: this.state.typeaheadFilteredChildren;
|
|
592
|
+
const activeElement = this.optionContainerRefCollection[typeaheadCurrIndex];
|
|
593
|
+
|
|
594
|
+
let typeaheadActiveChild = this.getTypeaheadActiveChild(typeaheadCurrIndex);
|
|
595
|
+
if (typeaheadActiveChild && typeaheadActiveChild.classList.contains('pf-m-description')) {
|
|
596
|
+
typeaheadActiveChild = typeaheadActiveChild.firstElementChild as HTMLElement;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
this.refCollection = [[]];
|
|
600
|
+
this.optionContainerRefCollection = [];
|
|
601
|
+
if (isGrouped) {
|
|
602
|
+
return React.Children.map(typeaheadChildren as React.ReactElement[], (group: React.ReactElement) => {
|
|
603
|
+
if (group.type === Divider) {
|
|
604
|
+
return group;
|
|
605
|
+
} else if (group.type === SelectGroup && onFavorite) {
|
|
606
|
+
return React.cloneElement(group, {
|
|
607
|
+
titleId: group.props.label && group.props.label.replace(/\W/g, '-'),
|
|
608
|
+
children: React.Children.map(group.props.children, (child: React.ReactElement) =>
|
|
609
|
+
child.type === Divider
|
|
610
|
+
? child
|
|
611
|
+
: React.cloneElement(child as React.ReactElement, {
|
|
612
|
+
isFocused:
|
|
613
|
+
activeElement &&
|
|
614
|
+
(activeElement.id === (child as React.ReactElement).props.id ||
|
|
615
|
+
(this.props.isCreatable &&
|
|
616
|
+
typeaheadActiveChild.textContent ===
|
|
617
|
+
`${createText} "${(group as React.ReactElement).props.value}"`))
|
|
618
|
+
})
|
|
619
|
+
)
|
|
620
|
+
});
|
|
621
|
+
} else if (group.type === SelectGroup) {
|
|
622
|
+
return React.cloneElement(group, {
|
|
623
|
+
titleId: group.props.label && group.props.label.replace(/\W/g, '-'),
|
|
624
|
+
children: React.Children.map(group.props.children, (child: React.ReactElement) =>
|
|
625
|
+
child.type === Divider
|
|
626
|
+
? child
|
|
627
|
+
: React.cloneElement(child as React.ReactElement, {
|
|
628
|
+
isFocused:
|
|
629
|
+
typeaheadActiveChild &&
|
|
630
|
+
(typeaheadActiveChild.textContent === (child as React.ReactElement).props.value.toString() ||
|
|
631
|
+
(this.props.isCreatable &&
|
|
632
|
+
typeaheadActiveChild.textContent ===
|
|
633
|
+
`${createText} "${(child as React.ReactElement).props.value}"`))
|
|
634
|
+
})
|
|
635
|
+
)
|
|
636
|
+
});
|
|
637
|
+
} else {
|
|
638
|
+
// group has been filtered down to SelectOption
|
|
639
|
+
return React.cloneElement(group as React.ReactElement, {
|
|
640
|
+
isFocused:
|
|
641
|
+
typeaheadActiveChild &&
|
|
642
|
+
(typeaheadActiveChild.textContent === group.props.value.toString() ||
|
|
643
|
+
(this.props.isCreatable && typeaheadActiveChild.textContent === `${createText} "${group.props.value}"`))
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
return typeaheadChildren.map((child: React.ReactNode, index) => {
|
|
649
|
+
const childElement = child as any;
|
|
650
|
+
return childElement.type.displayName === 'Divider'
|
|
651
|
+
? child
|
|
652
|
+
: React.cloneElement(child as React.ReactElement, {
|
|
653
|
+
isFocused: typeaheadActiveChild
|
|
654
|
+
? typeaheadActiveChild.textContent === (child as React.ReactElement).props.value.toString() ||
|
|
655
|
+
(this.props.isCreatable &&
|
|
656
|
+
typeaheadActiveChild.textContent === `${createText} "${(child as React.ReactElement).props.value}"`)
|
|
657
|
+
: index === typeaheadCurrIndex // fallback for view more + typeahead use cases, when the new expanded list is loaded and refCollection hasn't be updated yet
|
|
658
|
+
});
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
sendRef = (
|
|
663
|
+
optionRef: React.ReactNode,
|
|
664
|
+
favoriteRef: React.ReactNode,
|
|
665
|
+
optionContainerRef: React.ReactNode,
|
|
666
|
+
index: number
|
|
667
|
+
) => {
|
|
668
|
+
this.refCollection[index] = [(optionRef as unknown) as HTMLElement, (favoriteRef as unknown) as HTMLElement];
|
|
669
|
+
this.optionContainerRefCollection[index] = (optionContainerRef as unknown) as HTMLElement;
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
handleMenuKeys = (index: number, innerIndex: number, position: string) => {
|
|
673
|
+
keyHandler(index, innerIndex, position, this.refCollection, this.refCollection);
|
|
674
|
+
if (this.props.variant === SelectVariant.typeahead || this.props.variant === SelectVariant.typeaheadMulti) {
|
|
675
|
+
if (position !== 'tab') {
|
|
676
|
+
this.handleTypeaheadKeys(position);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
moveFocus = (nextIndex: number, updateCurrentIndex: boolean = true) => {
|
|
682
|
+
const { isCreatable, createText } = this.props;
|
|
683
|
+
|
|
684
|
+
const hasDescriptionElm = Boolean(
|
|
685
|
+
this.refCollection[nextIndex][0] && this.refCollection[nextIndex][0].classList.contains('pf-m-description')
|
|
686
|
+
);
|
|
687
|
+
const isLoad = Boolean(
|
|
688
|
+
this.refCollection[nextIndex][0] && this.refCollection[nextIndex][0].classList.contains('pf-m-load')
|
|
689
|
+
);
|
|
690
|
+
const optionTextElm = hasDescriptionElm
|
|
691
|
+
? (this.refCollection[nextIndex][0].firstElementChild as HTMLElement)
|
|
692
|
+
: this.refCollection[nextIndex][0];
|
|
693
|
+
|
|
694
|
+
let typeaheadInputValue = '';
|
|
695
|
+
if (isCreatable && optionTextElm.textContent.includes(createText)) {
|
|
696
|
+
typeaheadInputValue = this.state.creatableValue;
|
|
697
|
+
} else if (optionTextElm && !isLoad) {
|
|
698
|
+
// !isLoad prevents the view more button text from appearing the typeahead input
|
|
699
|
+
typeaheadInputValue = optionTextElm.textContent;
|
|
700
|
+
}
|
|
701
|
+
this.setState(prevState => ({
|
|
702
|
+
typeaheadCurrIndex: updateCurrentIndex ? nextIndex : prevState.typeaheadCurrIndex,
|
|
703
|
+
typeaheadStoredIndex: nextIndex,
|
|
704
|
+
typeaheadInputValue
|
|
705
|
+
}));
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
switchFocusToFavoriteMenu = () => {
|
|
709
|
+
const { typeaheadCurrIndex, typeaheadStoredIndex } = this.state;
|
|
710
|
+
let indexForFocus = 0;
|
|
711
|
+
|
|
712
|
+
if (typeaheadCurrIndex !== -1) {
|
|
713
|
+
indexForFocus = typeaheadCurrIndex;
|
|
714
|
+
} else if (typeaheadStoredIndex !== -1) {
|
|
715
|
+
indexForFocus = typeaheadStoredIndex;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
if (this.refCollection[indexForFocus] !== null && this.refCollection[indexForFocus][0] !== null) {
|
|
719
|
+
this.refCollection[indexForFocus][0].focus();
|
|
720
|
+
} else {
|
|
721
|
+
this.clearRef.current.focus();
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
this.setState({
|
|
725
|
+
tabbedIntoFavoritesMenu: true,
|
|
726
|
+
typeaheadCurrIndex: -1
|
|
727
|
+
});
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
moveFocusToLastMenuItem = () => {
|
|
731
|
+
const refCollectionLen = this.refCollection.length;
|
|
732
|
+
if (
|
|
733
|
+
refCollectionLen > 0 &&
|
|
734
|
+
this.refCollection[refCollectionLen - 1] !== null &&
|
|
735
|
+
this.refCollection[refCollectionLen - 1][0] !== null
|
|
736
|
+
) {
|
|
737
|
+
this.refCollection[refCollectionLen - 1][0].focus();
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
handleTypeaheadKeys = (position: string, shiftKey: boolean = false) => {
|
|
742
|
+
const { isOpen, onFavorite, isCreatable } = this.props;
|
|
743
|
+
const { typeaheadCurrIndex, tabbedIntoFavoritesMenu } = this.state;
|
|
744
|
+
const typeaheadActiveChild = this.getTypeaheadActiveChild(typeaheadCurrIndex);
|
|
745
|
+
if (isOpen) {
|
|
746
|
+
if (position === 'enter') {
|
|
747
|
+
if (
|
|
748
|
+
(typeaheadCurrIndex !== -1 || (isCreatable && this.refCollection.length === 1)) && // do not allow selection without moving to an initial option unless it is a single create option
|
|
749
|
+
(typeaheadActiveChild || (this.refCollection[0] && this.refCollection[0][0]))
|
|
750
|
+
) {
|
|
751
|
+
if (typeaheadActiveChild) {
|
|
752
|
+
if (!typeaheadActiveChild.classList.contains('pf-m-load')) {
|
|
753
|
+
const hasDescriptionElm = typeaheadActiveChild.childElementCount > 1;
|
|
754
|
+
const typeaheadActiveChildText = hasDescriptionElm
|
|
755
|
+
? (typeaheadActiveChild.firstChild as HTMLElement).textContent
|
|
756
|
+
: typeaheadActiveChild.textContent;
|
|
757
|
+
this.setState({
|
|
758
|
+
typeaheadInputValue: typeaheadActiveChildText
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
} else if (this.refCollection[0] && this.refCollection[0][0]) {
|
|
762
|
+
this.setState({
|
|
763
|
+
typeaheadInputValue: this.refCollection[0][0].textContent
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
if (typeaheadActiveChild) {
|
|
767
|
+
typeaheadActiveChild.click();
|
|
768
|
+
} else {
|
|
769
|
+
this.refCollection[0][0].click();
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
} else if (position === 'tab') {
|
|
773
|
+
if (onFavorite) {
|
|
774
|
+
// if the input has focus, tab to the first item or the last item that was previously focused.
|
|
775
|
+
if (this.inputRef.current === document.activeElement) {
|
|
776
|
+
// If shift is also clicked and there is a footer, tab to the last item in tabbable footer
|
|
777
|
+
if (this.props.footer && shiftKey) {
|
|
778
|
+
const tabbableItems = findTabbableElements(this.footerRef, SelectFooterTabbableItems);
|
|
779
|
+
if (tabbableItems.length > 0) {
|
|
780
|
+
if (tabbableItems[tabbableItems.length - 1]) {
|
|
781
|
+
tabbableItems[tabbableItems.length - 1].focus();
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
} else {
|
|
785
|
+
this.switchFocusToFavoriteMenu();
|
|
786
|
+
}
|
|
787
|
+
} else {
|
|
788
|
+
// focus is on menu or footer
|
|
789
|
+
if (this.props.footer) {
|
|
790
|
+
let tabbedIntoMenu = false;
|
|
791
|
+
const tabbableItems = findTabbableElements(this.footerRef, SelectFooterTabbableItems);
|
|
792
|
+
if (tabbableItems.length > 0) {
|
|
793
|
+
// if current element is not in footer, tab to first tabbable element in footer,
|
|
794
|
+
// if shift was clicked, tab to input since focus is on menu
|
|
795
|
+
const currentElementIndex = tabbableItems.findIndex((item: any) => item === document.activeElement);
|
|
796
|
+
if (currentElementIndex === -1) {
|
|
797
|
+
if (shiftKey) {
|
|
798
|
+
// currently in menu, shift back to input
|
|
799
|
+
this.inputRef.current.focus();
|
|
800
|
+
} else {
|
|
801
|
+
// currently in menu, tab to first tabbable item in footer
|
|
802
|
+
tabbableItems[0].focus();
|
|
803
|
+
}
|
|
804
|
+
} else {
|
|
805
|
+
// already in footer
|
|
806
|
+
if (shiftKey) {
|
|
807
|
+
// shift to previous item
|
|
808
|
+
if (currentElementIndex === 0) {
|
|
809
|
+
// on first footer item, shift back to menu
|
|
810
|
+
this.switchFocusToFavoriteMenu();
|
|
811
|
+
tabbedIntoMenu = true;
|
|
812
|
+
} else {
|
|
813
|
+
// shift to previous footer item
|
|
814
|
+
tabbableItems[currentElementIndex - 1].focus();
|
|
815
|
+
}
|
|
816
|
+
} else {
|
|
817
|
+
// tab to next tabbable item in footer or to input.
|
|
818
|
+
if (tabbableItems[currentElementIndex + 1]) {
|
|
819
|
+
tabbableItems[currentElementIndex + 1].focus();
|
|
820
|
+
} else {
|
|
821
|
+
this.inputRef.current.focus();
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
} else {
|
|
826
|
+
// no tabbable items in footer, tab to input
|
|
827
|
+
this.inputRef.current.focus();
|
|
828
|
+
tabbedIntoMenu = false;
|
|
829
|
+
}
|
|
830
|
+
this.setState({ tabbedIntoFavoritesMenu: tabbedIntoMenu });
|
|
831
|
+
} else {
|
|
832
|
+
this.inputRef.current.focus();
|
|
833
|
+
this.setState({ tabbedIntoFavoritesMenu: false });
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
} else {
|
|
837
|
+
// Close if there is no footer
|
|
838
|
+
if (!this.props.footer) {
|
|
839
|
+
this.onToggle(null, false);
|
|
840
|
+
this.onClose();
|
|
841
|
+
} else {
|
|
842
|
+
// has footer
|
|
843
|
+
const tabbableItems = findTabbableElements(this.footerRef, SelectFooterTabbableItems);
|
|
844
|
+
const currentElementIndex = tabbableItems.findIndex((item: any) => item === document.activeElement);
|
|
845
|
+
if (this.inputRef.current === document.activeElement) {
|
|
846
|
+
if (shiftKey) {
|
|
847
|
+
// close toggle if shift key and tab on input
|
|
848
|
+
this.onToggle(null, false);
|
|
849
|
+
this.onClose();
|
|
850
|
+
} else {
|
|
851
|
+
// tab to first tabbable item in footer
|
|
852
|
+
if (tabbableItems[0]) {
|
|
853
|
+
tabbableItems[0].focus();
|
|
854
|
+
} else {
|
|
855
|
+
this.onToggle(null, false);
|
|
856
|
+
this.onClose();
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
} else {
|
|
860
|
+
// focus is in footer
|
|
861
|
+
if (shiftKey) {
|
|
862
|
+
if (currentElementIndex === 0) {
|
|
863
|
+
// shift tab back to input
|
|
864
|
+
this.inputRef.current.focus();
|
|
865
|
+
} else {
|
|
866
|
+
// shift to previous footer item
|
|
867
|
+
tabbableItems[currentElementIndex - 1].focus();
|
|
868
|
+
}
|
|
869
|
+
} else {
|
|
870
|
+
// tab to next footer item or close tab if last item
|
|
871
|
+
if (tabbableItems[currentElementIndex + 1]) {
|
|
872
|
+
tabbableItems[currentElementIndex + 1].focus();
|
|
873
|
+
} else {
|
|
874
|
+
// no next item, close toggle
|
|
875
|
+
this.onToggle(null, false);
|
|
876
|
+
this.inputRef.current.focus();
|
|
877
|
+
this.onClose();
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
} else if (!tabbedIntoFavoritesMenu) {
|
|
884
|
+
if (this.refCollection[0][0] === null) {
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
let nextIndex;
|
|
888
|
+
if (typeaheadCurrIndex === -1 && position === 'down') {
|
|
889
|
+
nextIndex = 0;
|
|
890
|
+
} else if (typeaheadCurrIndex === -1 && position === 'up') {
|
|
891
|
+
nextIndex = this.refCollection.length - 1;
|
|
892
|
+
} else if (position !== 'left' && position !== 'right') {
|
|
893
|
+
nextIndex = getNextIndex(typeaheadCurrIndex, position, this.refCollection);
|
|
894
|
+
} else {
|
|
895
|
+
nextIndex = typeaheadCurrIndex;
|
|
896
|
+
}
|
|
897
|
+
if (this.refCollection[nextIndex] === null) {
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
this.moveFocus(nextIndex);
|
|
901
|
+
} else {
|
|
902
|
+
const nextIndex = this.refCollection.findIndex(
|
|
903
|
+
ref => ref !== undefined && (ref[0] === document.activeElement || ref[1] === document.activeElement)
|
|
904
|
+
);
|
|
905
|
+
this.moveFocus(nextIndex);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
onClickTypeaheadToggleButton = () => {
|
|
911
|
+
if (this.inputRef && this.inputRef.current) {
|
|
912
|
+
this.inputRef.current.focus();
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
getDisplay = (value: string | SelectOptionObject, type: 'node' | 'text' = 'node') => {
|
|
917
|
+
if (!value) {
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
const item = this.props.isGrouped
|
|
921
|
+
? (React.Children.toArray(this.props.children) as React.ReactElement[])
|
|
922
|
+
.reduce((acc, curr) => [...acc, ...React.Children.toArray(curr.props.children)], [])
|
|
923
|
+
.find(child => child.props.value.toString() === value.toString())
|
|
924
|
+
: React.Children.toArray(this.props.children).find(
|
|
925
|
+
child =>
|
|
926
|
+
(child as React.ReactElement).props.value &&
|
|
927
|
+
(child as React.ReactElement).props.value.toString() === value.toString()
|
|
928
|
+
);
|
|
929
|
+
if (item) {
|
|
930
|
+
if (item && item.props.children) {
|
|
931
|
+
if (type === 'node') {
|
|
932
|
+
return item.props.children;
|
|
933
|
+
}
|
|
934
|
+
return this.findText(item);
|
|
935
|
+
}
|
|
936
|
+
return item.props.value.toString();
|
|
937
|
+
}
|
|
938
|
+
return value.toString();
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
findText = (item: React.ReactNode) => {
|
|
942
|
+
if (typeof item === 'string') {
|
|
943
|
+
return item;
|
|
944
|
+
} else if (!React.isValidElement(item)) {
|
|
945
|
+
return '';
|
|
946
|
+
} else {
|
|
947
|
+
const multi: string[] = [];
|
|
948
|
+
React.Children.toArray(item.props.children).forEach(child => multi.push(this.findText(child)));
|
|
949
|
+
return multi.join('');
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
|
|
953
|
+
generateSelectedBadge = () => {
|
|
954
|
+
const { customBadgeText, selections } = this.props;
|
|
955
|
+
if (customBadgeText !== null) {
|
|
956
|
+
return customBadgeText;
|
|
957
|
+
}
|
|
958
|
+
if (Array.isArray(selections) && selections.length > 0) {
|
|
959
|
+
return selections.length;
|
|
960
|
+
}
|
|
961
|
+
return null;
|
|
962
|
+
};
|
|
963
|
+
|
|
964
|
+
setVieMoreNextIndex = () => {
|
|
965
|
+
this.setState({ viewMoreNextIndex: this.refCollection.length - 1 });
|
|
966
|
+
};
|
|
967
|
+
|
|
968
|
+
isLastOptionBeforeFooter = (index: any) =>
|
|
969
|
+
this.props.footer && index === this.refCollection.length - 1 ? true : false;
|
|
970
|
+
|
|
971
|
+
render() {
|
|
972
|
+
const {
|
|
973
|
+
children,
|
|
974
|
+
chipGroupProps,
|
|
975
|
+
chipGroupComponent,
|
|
976
|
+
className,
|
|
977
|
+
customContent,
|
|
978
|
+
variant,
|
|
979
|
+
direction,
|
|
980
|
+
onSelect,
|
|
981
|
+
onClear,
|
|
982
|
+
onBlur,
|
|
983
|
+
toggleId,
|
|
984
|
+
toggleRef,
|
|
985
|
+
isOpen,
|
|
986
|
+
isGrouped,
|
|
987
|
+
isPlain,
|
|
988
|
+
isDisabled,
|
|
989
|
+
hasPlaceholderStyle,
|
|
990
|
+
validated,
|
|
991
|
+
selections: selectionsProp,
|
|
992
|
+
typeAheadAriaLabel,
|
|
993
|
+
typeAheadAriaDescribedby,
|
|
994
|
+
clearSelectionsAriaLabel,
|
|
995
|
+
toggleAriaLabel,
|
|
996
|
+
removeSelectionAriaLabel,
|
|
997
|
+
'aria-label': ariaLabel,
|
|
998
|
+
'aria-labelledby': ariaLabelledBy,
|
|
999
|
+
'aria-describedby': ariaDescribedby,
|
|
1000
|
+
'aria-invalid': ariaInvalid,
|
|
1001
|
+
placeholderText,
|
|
1002
|
+
width,
|
|
1003
|
+
maxHeight,
|
|
1004
|
+
toggleIcon,
|
|
1005
|
+
toggleIndicator,
|
|
1006
|
+
ouiaId,
|
|
1007
|
+
ouiaSafe,
|
|
1008
|
+
hasInlineFilter,
|
|
1009
|
+
isCheckboxSelectionBadgeHidden,
|
|
1010
|
+
inlineFilterPlaceholderText,
|
|
1011
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
1012
|
+
onFilter,
|
|
1013
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
1014
|
+
onTypeaheadInputChanged,
|
|
1015
|
+
onCreateOption,
|
|
1016
|
+
isCreatable,
|
|
1017
|
+
onToggle,
|
|
1018
|
+
createText,
|
|
1019
|
+
noResultsFoundText,
|
|
1020
|
+
customBadgeText,
|
|
1021
|
+
inputIdPrefix,
|
|
1022
|
+
inputAutoComplete,
|
|
1023
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
1024
|
+
isInputValuePersisted,
|
|
1025
|
+
isInputFilterPersisted,
|
|
1026
|
+
/* eslint-enable @typescript-eslint/no-unused-vars */
|
|
1027
|
+
menuAppendTo,
|
|
1028
|
+
favorites,
|
|
1029
|
+
onFavorite,
|
|
1030
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
1031
|
+
favoritesLabel,
|
|
1032
|
+
footer,
|
|
1033
|
+
loadingVariant,
|
|
1034
|
+
isCreateSelectOptionObject,
|
|
1035
|
+
isCreateOptionOnTop,
|
|
1036
|
+
shouldResetOnSelect,
|
|
1037
|
+
isFlipEnabled,
|
|
1038
|
+
zIndex,
|
|
1039
|
+
...props
|
|
1040
|
+
} = this.props;
|
|
1041
|
+
const {
|
|
1042
|
+
focusFirstOption: openedOnEnter,
|
|
1043
|
+
typeaheadCurrIndex,
|
|
1044
|
+
typeaheadInputValue,
|
|
1045
|
+
typeaheadFilteredChildren,
|
|
1046
|
+
favoritesGroup
|
|
1047
|
+
} = this.state;
|
|
1048
|
+
const selectToggleId = toggleId || `pf-select-toggle-id-${currentId++}`;
|
|
1049
|
+
const selections = Array.isArray(selectionsProp) ? selectionsProp : [selectionsProp];
|
|
1050
|
+
// Find out if the selected option is a placeholder
|
|
1051
|
+
const selectedOption = React.Children.toArray(children).find(
|
|
1052
|
+
(option: any) => option.props.value === selections[0]
|
|
1053
|
+
) as any;
|
|
1054
|
+
const isSelectedPlaceholder = selectedOption && selectedOption.props.isPlaceholder;
|
|
1055
|
+
const hasAnySelections = Boolean(selections[0] && selections[0] !== '');
|
|
1056
|
+
const typeaheadActiveChild = this.getTypeaheadActiveChild(typeaheadCurrIndex);
|
|
1057
|
+
let childPlaceholderText = null as string;
|
|
1058
|
+
|
|
1059
|
+
// If onFavorites is set, add isFavorite prop to children and add a Favorites group to the SelectMenu
|
|
1060
|
+
let renderableItems: React.ReactNode[] = [];
|
|
1061
|
+
if (onFavorite) {
|
|
1062
|
+
// if variant is type-ahead call the extendTypeaheadChildren before adding favorites
|
|
1063
|
+
let tempExtendedChildren: (React.ReactElement | React.ReactNode | {})[] = children;
|
|
1064
|
+
if (variant === 'typeahead' || variant === 'typeaheadmulti') {
|
|
1065
|
+
tempExtendedChildren = this.extendTypeaheadChildren(typeaheadCurrIndex, favoritesGroup);
|
|
1066
|
+
} else if (onFavorite) {
|
|
1067
|
+
tempExtendedChildren = favoritesGroup.concat(children);
|
|
1068
|
+
}
|
|
1069
|
+
// mark items that are favorited with isFavorite
|
|
1070
|
+
renderableItems = extendItemsWithFavorite(tempExtendedChildren, isGrouped, favorites);
|
|
1071
|
+
} else {
|
|
1072
|
+
renderableItems = children;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
if (!customContent) {
|
|
1076
|
+
if (!hasAnySelections && !placeholderText) {
|
|
1077
|
+
const childPlaceholder = React.Children.toArray(children).filter(
|
|
1078
|
+
(child: React.ReactNode) => (child as React.ReactElement).props.isPlaceholder === true
|
|
1079
|
+
);
|
|
1080
|
+
childPlaceholderText =
|
|
1081
|
+
(childPlaceholder[0] && this.getDisplay((childPlaceholder[0] as React.ReactElement).props.value, 'node')) ||
|
|
1082
|
+
(children[0] && this.getDisplay(children[0].props.value, 'node'));
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
if (isOpen) {
|
|
1087
|
+
if (renderableItems.find(item => (item as any)?.key === 'loading') === undefined) {
|
|
1088
|
+
if (loadingVariant === 'spinner') {
|
|
1089
|
+
renderableItems.push(
|
|
1090
|
+
<SelectOption isLoading key="loading" value="loading">
|
|
1091
|
+
<Spinner size="lg" />
|
|
1092
|
+
</SelectOption>
|
|
1093
|
+
);
|
|
1094
|
+
} else if (loadingVariant?.text) {
|
|
1095
|
+
renderableItems.push(
|
|
1096
|
+
<SelectOption
|
|
1097
|
+
isLoad
|
|
1098
|
+
key="loading"
|
|
1099
|
+
value={loadingVariant.text}
|
|
1100
|
+
setViewMoreNextIndex={this.setVieMoreNextIndex}
|
|
1101
|
+
onClick={loadingVariant?.onClick}
|
|
1102
|
+
/>
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
const hasOnClear = onClear !== Select.defaultProps.onClear;
|
|
1109
|
+
const clearBtn = (
|
|
1110
|
+
<button
|
|
1111
|
+
className={css(buttonStyles.button, buttonStyles.modifiers.plain, styles.selectToggleClear)}
|
|
1112
|
+
onClick={e => {
|
|
1113
|
+
this.clearSelection(e);
|
|
1114
|
+
onClear(e);
|
|
1115
|
+
e.stopPropagation();
|
|
1116
|
+
}}
|
|
1117
|
+
aria-label={clearSelectionsAriaLabel}
|
|
1118
|
+
type="button"
|
|
1119
|
+
disabled={isDisabled}
|
|
1120
|
+
ref={this.clearRef}
|
|
1121
|
+
onKeyDown={event => {
|
|
1122
|
+
if (event.key === KeyTypes.Enter) {
|
|
1123
|
+
this.clearRef.current.click();
|
|
1124
|
+
}
|
|
1125
|
+
}}
|
|
1126
|
+
>
|
|
1127
|
+
<TimesCircleIcon aria-hidden />
|
|
1128
|
+
</button>
|
|
1129
|
+
);
|
|
1130
|
+
|
|
1131
|
+
let selectedChips = null as any;
|
|
1132
|
+
if (variant === SelectVariant.typeaheadMulti) {
|
|
1133
|
+
selectedChips = chipGroupComponent ? (
|
|
1134
|
+
chipGroupComponent
|
|
1135
|
+
) : (
|
|
1136
|
+
<ChipGroup {...chipGroupProps}>
|
|
1137
|
+
{selections &&
|
|
1138
|
+
(selections as string[]).map(item => (
|
|
1139
|
+
<Chip
|
|
1140
|
+
key={item}
|
|
1141
|
+
onClick={(e: React.MouseEvent) => onSelect(e, item)}
|
|
1142
|
+
closeBtnAriaLabel={removeSelectionAriaLabel}
|
|
1143
|
+
>
|
|
1144
|
+
{this.getDisplay(item, 'node')}
|
|
1145
|
+
</Chip>
|
|
1146
|
+
))}
|
|
1147
|
+
</ChipGroup>
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
if (hasInlineFilter) {
|
|
1152
|
+
const filterBox = (
|
|
1153
|
+
<React.Fragment>
|
|
1154
|
+
<div key="inline-filter" className={css(styles.selectMenuSearch)}>
|
|
1155
|
+
<input
|
|
1156
|
+
key="inline-filter-input"
|
|
1157
|
+
type="search"
|
|
1158
|
+
className={css(formStyles.formControl, formStyles.modifiers.search)}
|
|
1159
|
+
onChange={this.onChange}
|
|
1160
|
+
placeholder={inlineFilterPlaceholderText}
|
|
1161
|
+
onKeyDown={event => {
|
|
1162
|
+
if (event.key === KeyTypes.ArrowUp) {
|
|
1163
|
+
this.handleMenuKeys(0, 0, 'up');
|
|
1164
|
+
event.preventDefault();
|
|
1165
|
+
} else if (event.key === KeyTypes.ArrowDown) {
|
|
1166
|
+
this.handleMenuKeys(0, 0, 'down');
|
|
1167
|
+
event.preventDefault();
|
|
1168
|
+
} else if (event.key === KeyTypes.ArrowLeft) {
|
|
1169
|
+
this.handleMenuKeys(0, 0, 'left');
|
|
1170
|
+
event.preventDefault();
|
|
1171
|
+
} else if (event.key === KeyTypes.ArrowRight) {
|
|
1172
|
+
this.handleMenuKeys(0, 0, 'right');
|
|
1173
|
+
event.preventDefault();
|
|
1174
|
+
} else if (event.key === KeyTypes.Tab && variant !== SelectVariant.checkbox && this.props.footer) {
|
|
1175
|
+
// tab to footer or close menu if shift key
|
|
1176
|
+
if (event.shiftKey) {
|
|
1177
|
+
this.onToggle(event, false);
|
|
1178
|
+
} else {
|
|
1179
|
+
const tabbableItems = findTabbableElements(this.footerRef, SelectFooterTabbableItems);
|
|
1180
|
+
if (tabbableItems.length > 0) {
|
|
1181
|
+
tabbableItems[0].focus();
|
|
1182
|
+
event.stopPropagation();
|
|
1183
|
+
event.preventDefault();
|
|
1184
|
+
} else {
|
|
1185
|
+
this.onToggle(event, false);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
} else if (event.key === KeyTypes.Tab && variant === SelectVariant.checkbox) {
|
|
1189
|
+
// More modal-like experience for checkboxes
|
|
1190
|
+
// Let SelectOption handle this
|
|
1191
|
+
if (event.shiftKey) {
|
|
1192
|
+
this.handleMenuKeys(0, 0, 'up');
|
|
1193
|
+
} else {
|
|
1194
|
+
this.handleMenuKeys(0, 0, 'down');
|
|
1195
|
+
}
|
|
1196
|
+
event.stopPropagation();
|
|
1197
|
+
event.preventDefault();
|
|
1198
|
+
}
|
|
1199
|
+
}}
|
|
1200
|
+
ref={this.filterRef}
|
|
1201
|
+
autoComplete={inputAutoComplete}
|
|
1202
|
+
/>
|
|
1203
|
+
</div>
|
|
1204
|
+
<Divider key="inline-filter-divider" />
|
|
1205
|
+
</React.Fragment>
|
|
1206
|
+
);
|
|
1207
|
+
renderableItems = [filterBox, ...(typeaheadFilteredChildren as React.ReactElement[])].map((option, index) =>
|
|
1208
|
+
React.cloneElement(option, { key: index })
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
let variantProps: any;
|
|
1213
|
+
let variantChildren: any;
|
|
1214
|
+
if (customContent) {
|
|
1215
|
+
variantProps = {
|
|
1216
|
+
selected: selections,
|
|
1217
|
+
openedOnEnter,
|
|
1218
|
+
isCustomContent: true
|
|
1219
|
+
};
|
|
1220
|
+
variantChildren = customContent;
|
|
1221
|
+
} else {
|
|
1222
|
+
switch (variant) {
|
|
1223
|
+
case 'single':
|
|
1224
|
+
variantProps = {
|
|
1225
|
+
selected: selections[0],
|
|
1226
|
+
hasInlineFilter,
|
|
1227
|
+
openedOnEnter
|
|
1228
|
+
};
|
|
1229
|
+
variantChildren = renderableItems;
|
|
1230
|
+
break;
|
|
1231
|
+
case 'checkbox':
|
|
1232
|
+
variantProps = {
|
|
1233
|
+
checked: selections,
|
|
1234
|
+
isGrouped,
|
|
1235
|
+
hasInlineFilter,
|
|
1236
|
+
openedOnEnter
|
|
1237
|
+
};
|
|
1238
|
+
variantChildren = renderableItems;
|
|
1239
|
+
break;
|
|
1240
|
+
case 'typeahead':
|
|
1241
|
+
variantProps = {
|
|
1242
|
+
selected: selections[0],
|
|
1243
|
+
openedOnEnter
|
|
1244
|
+
};
|
|
1245
|
+
variantChildren = onFavorite ? renderableItems : this.extendTypeaheadChildren(typeaheadCurrIndex);
|
|
1246
|
+
if (variantChildren.length === 0) {
|
|
1247
|
+
variantChildren.push(<SelectOption isDisabled key={0} value={noResultsFoundText} isNoResultsOption />);
|
|
1248
|
+
}
|
|
1249
|
+
break;
|
|
1250
|
+
case 'typeaheadmulti':
|
|
1251
|
+
variantProps = {
|
|
1252
|
+
selected: selections,
|
|
1253
|
+
openedOnEnter
|
|
1254
|
+
};
|
|
1255
|
+
variantChildren = onFavorite ? renderableItems : this.extendTypeaheadChildren(typeaheadCurrIndex);
|
|
1256
|
+
if (variantChildren.length === 0) {
|
|
1257
|
+
variantChildren.push(<SelectOption isDisabled key={0} value={noResultsFoundText} isNoResultsOption />);
|
|
1258
|
+
}
|
|
1259
|
+
break;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
const isStatic = isFlipEnabled && menuAppendTo !== 'inline';
|
|
1264
|
+
const innerMenu = (
|
|
1265
|
+
<SelectMenu
|
|
1266
|
+
className={css(isStatic && styles.modifiers.static)}
|
|
1267
|
+
{...props}
|
|
1268
|
+
isGrouped={isGrouped}
|
|
1269
|
+
selected={selections}
|
|
1270
|
+
{...variantProps}
|
|
1271
|
+
openedOnEnter={openedOnEnter}
|
|
1272
|
+
aria-label={ariaLabel}
|
|
1273
|
+
aria-labelledby={ariaLabelledBy}
|
|
1274
|
+
sendRef={this.sendRef}
|
|
1275
|
+
keyHandler={this.handleMenuKeys}
|
|
1276
|
+
maxHeight={maxHeight}
|
|
1277
|
+
ref={this.menuComponentRef}
|
|
1278
|
+
footer={footer}
|
|
1279
|
+
footerRef={this.footerRef}
|
|
1280
|
+
isLastOptionBeforeFooter={this.isLastOptionBeforeFooter}
|
|
1281
|
+
>
|
|
1282
|
+
{variantChildren}
|
|
1283
|
+
</SelectMenu>
|
|
1284
|
+
);
|
|
1285
|
+
|
|
1286
|
+
const menuContainer = footer ? <div className={css(styles.selectMenu)}> {innerMenu} </div> : innerMenu;
|
|
1287
|
+
|
|
1288
|
+
const popperRef = React.createRef<HTMLDivElement>();
|
|
1289
|
+
const popperContainer = (
|
|
1290
|
+
<div
|
|
1291
|
+
className={css(
|
|
1292
|
+
styles.select,
|
|
1293
|
+
isOpen && styles.modifiers.expanded,
|
|
1294
|
+
validated === ValidatedOptions.success && styles.modifiers.success,
|
|
1295
|
+
validated === ValidatedOptions.warning && styles.modifiers.warning,
|
|
1296
|
+
validated === ValidatedOptions.error && styles.modifiers.invalid,
|
|
1297
|
+
direction === SelectDirection.up && styles.modifiers.top,
|
|
1298
|
+
className
|
|
1299
|
+
)}
|
|
1300
|
+
ref={popperRef}
|
|
1301
|
+
{...(width && { style: { width } })}
|
|
1302
|
+
{...(validated !== ValidatedOptions.default && { 'aria-invalid': ariaInvalid })}
|
|
1303
|
+
>
|
|
1304
|
+
{isOpen && menuContainer}
|
|
1305
|
+
</div>
|
|
1306
|
+
);
|
|
1307
|
+
|
|
1308
|
+
const mainContainer = (
|
|
1309
|
+
<div
|
|
1310
|
+
className={css(
|
|
1311
|
+
styles.select,
|
|
1312
|
+
isOpen && styles.modifiers.expanded,
|
|
1313
|
+
validated === ValidatedOptions.success && styles.modifiers.success,
|
|
1314
|
+
validated === ValidatedOptions.warning && styles.modifiers.warning,
|
|
1315
|
+
validated === ValidatedOptions.error && styles.modifiers.invalid,
|
|
1316
|
+
direction === SelectDirection.up && styles.modifiers.top,
|
|
1317
|
+
className
|
|
1318
|
+
)}
|
|
1319
|
+
ref={this.parentRef}
|
|
1320
|
+
{...getOUIAProps(Select.displayName, ouiaId !== undefined ? ouiaId : this.state.ouiaStateId, ouiaSafe)}
|
|
1321
|
+
{...(width && { style: { width } })}
|
|
1322
|
+
{...(validated !== ValidatedOptions.default && { 'aria-invalid': ariaInvalid })}
|
|
1323
|
+
>
|
|
1324
|
+
<SelectToggle
|
|
1325
|
+
id={selectToggleId}
|
|
1326
|
+
parentRef={this.parentRef}
|
|
1327
|
+
menuRef={this.menuComponentRef}
|
|
1328
|
+
ref={toggleRef}
|
|
1329
|
+
{...(footer && { footerRef: this.footerRef })}
|
|
1330
|
+
isOpen={isOpen}
|
|
1331
|
+
isPlain={isPlain}
|
|
1332
|
+
hasPlaceholderStyle={
|
|
1333
|
+
hasPlaceholderStyle && (!selections.length || selections[0] === null || isSelectedPlaceholder)
|
|
1334
|
+
}
|
|
1335
|
+
onToggle={this.onToggle}
|
|
1336
|
+
onEnter={this.onEnter}
|
|
1337
|
+
onClose={this.onClose}
|
|
1338
|
+
onBlur={onBlur}
|
|
1339
|
+
variant={variant}
|
|
1340
|
+
toggleIndicator={toggleIndicator}
|
|
1341
|
+
aria-labelledby={`${ariaLabelledBy || ''} ${selectToggleId}`}
|
|
1342
|
+
aria-label={toggleAriaLabel}
|
|
1343
|
+
{...(ariaDescribedby && { 'aria-describedby': ariaDescribedby })}
|
|
1344
|
+
handleTypeaheadKeys={this.handleTypeaheadKeys}
|
|
1345
|
+
moveFocusToLastMenuItem={this.moveFocusToLastMenuItem}
|
|
1346
|
+
isDisabled={isDisabled}
|
|
1347
|
+
hasClearButton={hasOnClear}
|
|
1348
|
+
hasFooter={footer !== undefined}
|
|
1349
|
+
onClickTypeaheadToggleButton={this.onClickTypeaheadToggleButton}
|
|
1350
|
+
>
|
|
1351
|
+
{customContent && (
|
|
1352
|
+
<div className={css(styles.selectToggleWrapper)}>
|
|
1353
|
+
{toggleIcon && <span className={css(styles.selectToggleIcon)}>{toggleIcon}</span>}
|
|
1354
|
+
<span className={css(styles.selectToggleText)}>{placeholderText}</span>
|
|
1355
|
+
</div>
|
|
1356
|
+
)}
|
|
1357
|
+
{variant === SelectVariant.single && !customContent && (
|
|
1358
|
+
<React.Fragment>
|
|
1359
|
+
<div className={css(styles.selectToggleWrapper)}>
|
|
1360
|
+
{toggleIcon && <span className={css(styles.selectToggleIcon)}>{toggleIcon}</span>}
|
|
1361
|
+
<span className={css(styles.selectToggleText)}>
|
|
1362
|
+
{this.getDisplay(selections[0] as string, 'node') || placeholderText || childPlaceholderText}
|
|
1363
|
+
</span>
|
|
1364
|
+
</div>
|
|
1365
|
+
{hasOnClear && hasAnySelections && clearBtn}
|
|
1366
|
+
</React.Fragment>
|
|
1367
|
+
)}
|
|
1368
|
+
{variant === SelectVariant.checkbox && !customContent && (
|
|
1369
|
+
<React.Fragment>
|
|
1370
|
+
<div className={css(styles.selectToggleWrapper)}>
|
|
1371
|
+
{toggleIcon && <span className={css(styles.selectToggleIcon)}>{toggleIcon}</span>}
|
|
1372
|
+
<span className={css(styles.selectToggleText)}>{placeholderText}</span>
|
|
1373
|
+
{!isCheckboxSelectionBadgeHidden && hasAnySelections && (
|
|
1374
|
+
<div className={css(styles.selectToggleBadge)}>
|
|
1375
|
+
<span className={css(badgeStyles.badge, badgeStyles.modifiers.read)}>
|
|
1376
|
+
{this.generateSelectedBadge()}
|
|
1377
|
+
</span>
|
|
1378
|
+
</div>
|
|
1379
|
+
)}
|
|
1380
|
+
</div>
|
|
1381
|
+
{hasOnClear && hasAnySelections && clearBtn}
|
|
1382
|
+
</React.Fragment>
|
|
1383
|
+
)}
|
|
1384
|
+
{variant === SelectVariant.typeahead && !customContent && (
|
|
1385
|
+
<React.Fragment>
|
|
1386
|
+
<div className={css(styles.selectToggleWrapper)}>
|
|
1387
|
+
{toggleIcon && <span className={css(styles.selectToggleIcon)}>{toggleIcon}</span>}
|
|
1388
|
+
<input
|
|
1389
|
+
className={css(formStyles.formControl, styles.selectToggleTypeahead)}
|
|
1390
|
+
aria-activedescendant={typeaheadActiveChild && typeaheadActiveChild.id}
|
|
1391
|
+
id={`${selectToggleId}-select-typeahead`}
|
|
1392
|
+
aria-label={typeAheadAriaLabel}
|
|
1393
|
+
{...(typeAheadAriaDescribedby && { 'aria-describedby': typeAheadAriaDescribedby })}
|
|
1394
|
+
placeholder={placeholderText as string}
|
|
1395
|
+
value={
|
|
1396
|
+
typeaheadInputValue !== null
|
|
1397
|
+
? typeaheadInputValue
|
|
1398
|
+
: this.getDisplay(selections[0] as string, 'text') || ''
|
|
1399
|
+
}
|
|
1400
|
+
type="text"
|
|
1401
|
+
onClick={this.onClick}
|
|
1402
|
+
onChange={this.onChange}
|
|
1403
|
+
autoComplete={inputAutoComplete}
|
|
1404
|
+
disabled={isDisabled}
|
|
1405
|
+
ref={this.inputRef}
|
|
1406
|
+
/>
|
|
1407
|
+
</div>
|
|
1408
|
+
{hasOnClear && (selections[0] || typeaheadInputValue) && clearBtn}
|
|
1409
|
+
</React.Fragment>
|
|
1410
|
+
)}
|
|
1411
|
+
{variant === SelectVariant.typeaheadMulti && !customContent && (
|
|
1412
|
+
<React.Fragment>
|
|
1413
|
+
<div className={css(styles.selectToggleWrapper)}>
|
|
1414
|
+
{toggleIcon && <span className={css(styles.selectToggleIcon)}>{toggleIcon}</span>}
|
|
1415
|
+
{selections && Array.isArray(selections) && selections.length > 0 && selectedChips}
|
|
1416
|
+
<input
|
|
1417
|
+
className={css(formStyles.formControl, styles.selectToggleTypeahead)}
|
|
1418
|
+
aria-activedescendant={typeaheadActiveChild && typeaheadActiveChild.id}
|
|
1419
|
+
id={`${selectToggleId}-select-multi-typeahead-typeahead`}
|
|
1420
|
+
aria-label={typeAheadAriaLabel}
|
|
1421
|
+
aria-invalid={validated === ValidatedOptions.error}
|
|
1422
|
+
{...(typeAheadAriaDescribedby && { 'aria-describedby': typeAheadAriaDescribedby })}
|
|
1423
|
+
placeholder={placeholderText as string}
|
|
1424
|
+
value={typeaheadInputValue !== null ? typeaheadInputValue : ''}
|
|
1425
|
+
type="text"
|
|
1426
|
+
onChange={this.onChange}
|
|
1427
|
+
onClick={this.onClick}
|
|
1428
|
+
autoComplete={inputAutoComplete}
|
|
1429
|
+
disabled={isDisabled}
|
|
1430
|
+
ref={this.inputRef}
|
|
1431
|
+
/>
|
|
1432
|
+
</div>
|
|
1433
|
+
{hasOnClear && ((selections && selections.length > 0) || typeaheadInputValue) && clearBtn}
|
|
1434
|
+
</React.Fragment>
|
|
1435
|
+
)}
|
|
1436
|
+
{validated === ValidatedOptions.success && (
|
|
1437
|
+
<span className={css(styles.selectToggleStatusIcon)}>
|
|
1438
|
+
<CheckCircleIcon aria-hidden="true" />
|
|
1439
|
+
</span>
|
|
1440
|
+
)}
|
|
1441
|
+
{validated === ValidatedOptions.error && (
|
|
1442
|
+
<span className={css(styles.selectToggleStatusIcon)}>
|
|
1443
|
+
<ExclamationCircleIcon aria-hidden="true" />
|
|
1444
|
+
</span>
|
|
1445
|
+
)}
|
|
1446
|
+
{validated === ValidatedOptions.warning && (
|
|
1447
|
+
<span className={css(styles.selectToggleStatusIcon)}>
|
|
1448
|
+
<ExclamationTriangleIcon aria-hidden="true" />
|
|
1449
|
+
</span>
|
|
1450
|
+
)}
|
|
1451
|
+
</SelectToggle>
|
|
1452
|
+
{isOpen && menuAppendTo === 'inline' && menuContainer}
|
|
1453
|
+
</div>
|
|
1454
|
+
);
|
|
1455
|
+
|
|
1456
|
+
const getParentElement = () => {
|
|
1457
|
+
if (this.parentRef && this.parentRef.current) {
|
|
1458
|
+
return this.parentRef.current.parentElement;
|
|
1459
|
+
}
|
|
1460
|
+
return null;
|
|
1461
|
+
};
|
|
1462
|
+
|
|
1463
|
+
return (
|
|
1464
|
+
<GenerateId>
|
|
1465
|
+
{randomId => (
|
|
1466
|
+
<SelectContext.Provider
|
|
1467
|
+
value={{
|
|
1468
|
+
onSelect,
|
|
1469
|
+
onFavorite,
|
|
1470
|
+
onClose: this.onClose,
|
|
1471
|
+
variant,
|
|
1472
|
+
inputIdPrefix: inputIdPrefix || randomId,
|
|
1473
|
+
shouldResetOnSelect
|
|
1474
|
+
}}
|
|
1475
|
+
>
|
|
1476
|
+
{menuAppendTo === 'inline' ? (
|
|
1477
|
+
mainContainer
|
|
1478
|
+
) : (
|
|
1479
|
+
<Popper
|
|
1480
|
+
trigger={mainContainer}
|
|
1481
|
+
triggerRef={this.parentRef}
|
|
1482
|
+
popper={popperContainer}
|
|
1483
|
+
popperRef={popperRef}
|
|
1484
|
+
direction={direction}
|
|
1485
|
+
appendTo={menuAppendTo === 'parent' ? getParentElement() : menuAppendTo}
|
|
1486
|
+
isVisible={isOpen}
|
|
1487
|
+
zIndex={zIndex}
|
|
1488
|
+
/>
|
|
1489
|
+
)}
|
|
1490
|
+
</SelectContext.Provider>
|
|
1491
|
+
)}
|
|
1492
|
+
</GenerateId>
|
|
1493
|
+
);
|
|
1494
|
+
}
|
|
1495
|
+
}
|