@patternfly/react-core 6.6.1 → 6.6.2

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 (135) hide show
  1. package/CHANGELOG.md +6 -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/ClipboardCopy/package.json +1 -1
  21. package/dist/dynamic/components/CodeBlock/package.json +1 -1
  22. package/dist/dynamic/components/Compass/package.json +1 -1
  23. package/dist/dynamic/components/Content/package.json +1 -1
  24. package/dist/dynamic/components/DataList/package.json +1 -1
  25. package/dist/dynamic/components/DatePicker/package.json +1 -1
  26. package/dist/dynamic/components/DescriptionList/package.json +1 -1
  27. package/dist/dynamic/components/Divider/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/Hero/package.json +1 -1
  38. package/dist/dynamic/components/Hint/package.json +1 -1
  39. package/dist/dynamic/components/Icon/package.json +1 -1
  40. package/dist/dynamic/components/InputGroup/package.json +1 -1
  41. package/dist/dynamic/components/JumpLinks/package.json +1 -1
  42. package/dist/dynamic/components/Label/package.json +1 -1
  43. package/dist/dynamic/components/List/package.json +1 -1
  44. package/dist/dynamic/components/LoginPage/package.json +1 -1
  45. package/dist/dynamic/components/Masthead/package.json +1 -1
  46. package/dist/dynamic/components/Menu/package.json +1 -1
  47. package/dist/dynamic/components/MenuToggle/package.json +1 -1
  48. package/dist/dynamic/components/Modal/package.json +1 -1
  49. package/dist/dynamic/components/MultipleFileUpload/package.json +1 -1
  50. package/dist/dynamic/components/Nav/package.json +1 -1
  51. package/dist/dynamic/components/NotificationBadge/package.json +1 -1
  52. package/dist/dynamic/components/NotificationDrawer/package.json +1 -1
  53. package/dist/dynamic/components/NumberInput/package.json +1 -1
  54. package/dist/dynamic/components/OverflowMenu/package.json +1 -1
  55. package/dist/dynamic/components/Page/package.json +1 -1
  56. package/dist/dynamic/components/Pagination/package.json +1 -1
  57. package/dist/dynamic/components/Panel/package.json +1 -1
  58. package/dist/dynamic/components/Popover/package.json +1 -1
  59. package/dist/dynamic/components/Progress/package.json +1 -1
  60. package/dist/dynamic/components/ProgressStepper/package.json +1 -1
  61. package/dist/dynamic/components/Radio/package.json +1 -1
  62. package/dist/dynamic/components/SearchInput/package.json +1 -1
  63. package/dist/dynamic/components/Select/package.json +1 -1
  64. package/dist/dynamic/components/Sidebar/package.json +1 -1
  65. package/dist/dynamic/components/SimpleList/package.json +1 -1
  66. package/dist/dynamic/components/Skeleton/package.json +1 -1
  67. package/dist/dynamic/components/SkipToContent/package.json +1 -1
  68. package/dist/dynamic/components/Slider/package.json +1 -1
  69. package/dist/dynamic/components/Spinner/package.json +1 -1
  70. package/dist/dynamic/components/Switch/package.json +1 -1
  71. package/dist/dynamic/components/Tabs/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/TimePicker/package.json +1 -1
  76. package/dist/dynamic/components/Timestamp/package.json +1 -1
  77. package/dist/dynamic/components/Title/package.json +1 -1
  78. package/dist/dynamic/components/ToggleGroup/package.json +1 -1
  79. package/dist/dynamic/components/Toolbar/package.json +1 -1
  80. package/dist/dynamic/components/Tooltip/package.json +1 -1
  81. package/dist/dynamic/components/TreeView/package.json +1 -1
  82. package/dist/dynamic/components/Truncate/package.json +1 -1
  83. package/dist/dynamic/components/Wizard/hooks/package.json +1 -1
  84. package/dist/dynamic/components/Wizard/package.json +1 -1
  85. package/dist/dynamic/deprecated/components/Chip/package.json +1 -1
  86. package/dist/dynamic/deprecated/components/DragDrop/package.json +1 -1
  87. package/dist/dynamic/deprecated/components/DualListSelector/package.json +1 -1
  88. package/dist/dynamic/deprecated/components/Modal/package.json +1 -1
  89. package/dist/dynamic/deprecated/components/Tile/package.json +1 -1
  90. package/dist/dynamic/deprecated/components/Wizard/package.json +1 -1
  91. package/dist/dynamic/deprecated/components/package.json +1 -1
  92. package/dist/dynamic/helpers/AnimationsProvider/AnimationsProvider/package.json +1 -1
  93. package/dist/dynamic/helpers/AnimationsProvider/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/SSRSafeIds/SSRSafeIds/package.json +1 -1
  100. package/dist/dynamic/helpers/constants/package.json +1 -1
  101. package/dist/dynamic/helpers/datetimeUtils/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/useSSRSafeId/package.json +1 -1
  110. package/dist/dynamic/helpers/useUnmountEffect/package.json +1 -1
  111. package/dist/dynamic/helpers/util/package.json +1 -1
  112. package/dist/dynamic/layouts/Bullseye/package.json +1 -1
  113. package/dist/dynamic/layouts/Flex/package.json +1 -1
  114. package/dist/dynamic/layouts/Gallery/package.json +1 -1
  115. package/dist/dynamic/layouts/Grid/package.json +1 -1
  116. package/dist/dynamic/layouts/Level/package.json +1 -1
  117. package/dist/dynamic/layouts/Split/package.json +1 -1
  118. package/dist/dynamic/layouts/Stack/package.json +1 -1
  119. package/dist/dynamic/styles/package.json +1 -1
  120. package/dist/esm/components/Modal/Modal.d.ts +2 -0
  121. package/dist/esm/components/Modal/Modal.d.ts.map +1 -1
  122. package/dist/esm/components/Modal/Modal.js +39 -6
  123. package/dist/esm/components/Modal/Modal.js.map +1 -1
  124. package/dist/js/components/Modal/Modal.d.ts +2 -0
  125. package/dist/js/components/Modal/Modal.d.ts.map +1 -1
  126. package/dist/js/components/Modal/Modal.js +39 -6
  127. package/dist/js/components/Modal/Modal.js.map +1 -1
  128. package/dist/umd/assets/{output-Bdtn4DVz.css → output-1G93FGGm.css} +22734 -22734
  129. package/dist/umd/react-core.min.js +3 -3
  130. package/helpers/package.json +1 -1
  131. package/layouts/package.json +1 -1
  132. package/next/package.json +1 -1
  133. package/package.json +2 -2
  134. package/src/components/Modal/Modal.tsx +42 -6
  135. package/src/components/Modal/__tests__/Modal.test.tsx +152 -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":"6.6.0","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":"6.6.1","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":"6.6.0","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":"6.6.1","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":"6.6.0","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":"6.6.1","private":true}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/react-core",
