@patternfly/react-core 5.0.0-alpha.61 → 5.0.0-alpha.63

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.
Files changed (239) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/esm/components/CalendarMonth/CalendarMonth.d.ts.map +1 -1
  3. package/dist/esm/components/CalendarMonth/CalendarMonth.js +9 -11
  4. package/dist/esm/components/CalendarMonth/CalendarMonth.js.map +1 -1
  5. package/dist/esm/components/DualListSelector/DualListSelectorControl.d.ts +1 -1
  6. package/dist/esm/components/Menu/MenuItem.d.ts +1 -1
  7. package/dist/esm/components/MenuToggle/MenuToggle.d.ts.map +1 -1
  8. package/dist/esm/components/MenuToggle/MenuToggle.js +3 -1
  9. package/dist/esm/components/MenuToggle/MenuToggle.js.map +1 -1
  10. package/dist/esm/components/Select/Select.d.ts +26 -192
  11. package/dist/esm/components/Select/Select.d.ts.map +1 -1
  12. package/dist/esm/components/Select/Select.js +51 -935
  13. package/dist/esm/components/Select/Select.js.map +1 -1
  14. package/dist/esm/components/Select/SelectGroup.d.ts +7 -8
  15. package/dist/esm/components/Select/SelectGroup.d.ts.map +1 -1
  16. package/dist/esm/components/Select/SelectGroup.js +4 -7
  17. package/dist/esm/components/Select/SelectGroup.js.map +1 -1
  18. package/dist/{js/next → esm}/components/Select/SelectList.d.ts +1 -1
  19. package/dist/esm/components/Select/SelectList.d.ts.map +1 -0
  20. package/dist/esm/{next/components → components}/Select/SelectList.js +1 -1
  21. package/dist/esm/components/Select/SelectList.js.map +1 -0
  22. package/dist/esm/components/Select/SelectOption.d.ts +13 -66
  23. package/dist/esm/components/Select/SelectOption.d.ts.map +1 -1
  24. package/dist/esm/components/Select/SelectOption.js +6 -192
  25. package/dist/esm/components/Select/SelectOption.js.map +1 -1
  26. package/dist/esm/components/Select/index.d.ts +1 -1
  27. package/dist/esm/components/Select/index.d.ts.map +1 -1
  28. package/dist/esm/components/Select/index.js +1 -1
  29. package/dist/esm/components/Select/index.js.map +1 -1
  30. package/dist/esm/deprecated/components/Select/Select.d.ts +199 -0
  31. package/dist/esm/deprecated/components/Select/Select.d.ts.map +1 -0
  32. package/dist/esm/deprecated/components/Select/Select.js +940 -0
  33. package/dist/esm/deprecated/components/Select/Select.js.map +1 -0
  34. package/dist/esm/deprecated/components/Select/SelectGroup.d.ts +13 -0
  35. package/dist/esm/deprecated/components/Select/SelectGroup.d.ts.map +1 -0
  36. package/dist/esm/deprecated/components/Select/SelectGroup.js +13 -0
  37. package/dist/esm/deprecated/components/Select/SelectGroup.js.map +1 -0
  38. package/dist/esm/deprecated/components/Select/SelectMenu.d.ts.map +1 -0
  39. package/dist/esm/{components → deprecated/components}/Select/SelectMenu.js +1 -1
  40. package/dist/esm/deprecated/components/Select/SelectMenu.js.map +1 -0
  41. package/dist/esm/deprecated/components/Select/SelectOption.d.ts +73 -0
  42. package/dist/esm/deprecated/components/Select/SelectOption.d.ts.map +1 -0
  43. package/dist/esm/deprecated/components/Select/SelectOption.js +196 -0
  44. package/dist/esm/deprecated/components/Select/SelectOption.js.map +1 -0
  45. package/dist/esm/deprecated/components/Select/SelectToggle.d.ts.map +1 -0
  46. package/dist/esm/{components → deprecated/components}/Select/SelectToggle.js +2 -2
  47. package/dist/esm/deprecated/components/Select/SelectToggle.js.map +1 -0
  48. package/dist/esm/{next → deprecated}/components/Select/index.d.ts +1 -1
  49. package/dist/esm/deprecated/components/Select/index.d.ts.map +1 -0
  50. package/dist/esm/{next → deprecated}/components/Select/index.js +1 -1
  51. package/dist/esm/deprecated/components/Select/index.js.map +1 -0
  52. package/dist/esm/deprecated/components/Select/selectConstants.d.ts.map +1 -0
  53. package/dist/esm/deprecated/components/Select/selectConstants.js.map +1 -0
  54. package/dist/esm/deprecated/components/index.d.ts +1 -0
  55. package/dist/esm/deprecated/components/index.d.ts.map +1 -1
  56. package/dist/esm/deprecated/components/index.js +1 -0
  57. package/dist/esm/deprecated/components/index.js.map +1 -1
  58. package/dist/esm/next/components/index.d.ts +0 -1
  59. package/dist/esm/next/components/index.d.ts.map +1 -1
  60. package/dist/esm/next/components/index.js +0 -1
  61. package/dist/esm/next/components/index.js.map +1 -1
  62. package/dist/js/components/CalendarMonth/CalendarMonth.d.ts.map +1 -1
  63. package/dist/js/components/CalendarMonth/CalendarMonth.js +8 -10
  64. package/dist/js/components/CalendarMonth/CalendarMonth.js.map +1 -1
  65. package/dist/js/components/DualListSelector/DualListSelectorControl.d.ts +1 -1
  66. package/dist/js/components/Menu/MenuItem.d.ts +1 -1
  67. package/dist/js/components/MenuToggle/MenuToggle.d.ts.map +1 -1
  68. package/dist/js/components/MenuToggle/MenuToggle.js +3 -1
  69. package/dist/js/components/MenuToggle/MenuToggle.js.map +1 -1
  70. package/dist/js/components/Select/Select.d.ts +26 -192
  71. package/dist/js/components/Select/Select.d.ts.map +1 -1
  72. package/dist/js/components/Select/Select.js +51 -936
  73. package/dist/js/components/Select/Select.js.map +1 -1
  74. package/dist/js/components/Select/SelectGroup.d.ts +7 -8
  75. package/dist/js/components/Select/SelectGroup.d.ts.map +1 -1
  76. package/dist/js/components/Select/SelectGroup.js +4 -7
  77. package/dist/js/components/Select/SelectGroup.js.map +1 -1
  78. package/dist/{esm/next → js}/components/Select/SelectList.d.ts +1 -1
  79. package/dist/js/components/Select/SelectList.d.ts.map +1 -0
  80. package/dist/js/{next/components → components}/Select/SelectList.js +1 -1
  81. package/dist/js/components/Select/SelectList.js.map +1 -0
  82. package/dist/js/components/Select/SelectOption.d.ts +13 -66
  83. package/dist/js/components/Select/SelectOption.d.ts.map +1 -1
  84. package/dist/js/components/Select/SelectOption.js +7 -193
  85. package/dist/js/components/Select/SelectOption.js.map +1 -1
  86. package/dist/js/components/Select/index.d.ts +1 -1
  87. package/dist/js/components/Select/index.d.ts.map +1 -1
  88. package/dist/js/components/Select/index.js +1 -1
  89. package/dist/js/components/Select/index.js.map +1 -1
  90. package/dist/js/deprecated/components/Select/Select.d.ts +199 -0
  91. package/dist/js/deprecated/components/Select/Select.d.ts.map +1 -0
  92. package/dist/js/deprecated/components/Select/Select.js +944 -0
  93. package/dist/js/deprecated/components/Select/Select.js.map +1 -0
  94. package/dist/js/deprecated/components/Select/SelectGroup.d.ts +13 -0
  95. package/dist/js/deprecated/components/Select/SelectGroup.d.ts.map +1 -0
  96. package/dist/js/deprecated/components/Select/SelectGroup.js +17 -0
  97. package/dist/js/deprecated/components/Select/SelectGroup.js.map +1 -0
  98. package/dist/js/deprecated/components/Select/SelectMenu.d.ts.map +1 -0
  99. package/dist/js/{components → deprecated/components}/Select/SelectMenu.js +1 -1
  100. package/dist/js/deprecated/components/Select/SelectMenu.js.map +1 -0
  101. package/dist/js/deprecated/components/Select/SelectOption.d.ts +73 -0
  102. package/dist/js/deprecated/components/Select/SelectOption.d.ts.map +1 -0
  103. package/dist/js/deprecated/components/Select/SelectOption.js +200 -0
  104. package/dist/js/deprecated/components/Select/SelectOption.js.map +1 -0
  105. package/dist/js/deprecated/components/Select/SelectToggle.d.ts.map +1 -0
  106. package/dist/js/{components → deprecated/components}/Select/SelectToggle.js +2 -2
  107. package/dist/js/deprecated/components/Select/SelectToggle.js.map +1 -0
  108. package/dist/js/{next → deprecated}/components/Select/index.d.ts +1 -1
  109. package/dist/js/deprecated/components/Select/index.d.ts.map +1 -0
  110. package/dist/js/{next → deprecated}/components/Select/index.js +1 -1
  111. package/dist/js/deprecated/components/Select/index.js.map +1 -0
  112. package/dist/js/deprecated/components/Select/selectConstants.d.ts.map +1 -0
  113. package/dist/js/deprecated/components/Select/selectConstants.js.map +1 -0
  114. package/dist/js/deprecated/components/index.d.ts +1 -0
  115. package/dist/js/deprecated/components/index.d.ts.map +1 -1
  116. package/dist/js/deprecated/components/index.js +1 -0
  117. package/dist/js/deprecated/components/index.js.map +1 -1
  118. package/dist/js/next/components/index.d.ts +0 -1
  119. package/dist/js/next/components/index.d.ts.map +1 -1
  120. package/dist/js/next/components/index.js +0 -1
  121. package/dist/js/next/components/index.js.map +1 -1
  122. package/dist/umd/react-core.min.js +3 -3
  123. package/package.json +2 -2
  124. package/src/components/Alert/examples/Alert.md +5 -5
  125. package/src/components/Breadcrumb/examples/BreadcrumbBasic.tsx +1 -1
  126. package/src/components/Button/examples/ButtonVariations.tsx +4 -4
  127. package/src/components/CalendarMonth/CalendarMonth.tsx +25 -15
  128. package/src/components/Card/examples/CardBasic.tsx +1 -1
  129. package/src/components/DatePicker/__tests__/__snapshots__/DatePicker.test.tsx.snap +22 -31
  130. package/src/components/Dropdown/examples/DropdownBasic.tsx +1 -0
  131. package/src/components/FormSelect/examples/FormSelectBasic.tsx +1 -1
  132. package/src/components/LoginPage/examples/LoginPageLanguageSelect.tsx +28 -19
  133. package/src/components/MenuToggle/MenuToggle.tsx +2 -1
  134. package/src/components/MenuToggle/__tests__/__snapshots__/MenuToggle.test.tsx.snap +18 -10
  135. package/src/components/Modal/examples/ModalBasic.tsx +2 -1
  136. package/src/components/Nav/examples/NavDefault.tsx +1 -1
  137. package/src/components/Pagination/examples/PaginationTop.tsx +1 -0
  138. package/src/components/Select/Select.tsx +113 -1475
  139. package/src/components/Select/SelectGroup.tsx +13 -25
  140. package/src/{next/components → components}/Select/SelectList.tsx +1 -1
  141. package/src/components/Select/SelectOption.tsx +22 -452
  142. package/src/components/Select/examples/Select.md +11 -2896
  143. package/src/{next/components → components}/Select/examples/SelectBasic.tsx +1 -2
  144. package/src/{next/components → components}/Select/examples/SelectCheckbox.tsx +1 -2
  145. package/src/{next/components → components}/Select/examples/SelectGrouped.tsx +1 -2
  146. package/src/{next/components → components}/Select/examples/SelectMultiTypeahead.tsx +4 -1
  147. package/src/{next/components → components}/Select/examples/SelectTypeahead.tsx +4 -1
  148. package/src/components/Select/index.ts +1 -1
  149. package/src/components/Switch/examples/SwitchBasic.tsx +1 -0
  150. package/src/components/Toolbar/__tests__/Toolbar.test.tsx +3 -21
  151. package/src/components/Toolbar/__tests__/__snapshots__/Toolbar.test.tsx.snap +12 -124
  152. package/src/components/Toolbar/examples/Toolbar.md +0 -1
  153. package/src/components/Toolbar/examples/ToolbarComponentManagedToggleGroups.tsx +59 -63
  154. package/src/components/Toolbar/examples/ToolbarConsumerManagedToggleGroups.tsx +58 -62
  155. package/src/components/Toolbar/examples/ToolbarCustomChipGroupContent.tsx +107 -23
  156. package/src/components/Toolbar/examples/ToolbarGroups.tsx +96 -59
  157. package/src/components/Toolbar/examples/ToolbarStacked.tsx +56 -39
  158. package/src/components/Toolbar/examples/ToolbarWithFilters.tsx +115 -28
  159. package/src/components/Tooltip/examples/TooltipOptions.tsx +53 -23
  160. package/src/demos/Card/Card.md +39 -23
  161. package/src/demos/CardDemos.md +99 -46
  162. package/src/demos/Toolbar.md +51 -21
  163. package/src/demos/examples/Tabs/TabsAndTable.tsx +5 -22
  164. package/src/deprecated/components/ContextSelector/examples/ContextSelectorBasic.tsx +1 -0
  165. package/src/deprecated/components/OptionsMenu/examples/OptionsMenu.md +2 -1
  166. package/src/deprecated/components/Select/Select.tsx +1495 -0
  167. package/src/deprecated/components/Select/SelectGroup.tsx +36 -0
  168. package/src/{components → deprecated/components}/Select/SelectMenu.tsx +2 -2
  169. package/src/deprecated/components/Select/SelectOption.tsx +461 -0
  170. package/src/{components → deprecated/components}/Select/SelectToggle.tsx +3 -3
  171. package/src/{components → deprecated/components}/Select/__tests__/Select.test.tsx +1 -1
  172. package/src/deprecated/components/Select/examples/Select.md +2937 -0
  173. package/src/{next → deprecated}/components/Select/index.ts +1 -1
  174. package/src/deprecated/components/index.ts +1 -0
  175. package/src/next/components/index.ts +0 -1
  176. package/dist/esm/components/Select/SelectMenu.d.ts.map +0 -1
  177. package/dist/esm/components/Select/SelectMenu.js.map +0 -1
  178. package/dist/esm/components/Select/SelectToggle.d.ts.map +0 -1
  179. package/dist/esm/components/Select/SelectToggle.js.map +0 -1
  180. package/dist/esm/components/Select/selectConstants.d.ts.map +0 -1
  181. package/dist/esm/components/Select/selectConstants.js.map +0 -1
  182. package/dist/esm/next/components/Select/Select.d.ts +0 -33
  183. package/dist/esm/next/components/Select/Select.d.ts.map +0 -1
  184. package/dist/esm/next/components/Select/Select.js +0 -56
  185. package/dist/esm/next/components/Select/Select.js.map +0 -1
  186. package/dist/esm/next/components/Select/SelectGroup.d.ts +0 -12
  187. package/dist/esm/next/components/Select/SelectGroup.d.ts.map +0 -1
  188. package/dist/esm/next/components/Select/SelectGroup.js +0 -10
  189. package/dist/esm/next/components/Select/SelectGroup.js.map +0 -1
  190. package/dist/esm/next/components/Select/SelectList.d.ts.map +0 -1
  191. package/dist/esm/next/components/Select/SelectList.js.map +0 -1
  192. package/dist/esm/next/components/Select/SelectOption.d.ts +0 -20
  193. package/dist/esm/next/components/Select/SelectOption.d.ts.map +0 -1
  194. package/dist/esm/next/components/Select/SelectOption.js +0 -10
  195. package/dist/esm/next/components/Select/SelectOption.js.map +0 -1
  196. package/dist/esm/next/components/Select/index.d.ts.map +0 -1
  197. package/dist/esm/next/components/Select/index.js.map +0 -1
  198. package/dist/js/components/Select/SelectMenu.d.ts.map +0 -1
  199. package/dist/js/components/Select/SelectMenu.js.map +0 -1
  200. package/dist/js/components/Select/SelectToggle.d.ts.map +0 -1
  201. package/dist/js/components/Select/SelectToggle.js.map +0 -1
  202. package/dist/js/components/Select/selectConstants.d.ts.map +0 -1
  203. package/dist/js/components/Select/selectConstants.js.map +0 -1
  204. package/dist/js/next/components/Select/Select.d.ts +0 -33
  205. package/dist/js/next/components/Select/Select.d.ts.map +0 -1
  206. package/dist/js/next/components/Select/Select.js +0 -59
  207. package/dist/js/next/components/Select/Select.js.map +0 -1
  208. package/dist/js/next/components/Select/SelectGroup.d.ts +0 -12
  209. package/dist/js/next/components/Select/SelectGroup.d.ts.map +0 -1
  210. package/dist/js/next/components/Select/SelectGroup.js +0 -14
  211. package/dist/js/next/components/Select/SelectGroup.js.map +0 -1
  212. package/dist/js/next/components/Select/SelectList.d.ts.map +0 -1
  213. package/dist/js/next/components/Select/SelectList.js.map +0 -1
  214. package/dist/js/next/components/Select/SelectOption.d.ts +0 -20
  215. package/dist/js/next/components/Select/SelectOption.d.ts.map +0 -1
  216. package/dist/js/next/components/Select/SelectOption.js +0 -14
  217. package/dist/js/next/components/Select/SelectOption.js.map +0 -1
  218. package/dist/js/next/components/Select/index.d.ts.map +0 -1
  219. package/dist/js/next/components/Select/index.js.map +0 -1
  220. package/src/next/components/Select/Select.tsx +0 -133
  221. package/src/next/components/Select/SelectGroup.tsx +0 -24
  222. package/src/next/components/Select/SelectOption.tsx +0 -31
  223. package/src/next/components/Select/examples/Select.md +0 -38
  224. /package/dist/esm/{components → deprecated/components}/Select/SelectMenu.d.ts +0 -0
  225. /package/dist/esm/{components → deprecated/components}/Select/SelectToggle.d.ts +0 -0
  226. /package/dist/esm/{components → deprecated/components}/Select/selectConstants.d.ts +0 -0
  227. /package/dist/esm/{components → deprecated/components}/Select/selectConstants.js +0 -0
  228. /package/dist/js/{components → deprecated/components}/Select/SelectMenu.d.ts +0 -0
  229. /package/dist/js/{components → deprecated/components}/Select/SelectToggle.d.ts +0 -0
  230. /package/dist/js/{components → deprecated/components}/Select/selectConstants.d.ts +0 -0
  231. /package/dist/js/{components → deprecated/components}/Select/selectConstants.js +0 -0
  232. /package/src/{components → deprecated/components}/Select/__tests__/SelectGroup.test.tsx +0 -0
  233. /package/src/{components → deprecated/components}/Select/__tests__/SelectOption.test.tsx +0 -0
  234. /package/src/{components → deprecated/components}/Select/__tests__/SelectToggle.test.tsx +0 -0
  235. /package/src/{components → deprecated/components}/Select/__tests__/__snapshots__/Select.test.tsx.snap +0 -0
  236. /package/src/{components → deprecated/components}/Select/__tests__/__snapshots__/SelectGroup.test.tsx.snap +0 -0
  237. /package/src/{components → deprecated/components}/Select/__tests__/__snapshots__/SelectOption.test.tsx.snap +0 -0
  238. /package/src/{components → deprecated/components}/Select/__tests__/__snapshots__/SelectToggle.test.tsx.snap +0 -0
  239. /package/src/{components → deprecated/components}/Select/selectConstants.tsx +0 -0
