@patternfly/react-core 4.245.0 → 4.247.0

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 (51) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/esm/components/DualListSelector/DualListSelectorPane.d.ts +2 -0
  3. package/dist/esm/components/DualListSelector/DualListSelectorPane.d.ts.map +1 -1
  4. package/dist/esm/components/DualListSelector/DualListSelectorPane.js +4 -5
  5. package/dist/esm/components/DualListSelector/DualListSelectorPane.js.map +1 -1
  6. package/dist/esm/components/SearchInput/SearchInput.d.ts +2 -0
  7. package/dist/esm/components/SearchInput/SearchInput.d.ts.map +1 -1
  8. package/dist/esm/components/SearchInput/SearchInput.js +2 -2
  9. package/dist/esm/components/SearchInput/SearchInput.js.map +1 -1
  10. package/dist/esm/components/TreeView/TreeView.d.ts.map +1 -1
  11. package/dist/esm/components/TreeView/TreeView.js +1 -1
  12. package/dist/esm/components/TreeView/TreeView.js.map +1 -1
  13. package/dist/esm/components/TreeView/TreeViewListItem.d.ts.map +1 -1
  14. package/dist/esm/components/TreeView/TreeViewListItem.js +2 -2
  15. package/dist/esm/components/TreeView/TreeViewListItem.js.map +1 -1
  16. package/dist/esm/components/TreeView/TreeViewRoot.d.ts +5 -3
  17. package/dist/esm/components/TreeView/TreeViewRoot.d.ts.map +1 -1
  18. package/dist/esm/components/TreeView/TreeViewRoot.js +26 -17
  19. package/dist/esm/components/TreeView/TreeViewRoot.js.map +1 -1
  20. package/dist/js/components/DualListSelector/DualListSelectorPane.d.ts +2 -0
  21. package/dist/js/components/DualListSelector/DualListSelectorPane.d.ts.map +1 -1
  22. package/dist/js/components/DualListSelector/DualListSelectorPane.js +4 -5
  23. package/dist/js/components/DualListSelector/DualListSelectorPane.js.map +1 -1
  24. package/dist/js/components/SearchInput/SearchInput.d.ts +2 -0
  25. package/dist/js/components/SearchInput/SearchInput.d.ts.map +1 -1
  26. package/dist/js/components/SearchInput/SearchInput.js +2 -2
  27. package/dist/js/components/SearchInput/SearchInput.js.map +1 -1
  28. package/dist/js/components/TreeView/TreeView.d.ts.map +1 -1
  29. package/dist/js/components/TreeView/TreeView.js +1 -1
  30. package/dist/js/components/TreeView/TreeView.js.map +1 -1
  31. package/dist/js/components/TreeView/TreeViewListItem.d.ts.map +1 -1
  32. package/dist/js/components/TreeView/TreeViewListItem.js +2 -2
  33. package/dist/js/components/TreeView/TreeViewListItem.js.map +1 -1
  34. package/dist/js/components/TreeView/TreeViewRoot.d.ts +5 -3
  35. package/dist/js/components/TreeView/TreeViewRoot.d.ts.map +1 -1
  36. package/dist/js/components/TreeView/TreeViewRoot.js +26 -17
  37. package/dist/js/components/TreeView/TreeViewRoot.js.map +1 -1
  38. package/dist/umd/react-core.min.css +3370 -3370
  39. package/dist/umd/react-core.min.js +2 -2
  40. package/package.json +2 -2
  41. package/src/components/Dropdown/examples/Dropdown.md +79 -55
  42. package/src/components/DualListSelector/DualListSelectorPane.tsx +11 -7
  43. package/src/components/DualListSelector/__tests__/__snapshots__/DualListSelector.test.tsx.snap +70 -10
  44. package/src/components/SearchInput/SearchInput.tsx +15 -0
  45. package/src/components/TreeView/TreeView.tsx +8 -1
  46. package/src/components/TreeView/TreeViewListItem.tsx +4 -3
  47. package/src/components/TreeView/TreeViewRoot.tsx +38 -18
  48. package/src/components/TreeView/__tests__/__snapshots__/TreeView.test.tsx.snap +0 -51
  49. package/src/demos/ComposableMenu/ComposableMenu.md +7 -2
  50. package/src/demos/ComposableMenu/examples/ComposableMultipleTypeaheadSelect.tsx +220 -0
  51. package/src/components/Dropdown/examples/DropdownSplitButtonIndeterminate.tsx +0 -78
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/react-core",
3
- "version": "4.245.0",
3
+ "version": "4.247.0",
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",
@@ -70,5 +70,5 @@
70
70
  "react": "^16.8 || ^17 || ^18",