3
- "version": "6.6.1",
3
+ "version": "6.6.2",
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",
@@ -63,5 +63,5 @@
63
63
  "react": "^17 || ^18 || ^19",
64
64
  "react-dom": "^17 || ^18 || ^19"
65
65
  },
66
- "gitHead": "a9477a0faacc1cd89ac4e65cec6b8b806c7ba2b3"
66
+ "gitHead": "02bbb5540dc894017949e1b3b66083bce4c9deee"
67
67
  }
@@ -69,6 +69,7 @@ interface ModalState {
69
69
  class Modal extends Component<ModalProps, ModalState> {
70
70
  static displayName = 'Modal';
71
71
  static currentId = 0;
72
+ static openModalStacks: Map<HTMLElement, string[]> = new Map();
72
73
  boxId = '';
73
74
  backdropId = '';
74
75
 
@@ -106,15 +107,46 @@ class Modal extends Component<ModalProps, ModalState> {
106
107
  return appendTo || document.body;
107
108
  };
108
109
 
110
+ static getStackForTarget(target: HTMLElement): string[] {
111
+ if (!Modal.openModalStacks.has(target)) {
112
+ Modal.openModalStacks.set(target, []);
113
+ }
114
+ return Modal.openModalStacks.get(target)!;
115
+ }
116
+
109
117
  toggleSiblingsFromScreenReaders = (hide: boolean) => {
110
118
  const { appendTo } = this.props;
111
119
  const target: HTMLElement = this.getElement(appendTo);
112
- const bodyChildren = target.children;
113
- for (const child of Array.from(bodyChildren)) {
114
- if (child.id !== this.backdropId) {
115
- hide ? child.setAttribute('aria-hidden', '' + hide) : child.removeAttribute('aria-hidden');
120
+
121
+ if (hide) {
122
+ const stack = Modal.getStackForTarget(target);
123
+ if (stack.indexOf(this.backdropId) === -1) {
124
+ stack.push(this.backdropId);
125
+ }
126
+ } else {
127
+ const stack = Modal.openModalStacks.get(target);
128
+ if (!stack) {
129
+ return;
130
+ }
131
+ const idx = stack.indexOf(this.backdropId);
132
+ if (idx !== -1) {
133
+ stack.splice(idx, 1);
134
+ }
135
+ if (stack.length === 0) {
136
+ Modal.openModalStacks.delete(target);
116
137
  }
117
138
  }
139
+
140
+ const stack = Modal.openModalStacks.get(target);
141
+ const activeBackdropId = stack?.length ? stack[stack.length - 1] : null;
142
+
143
+ for (const child of Array.from(target.children)) {
144
+ if (child.hasAttribute('data-popper-placement')) {
145
+ continue;
146
+ }
147
+ const shouldHide = activeBackdropId && child.id !== activeBackdropId;
148
+ shouldHide ? child.setAttribute('aria-hidden', 'true') : child.removeAttribute('aria-hidden');
149
+ }
118
150
  };
119
151
 
120
152
  isEmpty = (value: string | null | undefined) => value === null || value === undefined || value === '';
@@ -139,8 +171,10 @@ class Modal extends Component<ModalProps, ModalState> {
139
171
  this.toggleSiblingsFromScreenReaders(true);
140
172
  } else {
141
173
  if (prevProps.isOpen !== this.props.isOpen) {
142
- target.classList.remove(css(styles.backdropOpen));
143
174
  this.toggleSiblingsFromScreenReaders(false);
175
+ if (!Modal.openModalStacks.has(target)) {
176
+ target.classList.remove(css(styles.backdropOpen));
177
+ }
144
178
  }
145
179
  }
146
180
  }
@@ -149,8 +183,10 @@ class Modal extends Component<ModalProps, ModalState> {
149
183
  const { appendTo } = this.props;
150
184
  const target: HTMLElement = this.getElement(appendTo);
151
185
  target.removeEventListener('keydown', this.handleEscKeyClick, false);
152
- target.classList.remove(css(styles.backdropOpen));
153
186
  this.toggleSiblingsFromScreenReaders(false);
187
+ if (!Modal.openModalStacks.has(target)) {
188
+ target.classList.remove(css(styles.backdropOpen));
189
+ }
154
190
  }
155
191
 
156
192
  render() {
@@ -64,7 +64,28 @@ const ModalWithAdjacentModal = () => {
64
64
  );
65
65
  };
66
66
 
67
+ const MultipleOpenModals = () => {
68
+ const [isFirstOpen, setIsFirstOpen] = useState(true);
69
+ const [isSecondOpen, setIsSecondOpen] = useState(false);
70
+
71
+ return (
72
+ <>
73
+ <aside>Aside sibling</aside>
74
+ <Modal isOpen={isFirstOpen} appendTo={target} onClose={() => setIsFirstOpen(false)} aria-label="First modal">
75
+ <button onClick={() => setIsSecondOpen(true)}>Open second modal</button>
76
+ </Modal>
77
+ <Modal isOpen={isSecondOpen} appendTo={target} onClose={() => setIsSecondOpen(false)} aria-label="Second modal">
78
+ Second modal content
79
+ </Modal>
80
+ </>
81
+ );
82
+ };
83
+
67
84
  describe('Modal', () => {
85
+ beforeEach(() => {
86
+ Modal.openModalStacks = new Map();
87
+ });
88
+
68
89
  test('Modal creates a container element once for div', () => {
69
90
  render(<Modal {...props} />);
70
91
  expect(document.createElement).toHaveBeenCalledWith('div');
@@ -181,4 +202,135 @@ describe('Modal', () => {
181
202
  'pf-v6-l-bullseye'
182
203
  );
183
204
  });
205
+
206
+ test('backdropOpen class remains when closing one of multiple open modals', async () => {
207
+ const user = userEvent.setup();
208
+
209
+ render(<MultipleOpenModals />, { container: document.body.appendChild(target) });
210
+
211
+ await user.click(screen.getByRole('button', { name: 'Open second modal' }));
212
+
213
+ expect(target).toHaveClass(css(styles.backdropOpen));
214
+
215
+ const closeButtons = screen.getAllByRole('button', { name: 'Close', hidden: true });
216
+ await user.click(closeButtons[closeButtons.length - 1]);
217
+
218
+ expect(target).toHaveClass(css(styles.backdropOpen));
219
+ });
220
+
221
+ test('backdropOpen class is removed when all modals are closed', async () => {
222
+ const user = userEvent.setup();
223
+
224
+ render(<MultipleOpenModals />, { container: document.body.appendChild(target) });
225
+
226
+ await user.click(screen.getByRole('button', { name: 'Open second modal' }));
227
+
228
+ const closeButtons = screen.getAllByRole('button', { name: 'Close', hidden: true });
229
+ await user.click(closeButtons[closeButtons.length - 1]);
230
+ await user.click(screen.getByRole('button', { name: 'Close' }));
231
+
232
+ expect(target).not.toHaveClass(css(styles.backdropOpen));
233
+ });
234
+
235
+ test('only the most recent modal does not have aria-hidden when multiple modals are open', async () => {
236
+ const user = userEvent.setup();
237
+
238
+ render(<MultipleOpenModals />, { container: document.body.appendChild(target) });
239
+
240
+ const firstBackdrop = screen.getByLabelText('First modal').closest('[class*="backdrop"]');
241
+
242
+ await user.click(screen.getByRole('button', { name: 'Open second modal' }));
243
+
244
+ const secondBackdrop = screen.getByLabelText('Second modal').closest('[class*="backdrop"]');
245
+
246
+ expect(firstBackdrop).toHaveAttribute('aria-hidden', 'true');
247
+ expect(secondBackdrop).not.toHaveAttribute('aria-hidden');
248
+ });
249
+
250
+ test('closing the active modal reveals the previous modal', async () => {
251
+ const user = userEvent.setup();
252
+
253
+ render(<MultipleOpenModals />, { container: document.body.appendChild(target) });
254
+
255
+ await user.click(screen.getByRole('button', { name: 'Open second modal' }));
256
+
257
+ const firstBackdrop = screen
258
+ .getByLabelText('First modal', { selector: '[role="dialog"]' })
259
+ .closest('[class*="backdrop"]');
260
+
261
+ expect(firstBackdrop).toHaveAttribute('aria-hidden', 'true');
262
+
263
+ const closeButtons = screen.getAllByRole('button', { name: 'Close', hidden: true });
264
+ await user.click(closeButtons[closeButtons.length - 1]);
265
+
266
+ expect(firstBackdrop).not.toHaveAttribute('aria-hidden');
267
+ });
268
+
269
+ test('modals with different appendTo targets have independent stacks', async () => {
270
+ const user = userEvent.setup();
271
+ const targetA = document.createElement('div');
272
+ const targetB = document.createElement('div');
273
+ document.body.appendChild(targetA);
274
+ document.body.appendChild(targetB);
275
+
276
+ const siblingA = document.createElement('aside');
277
+ siblingA.textContent = 'Sibling A';
278
+ targetA.appendChild(siblingA);
279
+
280
+ const siblingB = document.createElement('aside');
281
+ siblingB.textContent = 'Sibling B';
282
+ targetB.appendChild(siblingB);
283
+
284
+ const DistinctTargetModals = () => {
285
+ const [isAOpen, setIsAOpen] = useState(true);
286
+ const [isBOpen, setIsBOpen] = useState(true);
287
+
288
+ return (
289
+ <>
290
+ <Modal isOpen={isAOpen} appendTo={targetA} onClose={() => setIsAOpen(false)} aria-label="Modal A">
291
+ Modal A content
292
+ </Modal>
293
+ <Modal isOpen={isBOpen} appendTo={targetB} onClose={() => setIsBOpen(false)} aria-label="Modal B">
294
+ Modal B content
295
+ </Modal>
296
+ </>
297
+ );
298
+ };
299
+
300
+ render(<DistinctTargetModals />);
301
+
302
+ expect(siblingA).toHaveAttribute('aria-hidden', 'true');
303
+ expect(siblingB).toHaveAttribute('aria-hidden', 'true');
304
+ expect(targetA).toHaveClass(css(styles.backdropOpen));
305
+ expect(targetB).toHaveClass(css(styles.backdropOpen));
306
+
307
+ const closeButtons = screen.getAllByRole('button', { name: 'Close', hidden: true });
308
+ await user.click(closeButtons[1]);
309
+
310
+ expect(targetB).not.toHaveClass(css(styles.backdropOpen));
311
+ expect(siblingB).not.toHaveAttribute('aria-hidden');
312
+
313
+ expect(targetA).toHaveClass(css(styles.backdropOpen));
314
+ expect(siblingA).toHaveAttribute('aria-hidden', 'true');
315
+
316
+ document.body.removeChild(targetA);
317
+ document.body.removeChild(targetB);
318
+ });
319
+
320
+ test('unmounting a never-opened modal with a custom target does not leak a stack entry', () => {
321
+ const customTarget = document.createElement('div');
322
+ document.body.appendChild(customTarget);
323
+
324
+ const { unmount } = render(
325
+ <Modal isOpen={false} appendTo={customTarget} onClose={() => {}}>
326
+ Never opened
327
+ </Modal>
328
+ );
329
+
330
+ unmount();
331
+
332
+ expect(Modal.openModalStacks.has(customTarget)).toBe(false);
333
+
334
+ document.body.removeChild(customTarget);
335
+ });
184
336
  });