@carbon/react 1.107.0 → 1.108.0-rc.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 (59) hide show
  1. package/.playwright/INTERNAL_AVT_REPORT_DO_NOT_USE.json +909 -909
  2. package/es/components/Button/Button.d.ts +2 -2
  3. package/es/components/ChatButton/ChatButton.d.ts +1 -1
  4. package/es/components/ChatButton/ChatButton.js +1 -1
  5. package/es/components/ComposedModal/ComposedModal.js +10 -5
  6. package/es/components/ErrorBoundary/ErrorBoundaryContext.d.ts +1 -1
  7. package/es/components/ErrorBoundary/ErrorBoundaryContext.js +1 -1
  8. package/es/components/FileUploader/FileUploader.d.ts +5 -0
  9. package/es/components/FileUploader/FileUploader.js +14 -6
  10. package/es/components/FileUploader/FileUploaderItem.d.ts +5 -1
  11. package/es/components/FileUploader/FileUploaderItem.js +4 -2
  12. package/es/components/FileUploader/Filename.d.ts +5 -1
  13. package/es/components/FileUploader/Filename.js +2 -1
  14. package/es/components/Menu/MenuItem.js +13 -5
  15. package/es/components/Modal/Modal.js +22 -12
  16. package/es/components/Modal/isTopmostVisibleModal.d.ts +7 -0
  17. package/es/components/Modal/isTopmostVisibleModal.js +21 -0
  18. package/es/components/MultiSelect/FilterableMultiSelect.js +9 -8
  19. package/es/components/MultiSelect/MultiSelect.js +9 -8
  20. package/es/components/MultiSelect/tools/isSelectAllItem.d.ts +9 -0
  21. package/es/components/MultiSelect/tools/isSelectAllItem.js +17 -0
  22. package/es/components/MultiSelect/tools/sorting.js +1 -1
  23. package/es/components/OverflowMenu/OverflowMenu.js +12 -11
  24. package/es/components/PageHeader/PageHeader.d.ts +4 -0
  25. package/es/components/PageHeader/PageHeader.js +18 -0
  26. package/es/components/PageHeader/index.d.ts +4 -0
  27. package/es/components/UIShell/SwitcherDivider.d.ts +2 -2
  28. package/es/components/UIShell/SwitcherDivider.js +2 -2
  29. package/es/internal/warning.d.ts +1 -1
  30. package/lib/components/Button/Button.d.ts +2 -2
  31. package/lib/components/ChatButton/ChatButton.d.ts +1 -1
  32. package/lib/components/ChatButton/ChatButton.js +1 -1
  33. package/lib/components/ComposedModal/ComposedModal.js +10 -5
  34. package/lib/components/ErrorBoundary/ErrorBoundaryContext.d.ts +1 -1
  35. package/lib/components/ErrorBoundary/ErrorBoundaryContext.js +1 -1
  36. package/lib/components/FileUploader/FileUploader.d.ts +5 -0
  37. package/lib/components/FileUploader/FileUploader.js +14 -6
  38. package/lib/components/FileUploader/FileUploaderItem.d.ts +5 -1
  39. package/lib/components/FileUploader/FileUploaderItem.js +4 -2
  40. package/lib/components/FileUploader/Filename.d.ts +5 -1
  41. package/lib/components/FileUploader/Filename.js +2 -1
  42. package/lib/components/Menu/MenuItem.js +12 -4
  43. package/lib/components/Modal/Modal.js +22 -12
  44. package/lib/components/Modal/isTopmostVisibleModal.d.ts +7 -0
  45. package/lib/components/Modal/isTopmostVisibleModal.js +21 -0
  46. package/lib/components/MultiSelect/FilterableMultiSelect.js +9 -8
  47. package/lib/components/MultiSelect/MultiSelect.js +9 -8
  48. package/lib/components/MultiSelect/tools/isSelectAllItem.d.ts +9 -0
  49. package/lib/components/MultiSelect/tools/isSelectAllItem.js +17 -0
  50. package/lib/components/MultiSelect/tools/sorting.js +3 -3
  51. package/lib/components/OverflowMenu/OverflowMenu.js +11 -10
  52. package/lib/components/PageHeader/PageHeader.d.ts +4 -0
  53. package/lib/components/PageHeader/PageHeader.js +18 -0
  54. package/lib/components/PageHeader/index.d.ts +4 -0
  55. package/lib/components/UIShell/SwitcherDivider.d.ts +2 -2
  56. package/lib/components/UIShell/SwitcherDivider.js +2 -2
  57. package/lib/internal/warning.d.ts +1 -1
  58. package/lib/internal/warning.js +1 -1
  59. 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;