71
71
  "react-dom": "^16.8 || ^17 || ^18"
72
72
  },
73
- "gitHead": "36df59ed72c668ee3f4c43a7b3d87b7375266f3c"
73
+ "gitHead": "064d341a881a7ab291add565ed1929cf915ab049"
74
74
  }
@@ -18,133 +18,157 @@ import avatarImg from '../../Avatar/examples/avatarImg.svg';
18
18
 
19
19
  ## Examples
20
20
 
21
- ### Basic
21
+ ### Basic dropdowns
22
+
23
+ Basic dropdowns present users with a menu of items upon clicking a dropdown toggle. The `isOpen` property is used to determine whether the menu is opened or not.
24
+
25
+ All of the items in a dropdown list are created as `<DropdownItem>` components. The following example shows a few different states of dropdown items. As shown in this example, dropdown items may:
26
+ - Use the `tooltip` property to enable tooltip support.
27
+ - Use the `isDisabled` property to disable an item.
28
+ - Use the `isAriaDisabled` property to style an item as disabled, while still allowing the item to receive keyboard focus and display a tooltip.
29
+
30
+ This example also uses a `<DropdownSeparator>`, which adds a horizontal line that splits the menu into 2 sections.
22
31
 
23
32
  ```ts file='./DropdownBasic.tsx'
24
33
  ```
25
34
 
26
- ### With initial selection
35
+ ### Autofocus on dropdown expansion
36
+
37
+ A `<DropdownItem>` may use the `autoFocus` property so that it automatically receives focus when the menu is expanded. `autoFocus` cannot be applied to disabled dropdown items.
27
38
 
28
39
  ```ts file="./DropdownInitialSelection.tsx"
29
40
  ```
30
41
 
31
- ### With groups
42
+ ### With groups of items
43
+
44
+ Use the `<DropdownGroup>` component to group sets of related dropdown items together. Use the `isGrouped` property on the `Dropdown` component to indicate that a dropdown contains grouped items.
32
45
 
33
46
  ```ts file="./DropdownGroups.tsx"
34
47
  ```
35
48
 
36
- ### Disabled
49
+ ### With item descriptions
37
50
 
38
- ```ts file="./DropdownDisabled.tsx"
51
+ A `<DropdownItem>` may use the `description` property to provide users with more context. This description will appear in the menu below the dropdown item it corresponds to.
52
+
53
+ ```ts file="./DropdownDescriptions.tsx"
39
54
  ```
40
55
 
41
- ### Primary toggle
56
+ ### With custom content
42
57
 
43
- ```ts file="./DropdownPrimaryToggle.tsx"
58
+ You may replace standard dropdown items with custom content. If so, you must style the menu contents and manage focus within your custom implementation.
59
+
60
+ ```ts file="./DropdownBasicPanel.tsx"
44
61
  ```
45
62
 
46
- ### Secondary toggle
63
+ ### With router link items
47
64
 
