@carbon/react 1.107.1 → 1.108.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 (75) hide show
  1. package/.playwright/INTERNAL_AVT_REPORT_DO_NOT_USE.json +960 -960
  2. package/es/components/Button/Button.d.ts +2 -2
  3. package/es/components/Button/ButtonBase.js +5 -5
  4. package/es/components/ChatButton/ChatButton.d.ts +1 -1
  5. package/es/components/ChatButton/ChatButton.js +1 -1
  6. package/es/components/ComposedModal/ComposedModal.js +10 -5
  7. package/es/components/ComposedModal/ModalFooter.d.ts +5 -0
  8. package/es/components/ComposedModal/ModalFooter.js +3 -1
  9. package/es/components/Dialog/Dialog.d.ts +5 -0
  10. package/es/components/Dialog/Dialog.js +3 -1
  11. package/es/components/ErrorBoundary/ErrorBoundaryContext.d.ts +1 -1
  12. package/es/components/ErrorBoundary/ErrorBoundaryContext.js +1 -1
  13. package/es/components/FileUploader/FileUploader.d.ts +5 -0
  14. package/es/components/FileUploader/FileUploader.js +14 -6
  15. package/es/components/FileUploader/FileUploaderItem.d.ts +5 -1
  16. package/es/components/FileUploader/FileUploaderItem.js +4 -2
  17. package/es/components/FileUploader/Filename.d.ts +5 -1
  18. package/es/components/FileUploader/Filename.js +2 -1
  19. package/es/components/Menu/MenuItem.js +16 -7
  20. package/es/components/Modal/Modal.d.ts +5 -0
  21. package/es/components/Modal/Modal.js +26 -13
  22. package/es/components/Modal/isTopmostVisibleModal.d.ts +7 -0
  23. package/es/components/Modal/isTopmostVisibleModal.js +21 -0
  24. package/es/components/MultiSelect/FilterableMultiSelect.js +9 -8
  25. package/es/components/MultiSelect/MultiSelect.js +9 -8
  26. package/es/components/MultiSelect/tools/isSelectAllItem.d.ts +9 -0
  27. package/es/components/MultiSelect/tools/isSelectAllItem.js +17 -0
  28. package/es/components/MultiSelect/tools/sorting.js +1 -1
  29. package/es/components/OverflowMenuItem/OverflowMenuItem.js +3 -2
  30. package/es/components/PageHeader/PageHeader.d.ts +4 -0
  31. package/es/components/PageHeader/PageHeader.js +18 -0
  32. package/es/components/PageHeader/index.d.ts +4 -0
  33. package/es/components/Tabs/Tabs.d.ts +5 -1
  34. package/es/components/Tabs/Tabs.js +2 -1
  35. package/es/components/UIShell/SwitcherDivider.d.ts +2 -2
  36. package/es/components/UIShell/SwitcherDivider.js +2 -2
  37. package/es/internal/warning.d.ts +1 -1
  38. package/lib/components/Button/Button.d.ts +2 -2
  39. package/lib/components/Button/ButtonBase.js +5 -5
  40. package/lib/components/ChatButton/ChatButton.d.ts +1 -1
  41. package/lib/components/ChatButton/ChatButton.js +1 -1
  42. package/lib/components/ComposedModal/ComposedModal.js +10 -5
  43. package/lib/components/ComposedModal/ModalFooter.d.ts +5 -0
  44. package/lib/components/ComposedModal/ModalFooter.js +3 -1
  45. package/lib/components/Dialog/Dialog.d.ts +5 -0
  46. package/lib/components/Dialog/Dialog.js +3 -1
  47. package/lib/components/ErrorBoundary/ErrorBoundaryContext.d.ts +1 -1
  48. package/lib/components/ErrorBoundary/ErrorBoundaryContext.js +1 -1
  49. package/lib/components/FileUploader/FileUploader.d.ts +5 -0
  50. package/lib/components/FileUploader/FileUploader.js +14 -6
  51. package/lib/components/FileUploader/FileUploaderItem.d.ts +5 -1
  52. package/lib/components/FileUploader/FileUploaderItem.js +4 -2
  53. package/lib/components/FileUploader/Filename.d.ts +5 -1
  54. package/lib/components/FileUploader/Filename.js +2 -1
  55. package/lib/components/Menu/MenuItem.js +15 -6
  56. package/lib/components/Modal/Modal.d.ts +5 -0
  57. package/lib/components/Modal/Modal.js +26 -13
  58. package/lib/components/Modal/isTopmostVisibleModal.d.ts +7 -0
  59. package/lib/components/Modal/isTopmostVisibleModal.js +21 -0
  60. package/lib/components/MultiSelect/FilterableMultiSelect.js +9 -8
  61. package/lib/components/MultiSelect/MultiSelect.js +9 -8
  62. package/lib/components/MultiSelect/tools/isSelectAllItem.d.ts +9 -0
  63. package/lib/components/MultiSelect/tools/isSelectAllItem.js +17 -0
  64. package/lib/components/MultiSelect/tools/sorting.js +3 -3
  65. package/lib/components/OverflowMenuItem/OverflowMenuItem.js +3 -2
  66. package/lib/components/PageHeader/PageHeader.d.ts +4 -0
  67. package/lib/components/PageHeader/PageHeader.js +18 -0
  68. package/lib/components/PageHeader/index.d.ts +4 -0
  69. package/lib/components/Tabs/Tabs.d.ts +5 -1
  70. package/lib/components/Tabs/Tabs.js +2 -1
  71. package/lib/components/UIShell/SwitcherDivider.d.ts +2 -2
  72. package/lib/components/UIShell/SwitcherDivider.js +2 -2
  73. package/lib/internal/warning.d.ts +1 -1
  74. package/lib/internal/warning.js +1 -1
  75. package/package.json +10 -10
