@patternfly/react-core 5.4.7 → 5.4.9

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 (165) hide show
  1. package/CHANGELOG.md +10 -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/esm/components/Modal/Modal.d.ts +2 -2
  123. package/dist/esm/components/Modal/Modal.d.ts.map +1 -1
  124. package/dist/esm/components/Modal/Modal.js +13 -20
  125. package/dist/esm/components/Modal/Modal.js.map +1 -1
  126. package/dist/esm/components/Modal/ModalContent.d.ts +2 -0
  127. package/dist/esm/components/Modal/ModalContent.d.ts.map +1 -1
  128. package/dist/esm/components/Modal/ModalContent.js +2 -2
  129. package/dist/esm/components/Modal/ModalContent.js.map +1 -1
  130. package/dist/esm/next/components/Modal/Modal.d.ts +2 -2
  131. package/dist/esm/next/components/Modal/Modal.d.ts.map +1 -1
  132. package/dist/esm/next/components/Modal/Modal.js +13 -20
  133. package/dist/esm/next/components/Modal/Modal.js.map +1 -1
  134. package/dist/esm/next/components/Modal/ModalContent.d.ts +2 -0
  135. package/dist/esm/next/components/Modal/ModalContent.d.ts.map +1 -1
  136. package/dist/esm/next/components/Modal/ModalContent.js +2 -2
  137. package/dist/esm/next/components/Modal/ModalContent.js.map +1 -1
  138. package/dist/js/components/Modal/Modal.d.ts +2 -2
  139. package/dist/js/components/Modal/Modal.d.ts.map +1 -1
  140. package/dist/js/components/Modal/Modal.js +13 -20
  141. package/dist/js/components/Modal/Modal.js.map +1 -1
  142. package/dist/js/components/Modal/ModalContent.d.ts +2 -0
  143. package/dist/js/components/Modal/ModalContent.d.ts.map +1 -1
  144. package/dist/js/components/Modal/ModalContent.js +2 -2
  145. package/dist/js/components/Modal/ModalContent.js.map +1 -1
  146. package/dist/js/next/components/Modal/Modal.d.ts +2 -2
  147. package/dist/js/next/components/Modal/Modal.d.ts.map +1 -1
  148. package/dist/js/next/components/Modal/Modal.js +13 -20
  149. package/dist/js/next/components/Modal/Modal.js.map +1 -1
  150. package/dist/js/next/components/Modal/ModalContent.d.ts +2 -0
  151. package/dist/js/next/components/Modal/ModalContent.d.ts.map +1 -1
  152. package/dist/js/next/components/Modal/ModalContent.js +2 -2
  153. package/dist/js/next/components/Modal/ModalContent.js.map +1 -1
  154. package/dist/umd/assets/{output-DKcRun4G.css → output-DasTFpST.css} +3978 -3969
  155. package/dist/umd/react-core.min.js +1 -1
  156. package/helpers/package.json +1 -1
  157. package/layouts/package.json +1 -1
  158. package/next/package.json +1 -1
  159. package/package.json +6 -6
  160. package/src/components/Modal/Modal.tsx +14 -18
  161. package/src/components/Modal/ModalContent.tsx +4 -1
  162. package/src/components/Modal/__tests__/Modal.test.tsx +30 -7
  163. package/src/next/components/Modal/Modal.tsx +13 -18
  164. package/src/next/components/Modal/ModalContent.tsx +4 -1
  165. package/src/next/components/Modal/__tests__/Modal.test.tsx +45 -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.6","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.8","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.6","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.8","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.6","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.8","private":true}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/react-core",
3
- "version": "5.4.7",
3
+ "version": "5.4.9",
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",
@@ -46,15 +46,15 @@
46
46
  "clean:exports": "node scripts/cleanDistExports.js"
47
47
  },