@@ -1,1495 +1,133 @@
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';
1
+ import React from 'react';
6
2
  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 '../ChipGroup';
23
- import { Chip } from '../Chip';
24
- import { Spinner } from '../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 '../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 */
3
+ import { Menu, MenuContent, MenuProps } from '../Menu';
4
+ import { Popper, PopperProps } from '../../helpers/Popper/Popper';
5
+ import { getOUIAProps, OUIAProps, getDefaultOUIAId } from '../../helpers';
6
+
7
+ export interface SelectProps extends MenuProps, OUIAProps {
8
+ /** Anything which can be rendered in a select */
9
+ children?: React.ReactNode;
10
+ /** Classes applied to root element of select */
56
11
  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
12
  /** Flag to indicate if select is open */
62
13
  isOpen?: boolean;
63
- /** Flag to indicate if select options are grouped */
64
- isGrouped?: boolean;
65
- /** Display the toggle with no border or background */
14
+ /** Single itemId for single select menus, or array of itemIds for multi select. You can also specify isSelected on the SelectOption. */
15
+ selected?: any | any[];
16
+ /** Renderer for a custom select toggle. Forwards a ref to the toggle. */
17
+ toggle: (toggleRef: React.RefObject<any>) => React.ReactNode;
18
+ /** Function callback when user selects an option. */
19
+ onSelect?: (event?: React.MouseEvent<Element, MouseEvent>, itemId?: string | number) => void;
20
+ /** Callback to allow the select component to change the open state of the menu.
21
+ * Triggered by clicking outside of the menu, or by pressing either tab or escape. */
22
+ onOpenChange?: (isOpen: boolean) => void;
23
+ /** Indicates if the select should be without the outer box-shadow */
66
24
  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. */
25
+ /** @hide Forwarded ref */
26
+ innerRef?: React.Ref<HTMLDivElement>;
27
+ /** z-index of the select menu */
189
28
  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;
29
+ /** @beta Determines the accessible role of the select. For a checkbox select pass in "menu". */
30
+ role?: string;
31
+ /** Additional properties to pass to the popper */
32
+ popperProps?: Partial<PopperProps>;
194
33
  }