@@ -4,7 +4,7 @@
4
4
  * This source code is licensed under the Apache-2.0 license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
- import React, { type ReactNode } from 'react';
7
+ import React from 'react';
8
8
  import { IconButtonKind } from '../IconButton';
9
9
  import { PolymorphicComponentPropWithRef } from '../../internal/PolymorphicProps';
10
10
  export declare const ButtonKinds: readonly ["primary", "secondary", "danger", "ghost", "danger--primary", "danger--ghost", "danger--tertiary", "tertiary"];
@@ -82,6 +82,6 @@ export interface ButtonBaseProps extends React.ButtonHTMLAttributes<HTMLButtonEl
82
82
  tooltipPosition?: ButtonTooltipPosition;
83
83
  }
84
84
  export type ButtonProps<T extends React.ElementType> = PolymorphicComponentPropWithRef<T, ButtonBaseProps>;
85
- export type ButtonComponent = <T extends React.ElementType = 'button'>(props: ButtonProps<T>) => ReactNode;
85
+ export type ButtonComponent = <T extends React.ElementType = 'button'>(props: ButtonProps<T>, context?: any) => React.ReactElement | any;
86
86
  declare const _default: ButtonComponent;
87
87
  export default _default;
@@ -17,7 +17,7 @@ import { jsx } from "react/jsx-runtime";
17
17
  * This source code is licensed under the Apache-2.0 license found in the
18
18
  * LICENSE file in the root directory of this source tree.
19
19
  */