48
48
  "dependencies": {
49
- "@patternfly/react-icons": "^5.4.1",
50
- "@patternfly/react-styles": "^5.4.0",
51
- "@patternfly/react-tokens": "^5.4.0",
49
+ "@patternfly/react-icons": "^5.4.2",
50
+ "@patternfly/react-styles": "^5.4.1",
51
+ "@patternfly/react-tokens": "^5.4.1",
52
52
  "focus-trap": "7.6.0",
53
53
  "react-dropzone": "^14.2.3",
54
54
  "tslib": "^2.7.0"
55
55
  },
56
56
  "devDependencies": {
57
- "@patternfly/patternfly": "5.4.0",
57
+ "@patternfly/patternfly": "5.4.2",
58
58
  "css": "^3.0.0",
59
59
  "fs-extra": "^11.2.0"
60
60
  },
@@ -62,5 +62,5 @@
62
62
  "react": "^17 || ^18",
63
63
  "react-dom": "^17 || ^18"
64
64
  },
65
- "gitHead": "9b64d81d7401fc4dee1f43b8a2dfe3855fc68880"
65
+ "gitHead": "9f4b31f42061a72ad67816d5a896a14b005f7c14"
66
66
  }
@@ -92,7 +92,6 @@ export enum ModalVariant {
92
92
  }
93
93
 
94
94
  interface ModalState {
95
- container: HTMLElement;
96
95
  ouiaStateId: string;
97
96
  }
98
97
 