48
- ```ts file="./DropdownSecondaryToggle.tsx"
65
+ `<DropdownItem>` may use the `component` property to include router links.
66
+
67
+ ```ts file="./DropdownRouterLink.tsx"
49
68
  ```
50
69
 
51
- ### Plain with text toggle
70
+ ### Toggle styling
52
71
 
53
- ```ts file="./DropdownPlainTextToggle.tsx"
72
+ You can adjust the styling of a `<DropdownToggle>` using the `toggleVariant` property. `toggleVariant` may be set to 'primary', 'secondary', or 'default'.
73
+
74
+ ```ts file="./DropdownPrimaryToggle.tsx"
54
75
  ```
55
76
 
56
- ### Position right
77
+ ### Plain text toggle
57
78
 
58
- ```ts file="./DropdownPositionRight.tsx"
79
+ A toggle may be styled as plain text using the `isText` and `isPlain`properties. The following example applies these properties to a `<Dropdown>` component.
80
+
81
+ ```ts file="./DropdownPlainTextToggle.tsx"
59
82
  ```
60
83
 
61
- ### Alignments on different breakpoints
84
+ ### Disabled toggles
62
85
 
63
- ```ts file="./DropdownAlignmentOnBreakpoints.tsx"
86
+ A `<DropdownToggle>` may use the `isDisabled` property to disable the expansion of the dropdown menu.
87
+
88
+ ```ts file="./DropdownDisabled.tsx"
64
89
  ```
65
90
 
66
- ### Direction up
91
+ ### Icon toggle
67
92
 
68
- ```ts file="./DropdownDirectionUp.tsx"
93
+ `<DropdownToggle>` may use a recognizable icon instead of a text label if text would take up too much room. Set the `toggleIndicator` property to `{null}` to remove the default dropdown toggle caret.
94
+
95
+ ```ts file="./DropdownIconOnly.tsx"
69
96
  ```
70
97
 
71
- ### With kebab
98
+ ### Kebab toggle
99
+
100
+ Use `<KebabToggle>` to replace the default text label and caret with a kebab icon. A kebab toggle can be used to indicate that a dropdown connects to a menu.
72
101
 
73
102
  ```ts file="./DropdownKebab.tsx"
74
103
  ```
75
104
 
76
- ### With badge
105
+ ### Toggle with image
77
106
 
78
- ```ts file="./DropdownBadge.tsx"
107
+ The `icon` property can be used to display an image in the `DropdownToggle` and can be used alongside a text label.
108
+
109
+ ```ts file="./DropdownImageAndText.tsx"
79
110
  ```
80
111
 
81
- ### Icon only
112
+ ### Toggle with badge
82
113
 
83
- ```ts file="./DropdownIconOnly.tsx"
114
+ You may use a `<BadgeToggle>` to display the number of dropdown menu items.
115
+
116
+ ```ts file="./DropdownBadge.tsx"
84
117
  ```
85
118
 
86
119
  ### Split button
87
120
 
88
- ```ts file="./DropdownSplitButton.tsx"
89
- ```
121
+ To combine an action button or other control with a dropdown menu, use a split button. A `<DropdownToggle>` can be rendered as a split button by using the `splitButtonItems` property. Elements to be displayed before the toggle button must be included in `splitButtonItems`.
90
122
 
91
- ### Split button (with text)
123
+ The following example shows a split button with a `<DropdownToggleCheckbox>`, which is primarily used for bulk select operations on large data sets.
92
124
 
93
- ```ts file="./DropdownSplitButtonText.tsx"
125
+ ```ts file="./DropdownSplitButton.tsx"
94
126
  ```
95
127
 
96
- ### Split button (indeterminate state)
97
-
98
- ```ts file="./DropdownSplitButtonIndeterminate.tsx"
99
- ```
128
+ ### Split button text label
100
129
 
101
- ### Split button (disabled)
130
+ Text labels may optionally be used alongside actions within split buttons.
102
131
 