20
- const ButtonBase = React.forwardRef(function ButtonBase({ as, children, className, dangerDescription = "danger", disabled = false, hasIconOnly = false, href, iconDescription, isExpressive = false, isSelected, kind = "primary", onBlur, onClick, onFocus, onMouseEnter, onMouseLeave, renderIcon: ButtonImageElement, size, tabIndex, type = "button", ...rest }, ref) {
20
+ const ButtonBase = React.forwardRef(function ButtonBase({ as, children, className, dangerDescription = "", disabled = false, hasIconOnly = false, href, iconDescription, isExpressive = false, isSelected, kind = "primary", onBlur, onClick, onFocus, onMouseEnter, onMouseLeave, renderIcon: ButtonImageElement, size, tabIndex, type = "button", ...rest }, ref) {
21
21
  const prefix = usePrefix();
22
22
  const commonProps = {
23
23
  tabIndex,
@@ -43,23 +43,23 @@ const ButtonBase = React.forwardRef(function ButtonBase({ as, children, classNam
43
43
  className: `${prefix}--btn__icon`,
44
44
  "aria-hidden": "true"
45
45
  });
46
- const dangerButtonVariants = [
46
+ const hasDangerDescription = [
47
47
  "danger",
48
48
  "danger--tertiary",
49
49
  "danger--ghost"
50
- ];
50
+ ].includes(kind) && Boolean(dangerDescription);
51
51
  let component = "button";
52
52
  const assistiveId = useId("danger-description");
53
53
  const { "aria-pressed": ariaPressed, "aria-describedby": ariaDescribedBy } = rest;
54
54
  let otherProps = {
55
55
  disabled,
56
56
  type,
57
- "aria-describedby": dangerButtonVariants.includes(kind) ? assistiveId : ariaDescribedBy || void 0,
57
+ "aria-describedby": hasDangerDescription ? assistiveId : ariaDescribedBy || void 0,
58
58
  "aria-pressed": ariaPressed ?? (hasIconOnly && kind === "ghost" ? isSelected : void 0)
59
59
  };
60
60
  const anchorProps = { href };
61
61
  let assistiveText = null;
62
- if (dangerButtonVariants.includes(kind)) assistiveText = /* @__PURE__ */ jsx("span", {
62
+ if (hasDangerDescription) assistiveText = /* @__PURE__ */ jsx("span", {
63
63
  id: assistiveId,
64
64
  className: `${prefix}--visually-hidden`,
65
65
  children: dangerDescription
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright IBM Corp. 2024, 2025
2
+ * Copyright IBM Corp. 2024, 2026
3
3
  *
4
4
  * This source code is licensed under the Apache-2.0 license found in the
5
5
  * LICENSE file in the root directory of this source tree.
@@ -13,7 +13,7 @@ import PropTypes from "prop-types";
13
13
  import { jsx } from "react/jsx-runtime";
14
14
  //#region src/components/ChatButton/ChatButton.tsx
15
15
  /**
16
- * Copyright IBM Corp. 2024, 2025
16
+ * Copyright IBM Corp. 2024, 2026
17
17
  *
18
18
  * This source code is licensed under the Apache-2.0 license found in the
19
19
  * LICENSE file in the root directory of this source tree.
@@ -26,6 +26,7 @@ import { elementOrParentIsFloatingMenu, wrapFocus, wrapFocusWithoutSentinels } f
26
26
  import { Dialog } from "../Dialog/Dialog.js";
27
27
  import { useComposedModalState } from "./useComposedModalState.js";
28
28
  import { ComposedModalPresence, ComposedModalPresenceContext, useExclusiveComposedModalPresenceContext } from "./ComposedModalPresence.js";
29
+ import { isTopmostVisibleModal } from "../Modal/isTopmostVisibleModal.js";
29
30
  import classNames from "classnames";
30
31
  import React, { Children, cloneElement, useContext, useEffect, useRef } from "react";
31
32
  import PropTypes from "prop-types";
@@ -98,9 +99,14 @@ const ComposedModalDialog = React.forwardRef(function ComposedModalDialog({ ["ar
98
99
  const button = useRef(null);
99
100
  const startSentinel = useRef(null);
100
101
  const endSentinel = useRef(null);
102
+ const modalRef = useRef(null);
101
103
  const onMouseDownTarget = useRef(null);
102
104
  const presenceContext = useContext(ComposedModalPresenceContext);
103
- const mergedRefs = useMergeRefs([ref, presenceContext?.presenceRef]);
105
+ const mergedRefs = useMergeRefs([
106
+ modalRef,
107
+ ref,
108
+ presenceContext?.presenceRef
109
+ ]);
104
110
  const enablePresence = useFeatureFlag("enable-presence") || presenceContext?.autoEnablePresence;
105
111
  const open = externalOpen || enablePresence;
106
112
  const modalState = useComposedModalState(open);
@@ -184,15 +190,14 @@ const ComposedModalDialog = React.forwardRef(function ComposedModalDialog({ ["ar
184
190
  useEffect(() => {
185
191
  if (!open) return;
186
192
  const handleEscapeKey = (event) => {
187
- if (match(event, Escape)) {
193
+ if (match(event, Escape) && isTopmostVisibleModal(modalRef.current, prefix)) {
188
194
  event.preventDefault();
189
- event.stopPropagation();
190
195
  closeModal(event);
191
196
  }
192
197
  };
193
- document.addEventListener("keydown", handleEscapeKey, true);
198
+ document.addEventListener("keydown", handleEscapeKey);
194
199
  return () => {
195
- document.removeEventListener("keydown", handleEscapeKey, true);
200
+ document.removeEventListener("keydown", handleEscapeKey);
196
201
  };
197
202
  }, [open]);
198
203
  useEffect(() => {
@@ -36,6 +36,11 @@ export interface ModalFooterProps {
36
36
  * Note that this prop is not applied if you render primary/danger button by yourself
37
37
  */
38
38
  danger?: boolean;
39
+ /**
40
+ * Specify the message read by screen readers for the danger primary button.
41
+ * Defaults to an empty string; provide localized text to opt in.
42
+ */
43
+ dangerDescription?: string;
39
44
  /**
40
45
  * The `ref` callback for the primary button.
41
46
  */
@@ -61,7 +61,7 @@ SecondaryButtonSet.propTypes = {
61
61
  },
62
62
  secondaryClassName: PropTypes.string
63
63
  };
64
- const ModalFooter = React.forwardRef(function ModalFooter({ children, className: customClassName, closeModal = noopFn, danger, inputref, onRequestClose = noopFn, onRequestSubmit = noopFn, primaryButtonDisabled, primaryButtonText, primaryClassName, secondaryButtonText, secondaryButtons, secondaryClassName, loadingStatus = "inactive", loadingDescription, loadingIconDescription, onLoadingSuccess = noopFn, ...rest }, ref) {
64
+ const ModalFooter = React.forwardRef(function ModalFooter({ children, className: customClassName, closeModal = noopFn, danger, dangerDescription = "", inputref, onRequestClose = noopFn, onRequestSubmit = noopFn, primaryButtonDisabled, primaryButtonText, primaryClassName, secondaryButtonText, secondaryButtons, secondaryClassName, loadingStatus = "inactive", loadingDescription, loadingIconDescription, onLoadingSuccess = noopFn, ...rest }, ref) {
65
65
  const prefix = usePrefix();
66
66
  const footerClass = classNames(`${prefix}--modal-footer`, customClassName, Array.isArray(secondaryButtons) && secondaryButtons.length === 2 ? `${prefix}--modal-footer--three-button` : null);
67
67
  const primaryButtonClass = classNames(primaryClassName, loadingStatus !== "inactive" ? `${prefix}--btn--loading` : null);
@@ -85,6 +85,7 @@ const ModalFooter = React.forwardRef(function ModalFooter({ children, className:
85
85
  onClick: onRequestSubmit,
86
86
  className: primaryButtonClass,
87
87
  disabled: loadingActive || primaryButtonDisabled,
88
+ dangerDescription,
88
89
  kind: danger ? "danger" : "primary",
89
90
  ref: inputref,
90
91
  children: loadingStatus === "inactive" ? primaryButtonText : /* @__PURE__ */ jsx(InlineLoading_default, {
@@ -104,6 +105,7 @@ ModalFooter.propTypes = {
104
105
  className: PropTypes.string,
105
106
  closeModal: PropTypes.func,
106
107
  danger: PropTypes.bool,
108
+ dangerDescription: PropTypes.string,
107
109
  inputref: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({ current: PropTypes.any })]),
108
110
  loadingDescription: PropTypes.string,
109
111
  loadingIconDescription: PropTypes.string,
@@ -232,6 +232,11 @@ interface DialogFooterProps extends HTMLAttributes<HTMLDivElement> {
232
232
  * Specify whether the Dialog is for dangerous actions
233
233
  */
234
234
  danger?: boolean;
235
+ /**
236
+ * Specify the message read by screen readers for the danger primary button.
237
+ * Defaults to an empty string; provide localized text to opt in.
238
+ */
239
+ dangerDescription?: string;
235
240
  /**
236
241
  * Specify loading status
237
242
  */
@@ -248,7 +248,7 @@ DialogBody.propTypes = {
248
248
  className: PropTypes.string,
249
249
  hasScrollingContent: PropTypes.bool
250
250
  };
251
- const DialogFooter = React.forwardRef(({ children, className, onRequestClose = noopFn, onSecondarySubmit, onRequestSubmit = noopFn, primaryButtonText = "Save", primaryButtonDisabled = false, secondaryButtonText = "Cancel", secondaryButtons, loadingStatus = "inactive", loadingDescription, loadingIconDescription, onLoadingSuccess = noopFn, danger = false, ...rest }, ref) => {
251
+ const DialogFooter = React.forwardRef(({ children, className, onRequestClose = noopFn, onSecondarySubmit, onRequestSubmit = noopFn, primaryButtonText = "Save", primaryButtonDisabled = false, secondaryButtonText = "Cancel", secondaryButtons, loadingStatus = "inactive", loadingDescription, loadingIconDescription, onLoadingSuccess = noopFn, danger = false, dangerDescription = "", ...rest }, ref) => {
252
252
  const prefix = usePrefix();
253
253
  const button = useRef(null);
254
254
  const { isOpen } = useContext(DialogContext);
@@ -296,6 +296,7 @@ const DialogFooter = React.forwardRef(({ children, className, onRequestClose = n
296
296
  }), /* @__PURE__ */ jsx(Button_default, {
297
297
  className: primaryButtonClass,
298
298
  kind: danger ? "danger" : "primary",
299
+ dangerDescription,
299
300
  disabled: loadingActive || primaryButtonDisabled,
300
301
  onClick: onRequestSubmit,
301
302
  ref: button,
@@ -333,6 +334,7 @@ DialogFooter.propTypes = {
333
334
  return null;
334
335
  },
335
336
  danger: PropTypes.bool,
337
+ dangerDescription: PropTypes.string,
336
338
  loadingStatus: PropTypes.oneOf([
337
339
  "inactive",
338
340
  "active",
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright IBM Corp. 2016, 2023
2
+ * Copyright IBM Corp. 2016, 2026
3
3
  *
4
4
  * This source code is licensed under the Apache-2.0 license found in the
5
5
  * LICENSE file in the root directory of this source tree.
@@ -8,7 +8,7 @@
8
8
  import { createContext } from "react";
9
9
  //#region src/components/ErrorBoundary/ErrorBoundaryContext.ts
10
10
  /**
11
- * Copyright IBM Corp. 2016, 2023
11
+ * Copyright IBM Corp. 2016, 2026
12
12
  *
13
13
  * This source code is licensed under the Apache-2.0 license found in the
14
14
  * LICENSE file in the root directory of this source tree.
@@ -11,6 +11,7 @@ interface FileItem {
11
11
  file: File & {
12
12
  invalidFileType?: boolean;
13
13
  };
14
+ disabled?: boolean;
14
15
  }
15
16
  export interface FileChangeData {
16
17
  addedFiles: FileItem[];
@@ -113,6 +114,10 @@ export interface FileUploaderHandle {
113
114
  * Get current files (only available when 'enable-enhanced-file-uploader' feature flag is enabled)
114
115
  */
115
116
  getCurrentFiles?: () => FileItem[];
117
+ /**
118
+ * Set current files (only available when 'enable-enhanced-file-uploader' feature flag is enabled)
119
+ */
120
+ setCurrentFiles?: (files: FileItem[]) => void;
116
121
  }
117
122
  declare const FileUploader: {
118
123
  (props: FileUploaderProps): React.ReactElement;
@@ -154,9 +154,14 @@ const FileUploader = forwardRef(({ accept, buttonKind, buttonLabel, className, d
154
154
  });
155
155
  } else setLegacyFileNames([]);
156
156
  },
157
- ...enhancedFileUploaderEnabled && { getCurrentFiles() {
158
- return [...fileItems];
159
- } }
157
+ ...enhancedFileUploaderEnabled && {
158
+ getCurrentFiles() {
159
+ return [...fileItems];
160
+ },
161
+ setCurrentFiles: (files) => {
162
+ setFileItems(files);
163
+ }
164
+ }
160
165
  }), [
161
166
  enhancedFileUploaderEnabled,
162
167
  fileItems,
@@ -167,12 +172,14 @@ const FileUploader = forwardRef(({ accept, buttonKind, buttonLabel, className, d
167
172
  [className]: className
168
173
  });
169
174
  const getHelperLabelClasses = (baseClass) => classNames(baseClass, { [`${prefix}--label-description--disabled`]: disabled });
170
- const selectedFileClasses = classNames(`${prefix}--file__selected-file`, {
175
+ const getSelectedFileClasses = (file) => classNames(`${prefix}--file__selected-file`, {
171
176
  [`${prefix}--file__selected-file--md`]: size === "field" || size === "md",
172
- [`${prefix}--file__selected-file--sm`]: size === "small" || size === "sm"
177
+ [`${prefix}--file__selected-file--sm`]: size === "small" || size === "sm",
178
+ [`${prefix}--file__selected-file--disabled`]: file.disabled
173
179
  });
174
180
  const displayFiles = enhancedFileUploaderEnabled ? fileItems.map((item, index) => ({
175
181
  name: item.name,
182
+ disabled: item.disabled,
176
183
  key: item.uuid,
177
184
  index
178
185
  })) : legacyFileNames.map((name, index) => ({
@@ -211,7 +218,7 @@ const FileUploader = forwardRef(({ accept, buttonKind, buttonLabel, className, d
211
218
  /* @__PURE__ */ jsx("div", {
212
219
  className: `${prefix}--file-container`,
213
220
  children: displayFiles.length === 0 ? null : displayFiles.map((file) => /* @__PURE__ */ jsxs("span", {
214
- className: selectedFileClasses,
221
+ className: getSelectedFileClasses(file),
215
222
  ref: (node) => {
216
223
  nodes[file.index] = node;
217
224
  },
@@ -225,6 +232,7 @@ const FileUploader = forwardRef(({ accept, buttonKind, buttonLabel, className, d
225
232
  className: `${prefix}--file__state-container`,
226
233
  children: /* @__PURE__ */ jsx(Filename, {
227
234
  name: file.name,
235
+ disabled: file.disabled,
228
236
  iconDescription,
229
237
  status: filenameStatus,
230
238
  onKeyDown: (evt) => {
@@ -7,6 +7,10 @@
7
7
  import PropTypes from 'prop-types';
8
8
  import React, { type HTMLAttributes } from 'react';
9
9
  export interface FileUploaderItemProps extends HTMLAttributes<HTMLSpanElement> {
10
+ /**
11
+ * Specify whether file uploader item is disabled
12
+ */
13
+ disabled?: boolean;
10
14
  /**
11
15
  * Error message body for an invalid file upload
12
16
  */
@@ -52,7 +56,7 @@ export interface FileUploaderItemProps extends HTMLAttributes<HTMLSpanElement> {
52
56
  */
53
57
  uuid?: string;
54
58
  }
55
- declare function FileUploaderItem({ uuid, name, status, iconDescription, onDelete, invalid, errorSubject, errorBody, size, className, ...other }: FileUploaderItemProps): import("react/jsx-runtime").JSX.Element;
59
+ declare function FileUploaderItem({ uuid, name, status, iconDescription, onDelete, invalid, errorSubject, errorBody, size, className, disabled, ...other }: FileUploaderItemProps): import("react/jsx-runtime").JSX.Element;
56
60
  declare namespace FileUploaderItem {
57
61
  var propTypes: {
58
62
  /**
@@ -25,7 +25,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
25
25
  * This source code is licensed under the Apache-2.0 license found in the
26
26
  * LICENSE file in the root directory of this source tree.
27
27
  */
28
- function FileUploaderItem({ uuid, name, status = "uploading", iconDescription, onDelete = noopFn, invalid, errorSubject, errorBody, size, className, ...other }) {
28
+ function FileUploaderItem({ uuid, name, status = "uploading", iconDescription, onDelete = noopFn, invalid, errorSubject, errorBody, size, className, disabled, ...other }) {
29
29
  const textRef = useRef(null);
30
30
  const [isEllipsisApplied, setIsEllipsisApplied] = useState(false);
31
31
  const prefix = usePrefix();
@@ -34,7 +34,8 @@ function FileUploaderItem({ uuid, name, status = "uploading", iconDescription, o
34
34
  const classes = classNames(`${prefix}--file__selected-file`, className, {
35
35
  [`${prefix}--file__selected-file--invalid`]: invalid,
36
36
  [`${prefix}--file__selected-file--md`]: size === "md",
37
- [`${prefix}--file__selected-file--sm`]: size === "sm"
37
+ [`${prefix}--file__selected-file--sm`]: size === "sm",
38
+ [`${prefix}--file__selected-file--disabled`]: disabled
38
39
  });
39
40
  const isInvalid = invalid ? `${prefix}--file-filename-container-wrap-invalid` : `${prefix}--file-filename-container-wrap`;
40
41
  const filterSpaceName = (name) => {
@@ -89,6 +90,7 @@ function FileUploaderItem({ uuid, name, status = "uploading", iconDescription, o
89
90
  className: `${prefix}--file__state-container`,
90
91
  children: /* @__PURE__ */ jsx(Filename, {
91
92
  name,
93
+ disabled,
92
94
  iconDescription,
93
95
  status,
94
96
  invalid,
@@ -17,6 +17,10 @@ export interface FilenameProps extends Omit<HTMLAttributes<HTMLElement> & SVGAtt
17
17
  * Provide a description of the SVG icon to denote file upload status
18
18
  */
19
19
  iconDescription?: string;
20
+ /**
21
+ * Specify whether the file uploader item is disabled
22
+ */
23
+ disabled?: boolean;
20
24
  /**
21
25
  * Specify if the file is invalid
22
26
  */
@@ -34,7 +38,7 @@ export interface FilenameProps extends Omit<HTMLAttributes<HTMLElement> & SVGAtt
34
38
  */
35
39
  tabIndex?: number;
36
40
  }
37
- declare function Filename({ iconDescription, status, invalid, name, tabIndex, ['aria-describedby']: ariaDescribedBy, ...rest }: FilenameProps): import("react/jsx-runtime").JSX.Element | null;
41
+ declare function Filename({ iconDescription, status, invalid, disabled, name, tabIndex, ['aria-describedby']: ariaDescribedBy, ...rest }: FilenameProps): import("react/jsx-runtime").JSX.Element | null;
38
42
  declare namespace Filename {
39
43
  var propTypes: {
40
44
  /**
@@ -18,7 +18,7 @@ import { CheckmarkFilled, Close, WarningFilled } from "@carbon/icons-react";
18
18
  * This source code is licensed under the Apache-2.0 license found in the
19
19
  * LICENSE file in the root directory of this source tree.
20
20
  */
21
- function Filename({ iconDescription = "Uploading file", status = "uploading", invalid, name, tabIndex = 0, ["aria-describedby"]: ariaDescribedBy, ...rest }) {
21
+ function Filename({ iconDescription = "Uploading file", status = "uploading", invalid, disabled, name, tabIndex = 0, ["aria-describedby"]: ariaDescribedBy, ...rest }) {
22
22
  const prefix = usePrefix();
23
23
  switch (status) {
24
24
  case "uploading": return /* @__PURE__ */ jsx(Loading_default, {
@@ -28,6 +28,7 @@ function Filename({ iconDescription = "Uploading file", status = "uploading", in
28
28
  className: `${prefix}--file-loading`
29
29
  });
30
30
  case "edit": return /* @__PURE__ */ jsxs(Fragment, { children: [invalid && /* @__PURE__ */ jsx(WarningFilled, { className: `${prefix}--file-invalid` }), /* @__PURE__ */ jsx("button", {
31
+ disabled,
31
32
  "aria-label": `${iconDescription} - ${name}`,
32
33
  className: `${prefix}--file-close`,
33
34
  type: "button",
@@ -21,7 +21,7 @@ import React, { forwardRef, useContext, useEffect, useRef, useState } from "reac
21
21
  import PropTypes from "prop-types";
22
22
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
23
23
  import { CaretLeft, CaretRight, Checkmark } from "@carbon/icons-react";
24
- import { FloatingFocusManager, autoUpdate, offset, safePolygon, useFloating, useHover, useInteractions } from "@floating-ui/react";
24
+ import { FloatingFocusManager, autoUpdate, flip, offset, safePolygon, useFloating, useHover, useInteractions } from "@floating-ui/react";
25
25
  //#region src/components/Menu/MenuItem.tsx
26
26
  /**
27
27
  * Copyright IBM Corp. 2023, 2026
@@ -29,18 +29,26 @@ import { FloatingFocusManager, autoUpdate, offset, safePolygon, useFloating, use
29
29
  * This source code is licensed under the Apache-2.0 license found in the
30
30
  * LICENSE file in the root directory of this source tree.
31
31
  */
32
- const MenuItem = forwardRef(function MenuItem({ children, className, dangerDescription = "danger", disabled, kind = "default", label, onClick, renderIcon: IconElement, shortcut, ...rest }, forwardRef) {
32
+ const MenuItem = forwardRef(function MenuItem({ children, className, dangerDescription = "", disabled, kind = "default", label, onClick, renderIcon: IconElement, shortcut, ...rest }, forwardRef) {
33
33
  const [submenuOpen, setSubmenuOpen] = useState(false);
34
34
  const [rtl, setRtl] = useState(false);
35
35
  const { refs, floatingStyles, context: floatingContext } = useFloating({
36
36
  open: submenuOpen,
37
- onOpenChange: setSubmenuOpen,
37
+ onOpenChange: (open, event) => {
38
+ if (open) setSubmenuOpen(true);
39
+ else {
40
+ const relatedTarget = event && "relatedTarget" in event ? event.relatedTarget : null;
41
+ if (relatedTarget instanceof Node && menuItem.current?.contains(relatedTarget)) return;
42
+ setSubmenuOpen(false);
43
+ if (!(relatedTarget instanceof HTMLElement && relatedTarget.closest("[role=\"menuitem\"]")?.querySelector("[role=\"menu\"]"))) menuItem.current?.focus();
44
+ }
45
+ },
38
46
  placement: rtl ? "left-start" : "right-start",
39
47
  whileElementsMounted: autoUpdate,
40
- middleware: [offset({
41
- mainAxis: -6,
48
+ middleware: [flip(), offset(({ placement }) => ({
49
+ mainAxis: placement.startsWith("left") ? 10 : -6,
42
50
  crossAxis: -6
43
- })],
51
+ }))],
44
52
  strategy: "fixed"
45
53
  });
46
54
  const { getReferenceProps, getFloatingProps } = useInteractions([useHover(floatingContext, {
@@ -59,6 +67,7 @@ const MenuItem = forwardRef(function MenuItem({ children, className, dangerDescr
59
67
  const hasChildren = React.Children.toArray(children).length > 0;
60
68
  const isDisabled = disabled && !hasChildren;
61
69
  const isDanger = kind === "danger" && !hasChildren;
70
+ const hasDangerDescription = isDanger && Boolean(dangerDescription);
62
71
  function registerItem() {
63
72
  context.dispatch({
64
73
  type: "registerItem",
@@ -161,7 +170,7 @@ const MenuItem = forwardRef(function MenuItem({ children, className, dangerDescr
161
170
  className: `${prefix}--menu-item__label`,
162
171
  children: label
163
172
  }),
164
- isDanger && /* @__PURE__ */ jsx("span", {
173
+ hasDangerDescription && /* @__PURE__ */ jsx("span", {
165
174
  id: assistiveId,
166
175
  className: `${prefix}--visually-hidden`,
167
176
  children: dangerDescription
@@ -38,6 +38,11 @@ export interface ModalProps extends HTMLAttributes<HTMLDivElement> {
38
38
  * Specify whether the Modal is for dangerous actions
39
39
  */
40
40
  danger?: boolean;
41
+ /**
42
+ * Specify the message read by screen readers for the danger primary button.
43
+ * Defaults to an empty string; provide localized text to opt in.
44
+ */
45
+ dangerDescription?: string;
41
46
  /**
42
47
  * **Experimental**: Provide a decorator component to be rendered inside the `Modal` component
43
48
  */
@@ -29,6 +29,7 @@ import { toggleClass } from "../../tools/toggleClass.js";
29
29
  import { requiredIfGivenPropIsTruthy } from "../../prop-types/requiredIfGivenPropIsTruthy.js";
30
30
  import { elementOrParentIsFloatingMenu, wrapFocus, wrapFocusWithoutSentinels } from "../../internal/wrapFocus.js";
31
31
  import { Dialog } from "../Dialog/Dialog.js";
32
+ import { isTopmostVisibleModal } from "./isTopmostVisibleModal.js";
32
33
  import { usePreviousValue } from "../../internal/usePreviousValue.js";
33
34
  import { ModalPresence, ModalPresenceContext, useExclusiveModalPresenceContext } from "./ModalPresence.js";
34
35
  import classNames from "classnames";
@@ -72,12 +73,13 @@ const Modal = React.forwardRef(function Modal({ open, ...props }, ref) {
72
73
  ...props
73
74
  });
74
75
  });
75
- const ModalDialog = React.forwardRef(function ModalDialog({ "aria-label": ariaLabelProp, children, className, decorator, modalHeading = "", modalLabel = "", modalAriaLabel, passiveModal = false, secondaryButtonText, primaryButtonText, open: externalOpen, onRequestClose = noopFn, onRequestSubmit = noopFn, onSecondarySubmit, primaryButtonDisabled = false, danger, alert, secondaryButtons, selectorPrimaryFocus = "[data-modal-primary-focus]", selectorsFloatingMenus, shouldSubmitOnEnter, size, hasScrollingContent = false, closeButtonLabel = "Close", preventCloseOnClickOutside, isFullWidth, launcherButtonRef, loadingStatus = "inactive", loadingDescription, loadingIconDescription, onLoadingSuccess = noopFn, slug, ...rest }, ref) {
76
+ const ModalDialog = React.forwardRef(function ModalDialog({ "aria-label": ariaLabelProp, children, className, decorator, modalHeading = "", modalLabel = "", modalAriaLabel, passiveModal = false, secondaryButtonText, primaryButtonText, open: externalOpen, onRequestClose = noopFn, onRequestSubmit = noopFn, onSecondarySubmit, primaryButtonDisabled = false, danger, dangerDescription = "", alert, secondaryButtons, selectorPrimaryFocus = "[data-modal-primary-focus]", selectorsFloatingMenus, shouldSubmitOnEnter, size, hasScrollingContent = false, closeButtonLabel = "Close", preventCloseOnClickOutside, isFullWidth, launcherButtonRef, loadingStatus = "inactive", loadingDescription, loadingIconDescription, onLoadingSuccess = noopFn, slug, ...rest }, ref) {
76
77
  const prefix = usePrefix();
77
78
  const button = useRef(null);
78
79
  const secondaryButton = useRef(null);
79
80
  const contentRef = useRef(null);
80
81
  const innerModal = useRef(null);
82
+ const modalRef = useRef(null);
81
83
  const startTrap = useRef(null);
82
84
  const endTrap = useRef(null);
83
85
  const wrapFocusTimeout = useRef(null);
@@ -89,7 +91,11 @@ const ModalDialog = React.forwardRef(function ModalDialog({ "aria-label": ariaLa
89
91
  const primaryButtonClass = classNames({ [`${prefix}--btn--loading`]: loadingStatus !== "inactive" });
90
92
  const loadingActive = loadingStatus !== "inactive";
91
93
  const presenceContext = useContext(ModalPresenceContext);
92
- const mergedRefs = useMergedRefs([ref, presenceContext?.presenceRef]);
94
+ const mergedRefs = useMergedRefs([
95
+ modalRef,
96
+ ref,
97
+ presenceContext?.presenceRef
98
+ ]);
93
99
  const enablePresence = useFeatureFlag("enable-presence") || presenceContext?.autoEnablePresence;
94
100
  const open = externalOpen || enablePresence;
95
101
  const prevOpen = usePreviousValue(open);
@@ -103,14 +109,19 @@ const ModalDialog = React.forwardRef(function ModalDialog({ "aria-label": ariaLa
103
109
  }
104
110
  function handleKeyDown(evt) {
105
111
  const { target } = evt;
106
- evt.stopPropagation();
107
112
  if (open && target instanceof HTMLElement) {
108
- if (match(evt, Enter) && shouldSubmitOnEnter && !isCloseButton(target) && document.activeElement !== button.current) onRequestSubmit(evt);
109
- if (focusTrapWithoutSentinels && !enableDialogElement && match(evt, Tab) && innerModal.current) wrapFocusWithoutSentinels({
110
- containerNode: innerModal.current,
111
- currentActiveNode: target,
112
- event: evt
113
- });
113
+ if (match(evt, Enter) && shouldSubmitOnEnter && !isCloseButton(target) && document.activeElement !== button.current) {
114
+ evt.stopPropagation();
115
+ onRequestSubmit(evt);
116
+ }
117
+ if (focusTrapWithoutSentinels && !enableDialogElement && match(evt, Tab) && innerModal.current) {
118
+ evt.stopPropagation();
119
+ wrapFocusWithoutSentinels({
120
+ containerNode: innerModal.current,
121
+ currentActiveNode: target,
122
+ event: evt
123
+ });
124
+ }
114
125
  }
115
126
  }
116
127
  function handleOnClick(evt) {
@@ -187,15 +198,14 @@ const ModalDialog = React.forwardRef(function ModalDialog({ "aria-label": ariaLa
187
198
  useEffect(() => {
188
199
  if (!open) return;
189
200
  const handleEscapeKey = (event) => {
190
- if (match(event, Escape)) {
201
+ if (match(event, Escape) && isTopmostVisibleModal(modalRef.current, prefix)) {
191
202
  event.preventDefault();
192
- event.stopPropagation();
193
203
  onRequestClose(event);
194
204
  }
195
205
  };
196
- document.addEventListener("keydown", handleEscapeKey, true);
206
+ document.addEventListener("keydown", handleEscapeKey);
197
207
  return () => {
198
- document.removeEventListener("keydown", handleEscapeKey, true);
208
+ document.removeEventListener("keydown", handleEscapeKey);
199
209
  };
200
210
  }, [open]);
201
211
  useEffect(() => {
@@ -352,6 +362,7 @@ const ModalDialog = React.forwardRef(function ModalDialog({ "aria-label": ariaLa
352
362
  }), /* @__PURE__ */ jsx(Button_default, {
353
363
  className: primaryButtonClass,
354
364
  kind: danger ? "danger" : "primary",
365
+ dangerDescription,
355
366
  disabled: loadingActive || primaryButtonDisabled,
356
367
  onClick: onRequestSubmit,
357
368
  ref: button,
@@ -428,6 +439,7 @@ const ModalDialog = React.forwardRef(function ModalDialog({ "aria-label": ariaLa
428
439
  }), /* @__PURE__ */ jsx(Button_default, {
429
440
  className: primaryButtonClass,
430
441
  kind: danger ? "danger" : "primary",
442
+ dangerDescription,
431
443
  disabled: loadingActive || primaryButtonDisabled,
432
444
  onClick: onRequestSubmit,
433
445
  ref: button,
@@ -470,6 +482,7 @@ Modal.propTypes = {
470
482
  className: PropTypes.string,
471
483
  closeButtonLabel: PropTypes.string,
472
484
  danger: PropTypes.bool,
485
+ dangerDescription: PropTypes.string,
473
486
  decorator: PropTypes.node,
474
487
  hasScrollingContent: PropTypes.bool,
475
488
  id: PropTypes.string,
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Copyright IBM Corp. 2026
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ export declare const isTopmostVisibleModal: (node: HTMLElement | null, prefix: string) => boolean;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2026
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ //#region src/components/Modal/isTopmostVisibleModal.ts
9
+ /**
10
+ * Copyright IBM Corp. 2026
11
+ *
12
+ * This source code is licensed under the Apache-2.0 license found in the
13
+ * LICENSE file in the root directory of this source tree.
14
+ */
15
+ const isTopmostVisibleModal = (node, prefix) => {
16
+ if (!node) return false;
17
+ const visibleModals = document.querySelectorAll(`.${prefix}--modal.is-visible`);
18
+ return visibleModals.item(visibleModals.length - 1) === node;
19
+ };
20
+ //#endregion
21
+ export { isTopmostVisibleModal };