@@ -102,6 +101,7 @@ class Modal extends React.Component<ModalProps, ModalState> {
102
101
  boxId = '';
103
102
  labelId = '';
104
103
  descriptorId = '';
104
+ backdropId = '';
105
105
 
106
106
  static defaultProps: PickOptional<ModalProps> = {
107
107
  className: '',
@@ -128,12 +128,13 @@ class Modal extends React.Component<ModalProps, ModalState> {
128
128
  const boxIdNum = Modal.currentId++;
129
129
  const labelIdNum = boxIdNum + 1;
130
130
  const descriptorIdNum = boxIdNum + 2;
131
+ const backdropIdNum = boxIdNum + 3;
131
132
  this.boxId = props.id || `pf-modal-part-${boxIdNum}`;
132
133
  this.labelId = `pf-modal-part-${labelIdNum}`;
133
134
  this.descriptorId = `pf-modal-part-${descriptorIdNum}`;
135
+ this.backdropId = `pf-modal-part-${backdropIdNum}`;
134
136
 
135
137
  this.state = {
136
- container: undefined,
137
138
  ouiaStateId: getDefaultOUIAId(Modal.displayName, props.variant)
138
139
  };
139
140
  }
@@ -157,7 +158,7 @@ class Modal extends React.Component<ModalProps, ModalState> {
157
158
  const target: HTMLElement = this.getElement(appendTo);
158
159
  const bodyChildren = target.children;
159
160
  for (const child of Array.from(bodyChildren)) {
160
- if (child !== this.state.container) {
161
+ if (child.id !== this.backdropId) {
161
162
  hide ? child.setAttribute('aria-hidden', '' + hide) : child.removeAttribute('aria-hidden');
162
163
  }
163
164
  }
@@ -175,15 +176,11 @@ class Modal extends React.Component<ModalProps, ModalState> {
175
176
  header
176
177
  } = this.props;
177
178
  const target: HTMLElement = this.getElement(appendTo);
178
- const container = document.createElement('div');
179
- this.setState({ container });
180
- target.appendChild(container);
181
179
  target.addEventListener('keydown', this.handleEscKeyClick, false);
182
180
 
183
181
  if (this.props.isOpen) {
184
182
  target.classList.add(css(styles.backdropOpen));
185
- } else {
186
- target.classList.remove(css(styles.backdropOpen));
183
+ this.toggleSiblingsFromScreenReaders(true);
187
184
  }
188
185
 
189
186
  if (!title && this.isEmpty(ariaLabel) && this.isEmpty(ariaLabelledby)) {
@@ -199,24 +196,24 @@ class Modal extends React.Component<ModalProps, ModalState> {
199
196
  }
200
197
  }
201
198
 
202
- componentDidUpdate() {
199
+ componentDidUpdate(prevProps: ModalProps) {
203
200
  const { appendTo } = this.props;
204
201
  const target: HTMLElement = this.getElement(appendTo);
205
202
  if (this.props.isOpen) {
206
203
  target.classList.add(css(styles.backdropOpen));
207
204
  this.toggleSiblingsFromScreenReaders(true);
208
205
  } else {
209
- target.classList.remove(css(styles.backdropOpen));
210
- this.toggleSiblingsFromScreenReaders(false);
206
+ if (prevProps.isOpen !== this.props.isOpen) {
207
+ target.classList.remove(css(styles.backdropOpen));
208
+ this.toggleSiblingsFromScreenReaders(false);
209
+ }
211
210
  }
212
211
  }
213
212
 
214
213
  componentWillUnmount() {
215
214
  const { appendTo } = this.props;
216
215
  const target: HTMLElement = this.getElement(appendTo);
217
- if (this.state.container) {
218
- target.removeChild(this.state.container);
219
- }
216
+
220
217
  target.removeEventListener('keydown', this.handleEscKeyClick, false);
221
218
  target.classList.remove(css(styles.backdropOpen));
222
219
  this.toggleSiblingsFromScreenReaders(false);
@@ -224,7 +221,6 @@ class Modal extends React.Component<ModalProps, ModalState> {
224
221
 
225
222
  render() {
226
223
  const {
227
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
228
224
  appendTo,
229
225
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
230
226
  onEscapePress,
@@ -242,9 +238,8 @@ class Modal extends React.Component<ModalProps, ModalState> {
242
238
  elementToFocus,
243
239
  ...props
244
240
  } = this.props;
245
- const { container } = this.state;
246
241
 
247
- if (!canUseDOM || !container) {
242
+ if (!canUseDOM || !this.getElement(appendTo)) {
248
243
  return null;
249
244
  }
250
245
 
@@ -254,6 +249,7 @@ class Modal extends React.Component<ModalProps, ModalState> {
254
249
  boxId={this.boxId}
255
250
  labelId={this.labelId}
256
251
  descriptorId={this.descriptorId}
252
+ backdropId={this.backdropId}
257
253
  title={title}
258
254
  titleIconVariant={titleIconVariant}
259
255
  titleLabel={titleLabel}
@@ -267,7 +263,7 @@ class Modal extends React.Component<ModalProps, ModalState> {
267
263
  position={position}
268
264
  elementToFocus={elementToFocus}
269
265
  />,
270
- container
266
+ this.getElement(appendTo)
271
267
  ) as React.ReactElement;
272
268
  }
273
269
  }
@@ -25,6 +25,8 @@ export interface ModalContentProps extends OUIAProps {
25
25
  'aria-label'?: string;
26
26
  /** Id to use for the modal box label. */
27
27
  'aria-labelledby'?: string | null;
28
+ /** Id of the backdrop. */
29
+ backdropId?: string;
28
30
  /** Accessible label applied to the modal box body. This should be used to communicate
29
31
  * important information about the modal box body div element if needed, such as that it
30
32
  * is scrollable.
@@ -117,6 +119,7 @@ export const ModalContent: React.FunctionComponent<ModalContentProps> = ({
117
119
  maxWidth,
118
120
  boxId,
119
121
  labelId,
122
+ backdropId,
120
123
  descriptorId,
121
124
  disableFocusTrap = false,
122
125
  hasNoBodyWrapper = false,
@@ -202,7 +205,7 @@ export const ModalContent: React.FunctionComponent<ModalContentProps> = ({
202
205
  </ModalBox>
203
206
  );
204
207
  return (
205
- <Backdrop>
208
+ <Backdrop id={backdropId}>
206
209
  <FocusTrap
207
210
  active={!disableFocusTrap}
208
211
  focusTrapOptions={{
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
 
3
3
  import { render, screen } from '@testing-library/react';
4
4
  import userEvent from '@testing-library/user-event';
5
+ import '@testing-library/jest-dom';
5
6
 
6
7
  import { css } from '../../../../../react-styles/dist/js';
7
8
  import styles from '@patternfly/react-styles/css/components/Backdrop/backdrop';
@@ -39,12 +40,33 @@ const ModalWithSiblings = () => {
39
40
  );
40
41
  };
41
42
 
42
- describe('Modal', () => {
43
- test('Modal creates a container element once for div', () => {
44
- render(<Modal {...props} />);
45
- expect(document.createElement).toHaveBeenCalledWith('div');
46
- });
43
+ const ModalWithAdjacentModal = () => {
44
+ const [isOpen, setIsOpen] = React.useState(true);
45
+ const [isModalMounted, setIsModalMounted] = React.useState(true);
46
+ const modalProps = { ...props, isOpen, appendTo: target, onClose: () => setIsOpen(false) };
47
47
 
48
+ return (
49
+ <>
50
+ <aside>Aside sibling</aside>
51
+ <article>Section sibling</article>
52
+ {isModalMounted && (
53
+ <>
54
+ <Modal {...modalProps}>
55
+ <button onClick={() => setIsModalMounted(false)}>Unmount Modal</button>
56
+ </Modal>
57
+ <Modal isOpen={false} onClose={() => {}}>
58
+ Modal closed for test
59
+ </Modal>
60
+ <Modal isOpen={false} onClose={() => {}}>
61
+ modal closed for test
62
+ </Modal>
63
+ </>
64
+ )}
65
+ </>
66
+ );
67
+ };
68
+
69
+ describe('Modal', () => {
48
70
  test('modal closes with escape', async () => {
49
71
  const user = userEvent.setup();
50
72
 
@@ -137,10 +159,10 @@ describe('Modal', () => {
137
159
  expect(asideSibling).not.toHaveAttribute('aria-hidden');
138
160
  });
139
161
 
140
- test('modal removes the aria-hidden attribute from its siblings when unmounted', async () => {
162
+ test('modal siblings have the aria-hidden attribute when it has adjacent modals', async () => {
141
163
  const user = userEvent.setup();
142
164
 
143
- render(<ModalWithSiblings />, { container: document.body.appendChild(target) });
165
+ render(<ModalWithAdjacentModal />, { container: document.body.appendChild(target) });
144
166
 
145
167
  const asideSibling = screen.getByRole('complementary', { hidden: true });
146
168
  const articleSibling = screen.getByRole('article', { hidden: true });
@@ -154,6 +176,7 @@ describe('Modal', () => {
154
176
  expect(asideSibling).not.toHaveAttribute('aria-hidden');
155
177
  expect(articleSibling).not.toHaveAttribute('aria-hidden');
156
178
  });
179
+
157
180
  test('The modalBoxBody has no aria-label when bodyAriaLabel is not passed', () => {
158
181
  const props = {
159
182
  isOpen: true
@@ -58,7 +58,6 @@ export enum ModalVariant {
58
58
  }
59
59
 
60
60
  interface ModalState {
61
- container: HTMLElement;
62
61
  ouiaStateId: string;
63
62
  }
64
63
 
@@ -66,6 +65,7 @@ class Modal extends React.Component<ModalProps, ModalState> {
66
65
  static displayName = 'Modal';
67
66
  static currentId = 0;
68
67
  boxId = '';
68
+ backdropId = '';
69
69
 
70
70
  static defaultProps: PickOptional<ModalProps> = {
71
71
  isOpen: false,
@@ -78,10 +78,11 @@ class Modal extends React.Component<ModalProps, ModalState> {
78
78
  constructor(props: ModalProps) {
79
79
  super(props);
80
80
  const boxIdNum = Modal.currentId++;
81
+ const backdropId = boxIdNum + 1;
81
82
  this.boxId = props.id || `pf-modal-part-${boxIdNum}`;
83
+ this.backdropId = `pf-modal-part-${backdropId}`;
82
84
 
83
85
  this.state = {
84
- container: undefined,
85
86
  ouiaStateId: getDefaultOUIAId(Modal.displayName, props.variant)
86
87
  };
87
88
  }
@@ -105,7 +106,7 @@ class Modal extends React.Component<ModalProps, ModalState> {
105
106
  const target: HTMLElement = this.getElement(appendTo);
106
107
  const bodyChildren = target.children;
107
108
  for (const child of Array.from(bodyChildren)) {
108
- if (child !== this.state.container) {
109
+ if (child.id !== this.backdropId) {
109
110
  hide ? child.setAttribute('aria-hidden', '' + hide) : child.removeAttribute('aria-hidden');
110
111
  }
111
112
  }
@@ -116,36 +117,31 @@ class Modal extends React.Component<ModalProps, ModalState> {
116
117
  componentDidMount() {
117
118
  const { appendTo } = this.props;
118
119
  const target: HTMLElement = this.getElement(appendTo);
119
- const container = document.createElement('div');
120
- this.setState({ container });
121
- target.appendChild(container);
122
120
  target.addEventListener('keydown', this.handleEscKeyClick, false);
123
121
 
124
122
  if (this.props.isOpen) {
125
123
  target.classList.add(css(styles.backdropOpen));
126
- } else {
127
- target.classList.remove(css(styles.backdropOpen));
124
+ this.toggleSiblingsFromScreenReaders(true);
128
125
  }
129
126
  }
130
127
 
131
- componentDidUpdate() {
128
+ componentDidUpdate(prevProps: ModalProps) {
132
129
  const { appendTo } = this.props;
133
130
  const target: HTMLElement = this.getElement(appendTo);
134
131
  if (this.props.isOpen) {
135
132
  target.classList.add(css(styles.backdropOpen));
136
133
  this.toggleSiblingsFromScreenReaders(true);
137
134
  } else {
138
- target.classList.remove(css(styles.backdropOpen));
139
- this.toggleSiblingsFromScreenReaders(false);
135
+ if (prevProps.isOpen !== this.props.isOpen) {
136
+ target.classList.remove(css(styles.backdropOpen));
137
+ this.toggleSiblingsFromScreenReaders(false);
138
+ }
140
139
  }
141
140
  }
142
141
 
143
142
  componentWillUnmount() {
144
143
  const { appendTo } = this.props;
145
144
  const target: HTMLElement = this.getElement(appendTo);
146
- if (this.state.container) {
147
- target.removeChild(this.state.container);
148
- }
149
145
  target.removeEventListener('keydown', this.handleEscKeyClick, false);
150
146
  target.classList.remove(css(styles.backdropOpen));
151
147
  this.toggleSiblingsFromScreenReaders(false);
@@ -153,7 +149,6 @@ class Modal extends React.Component<ModalProps, ModalState> {
153
149
 
154
150
  render() {
155
151
  const {
156
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
157
152
  appendTo,
158
153
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
159
154
  onEscapePress,
@@ -166,15 +161,15 @@ class Modal extends React.Component<ModalProps, ModalState> {
166
161
  elementToFocus,
167
162
  ...props
168
163
  } = this.props;
169
- const { container } = this.state;
170
164
 
171
- if (!canUseDOM || !container) {
165
+ if (!canUseDOM || !this.getElement(appendTo)) {
172
166
  return null;
173
167
  }
174
168
 
175
169
  return ReactDOM.createPortal(
176
170
  <ModalContent
177
171
  boxId={this.boxId}
172
+ backdropId={this.backdropId}
178
173
  aria-label={ariaLabel}
179
174
  aria-describedby={ariaDescribedby}
180
175
  aria-labelledby={ariaLabelledby}
@@ -184,7 +179,7 @@ class Modal extends React.Component<ModalProps, ModalState> {
184
179
  elementToFocus={elementToFocus}
185
180
  {...props}
186
181
  />,
187
- container
182
+ this.getElement(appendTo)
188
183
  ) as React.ReactElement;
189
184
  }
190
185
  }
@@ -16,6 +16,8 @@ export interface ModalContentProps extends OUIAProps {
16
16
  'aria-labelledby'?: string;
17
17
  /** Id of the modal box container. */
18
18
  boxId: string;
19
+ /** Id of the backdrop. */
20
+ backdropId?: string;
19
21
  /** Content rendered inside the modal. */
20
22
  children: React.ReactNode;
21
23
  /** Additional classes added to the modal box. */
@@ -60,6 +62,7 @@ export const ModalContent: React.FunctionComponent<ModalContentProps> = ({
60
62
  width,
61
63
  maxWidth,
62
64
  boxId,
65
+ backdropId,
63
66
  disableFocusTrap = false,
64
67
  ouiaId,
65
68
  ouiaSafe = true,
@@ -106,7 +109,7 @@ export const ModalContent: React.FunctionComponent<ModalContentProps> = ({
106
109
  </ModalBox>
107
110
  );
108
111
  return (
109
- <Backdrop>
112
+ <Backdrop id={backdropId}>
110
113
  <FocusTrap
111
114
  active={!disableFocusTrap}
112
115
  focusTrapOptions={{
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
 
3
3
  import { render, screen } from '@testing-library/react';
4
4
  import userEvent from '@testing-library/user-event';
5
+ import '@testing-library/jest-dom';
5
6
 
6
7
  import { css } from '../../../../../../react-styles/dist/js';
7
8
  import styles from '@patternfly/react-styles/css/components/Backdrop/backdrop';
@@ -38,6 +39,32 @@ const ModalWithSiblings = () => {
38
39
  );
39
40
  };
40
41
 
42
+ const ModalWithAdjacentModal = () => {
43
+ const [isOpen, setIsOpen] = React.useState(true);
44
+ const [isModalMounted, setIsModalMounted] = React.useState(true);
45
+ const modalProps = { ...props, isOpen, appendTo: target, onClose: () => setIsOpen(false) };
46
+
47
+ return (
48
+ <>
49
+ <aside>Aside sibling</aside>
50
+ <article>Section sibling</article>
51
+ {isModalMounted && (
52
+ <>
53
+ <Modal {...modalProps}>
54
+ <button onClick={() => setIsModalMounted(false)}>Unmount Modal</button>
55
+ </Modal>
56
+ <Modal isOpen={false} onClose={() => {}}>
57
+ Modal closed for test
58
+ </Modal>
59
+ <Modal isOpen={false} onClose={() => {}}>
60
+ modal closed for test
61
+ </Modal>
62
+ </>
63
+ )}
64
+ </>
65
+ );
66
+ };
67
+
41
68
  describe('Modal', () => {
42
69
  test('Modal creates a container element once for div', () => {
43
70
  render(<Modal {...props} />);
@@ -128,4 +155,22 @@ describe('Modal', () => {
128
155
  expect(asideSibling).not.toHaveAttribute('aria-hidden');
129
156
  expect(articleSibling).not.toHaveAttribute('aria-hidden');
130
157
  });
158
+
159
+ test('modal siblings have the aria-hidden attribute when it has adjacent modals', async () => {
160
+ const user = userEvent.setup();
161
+
162
+ render(<ModalWithAdjacentModal />, { container: document.body.appendChild(target) });
163
+
164
+ const asideSibling = screen.getByRole('complementary', { hidden: true });
165
+ const articleSibling = screen.getByRole('article', { hidden: true });
166
+ const unmountButton = screen.getByRole('button', { name: 'Unmount Modal' });
167
+
168
+ expect(asideSibling).toHaveAttribute('aria-hidden');
169
+ expect(articleSibling).toHaveAttribute('aria-hidden');
170
+
171
+ await user.click(unmountButton);
172
+
173
+ expect(asideSibling).not.toHaveAttribute('aria-hidden');
174
+ expect(articleSibling).not.toHaveAttribute('aria-hidden');
175
+ });
131
176
  });