195
34
 
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;
35
+ const SelectBase: React.FunctionComponent<SelectProps & OUIAProps> = ({
36
+ children,
37
+ className,
38
+ onSelect,
39
+ isOpen,
40
+ selected,
41
+ toggle,
42
+ onOpenChange,
43
+ isPlain,
44
+ innerRef,
45
+ zIndex = 9999,
46
+ role = 'listbox',
47
+ popperProps,
48
+ ...props
49
+ }: SelectProps & OUIAProps) => {
50
+ const localMenuRef = React.useRef<HTMLDivElement>();
51
+ const toggleRef = React.useRef<HTMLButtonElement>();
52
+ const containerRef = React.useRef<HTMLDivElement>();
53
+
54
+ const menuRef = (innerRef as React.RefObject<HTMLDivElement>) || localMenuRef;
55
+ React.useEffect(() => {
56
+ const handleMenuKeys = (event: KeyboardEvent) => {
57
+ // Close the menu on tab or escape if onOpenChange is provided
534
58
  if (
535
- !typeaheadFilteredChildren.find(
536
- (i: React.ReactElement) =>
537
- i.props.value && i.props.value.toString().toLowerCase() === newValue.toString().toLowerCase()
538
- )
59
+ (isOpen && onOpenChange && menuRef.current?.contains(event.target as Node)) ||
60
+ toggleRef.current?.contains(event.target as Node)
539
61
  ) {
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);
62
+ if (event.key === 'Escape' || event.key === 'Tab') {
63
+ onOpenChange(false);
64
+ toggleRef.current?.focus();
564
65
  }
565
66
  }
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
- }
67
+ };
1074
68
 
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'));
69
+ const handleClick = (event: MouseEvent) => {
70
+ // toggle was clicked open via keyboard, focus on first menu item
71
+ if (isOpen && toggleRef.current?.contains(event.target as Node) && event.detail === 0) {
72
+ setTimeout(() => {
73
+ const firstElement = menuRef?.current?.querySelector('li button:not(:disabled),li input:not(:disabled)');
74
+ firstElement && (firstElement as HTMLElement).focus();
75
+ }, 0);
1083
76
  }
1084
- }
1085
77
 
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
- );
78
+ // If the event is not on the toggle and onOpenChange callback is provided, close the menu
79
+ if (isOpen && onOpenChange && !toggleRef?.current?.contains(event.target as Node)) {
80
+ if (isOpen && !menuRef.current?.contains(event.target as Node)) {
81
+ onOpenChange(false);
1104
82
  }
1105
83
  }
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
- );
84
+ };
1307
85
 
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
- );
86
+ window.addEventListener('keydown', handleMenuKeys);
87
+ window.addEventListener('click', handleClick);
1455
88
 
