@patternfly/react-core 5.4.10 → 5.4.12

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 (176) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/components/package.json +1 -1
  3. package/deprecated/package.json +1 -1
  4. package/dist/dynamic/components/AboutModal/package.json +1 -1
  5. package/dist/dynamic/components/Accordion/package.json +1 -1
  6. package/dist/dynamic/components/ActionList/package.json +1 -1
  7. package/dist/dynamic/components/Alert/package.json +1 -1
  8. package/dist/dynamic/components/Avatar/package.json +1 -1
  9. package/dist/dynamic/components/BackToTop/package.json +1 -1
  10. package/dist/dynamic/components/Backdrop/package.json +1 -1
  11. package/dist/dynamic/components/BackgroundImage/package.json +1 -1
  12. package/dist/dynamic/components/Badge/package.json +1 -1
  13. package/dist/dynamic/components/Banner/package.json +1 -1
  14. package/dist/dynamic/components/Brand/package.json +1 -1
  15. package/dist/dynamic/components/Breadcrumb/package.json +1 -1
  16. package/dist/dynamic/components/Button/package.json +1 -1
  17. package/dist/dynamic/components/CalendarMonth/package.json +1 -1
  18. package/dist/dynamic/components/Card/package.json +1 -1
  19. package/dist/dynamic/components/Checkbox/package.json +1 -1
  20. package/dist/dynamic/components/Chip/package.json +1 -1
  21. package/dist/dynamic/components/ClipboardCopy/package.json +1 -1
  22. package/dist/dynamic/components/CodeBlock/package.json +1 -1
  23. package/dist/dynamic/components/DataList/package.json +1 -1
  24. package/dist/dynamic/components/DatePicker/package.json +1 -1
  25. package/dist/dynamic/components/DescriptionList/package.json +1 -1
  26. package/dist/dynamic/components/Divider/package.json +1 -1
  27. package/dist/dynamic/components/DragDrop/package.json +1 -1
  28. package/dist/dynamic/components/Drawer/package.json +1 -1
  29. package/dist/dynamic/components/Dropdown/package.json +1 -1
  30. package/dist/dynamic/components/DualListSelector/package.json +1 -1
  31. package/dist/dynamic/components/EmptyState/package.json +1 -1
  32. package/dist/dynamic/components/ExpandableSection/package.json +1 -1
  33. package/dist/dynamic/components/FileUpload/package.json +1 -1
  34. package/dist/dynamic/components/Form/package.json +1 -1
  35. package/dist/dynamic/components/FormSelect/package.json +1 -1
  36. package/dist/dynamic/components/HelperText/package.json +1 -1
  37. package/dist/dynamic/components/Hint/package.json +1 -1
  38. package/dist/dynamic/components/Icon/package.json +1 -1
  39. package/dist/dynamic/components/InputGroup/package.json +1 -1
  40. package/dist/dynamic/components/JumpLinks/package.json +1 -1
  41. package/dist/dynamic/components/Label/package.json +1 -1
  42. package/dist/dynamic/components/List/package.json +1 -1
  43. package/dist/dynamic/components/LoginPage/package.json +1 -1
  44. package/dist/dynamic/components/Masthead/package.json +1 -1
  45. package/dist/dynamic/components/Menu/package.json +1 -1
  46. package/dist/dynamic/components/MenuToggle/package.json +1 -1
  47. package/dist/dynamic/components/Modal/package.json +1 -1
  48. package/dist/dynamic/components/MultipleFileUpload/package.json +1 -1
  49. package/dist/dynamic/components/Nav/package.json +1 -1
  50. package/dist/dynamic/components/NotificationBadge/package.json +1 -1
  51. package/dist/dynamic/components/NotificationDrawer/package.json +1 -1
  52. package/dist/dynamic/components/NumberInput/package.json +1 -1
  53. package/dist/dynamic/components/OverflowMenu/package.json +1 -1
  54. package/dist/dynamic/components/Page/package.json +1 -1
  55. package/dist/dynamic/components/Pagination/package.json +1 -1
  56. package/dist/dynamic/components/Panel/package.json +1 -1
  57. package/dist/dynamic/components/Popover/package.json +1 -1
  58. package/dist/dynamic/components/Progress/package.json +1 -1
  59. package/dist/dynamic/components/ProgressStepper/package.json +1 -1
  60. package/dist/dynamic/components/Radio/package.json +1 -1
  61. package/dist/dynamic/components/SearchInput/package.json +1 -1
  62. package/dist/dynamic/components/Select/package.json +1 -1
  63. package/dist/dynamic/components/Sidebar/package.json +1 -1
  64. package/dist/dynamic/components/SimpleList/package.json +1 -1
  65. package/dist/dynamic/components/Skeleton/package.json +1 -1
  66. package/dist/dynamic/components/SkipToContent/package.json +1 -1
  67. package/dist/dynamic/components/Slider/package.json +1 -1
  68. package/dist/dynamic/components/Spinner/package.json +1 -1
  69. package/dist/dynamic/components/Switch/package.json +1 -1
  70. package/dist/dynamic/components/Tabs/package.json +1 -1
  71. package/dist/dynamic/components/Text/package.json +1 -1
  72. package/dist/dynamic/components/TextArea/package.json +1 -1
  73. package/dist/dynamic/components/TextInput/package.json +1 -1
  74. package/dist/dynamic/components/TextInputGroup/package.json +1 -1
  75. package/dist/dynamic/components/Tile/package.json +1 -1
  76. package/dist/dynamic/components/TimePicker/package.json +1 -1
  77. package/dist/dynamic/components/Timestamp/package.json +1 -1
  78. package/dist/dynamic/components/Title/package.json +1 -1
  79. package/dist/dynamic/components/ToggleGroup/package.json +1 -1
  80. package/dist/dynamic/components/Toolbar/package.json +1 -1
  81. package/dist/dynamic/components/Tooltip/package.json +1 -1
  82. package/dist/dynamic/components/TreeView/package.json +1 -1
  83. package/dist/dynamic/components/Truncate/package.json +1 -1
  84. package/dist/dynamic/components/Wizard/hooks/package.json +1 -1
  85. package/dist/dynamic/components/Wizard/package.json +1 -1
  86. package/dist/dynamic/deprecated/components/ApplicationLauncher/package.json +1 -1
  87. package/dist/dynamic/deprecated/components/ContextSelector/package.json +1 -1
  88. package/dist/dynamic/deprecated/components/Dropdown/package.json +1 -1
  89. package/dist/dynamic/deprecated/components/OptionsMenu/package.json +1 -1
  90. package/dist/dynamic/deprecated/components/PageHeader/package.json +1 -1
  91. package/dist/dynamic/deprecated/components/Select/package.json +1 -1
  92. package/dist/dynamic/deprecated/components/Wizard/package.json +1 -1
  93. package/dist/dynamic/deprecated/components/package.json +1 -1
  94. package/dist/dynamic/helpers/FocusTrap/FocusTrap/package.json +1 -1
  95. package/dist/dynamic/helpers/GenerateId/GenerateId/package.json +1 -1
  96. package/dist/dynamic/helpers/KeyboardHandler/package.json +1 -1
  97. package/dist/dynamic/helpers/OUIA/ouia/package.json +1 -1
  98. package/dist/dynamic/helpers/Popper/Popper/package.json +1 -1
  99. package/dist/dynamic/helpers/constants/package.json +1 -1
  100. package/dist/dynamic/helpers/datetimeUtils/package.json +1 -1
  101. package/dist/dynamic/helpers/favorites/package.json +1 -1
  102. package/dist/dynamic/helpers/fileUtils/package.json +1 -1
  103. package/dist/dynamic/helpers/htmlConstants/package.json +1 -1
  104. package/dist/dynamic/helpers/package.json +1 -1
  105. package/dist/dynamic/helpers/resizeObserver/package.json +1 -1
  106. package/dist/dynamic/helpers/typeUtils/package.json +1 -1
  107. package/dist/dynamic/helpers/useInterval/package.json +1 -1
  108. package/dist/dynamic/helpers/useIsomorphicLayout/package.json +1 -1
  109. package/dist/dynamic/helpers/useUnmountEffect/package.json +1 -1
  110. package/dist/dynamic/helpers/util/package.json +1 -1
  111. package/dist/dynamic/layouts/Bullseye/package.json +1 -1
  112. package/dist/dynamic/layouts/Flex/package.json +1 -1
  113. package/dist/dynamic/layouts/Gallery/package.json +1 -1
  114. package/dist/dynamic/layouts/Grid/package.json +1 -1
  115. package/dist/dynamic/layouts/Level/package.json +1 -1
  116. package/dist/dynamic/layouts/Split/package.json +1 -1
  117. package/dist/dynamic/layouts/Stack/package.json +1 -1
  118. package/dist/dynamic/next/components/DualListSelector/package.json +1 -1
  119. package/dist/dynamic/next/components/Modal/package.json +1 -1
  120. package/dist/dynamic/next/components/package.json +1 -1
  121. package/dist/dynamic/styles/package.json +1 -1
  122. package/dist/dynamic-modules.json +1 -0
  123. package/dist/esm/components/Dropdown/Dropdown.d.ts +2 -0
  124. package/dist/esm/components/Dropdown/Dropdown.d.ts.map +1 -1
  125. package/dist/esm/components/Dropdown/Dropdown.js +12 -3
  126. package/dist/esm/components/Dropdown/Dropdown.js.map +1 -1
  127. package/dist/esm/components/DualListSelector/DualListSelector.d.ts +4 -4
  128. package/dist/esm/components/DualListSelector/DualListSelector.d.ts.map +1 -1
  129. package/dist/esm/components/DualListSelector/DualListSelector.js.map +1 -1
  130. package/dist/esm/components/Menu/MenuContainer.d.ts +4 -0
  131. package/dist/esm/components/Menu/MenuContainer.d.ts.map +1 -1
  132. package/dist/esm/components/Menu/MenuContainer.js +37 -15
  133. package/dist/esm/components/Menu/MenuContainer.js.map +1 -1
  134. package/dist/esm/components/Select/Select.d.ts +4 -0
  135. package/dist/esm/components/Select/Select.d.ts.map +1 -1
  136. package/dist/esm/components/Select/Select.js +12 -3
  137. package/dist/esm/components/Select/Select.js.map +1 -1
  138. package/dist/esm/helpers/KeyboardHandler.d.ts +7 -0
  139. package/dist/esm/helpers/KeyboardHandler.d.ts.map +1 -1
  140. package/dist/esm/helpers/KeyboardHandler.js +25 -0
  141. package/dist/esm/helpers/KeyboardHandler.js.map +1 -1
  142. package/dist/js/components/Dropdown/Dropdown.d.ts +2 -0
  143. package/dist/js/components/Dropdown/Dropdown.d.ts.map +1 -1
  144. package/dist/js/components/Dropdown/Dropdown.js +11 -2
  145. package/dist/js/components/Dropdown/Dropdown.js.map +1 -1
  146. package/dist/js/components/DualListSelector/DualListSelector.d.ts +4 -4
  147. package/dist/js/components/DualListSelector/DualListSelector.d.ts.map +1 -1
  148. package/dist/js/components/DualListSelector/DualListSelector.js.map +1 -1
  149. package/dist/js/components/Menu/MenuContainer.d.ts +4 -0
  150. package/dist/js/components/Menu/MenuContainer.d.ts.map +1 -1
  151. package/dist/js/components/Menu/MenuContainer.js +38 -16
  152. package/dist/js/components/Menu/MenuContainer.js.map +1 -1
  153. package/dist/js/components/Select/Select.d.ts +4 -0
  154. package/dist/js/components/Select/Select.d.ts.map +1 -1
  155. package/dist/js/components/Select/Select.js +11 -2
  156. package/dist/js/components/Select/Select.js.map +1 -1
  157. package/dist/js/helpers/KeyboardHandler.d.ts +7 -0
  158. package/dist/js/helpers/KeyboardHandler.d.ts.map +1 -1
  159. package/dist/js/helpers/KeyboardHandler.js +27 -1
  160. package/dist/js/helpers/KeyboardHandler.js.map +1 -1
  161. package/dist/umd/assets/{output-DxbNpZVR.css → output-CWjj_LpR.css} +14576 -14576
  162. package/dist/umd/react-core.min.js +1 -1
  163. package/helpers/package.json +1 -1
  164. package/layouts/package.json +1 -1
  165. package/next/package.json +1 -1
  166. package/package.json +2 -2
  167. package/src/components/Dropdown/Dropdown.tsx +13 -1
  168. package/src/components/DualListSelector/DualListSelector.tsx +20 -20
  169. package/src/components/Menu/MenuContainer.tsx +42 -12
  170. package/src/components/Select/Select.tsx +16 -1
  171. package/src/components/Select/examples/SelectMultiTypeahead.tsx +1 -1
  172. package/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx +1 -1
  173. package/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx +1 -1
  174. package/src/components/Select/examples/SelectTypeahead.tsx +1 -1
  175. package/src/components/Select/examples/SelectTypeaheadCreatable.tsx +1 -1
  176. package/src/helpers/KeyboardHandler.tsx +27 -0