103
- ```ts file="./DropdownSplitButtonDisabled.tsx"
132
+ ```ts file="./DropdownSplitButtonText.tsx"
104
133
  ```
105
134
 
106
- ### Split button action
135
+ ### Split button toggle variants
107
136
 
108
- ```ts file="./DropdownSplitButtonAction.tsx"
109
- ```
137
+ Use the `toggleVariant` and `splitButtonVariant` properties to adjust the styling of split button toggles.
110
138
 
111
- ### Split button primary action
139
+ The following example uses `toggleVariant="primary"` to apply primary visual styling and `splitButtonVariant="action"` to include an action.
112
140
 
113
141
  ```ts file="./DropdownSplitButtonActionPrimary.tsx"
114
142
  ```
115
143
 
116
- ### Split button secondary action
144
+ ### Dropdown alignment
117
145
 
118
- ```ts file="./DropdownSplitButtonActionSecondary.tsx"
119
- ```
146
+ The alignment of a dropdown menu may be changed to better fit the size of a user's browser window. A `<Dropdown>` may use the `alignments` property to indicate how the menu will align at different screen sizes.
120
147
 
121
- ### Basic panel
122
-
123
- ```ts file="./DropdownBasicPanel.tsx"
148
+ ```ts file="./DropdownAlignmentOnBreakpoints.tsx"
124
149
  ```
125
150
 
126
- ### Router link
151
+ ### Right-positioned menu
127
152
 
128
- ```ts file="./DropdownRouterLink.tsx"
153
+ The following example demonstrates a `<Dropdown>` with a `position` equal to 'right'. By default, a dropdown menu is left-positioned, but a right-positioned menu may work better depending on where your dropdown is placed on a page.
154
+
155
+ ```ts file="./DropdownPositionRight.tsx"
129
156
  ```
130
157
 
131
- ### Dropdown with image and text
158
+ ### Upwards directional menu
132
159
 
133
- ```ts file="./DropdownImageAndText.tsx"
160
+ The following example demonstrates a `<Dropdown>` with a `direction` equal to 'up'. This will alter the default menu behavior so that the dropdown menu is displayed above the dropdown toggle.
161
+
162
+ ```ts file="./DropdownDirectionUp.tsx"
134
163
  ```
135
164
 
136
- ### Appending document body vs parent
165
+ ### Using document parent element
137
166
 
138
- Avoid passing in `document.body` when passing a value to the `menuAppendTo` prop on the Dropdown component, as it can cause accessibility issues. These issues can include, but are not limited to, being unable to enter the contents of the Dropdown options via assistive technologies (like keyboards or screen readers).
167
+ Avoid passing in `document.body` to a `<Dropdown>` `menuAppendTo` property. Doing so can cause accessibility issues, such as being unable to navigate `<Dropdown>` options using keyboards or screen readers.
139
168
 
140
- Instead append to `"parent"` to achieve the same result without sacrificing accessibility.
169
+ Instead, append to ‘parent’ to avoid sacrificing accessibility.
141
170
 
142
- In this example, while, after making a selection, both variants retain focus on their respective Dropdown component, the options for the `document.body` variant cannot be navigated to via Voice Over.
171
+ The following example demonstrates both methods of appending. After making a selection, both dropdowns retain focus on their respective `<Dropdown>`, but the options for the `document.body` variant cannot be navigated via Voice Over.
143
172
 
144
173
  ```ts file="./DropdownAppendBodyVsParent.tsx"
145
174
  ```
146
-
147
- ### Dropdown with descriptions
148
-
149
- ```ts file="./DropdownDescriptions.tsx"
150
- ```
@@ -1,12 +1,12 @@
1
1
  import * as React from 'react';
2
2
  import styles from '@patternfly/react-styles/css/components/DualListSelector/dual-list-selector';
3
3
  import { css } from '@patternfly/react-styles';
4
- import formStyles from '@patternfly/react-styles/css/components/FormControl/form-control';
5
4
  import { DualListSelectorTree, DualListSelectorTreeItemData } from './DualListSelectorTree';
6
5
  import { getUniqueId } from '../../helpers';
7
6
  import { DualListSelectorListWrapper } from './DualListSelectorListWrapper';
8
7
  import { DualListSelectorContext, DualListSelectorPaneContext } from './DualListSelectorContext';