1456
- const getParentElement = () => {
1457
- if (this.parentRef && this.parentRef.current) {
1458
- return this.parentRef.current.parentElement;
1459
- }
1460
- return null;
89
+ return () => {
90
+ window.removeEventListener('keydown', handleMenuKeys);
91
+ window.removeEventListener('click', handleClick);
1461
92
  };
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
- }
93
+ }, [isOpen, menuRef, onOpenChange]);
94
+
95
+ const menu = (
96
+ <Menu
97
+ role={role}
98
+ className={css(className)}
99
+ ref={menuRef}
100
+ onSelect={(event, itemId) => onSelect && onSelect(event, itemId)}
101
+ isPlain={isPlain}
102
+ selected={selected}
103
+ {...getOUIAProps(
104
+ Select.displayName,
105
+ props.ouiaId !== undefined ? props.ouiaId : getDefaultOUIAId(Select.displayName),
106
+ props.ouiaSafe !== undefined ? props.ouiaSafe : true
107
+ )}
108
+ {...props}
109
+ >
110
+ <MenuContent>{children}</MenuContent>
111
+ </Menu>
112
+ );
113
+ return (
114
+ <div ref={containerRef}>
115
+ <Popper
116
+ trigger={toggle(toggleRef)}
117
+ triggerRef={toggleRef}
118
+ popper={menu}
119
+ popperRef={menuRef}
120
+ appendTo={containerRef.current || undefined}
121
+ isVisible={isOpen}
122
+ zIndex={zIndex}
123
+ {...popperProps}
124
+ />
125
+ </div>
126
+ );
127
+ };
128
+
129
+ export const Select = React.forwardRef((props: SelectProps, ref: React.Ref<any>) => (
130
+ <SelectBase innerRef={ref} {...props} />
131
+ ));
132
+
133
+ Select.displayName = 'Select';