@@ -1 +1 @@
1
- {"name":"@patternfly/react-core-helpers","main":"../dist/js/helpers/index.js","module":"../dist/esm/helpers/index.js","typings":"../dist/esm/helpers/index.d.ts","version":"5.4.9","private":true}
1
+ {"name":"@patternfly/react-core-helpers","main":"../dist/js/helpers/index.js","module":"../dist/esm/helpers/index.js","typings":"../dist/esm/helpers/index.d.ts","version":"5.4.11","private":true}
@@ -1 +1 @@
1
- {"name":"@patternfly/react-core-layouts","main":"../dist/js/layouts/index.js","module":"../dist/esm/layouts/index.js","typings":"../dist/esm/layouts/index.d.ts","version":"5.4.9","private":true}
1
+ {"name":"@patternfly/react-core-layouts","main":"../dist/js/layouts/index.js","module":"../dist/esm/layouts/index.js","typings":"../dist/esm/layouts/index.d.ts","version":"5.4.11","private":true}
package/next/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@patternfly/react-core-next","main":"../dist/js/next/index.js","module":"../dist/esm/next/index.js","typings":"../dist/esm/next/index.d.ts","version":"5.4.9","private":true}
1
+ {"name":"@patternfly/react-core-next","main":"../dist/js/next/index.js","module":"../dist/esm/next/index.js","typings":"../dist/esm/next/index.d.ts","version":"5.4.11","private":true}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/react-core",
3
- "version": "5.4.10",
3
+ "version": "5.4.12",
4
4
  "description": "This library provides a set of common React components for use with the PatternFly reference implementation.",
