@carbon/react 1.107.1 → 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.
- package/.playwright/INTERNAL_AVT_REPORT_DO_NOT_USE.json +965 -965
- package/es/components/Button/Button.d.ts +2 -2
- package/es/components/ChatButton/ChatButton.d.ts +1 -1
- package/es/components/ChatButton/ChatButton.js +1 -1
- package/es/components/ComposedModal/ComposedModal.js +10 -5
- package/es/components/ErrorBoundary/ErrorBoundaryContext.d.ts +1 -1
- package/es/components/ErrorBoundary/ErrorBoundaryContext.js +1 -1
- package/es/components/FileUploader/FileUploader.d.ts +5 -0
- package/es/components/FileUploader/FileUploader.js +14 -6
- package/es/components/FileUploader/FileUploaderItem.d.ts +5 -1
- package/es/components/FileUploader/FileUploaderItem.js +4 -2
- package/es/components/FileUploader/Filename.d.ts +5 -1
- package/es/components/FileUploader/Filename.js +2 -1
- package/es/components/Menu/MenuItem.js +13 -5
- package/es/components/Modal/Modal.js +22 -12
- package/es/components/Modal/isTopmostVisibleModal.d.ts +7 -0
- package/es/components/Modal/isTopmostVisibleModal.js +21 -0
- package/es/components/MultiSelect/FilterableMultiSelect.js +9 -8
- package/es/components/MultiSelect/MultiSelect.js +9 -8
- package/es/components/MultiSelect/tools/isSelectAllItem.d.ts +9 -0
- package/es/components/MultiSelect/tools/isSelectAllItem.js +17 -0
- package/es/components/MultiSelect/tools/sorting.js +1 -1
- package/es/components/PageHeader/PageHeader.d.ts +4 -0
- package/es/components/PageHeader/PageHeader.js +18 -0
- package/es/components/PageHeader/index.d.ts +4 -0
- package/es/components/UIShell/SwitcherDivider.d.ts +2 -2
- package/es/components/UIShell/SwitcherDivider.js +2 -2
- package/es/internal/warning.d.ts +1 -1
- package/lib/components/Button/Button.d.ts +2 -2
- package/lib/components/ChatButton/ChatButton.d.ts +1 -1
- package/lib/components/ChatButton/ChatButton.js +1 -1
- package/lib/components/ComposedModal/ComposedModal.js +10 -5
- package/lib/components/ErrorBoundary/ErrorBoundaryContext.d.ts +1 -1
- package/lib/components/ErrorBoundary/ErrorBoundaryContext.js +1 -1
- package/lib/components/FileUploader/FileUploader.d.ts +5 -0
- package/lib/components/FileUploader/FileUploader.js +14 -6
- package/lib/components/FileUploader/FileUploaderItem.d.ts +5 -1
- package/lib/components/FileUploader/FileUploaderItem.js +4 -2
- package/lib/components/FileUploader/Filename.d.ts +5 -1
- package/lib/components/FileUploader/Filename.js +2 -1
- package/lib/components/Menu/MenuItem.js +12 -4
- package/lib/components/Modal/Modal.js +22 -12
- package/lib/components/Modal/isTopmostVisibleModal.d.ts +7 -0
- package/lib/components/Modal/isTopmostVisibleModal.js +21 -0
- package/lib/components/MultiSelect/FilterableMultiSelect.js +9 -8
- package/lib/components/MultiSelect/MultiSelect.js +9 -8
- package/lib/components/MultiSelect/tools/isSelectAllItem.d.ts +9 -0
- package/lib/components/MultiSelect/tools/isSelectAllItem.js +17 -0
- package/lib/components/MultiSelect/tools/sorting.js +3 -3
- package/lib/components/PageHeader/PageHeader.d.ts +4 -0
- package/lib/components/PageHeader/PageHeader.js +18 -0
- package/lib/components/PageHeader/index.d.ts +4 -0
- package/lib/components/UIShell/SwitcherDivider.d.ts +2 -2
- package/lib/components/UIShell/SwitcherDivider.js +2 -2
- package/lib/internal/warning.d.ts +1 -1
- package/lib/internal/warning.js +1 -1
- 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
|
|
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
|
|
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;
|
|
@@ -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,
|
|
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([
|
|
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
|
|
198
|
+
document.addEventListener("keydown", handleEscapeKey);
|
|
194
199
|
return () => {
|
|
195
|
-
document.removeEventListener("keydown", handleEscapeKey
|
|
200
|
+
document.removeEventListener("keydown", handleEscapeKey);
|
|
196
201
|
};
|
|
197
202
|
}, [open]);
|
|
198
203
|
useEffect(() => {
|
|
@@ -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,
|
|
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 && {
|
|
158
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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([
|
|
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)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
206
|
+
document.addEventListener("keydown", handleEscapeKey);
|
|
197
207
|
return () => {
|
|
198
|
-
document.removeEventListener("keydown", handleEscapeKey
|
|
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
|
|
64
|
-
const selectAll = filteredItems.some(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
298
|
-
nonSelectAllSelectedCount: selectedItems.filter((selected) => !selected
|
|
299
|
-
totalSelectableCount: filteredItems.filter((item) => !item
|
|
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
|
|
376
|
-
const isIndeterminate = item
|
|
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
|
|
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.
|
|
@@ -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';
|