9
8
  import { DualListSelectorList } from './DualListSelectorList';
9
+ import { SearchInput } from '../SearchInput';
10
10
 
11
11
  /** Acts as the container for a list of options that are either available or chosen,
12
12
  * depending on the pane type (available or chosen). A search input and other actions,
@@ -57,6 +57,8 @@ export interface DualListSelectorPaneProps {
57
57
  onSearch?: (event: React.ChangeEvent<HTMLInputElement>) => void;
58
58
  /** @hide A callback for when the search input value for changes. To be used when isSearchable is true. */
59
59
  onSearchInputChanged?: (value: string, event: React.FormEvent<HTMLInputElement>) => void;
60
+ /** @hide Callback for search input clear button */
61
+ onSearchInputClear?: (event: React.SyntheticEvent<HTMLButtonElement>) => void;
60
62
  /** @hide Filter function for custom filtering based on search string. To be used when isSearchable is true. */
61
63
  filterOption?: (option: React.ReactNode, input: string) => boolean;
62
64
  /** @hide Accessible label for the search input. To be used when isSearchable is true. */
@@ -81,6 +83,7 @@ export const DualListSelectorPane: React.FunctionComponent<DualListSelectorPaneP
81
83
  searchInputAriaLabel = '',
82
84
  onFilterUpdate,
83
85
  onSearchInputChanged,
86
+ onSearchInputClear,
84
87
  filterOption,
85
88
  id = getUniqueId('dual-list-selector-pane'),
86
89
  isDisabled = false,
@@ -90,8 +93,7 @@ export const DualListSelectorPane: React.FunctionComponent<DualListSelectorPaneP
90
93
  const { isTree } = React.useContext(DualListSelectorContext);
91
94
 
92
95
  // only called when search input is dynamically built