5
5
  "main": "dist/js/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -62,5 +62,5 @@
62
62
  "react": "^17 || ^18",
63
63
  "react-dom": "^17 || ^18"
64
64
  },
65
- "gitHead": "0f3ece1d04a0f27f48f6369ab81eddf93c078846"
65
+ "gitHead": "0eb8f37580f7c68fec72396572aec0a8025881fd"
66
66
  }
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { css } from '@patternfly/react-styles';
3
3
  import { Menu, MenuContent, MenuProps } from '../Menu';
4
4
  import { Popper } from '../../helpers/Popper/Popper';
5
- import { useOUIAProps, OUIAProps } from '../../helpers';
5
+ import { useOUIAProps, OUIAProps, onToggleArrowKeydownDefault } from '../../helpers';
6
6
 
7
7
  export interface DropdownPopperProps {
8
8
  /** Vertical direction of the popper. If enableFlip is set to true, this will set the initial direction before the popper flips. */
@@ -51,6 +51,8 @@ export interface DropdownProps extends MenuProps, OUIAProps {
51
51
  onOpenChange?: (isOpen: boolean) => void;
52
52
  /** @beta Keys that trigger onOpenChange, defaults to tab and escape. It is highly recommended to include Escape in the array, while Tab may be omitted if the menu contains non-menu items that are focusable. */
53
53
  onOpenChangeKeys?: string[];
54
+ /** Callback to override the toggle keydown behavior. By default, when the toggle has focus and the menu is open, pressing the up/down arrow keys will focus a valid non-disabled menu item - the first item for the down arrow key and last item for the up arrow key. */
55
+ onToggleKeydown?: (event: KeyboardEvent) => void;
54
56
  /** Indicates if the menu should be without the outer box-shadow. */
55
57
  isPlain?: boolean;
56
58
  /** Indicates if the menu should be scrollable. */
@@ -85,6 +87,7 @@ const DropdownBase: React.FunctionComponent<DropdownProps> = ({
85
87
  toggle,
86
88
  shouldFocusToggleOnSelect = false,
87
89
  onOpenChange,
90
+ onToggleKeydown,
88
91
  isPlain,
89
92
  isScrollable,
90
93
  innerRef,
@@ -123,6 +126,14 @@ const DropdownBase: React.FunctionComponent<DropdownProps> = ({
123
126
  toggleRef.current?.focus();
124
127
  }
125
128
  }
129
+
130
+ if (toggleRef.current?.contains(event.target as Node)) {
131
+ if (onToggleKeydown) {
132
+ onToggleKeydown(event);
133
+ } else if (isOpen) {
134
+ onToggleArrowKeydownDefault(event, menuRef);
135
+ }
136
+ }
126
137
  };
127
138
 
128
139
  const handleClick = (event: MouseEvent) => {
@@ -157,6 +168,7 @@ const DropdownBase: React.FunctionComponent<DropdownProps> = ({
157
168
  toggleRef,
158
169
  onOpenChange,
159
170
  onOpenChangeKeys,
171
+ onToggleKeydown,
160
172
  shouldPreventScrollOnItemFocus,
161
173
  shouldFocusFirstItemOnOpen,
162
174
  focusTimeoutDelay
@@ -59,7 +59,7 @@ export interface DualListSelectorProps {
59
59
  addSelectedAriaLabel?: string;
60
60
  /** Tooltip content for the dynamically built add selected button */
61
61
  addSelectedTooltip?: React.ReactNode;
62
- /** Additonal tooltip properties for the dynamically built add selected tooltip */
62
+ /** Additional tooltip properties for the dynamically built add selected tooltip */
63
63
  addSelectedTooltipProps?: any;
64
64
  /** Callback fired every time dynamically built options are chosen or removed */
65
65
  onListChange?: (
@@ -73,7 +73,7 @@ export interface DualListSelectorProps {
73
73
  addAllAriaLabel?: string;
74
74
  /** Tooltip content for the dynamically built add all button */
75
75
  addAllTooltip?: React.ReactNode;
76
- /** Additonal tooltip properties for the dynamically built add all tooltip */
76
+ /** Additional tooltip properties for the dynamically built add all tooltip */
77
77
  addAllTooltipProps?: any;
78
78
  /** Optional callback for the dynamically built remove selected button */
79
79
  removeSelected?: (newAvailableOptions: React.ReactNode[], newChosenOptions: React.ReactNode[]) => void;
@@ -81,7 +81,7 @@ export interface DualListSelectorProps {
81
81
  removeSelectedAriaLabel?: string;
82
82
  /** Tooltip content for the dynamically built remove selected button */
83
83
  removeSelectedTooltip?: React.ReactNode;
84
- /** Additonal tooltip properties for the dynamically built remove selected tooltip */
84
+ /** Additional tooltip properties for the dynamically built remove selected tooltip */
85
85
  removeSelectedTooltipProps?: any;
86
86
  /** Optional callback for the dynamically built remove all button */
87
87
  removeAll?: (newAvailableOptions: React.ReactNode[], newChosenOptions: React.ReactNode[]) => void;
@@ -89,7 +89,7 @@ export interface DualListSelectorProps {
89
89
  removeAllAriaLabel?: string;
90
90
  /** Tooltip content for the dynamically built remove all button */
91
91
  removeAllTooltip?: React.ReactNode;
92
- /** Additonal tooltip properties for the dynamically built remove all tooltip */
92
+ /** Additional tooltip properties for the dynamically built remove all tooltip */
93
93
  removeAllTooltipProps?: any;
94
94
  /** Optional callback fired when a dynamically built option is selected */
95
95
  onOptionSelect?: (
@@ -286,8 +286,8 @@ class DualListSelector extends React.Component<DualListSelectorProps, DualListSe
286
286
  chosenFilteredOptions: newChosen,
287
287
  availableOptions: newAvailable,
288
288
  availableFilteredOptions: newAvailable,
289
- chosenOptionsSelected: [],
290
- availableOptionsSelected: []
289
+ chosenOptionsSelected: [] as number[],
290
+ availableOptionsSelected: [] as number[]
291
291
  };
292
292
  });
293
293
  };
@@ -319,8 +319,8 @@ class DualListSelector extends React.Component<DualListSelectorProps, DualListSe
319
319
  chosenFilteredOptions: newChosen,
320
320
  availableOptions: newAvailable,
321
321
  availableFilteredOptions: newAvailable,
322
- availableTreeOptionsChecked: [],
323
- chosenTreeOptionsChecked: []
322
+ availableTreeOptionsChecked: [] as string[],
323
+ chosenTreeOptionsChecked: [] as string[]
324
324
  };
325
325
  });
326
326
  };
@@ -342,8 +342,8 @@ class DualListSelector extends React.Component<DualListSelectorProps, DualListSe
342
342
  this.props.onListChange && this.props.onListChange(event, newAvailable, newChosen);
343
343
 
344
344
  return {
345
- chosenOptionsSelected: [],
346
- availableOptionsSelected: [],
345
+ chosenOptionsSelected: [] as number[],
346
+ availableOptionsSelected: [] as number[],
347
347
  chosenOptions: newChosen,
348
348
  chosenFilteredOptions: newChosen,
349
349
  availableOptions: newAvailable,
@@ -374,8 +374,8 @@ class DualListSelector extends React.Component<DualListSelectorProps, DualListSe
374
374
  this.props.onListChange && this.props.onListChange(event, newAvailable, newChosen);
375
375
 
376
376
  return {
377
- availableTreeOptionsChecked: [],
378
- chosenTreeOptionsChecked: [],
377
+ availableTreeOptionsChecked: [] as string[],
378
+ chosenTreeOptionsChecked: [] as string[],
379
379
  availableOptions: newAvailable,
380
380
  chosenOptions: newChosen
381
381
  };
@@ -404,8 +404,8 @@ class DualListSelector extends React.Component<DualListSelectorProps, DualListSe
404
404
  chosenFilteredOptions: newChosen,
405
405
  availableOptions: newAvailable,
406
406
  availableFilteredOptions: newAvailable,
407
- chosenOptionsSelected: [],
408
- availableOptionsSelected: []
407
+ chosenOptionsSelected: [] as number[],
408
+ availableOptionsSelected: [] as number[]
409
409
  };
410
410
  });
411
411
  };
@@ -433,8 +433,8 @@ class DualListSelector extends React.Component<DualListSelectorProps, DualListSe
433
433
  return {
434
434
  chosenOptions: newChosen,
435
435
  availableOptions: newAvailable,
436
- availableTreeOptionsChecked: [],
437
- chosenTreeOptionsChecked: []
436
+ availableTreeOptionsChecked: [] as string[],
437
+ chosenTreeOptionsChecked: [] as string[]
438
438
  };
439
439
  });