@@ -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(() => {
@@ -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
@@ -34,13 +34,21 @@ const MenuItem = forwardRef(function MenuItem({ children, className, dangerDescr
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, {
@@ -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";
@@ -78,6 +79,7 @@ const ModalDialog = React.forwardRef(function ModalDialog({ "aria-label": ariaLa
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(() => {
@@ -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 };
@@ -25,6 +25,7 @@ import ListBoxTrigger from "../ListBox/next/ListBoxTrigger.js";
25
25
  import { mergeRefs } from "../../tools/mergeRefs.js";
26
26
  import { defaultFilterItems } from "./filter.js";
27
27
  import { sortingPropTypes } from "./MultiSelectPropTypes.js";
28
+ import { isSelectAllItem } from "./tools/isSelectAllItem.js";
28
29
  import { defaultCompareItems, defaultSortItems } from "./tools/sorting.js";
29
30
  import { useSelection } from "../../internal/Selection.js";
30
31
  import classNames from "classnames";
@@ -60,8 +61,8 @@ const FilterableMultiSelect = forwardRef(function FilterableMultiSelect({ autoAl
60
61
  itemToString,
61
62
  filterItems
62
63
  ]);
63
- const nonSelectAllItems = useMemo(() => filteredItems.filter((item) => !item.isSelectAll), [filteredItems]);
64
- const selectAll = filteredItems.some((item) => item.isSelectAll);
64
+ const nonSelectAllItems = useMemo(() => filteredItems.filter((item) => !isSelectAllItem(item)), [filteredItems]);
65
+ const selectAll = filteredItems.some(isSelectAllItem);
65
66
  const { selectedItems: controlledSelectedItems, onItemChange, clearSelection, toggleAll } = useSelection({
66
67
  disabled,
67
68
  initialSelectedItems,
@@ -129,7 +130,7 @@ const FilterableMultiSelect = forwardRef(function FilterableMultiSelect({ autoAl
129
130
  setIsOpen(open);
130
131
  }, [open]);
131
132
  const sortedItems = useMemo(() => {
132
- const selectAllItem = items.find((item) => item.isSelectAll);
133
+ const selectAllItem = items.find(isSelectAllItem);
133
134
  const selectableRealItems = nonSelectAllItems.filter((item) => !item.disabled);
134
135
  const sortedReal = sortItems(nonSelectAllItems, {
135
136
  selectedItems: {
@@ -265,7 +266,7 @@ const FilterableMultiSelect = forwardRef(function FilterableMultiSelect({ autoAl
265
266
  switch (type) {
266
267
  case InputKeyDownEnter:
267
268
  if (sortedItems.length === 0) return changes;
268
- if (changes.selectedItem && changes.selectedItem.disabled !== true) if (changes.selectedItem.isSelectAll) handleSelectAllClick();
269
+ if (changes.selectedItem && changes.selectedItem.disabled !== true) if (isSelectAllItem(changes.selectedItem)) handleSelectAllClick();
269
270
  else onItemChange(changes.selectedItem);
270
271
  setHighlightedIndex(changes.selectedItem);
271
272
  return {
@@ -273,7 +274,7 @@ const FilterableMultiSelect = forwardRef(function FilterableMultiSelect({ autoAl
273
274
  highlightedIndex: state.highlightedIndex
274
275
  };
275
276
  case ItemClick:
276
- if (changes.selectedItem.isSelectAll) handleSelectAllClick();
277
+ if (isSelectAllItem(changes.selectedItem)) handleSelectAllClick();
277
278
  else onItemChange(changes.selectedItem);
278
279
  setHighlightedIndex(changes.selectedItem);
279
280
  return changes;
@@ -371,7 +372,7 @@ const FilterableMultiSelect = forwardRef(function FilterableMultiSelect({ autoAl
371
372
  const candidate = slug ?? decorator;
372
373
  const candidateIsAILabel = isComponentElement(candidate, AILabel);
373
374
  const normalizedDecorator = candidateIsAILabel ? cloneElement(candidate, { size: "mini" }) : candidate;
374
- const selectedItemsLength = controlledSelectedItems.filter((item) => !item.isSelectAll).length;
375
+ const selectedItemsLength = controlledSelectedItems.filter((item) => !isSelectAllItem(item)).length;
375
376
  const className = classNames(`${prefix}--multi-select`, `${prefix}--combo-box`, `${prefix}--multi-select--filterable`, {
376
377
  [`${prefix}--multi-select--invalid`]: normalizedProps.invalid,
377
378
  [`${prefix}--multi-select--invalid--focused`]: inputFocused && normalizedProps.invalid,
@@ -528,7 +529,7 @@ const FilterableMultiSelect = forwardRef(function FilterableMultiSelect({ autoAl
528
529
  children: isOpen ? sortedItems.map((item, index) => {
529
530
  let isChecked;
530
531
  let isIndeterminate = false;
531
- if (item.isSelectAll) {
532
+ if (isSelectAllItem(item)) {
532
533
  isChecked = selectAllStatus.checked;
533
534
  isIndeterminate = selectAllStatus.indeterminate;
534
535
  } else isChecked = controlledSelectedItems.filter((selected) => isEqual(selected, item)).length > 0;
@@ -542,7 +543,7 @@ const FilterableMultiSelect = forwardRef(function FilterableMultiSelect({ autoAl
542
543
  return /* @__PURE__ */ jsx(ListBox.MenuItem, {
543
544
  "aria-label": itemText,
544
545
  "aria-checked": isIndeterminate ? "mixed" : isChecked,
545
- isActive: isChecked && !item["isSelectAll"],
546
+ isActive: isChecked && !isSelectAllItem(item),
546
547
  isHighlighted: highlightedIndex === index,
547
548
  title: itemText,
548
549
  disabled,
@@ -23,6 +23,7 @@ import { FormContext } from "../FluidForm/FormContext.js";
23
23
  import ListBox from "../ListBox/index.js";
24
24
  import { mergeRefs } from "../../tools/mergeRefs.js";
25
25
  import { sortingPropTypes } from "./MultiSelectPropTypes.js";
26
+ import { isSelectAllItem } from "./tools/isSelectAllItem.js";
26
27
  import { defaultCompareItems, defaultSortItems } from "./tools/sorting.js";
27
28
  import { useSelection } from "../../internal/Selection.js";
28
29
  import classNames from "classnames";
@@ -50,7 +51,7 @@ const MultiSelect = React.forwardRef(({ autoAlign = false, className: containerC
50
51
  return true;
51
52
  });
52
53
  }, [items]);
53
- const selectAll = filteredItems.some((item) => item.isSelectAll);
54
+ const selectAll = filteredItems.some(isSelectAllItem);
54
55
  const prefix = usePrefix();
55
56
  const { isFluid } = useContext(FormContext);
56
57
  const multiSelectInstanceId = useId();
@@ -280,7 +281,7 @@ const MultiSelect = React.forwardRef(({ autoAlign = false, className: containerC
280
281
  const candidate = slug ?? decorator;
281
282
  const normalizedDecorator = isComponentElement(candidate, AILabel) ? cloneElement(candidate, { size: "mini" }) : candidate;
282
283
  const itemsSelectedText = selectedItems.length > 0 && selectedItems.map((item) => item?.text);
283
- const selectedItemsLength = selectAll ? selectedItems.filter((item) => !item.isSelectAll).length : selectedItems.length;
284
+ const selectedItemsLength = selectAll ? selectedItems.filter((item) => !isSelectAllItem(item)).length : selectedItems.length;
284
285
  const menuProps = useMemo(() => getMenuProps({
285
286
  ref: enableFloatingStyles ? refs.setFloating : null,
286
287
  hidden: !isOpen
@@ -294,9 +295,9 @@ const MultiSelect = React.forwardRef(({ autoAlign = false, className: containerC
294
295
  const labelProps = isValidElement(titleText) ? { id: allLabelProps.id } : allLabelProps;
295
296
  const getSelectionStats = useCallback((selectedItems, filteredItems) => {
296
297
  return {
297
- hasIndividualSelections: selectedItems.some((selected) => !selected.isSelectAll),
298
- nonSelectAllSelectedCount: selectedItems.filter((selected) => !selected.isSelectAll).length,
299
- totalSelectableCount: filteredItems.filter((item) => !item.isSelectAll && !item.disabled).length
298
+ hasIndividualSelections: selectedItems.some((selected) => !isSelectAllItem(selected)),
299
+ nonSelectAllSelectedCount: selectedItems.filter((selected) => !isSelectAllItem(selected)).length,
300
+ totalSelectableCount: filteredItems.filter((item) => !isSelectAllItem(item) && !item.disabled).length
300
301
  };
301
302
  }, [selectedItems, filteredItems]);
302
303
  return /* @__PURE__ */ jsxs("div", {
@@ -372,15 +373,15 @@ const MultiSelect = React.forwardRef(({ autoAlign = false, className: containerC
372
373
  ...menuProps,
373
374
  children: isOpen && sortItems(filteredItems, sortOptions).map((item, index) => {
374
375
  const { hasIndividualSelections, nonSelectAllSelectedCount, totalSelectableCount } = getSelectionStats(selectedItems, filteredItems);
375
- const isChecked = item.isSelectAll ? nonSelectAllSelectedCount === totalSelectableCount && totalSelectableCount > 0 : selectedItems.some((selected) => isEqual(selected, item));
376
- const isIndeterminate = item.isSelectAll && hasIndividualSelections && nonSelectAllSelectedCount < totalSelectableCount;
376
+ const isChecked = isSelectAllItem(item) ? nonSelectAllSelectedCount === totalSelectableCount && totalSelectableCount > 0 : selectedItems.some((selected) => isEqual(selected, item));
377
+ const isIndeterminate = isSelectAllItem(item) && hasIndividualSelections && nonSelectAllSelectedCount < totalSelectableCount;
377
378
  const itemProps = getItemProps({
378
379
  item,
379
380
  ["aria-selected"]: isChecked
380
381
  });
381
382
  const itemText = itemToString(item);
382
383
  return /* @__PURE__ */ jsx(ListBox.MenuItem, {
383
- isActive: isChecked && !item["isSelectAll"],
384
+ isActive: isChecked && !isSelectAllItem(item),
384
385
  "aria-label": itemText,
385
386
  "aria-checked": isIndeterminate ? "mixed" : isChecked,
386
387
  isHighlighted: highlightedIndex === index,
@@ -0,0 +1,9 @@
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 isSelectAllItem: (item: unknown) => item is {
8
+ isSelectAll?: boolean;
9
+ };
@@ -0,0 +1,17 @@
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/MultiSelect/tools/isSelectAllItem.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 isSelectAllItem = (item) => typeof item === "object" && item !== null && "isSelectAll" in item;
16
+ //#endregion
17
+ export { isSelectAllItem };
@@ -5,8 +5,8 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
+ import { isSelectAllItem } from "./isSelectAllItem.js";
8
9
  //#region src/components/MultiSelect/tools/sorting.ts
9
- const isSelectAllItem = (item) => typeof item === "object" && item !== null && "isSelectAll" in item;
10
10
  /**
11
11
  * Use `localeCompare` with the `numeric` option enabled to sort two
12
12
  * alphanumeric strings.
@@ -12,15 +12,13 @@ import { setupGetInstanceId } from "../../tools/setupGetInstanceId.js";
12
12
  import { noopFn } from "../../internal/noopFn.js";
13
13
  import { deprecate } from "../../prop-types/deprecate.js";
14
14
  import { deprecateValuesWithin } from "../../prop-types/deprecateValuesWithin.js";
15
- import { isComponentElement } from "../../internal/utils.js";
16
15
  import { mapPopoverAlign } from "../../tools/mapPopoverAlign.js";
17
16
  import { IconButton } from "../IconButton/index.js";
18
17
  import { mergeRefs } from "../../tools/mergeRefs.js";
19
- import OverflowMenuItem from "../OverflowMenuItem/OverflowMenuItem.js";
20
18
  import { DIRECTION_BOTTOM, FloatingMenu } from "../../internal/FloatingMenu.js";
21
19
  import { useOutsideClick } from "../../internal/useOutsideClick.js";
22
20
  import classNames from "classnames";
23
- import React, { Children, cloneElement, forwardRef, useCallback, useContext, useEffect, useRef, useState } from "react";
21
+ import React, { Children, cloneElement, forwardRef, isValidElement, useCallback, useContext, useEffect, useRef, useState } from "react";
24
22
  import PropTypes from "prop-types";
25
23
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
26
24
  import { OverflowMenuVertical } from "@carbon/icons-react";
@@ -224,14 +222,17 @@ const OverflowMenu = forwardRef(({ align, ["aria-label"]: ariaLabel = null, aria
224
222
  });
225
223
  const overflowMenuIconClasses = classNames(`${prefix}--overflow-menu__icon`, iconClass);
226
224
  const childrenWithProps = Children.toArray(children).map((child, index) => {
227
- if (isComponentElement(child, OverflowMenuItem)) return cloneElement(child, {
228
- closeMenu: child.props.closeMenu || closeMenuAndFocus,
229
- handleOverflowMenuItemFocus,
230
- ref: (el) => {
231
- menuItemRefs.current[index] = el;
232
- },
233
- index
234
- });
225
+ if (isValidElement(child)) {
226
+ const childElement = child;
227
+ return cloneElement(childElement, {
228
+ closeMenu: childElement.props.closeMenu || closeMenuAndFocus,
229
+ handleOverflowMenuItemFocus,
230
+ ref: (el) => {
231
+ menuItemRefs.current[index] = el;
232
+ },
233
+ index
234
+ });
235
+ }
235
236
  return null;
236
237
  });
237
238
  const wrappedMenuBody = /* @__PURE__ */ jsx(FloatingMenu, {
@@ -4,6 +4,10 @@
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
+ /**
8
+ * @deprecated PageHeader has moved to Carbon for IBM Products.
9
+ * See https://github.com/carbon-design-system/carbon/issues/21926
10
+ */
7
11
  import React, { type ElementType } from 'react';
8
12
  import PropTypes from 'prop-types';
9
13
  import { TYPES } from '../Tag/Tag';