93
- const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
94
- const newValue = e.target.value;
96
+ const onChange = (newValue: string, e: React.FormEvent<HTMLInputElement>) => {
95
97
  let filtered: React.ReactNode[];
96
98
  if (isTree) {
97
99
  filtered = options
@@ -160,12 +162,14 @@ export const DualListSelectorPane: React.FunctionComponent<DualListSelectorPaneP
160
162
  {searchInput ? (
161
163
  searchInput
162
164
  ) : (
163
- <input
164
- className={css(formStyles.formControl, formStyles.modifiers.search)}
165
- type="search"
165
+ <SearchInput
166
166
  onChange={isDisabled ? undefined : onChange}
167
+ onClear={
168
+ onSearchInputClear ? onSearchInputClear : e => onChange('', e as React.FormEvent<HTMLInputElement>)
169
+ }
170
+ isDisabled={isDisabled}
167
171
  aria-label={searchInputAriaLabel}
168
- disabled={isDisabled}
172
+ type="search"
169
173
  />
170
174
  )}
171
175
  </div>
@@ -1135,11 +1135,41 @@ exports[`DualListSelector with search inputs 1`] = `
1135
1135
  <div
1136
1136
  class="pf-c-dual-list-selector__tools-filter"
1137
1137
  >
1138
- <input
1139
- aria-label="Available search input"
1140
- class="pf-c-form-control pf-m-search"
1141
- type="search"
1142
- />
1138
+ <div
1139
+ class="pf-c-text-input-group"
1140
+ >
1141
+ <div
1142
+ class="pf-c-text-input-group__main pf-m-icon"
1143
+ >
1144
+ <span
1145
+ class="pf-c-text-input-group__text"
1146
+ >
1147
+ <span
1148
+ class="pf-c-text-input-group__icon"
1149
+ >
1150
+ <svg
1151
+ aria-hidden="true"
1152
+ fill="currentColor"
1153
+ height="1em"
1154
+ role="img"
1155
+ style="vertical-align: -0.125em;"
1156
+ viewBox="0 0 512 512"
1157
+ width="1em"
1158
+ >
1159
+ <path
1160
+ d="M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z"
1161
+ />
1162
+ </svg>
1163
+ </span>
1164
+ <input
1165
+ aria-label="Available search input"
1166
+ class="pf-c-text-input-group__text-input"
1167
+ type="search"
1168
+ value=""
1169
+ />
1170
+ </span>
1171
+ </div>
1172
+ </div>
1143
1173
  </div>
1144
1174
  </div>
1145
1175
  <div
@@ -1360,11 +1390,41 @@ exports[`DualListSelector with search inputs 1`] = `
1360
1390
  <div
1361
1391
  class="pf-c-dual-list-selector__tools-filter"
1362
1392
  >
1363
- <input
1364
- aria-label="Chosen search input"
1365
- class="pf-c-form-control pf-m-search"
1366
- type="search"
1367
- />
1393
+ <div
1394
+ class="pf-c-text-input-group"
1395
+ >
1396
+ <div
1397
+ class="pf-c-text-input-group__main pf-m-icon"
1398
+ >
1399
+ <span
1400
+ class="pf-c-text-input-group__text"
1401
+ >
1402
+ <span
1403
+ class="pf-c-text-input-group__icon"
1404
+ >
1405
+ <svg
1406
+ aria-hidden="true"
1407
+ fill="currentColor"
1408
+ height="1em"
1409
+ role="img"
1410
+ style="vertical-align: -0.125em;"
1411
+ viewBox="0 0 512 512"
1412
+ width="1em"
1413
+ >
1414
+ <path
1415
+ d="M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z"
1416
+ />
1417
+ </svg>
1418
+ </span>
1419
+ <input
1420
+ aria-label="Chosen search input"
1421
+ class="pf-c-text-input-group__text-input"
1422
+ type="search"
1423
+ value=""
1424
+ />
1425
+ </span>
1426
+ </div>
1427
+ </div>
1368
1428
  </div>
1369
1429
  </div>
1370
1430
  <div
@@ -70,6 +70,19 @@ export interface SearchInputProps extends Omit<React.HTMLProps<HTMLDivElement>,
70
70
  hasWordsAttrLabel?: React.ReactNode;
71
71
  /** A suggestion for autocompleting. */
72
72
  hint?: string;
73
+ /** Type of the input */
74
+ type?:
75
+ | 'text'
76
+ | 'date'
77
+ | 'datetime-local'
78
+ | 'email'
79
+ | 'month'
80
+ | 'number'
81
+ | 'password'
82
+ | 'search'
83
+ | 'tel'
84
+ | 'time'
85
+ | 'url';
73
86
  /** @hide A reference object to attach to the input box. */
74
87
  innerRef?: React.RefObject<any>;
75
88
  /** A flag for controlling the open state of a custom advanced search implementation. */
@@ -147,6 +160,7 @@ const SearchInputBase: React.FunctionComponent<SearchInputProps> = ({
147
160
  isDisabled = false,
148
161
  appendTo,
149
162
  removeFindDomNode = false,
163
+ type = 'text',
150
164
  ...props
151
165
  }: SearchInputProps) => {
152
166
  const [isSearchMenuOpen, setIsSearchMenuOpen] = React.useState(false);
@@ -263,6 +277,7 @@ const SearchInputBase: React.FunctionComponent<SearchInputProps> = ({
263
277
  aria-label={ariaLabel}
264
278
  onKeyDown={onEnter}
265
279
  onChange={onChangeHandler}
280
+ type={type}
266
281
  />
267
282
  {renderUtilities && (
268
283
  <TextInputGroupUtilities>
@@ -168,7 +168,14 @@ export const TreeView: React.FunctionComponent<TreeViewProps> = ({
168
168
  {parentItem ? (
169
169
  treeViewList
170
170
  ) : (
171
- <TreeViewRoot hasChecks={hasChecks} hasGuides={hasGuides} variant={variant} className={className} {...props}>
171
+ <TreeViewRoot
172
+ hasSelectableNodes={hasSelectableNodes}
173
+ hasChecks={hasChecks}
174
+ hasGuides={hasGuides}
175
+ variant={variant}
176
+ className={className}
177
+ {...props}
178
+ >
172
179
  {treeViewList}
173
180
  </TreeViewRoot>
174
181
  )}
@@ -155,8 +155,10 @@ const TreeViewListItemBase: React.FunctionComponent<TreeViewListItemProps> = ({
155
155
  const content = (
156
156
  <>
157
157
  {isCompact && title && <span className={css(styles.treeViewNodeTitle)}>{title}</span>}
158
- {hasCheck ? (
159
- <span className={css(styles.treeViewNodeText)}>{name}</span>
158
+ {isSelectable ? (
159
+ <button tabIndex={-1} className={css(styles.treeViewNodeText)}>
160
+ {name}
161
+ </button>
160
162
  ) : (
161
163
  <span className={css(styles.treeViewNodeText)}>{name}</span>
162
164
  )}
@@ -210,7 +212,6 @@ const TreeViewListItemBase: React.FunctionComponent<TreeViewListItemProps> = ({
210
212
  }
211
213
  }
212
214
  }}
213
- tabIndex={isSelectable ? 0 : -1}
214
215
  {...(hasCheck && { htmlFor: randomId })}
215
216
  {...((hasCheck || (isSelectable && children)) && { id: `label-${randomId}` })}
216
217
  >
@@ -3,18 +3,21 @@ import { css } from '@patternfly/react-styles';
3
3
  import styles from '@patternfly/react-styles/css/components/TreeView/tree-view';
4
4
  import { canUseDOM } from '../../helpers/util';
5
5
  import { handleArrows } from '../../helpers';
6
+ import { KeyTypes } from '../../helpers/constants';
6
7
 
7
8
  export interface TreeViewRootProps {
8
- /** Child nodes of the tree view. */
9
+ /** Child nodes of the tree view */
9
10
  children: React.ReactNode;
10
- /** Class to add to add if not passed a parentItem property. */
11
- className?: string;
12
11
  /** Flag indicating if the tree view has checkboxes. */
13
12
  hasChecks?: boolean;
14
13
  /** Flag indicating if tree view has guide lines. */
15
14
  hasGuides?: boolean;
16
15
  /** Variant presentation styles for the tree view. */
17
16
  variant?: 'default' | 'compact' | 'compactNoBackground';
17
+ /** Class to add to add if not passed a parentItem */
18
+ className?: string;
19
+ /** Flag indicating that tree nodes should be independently selectable, even when having children */
20
+ hasSelectableNodes?: boolean;
18
21
  }
19
22
 
20
23
  export class TreeViewRoot extends React.Component<TreeViewRootProps> {
@@ -23,16 +26,29 @@ export class TreeViewRoot extends React.Component<TreeViewRootProps> {
23
26
 
24
27
  componentDidMount() {
25
28
  if (canUseDOM) {
26
- window.addEventListener('keydown', this.props.hasChecks ? this.handleKeysCheckbox : this.handleKeys);
29
+ window.addEventListener(
30
+ 'keydown',
31
+ this.props.hasChecks || this.props.hasSelectableNodes ? this.handleKeysCheckbox : this.handleKeys
32
+ );
27
33
  }
28
- if (this.props.hasChecks) {
29
- const firstToggle = this.treeRef.current?.getElementsByClassName('pf-c-tree-view__node-toggle')[0] as HTMLElement;
34
+ if (this.props.hasChecks || this.props.hasSelectableNodes) {
35
+ const firstToggle = this.treeRef.current.getElementsByClassName('pf-c-tree-view__node-toggle')[0] as HTMLElement;
30
36
  if (firstToggle) {
31
37
  firstToggle.tabIndex = 0;
32
38
  }
33
- const firstInput = this.treeRef.current?.getElementsByTagName('INPUT')[0] as HTMLElement;
34
- if (firstInput) {
35
- firstInput.tabIndex = 0;
39
+ if (this.props.hasChecks) {
40
+ const firstInput = this.treeRef.current.getElementsByTagName('INPUT')[0] as HTMLElement;
41
+ if (firstInput) {
42
+ firstInput.tabIndex = 0;
43
+ }
44
+ }
45
+ if (this.props.hasSelectableNodes) {
46
+ const firstTextButton = this.treeRef.current.getElementsByClassName(
47
+ 'pf-c-tree-view__node-text'
48
+ )[0] as HTMLElement;
49
+ if (firstTextButton) {
50
+ firstTextButton.tabIndex = 0;
51
+ }
36
52
  }
37
53
  } else {
38
54
  (this.treeRef.current?.getElementsByClassName('pf-c-tree-view__node')[0] as HTMLElement).tabIndex = 0;
@@ -41,12 +57,15 @@ export class TreeViewRoot extends React.Component<TreeViewRootProps> {
41
57
 
42
58
  componentWillUnmount() {
43
59
  if (canUseDOM) {
44
- window.removeEventListener('keydown', this.props.hasChecks ? this.handleKeysCheckbox : this.handleKeys);
60
+ window.removeEventListener(
61
+ 'keydown',
62
+ this.props.hasChecks || this.props.hasSelectableNodes ? this.handleKeysCheckbox : this.handleKeys
63
+ );
45
64
  }
46
65
  }
47
66
 
48
67
  handleKeys = (event: KeyboardEvent) => {
49
- if (this.treeRef.current !== (event.target as HTMLElement).closest('.pf-c-tree-view')) {
68
+ if (!this.treeRef.current.contains(event.target as HTMLElement)) {
50
69
  return;
51
70
  }
52
71
  const activeElement = document.activeElement;
@@ -55,8 +74,8 @@ export class TreeViewRoot extends React.Component<TreeViewRootProps> {
55
74
  el => !el.classList.contains('pf-m-disabled')
56
75
  );
57
76
 
58
- if (key === 'Space') {
59
- (document.activeElement as HTMLElement).click();
77
+ if (key === KeyTypes.Space) {
78
+ (activeElement as HTMLElement).click();
60
79
  event.preventDefault();
61
80
  }
62
81
 
@@ -109,14 +128,15 @@ export class TreeViewRoot extends React.Component<TreeViewRootProps> {
109
128
  };
110
129
 
111
130
  handleKeysCheckbox = (event: KeyboardEvent) => {
112
- if (this.treeRef.current !== (event.target as HTMLElement).closest('.pf-c-tree-view')) {
131
+ if (!this.treeRef.current.contains(event.target as HTMLElement)) {
113
132
  return;
114
133
  }
134
+
115
135
  const activeElement = document.activeElement;
116
136
  const key = event.key;
117
137
 
118
- if (key === 'Space') {
119
- (document.activeElement as HTMLElement).click();
138
+ if (key === KeyTypes.Space) {
139
+ (activeElement as HTMLElement).click();
120
140
  event.preventDefault();
121
141
  }
122
142
 
@@ -126,7 +146,7 @@ export class TreeViewRoot extends React.Component<TreeViewRootProps> {
126
146
  event,
127
147
  treeNodes as HTMLElement[],
128
148
  (element: Element) => element.contains(activeElement),
129
- (element: Element) => element.querySelector('BUTTON,INPUT'),
149
+ (element: Element) => element.querySelector('button,input'),
130
150
  [],
131
151
  undefined,
132
152
  true,
@@ -168,7 +188,7 @@ export class TreeViewRoot extends React.Component<TreeViewRootProps> {
168
188
 
169
189
  render() {
170
190
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
171
- const { children, hasChecks, hasGuides, variant, className, ...props } = this.props;
191
+ const { children, hasChecks, hasGuides, variant, className, hasSelectableNodes, ...props } = this.props;
172
192
  return (
173
193
  <div
174
194
  className={css(