440
440
  };
@@ -456,8 +456,8 @@ class DualListSelector extends React.Component<DualListSelectorProps, DualListSe
456
456
  this.props.onListChange && this.props.onListChange(event, newAvailable, newChosen);
457
457
 
458
458
  return {
459
- chosenOptionsSelected: [],
460
- availableOptionsSelected: [],
459
+ chosenOptionsSelected: [] as number[],
460
+ availableOptionsSelected: [] as number[],
461
461
  chosenOptions: newChosen,
462
462
  chosenFilteredOptions: newChosen,
463
463
  availableOptions: newAvailable,
@@ -488,8 +488,8 @@ class DualListSelector extends React.Component<DualListSelectorProps, DualListSe
488
488
  this.props.onListChange && this.props.onListChange(event, newAvailable, newChosen);
489
489
 
490
490
  return {
491
- availableTreeOptionsChecked: [],
492
- chosenTreeOptionsChecked: [],
491
+ availableTreeOptionsChecked: [] as string[],
492
+ chosenTreeOptionsChecked: [] as string[],
493
493
  availableOptions: newAvailable,
494
494
  chosenOptions: newChosen
495
495
  };
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { Popper } from '../../helpers/Popper/Popper';
2
+ import { onToggleArrowKeydownDefault, Popper } from '../../helpers';
3
3
 
4
4
  export interface MenuPopperProps {
5
5
  /** Vertical direction of the popper. If enableFlip is set to true, this will set the initial direction before the popper flips. */
@@ -17,6 +17,7 @@ export interface MenuPopperProps {
17
17
  /** Flag to prevent the popper from overflowing its container and becoming partially obscured. */
18
18
  preventOverflow?: boolean;
19
19
  }
20
+
20
21
  export interface MenuContainerProps {
21
22
  /** Menu to be rendered */
22
23
  menu: React.ReactElement<any, string | React.JSXElementConstructor<any>>;
@@ -33,10 +34,14 @@ export interface MenuContainerProps {
33
34
  onOpenChange?: (isOpen: boolean) => void;
34
35
  /** Keys that trigger onOpenChange, defaults to tab and escape. It is highly recommended to include Escape in the array, while Tab may be omitted if the menu contains non-menu items that are focusable. */
35
36
  onOpenChangeKeys?: string[];
37
+ /** Callback to override the toggle keydown behavior. By default, when the toggle has focus and the menu is open, pressing the up/down arrow keys will focus a valid non-disabled menu item - the first item for the down arrow key and last item for the up arrow key. */
38
+ onToggleKeydown?: (event: KeyboardEvent) => void;
36
39
  /** z-index of the dropdown menu */
37
40
  zIndex?: number;
38
41
  /** Additional properties to pass to the Popper */
39
42
  popperProps?: MenuPopperProps;
43
+ /** @beta Flag indicating the first menu item should be focused after opening the dropdown. */
44
+ shouldFocusFirstItemOnOpen?: boolean;
40
45
  /** Flag indicating if scroll on focus of the first menu item should occur. */
41
46
  shouldPreventScrollOnItemFocus?: boolean;
42
47
  /** Time in ms to wait before firing the toggles' focus event. Defaults to 0 */
@@ -54,12 +59,30 @@ export const MenuContainer: React.FunctionComponent<MenuContainerProps> = ({
54
59
  toggle,
55
60
  toggleRef,
56
61
  onOpenChange,
62
+ onToggleKeydown,
57
63
  zIndex = 9999,
58
64
  popperProps,
59
65
  onOpenChangeKeys = ['Escape', 'Tab'],
66
+ shouldFocusFirstItemOnOpen = false,
60
67
  shouldPreventScrollOnItemFocus = true,
61
68
  focusTimeoutDelay = 0
62
69
  }: MenuContainerProps) => {
70
+ const prevIsOpen = React.useRef<boolean>(isOpen);
71
+ React.useEffect(() => {
72
+ // menu was opened, focus on first menu item
73
+ if (prevIsOpen.current === false && isOpen === true && shouldFocusFirstItemOnOpen) {
74
+ setTimeout(() => {
75
+ const firstElement = menuRef?.current?.querySelector(
76
+ 'li button:not(:disabled),li input:not(:disabled),li a:not([aria-disabled="true"])'
77
+ );
78
+ firstElement && (firstElement as HTMLElement).focus({ preventScroll: shouldPreventScrollOnItemFocus });
79
+ }, focusTimeoutDelay);
80
+ }
81
+
82
+ prevIsOpen.current = isOpen;
83
+ // eslint-disable-next-line react-hooks/exhaustive-deps
84
+ }, [isOpen]);
85
+
63
86
  React.useEffect(() => {
64
87
  const handleMenuKeys = (event: KeyboardEvent) => {
65
88
  // Close the menu on tab or escape if onOpenChange is provided
@@ -72,19 +95,17 @@ export const MenuContainer: React.FunctionComponent<MenuContainerProps> = ({
72
95
  toggleRef.current?.focus();
73
96
  }
74
97
  }
75
- };
76
98
 
77
- const handleClick = (event: MouseEvent) => {
78
- // toggle was opened, focus on first menu item
79
- if (isOpen && toggleRef.current?.contains(event.target as Node)) {
80
- setTimeout(() => {
81
- const firstElement = menuRef?.current?.querySelector(
82
- 'li button:not(:disabled),li input:not(:disabled),li a:not([aria-disabled="true"])'
83
- );
84
- firstElement && (firstElement as HTMLElement).focus({ preventScroll: shouldPreventScrollOnItemFocus });
85
- }, focusTimeoutDelay);
99
+ if (toggleRef.current?.contains(event.target as Node)) {
100
+ if (onToggleKeydown) {
101
+ onToggleKeydown(event);
102
+ } else if (isOpen) {
103
+ onToggleArrowKeydownDefault(event, menuRef);
104
+ }
86
105
  }
106
+ };
87
107
 
108
+ const handleClick = (event: MouseEvent) => {
88
109
  // If the event is not on the toggle and onOpenChange callback is provided, close the menu
89
110
  if (isOpen && onOpenChange && !toggleRef?.current?.contains(event.target as Node)) {
90
111
  if (isOpen && !menuRef.current?.contains(event.target as Node)) {
@@ -100,7 +121,16 @@ export const MenuContainer: React.FunctionComponent<MenuContainerProps> = ({
100
121
  window.removeEventListener('keydown', handleMenuKeys);
101
122
  window.removeEventListener('click', handleClick);
102
123
  };
103
- }, [focusTimeoutDelay, isOpen, menuRef, onOpenChange, onOpenChangeKeys, shouldPreventScrollOnItemFocus, toggleRef]);
124
+ }, [
125
+ focusTimeoutDelay,
126
+ isOpen,
127
+ menuRef,
128
+ onOpenChange,
129
+ onOpenChangeKeys,
130
+ onToggleKeydown,
131
+ shouldPreventScrollOnItemFocus,
132
+ toggleRef
133
+ ]);
104
134
 
105
135
  return (
106
136
  <Popper
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { css } from '@patternfly/react-styles';
3
3
  import { Menu, MenuContent, MenuProps } from '../Menu';
4
4
  import { Popper } from '../../helpers/Popper/Popper';
5
- import { getOUIAProps, OUIAProps, getDefaultOUIAId } from '../../helpers';
5
+ import { getOUIAProps, OUIAProps, getDefaultOUIAId, onToggleArrowKeydownDefault } from '../../helpers';
6
6
 
7
7
  export interface SelectPopperProps {
8
8
  /** Vertical direction of the popper. If enableFlip is set to true, this will set the initial direction before the popper flips. */
@@ -62,6 +62,10 @@ export interface SelectProps extends MenuProps, OUIAProps {
62
62
  onOpenChange?: (isOpen: boolean) => void;
63
63
  /** @beta Keys that trigger onOpenChange, defaults to tab and escape. It is highly recommended to include Escape in the array, while Tab may be omitted if the menu contains non-menu items that are focusable. */
64
64
  onOpenChangeKeys?: string[];
65
+ /** Callback to override the toggle keydown behavior. By default, when the toggle has focus and the menu is open, pressing the up/down arrow keys will focus a valid non-disabled menu item - the first item for the down arrow key and last item for the up arrow key. */
66
+ onToggleKeydown?: (event: KeyboardEvent) => void;
67
+ /** Select variant. For typeahead variant focus won't shift to menu items when pressing up/down arrows. */
68
+ variant?: 'default' | 'typeahead';
65
69
  /** Indicates if the select should be without the outer box-shadow */
66
70
  isPlain?: boolean;
67
71
  /** @hide Forwarded ref */
@@ -95,6 +99,8 @@ const SelectBase: React.FunctionComponent<SelectProps & OUIAProps> = ({
95
99
  shouldFocusFirstItemOnOpen = false,
96
100
  onOpenChange,
97
101
  onOpenChangeKeys = ['Escape', 'Tab'],
102
+ onToggleKeydown,
103
+ variant,
98
104
  isPlain,
99
105
  innerRef,
100
106
  zIndex = 9999,
@@ -130,6 +136,14 @@ const SelectBase: React.FunctionComponent<SelectProps & OUIAProps> = ({
130
136
  toggleRef.current?.focus();
131
137
  }
132
138
  }
139
+
140
+ if (toggleRef.current?.contains(event.target as Node)) {
141
+ if (onToggleKeydown) {
142
+ onToggleKeydown(event);
143
+ } else if (isOpen && variant !== 'typeahead') {
144
+ onToggleArrowKeydownDefault(event, menuRef);
145
+ }
146
+ }
133
147
  };
134
148
 
135
149
  const handleClick = (event: MouseEvent) => {
@@ -162,6 +176,7 @@ const SelectBase: React.FunctionComponent<SelectProps & OUIAProps> = ({
162
176
  toggleRef,
163
177
  onOpenChange,
164
178
  onOpenChangeKeys,
179
+ onToggleKeydown,
165
180
  shouldPreventScrollOnItemFocus,
166
181
  shouldFocusFirstItemOnOpen,
167
182
  focusTimeoutDelay
@@ -245,7 +245,7 @@ export const SelectMultiTypeahead: React.FunctionComponent = () => {
245
245
  !isOpen && closeMenu();
246
246
  }}
247
247
  toggle={toggle}
248
- shouldFocusFirstItemOnOpen={false}
248
+ variant="typeahead"
249
249
  >
250
250
  <SelectList isAriaMultiselectable id="select-multi-typeahead-listbox">
251
251
  {selectOptions.map((option, index) => (
@@ -238,7 +238,7 @@ export const SelectMultiTypeaheadCheckbox: React.FunctionComponent = () => {
238
238
  !isOpen && closeMenu();
239
239
  }}
240
240
  toggle={toggle}
241
- shouldFocusFirstItemOnOpen={false}
241
+ variant="typeahead"
242
242
  >
243
243
  <SelectList isAriaMultiselectable id="select-multi-typeahead-checkbox-listbox">
244
244
  {selectOptions.map((option, index) => (
@@ -256,7 +256,7 @@ export const SelectMultiTypeaheadCreatable: React.FunctionComponent = () => {
256
256
  !isOpen && closeMenu();
257
257
  }}
258
258
  toggle={toggle}
259
- shouldFocusFirstItemOnOpen={false}
259
+ variant="typeahead"
260
260
  >
261
261
  <SelectList isAriaMultiselectable id="select-multi-create-typeahead-listbox">
262
262
  {selectOptions.map((option, index) => (
@@ -241,7 +241,7 @@ export const SelectTypeahead: React.FunctionComponent = () => {
241
241
  !isOpen && closeMenu();
242
242
  }}
243
243
  toggle={toggle}
244
- shouldFocusFirstItemOnOpen={false}
244
+ variant="typeahead"
245
245
  >
246
246
  <SelectList id="select-typeahead-listbox">
247
247
  {selectOptions.map((option, index) => (
@@ -248,7 +248,7 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => {
248
248
  !isOpen && closeMenu();
249
249
  }}
250
250
  toggle={toggle}
251
- shouldFocusFirstItemOnOpen={false}
251
+ variant="typeahead"
252
252
  >
253
253
  <SelectList id="select-create-typeahead-listbox">
254
254
  {selectOptions.map((option, index) => (
@@ -170,6 +170,33 @@ export const setTabIndex = (options: HTMLElement[]) => {
170
170
  }
171
171
  };
172
172
 
173
+ /**
174
+ * This function is used in Dropdown, Select and MenuContainer as a default toggle keydown behavior. When the toggle has focus and the menu is open, pressing the up/down arrow keys will focus a valid non-disabled menu item - the first item for the down arrow key and last item for the up arrow key.
175
+ *
176
+ * @param event Event triggered by the keyboard
177
+ * @param menuRef Menu reference
178
+ */
179
+ export const onToggleArrowKeydownDefault = (event: KeyboardEvent, menuRef: React.RefObject<HTMLDivElement>) => {
180
+ if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') {
181
+ return;
182
+ }
183
+
184
+ event.preventDefault();
185
+
186
+ const listItems = Array.from(menuRef.current?.querySelectorAll('li'));
187
+ const focusableElements = listItems
188
+ .map((li) => li.querySelector('button:not(:disabled),input:not(:disabled),a:not([aria-disabled="true"])'))
189
+ .filter((el) => el !== null);
190
+
191
+ let focusableElement: Element;
192
+ if (event.key === 'ArrowDown') {
193
+ focusableElement = focusableElements[0];
194
+ } else {
195
+ focusableElement = focusableElements[focusableElements.length - 1];
196
+ }
197
+ focusableElement && (focusableElement as HTMLElement).focus();
198
+ };
199
+
173
200
  class KeyboardHandler extends React.Component<KeyboardHandlerProps> {
174
201
  static displayName = 'KeyboardHandler';
175
202
  static defaultProps: KeyboardHandlerProps = {