@carbon/react 1.55.0-rc.0 → 1.55.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.
@@ -68,7 +68,7 @@ export interface ComposedModalProps extends HTMLAttributes<HTMLDivElement> {
68
68
  */
69
69
  selectorPrimaryFocus?: string;
70
70
  /** Specify the CSS selectors that match the floating menus. */
71
- selectorsFloatingMenus?: Array<string | null | undefined>;
71
+ selectorsFloatingMenus?: string[];
72
72
  size?: 'xs' | 'sm' | 'md' | 'lg';
73
73
  /**
74
74
  * **Experimental**: Provide a `Slug` component to be rendered inside the `ComposedModal` component
@@ -17,7 +17,7 @@ import mergeRefs from '../../tools/mergeRefs.js';
17
17
  import cx from 'classnames';
18
18
  import toggleClass from '../../tools/toggleClass.js';
19
19
  import requiredIfGivenPropIsTruthy from '../../prop-types/requiredIfGivenPropIsTruthy.js';
20
- import wrapFocus, { wrapFocusWithoutSentinels } from '../../internal/wrapFocus.js';
20
+ import wrapFocus, { wrapFocusWithoutSentinels, elementOrParentIsFloatingMenu } from '../../internal/wrapFocus.js';
21
21
  import { usePrefix } from '../../internal/usePrefix.js';
22
22
  import { useFeatureFlag } from '../FeatureFlags/index.js';
23
23
  import { match } from '../../internal/keyboard/match.js';
@@ -148,9 +148,9 @@ const ComposedModal = /*#__PURE__*/React__default.forwardRef(function ComposedMo
148
148
  onKeyDown?.(event);
149
149
  }
150
150
  function handleMousedown(evt) {
151
+ const target = evt.target;
151
152
  evt.stopPropagation();
152
- const isInside = innerModal.current?.contains(evt.target);
153
- if (!isInside && !preventCloseOnClickOutside) {
153
+ if (!preventCloseOnClickOutside && !elementOrParentIsFloatingMenu(target, selectorsFloatingMenus) && innerModal.current && !innerModal.current.contains(target)) {
154
154
  closeModal(evt);
155
155
  }
156
156
  }
@@ -336,7 +336,7 @@ ComposedModal.propTypes = {
336
336
  /**
337
337
  * Specify the CSS selectors that match the floating menus
338
338
  */
339
- selectorsFloatingMenus: PropTypes.arrayOf(PropTypes.string),
339
+ selectorsFloatingMenus: PropTypes.arrayOf(PropTypes.string.isRequired),
340
340
  /**
341
341
  * Specify the size variant.
342
342
  */
@@ -108,7 +108,7 @@ const Modal = /*#__PURE__*/React__default.forwardRef(function Modal(_ref, ref) {
108
108
  function handleMousedown(evt) {
109
109
  const target = evt.target;
110
110
  evt.stopPropagation();
111
- if (innerModal.current && !innerModal.current.contains(target) && !elementOrParentIsFloatingMenu(target, selectorsFloatingMenus) && !preventCloseOnClickOutside) {
111
+ if (!preventCloseOnClickOutside && !elementOrParentIsFloatingMenu(target, selectorsFloatingMenus) && innerModal.current && !innerModal.current.contains(target)) {
112
112
  onRequestClose(evt);
113
113
  }
114
114
  }
@@ -199,7 +199,8 @@ function TabList(_ref2) {
199
199
  // TODO: V12 - Remove this class
200
200
  [`${prefix}--layout--size-lg`]: iconSize === 'lg',
201
201
  [`${prefix}--tabs--tall`]: hasSecondaryLabelTabs,
202
- [`${prefix}--tabs--full-width`]: distributeWidth
202
+ [`${prefix}--tabs--full-width`]: distributeWidth,
203
+ [`${prefix}--tabs--dismissable`]: dismissable
203
204
  }, customClassName);
204
205
 
205
206
  // Previous Button
@@ -541,6 +542,25 @@ const Tab = /*#__PURE__*/forwardRef(function Tab(_ref5, forwardRef) {
541
542
  const handleClose = evt => {
542
543
  evt.stopPropagation();
543
544
  onTabCloseRequest?.(index);
545
+
546
+ // set focus after removing tab
547
+ if (tabRef.current && tabRef.current.parentElement) {
548
+ // determine number of tabs, excluding disabled
549
+ const tabCount = Array.from(tabRef.current.parentElement.childNodes).filter(node => {
550
+ const element = node;
551
+ return element.classList.contains('cds--tabs__nav-link') && !element.classList.contains('cds--tabs__nav-item--disabled');
552
+ }).length;
553
+
554
+ // if not removing last tab focus on next tab
555
+ if (tabRef.current && index + 1 !== tabCount) {
556
+ tabRef.current.focus();
557
+ }
558
+ // if removing last tab focus on previous tab
559
+ else {
560
+ const prevTabIndex = (tabCount - 2) * 2;
561
+ tabRef.current.parentElement.childNodes[prevTabIndex]?.focus();
562
+ }
563
+ }
544
564
  };
545
565
  const handleKeyDown = event => {
546
566
  if (dismissable && match(event, Delete)) {
@@ -549,20 +569,31 @@ const Tab = /*#__PURE__*/forwardRef(function Tab(_ref5, forwardRef) {
549
569
  onKeyDown?.(event);
550
570
  };
551
571
  const DismissIcon = /*#__PURE__*/React__default.createElement("div", {
552
- tabIndex: -1,
553
- "aria-hidden": true,
554
- className: cx(`${prefix}--tabs__nav-item--close-icon`, {
555
- [`${prefix}--visually-hidden`]: !dismissable
572
+ className: cx({
573
+ [`${prefix}--tabs__nav-item--close`]: dismissable,
574
+ [`${prefix}--tabs__nav-item--close--hidden`]: !dismissable
575
+ })
576
+ }, /*#__PURE__*/React__default.createElement("button", {
577
+ type: "button",
578
+ tabIndex: selectedIndex === index && dismissable ? 0 : -1,
579
+ "aria-disabled": disabled,
580
+ "aria-hidden": selectedIndex === index && dismissable ? 'false' : 'true',
581
+ disabled: disabled,
582
+ className: cx({
583
+ [`${prefix}--tabs__nav-item--close-icon`]: dismissable,
584
+ [`${prefix}--visually-hidden`]: !dismissable,
585
+ [`${prefix}--tabs__nav-item--close-icon--selected`]: selectedIndex === index,
586
+ [`${prefix}--tabs__nav-item--close-icon--disabled`]: disabled
556
587
  }),
557
588
  onClick: handleClose,
558
- title: "Close tab",
589
+ title: `Remove ${typeof children === 'string' ? children : ''} tab`,
559
590
  ref: dismissIconRef
560
591
  }, /*#__PURE__*/React__default.createElement(Close, {
561
- "aria-hidden": dismissable ? 'false' : 'true',
562
- "aria-label": "Press delete to close tab"
563
- }));
592
+ "aria-hidden": selectedIndex === index && dismissable ? 'false' : 'true',
593
+ "aria-label": `Press delete to remove ${typeof children === 'string' ? children : ''} tab`
594
+ })));
564
595
  const hasIcon = Icon ?? dismissable;
565
- return /*#__PURE__*/React__default.createElement(BaseComponent, _extends({}, rest, {
596
+ return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(BaseComponent, _extends({}, rest, {
566
597
  "aria-controls": panelId,
567
598
  "aria-disabled": disabled,
568
599
  "aria-selected": selectedIndex === index,
@@ -589,17 +620,17 @@ const Tab = /*#__PURE__*/forwardRef(function Tab(_ref5, forwardRef) {
589
620
  size: 16
590
621
  })), /*#__PURE__*/React__default.createElement(Text, {
591
622
  className: `${prefix}--tabs__nav-item-label`
592
- }, children), /*#__PURE__*/React__default.createElement("div", {
623
+ }, children), !dismissable && Icon && /*#__PURE__*/React__default.createElement("div", {
593
624
  className: cx(`${prefix}--tabs__nav-item--icon`, {
594
625
  [`${prefix}--visually-hidden`]: !hasIcon
595
626
  })
596
- }, DismissIcon, !dismissable && Icon && /*#__PURE__*/React__default.createElement(Icon, {
627
+ }, !dismissable && Icon && /*#__PURE__*/React__default.createElement(Icon, {
597
628
  size: 16
598
629
  }))), hasSecondaryLabel && secondaryLabel && /*#__PURE__*/React__default.createElement(Text, {
599
630
  as: "div",
600
631
  className: `${prefix}--tabs__nav-item-secondary-label`,
601
632
  title: secondaryLabel
602
- }, secondaryLabel));
633
+ }, secondaryLabel)), DismissIcon);
603
634
  });
604
635
  Tab.propTypes = {
605
636
  /**
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2023
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
+ import React from 'react';
8
+ import PropTypes from 'prop-types';
9
+ /**
10
+ * `HeaderMenu` is used to render submenu's in the `Header`. Most often children
11
+ * will be a `HeaderMenuItem`. It handles certain keyboard events to help
12
+ * with managing focus. It also passes along refs to each child so that it can
13
+ * help manage focus state of its children.
14
+ */
15
+ interface HeaderMenuProps {
16
+ /**
17
+ * Required props for the accessibility label of the menu
18
+ */
19
+ 'aria-label'?: string;
20
+ 'aria-labelledby'?: string;
21
+ /**
22
+ * Optionally provide a custom class to apply to the underlying `<li>` node
23
+ */
24
+ className?: string;
25
+ /**
26
+ * Provide a custom ref handler for the menu button
27
+ */
28
+ focusRef?: React.Ref<any>;
29
+ /**
30
+ * Applies selected styles to the item if a user sets this to true and `aria-current !== 'page'`.
31
+ */
32
+ isActive?: boolean;
33
+ /**
34
+ * Applies selected styles to the item if a user sets this to true and `aria-current !== 'page'`.
35
+ * @deprecated Please use `isActive` instead. This will be removed in the next major release.
36
+ */
37
+ isCurrentPage?: boolean;
38
+ /**
39
+ * Provide a label for the link text
40
+ */
41
+ menuLinkName: string;
42
+ /**
43
+ * Optionally provide an onBlur handler that is called when the underlying
44
+ * button fires it's onblur event
45
+ */
46
+ onBlur?: (event: React.FocusEvent<HTMLLIElement>) => void;
47
+ /**
48
+ * Optionally provide an onClick handler that is called when the underlying
49
+ * button fires it's onclick event
50
+ */
51
+ onClick?: (event: React.MouseEvent<HTMLLIElement, MouseEvent>) => void;
52
+ /**
53
+ * Optionally provide an onKeyDown handler that is called when the underlying
54
+ * button fires it's onkeydown event
55
+ */
56
+ onKeyDown?: (event: React.KeyboardEvent<HTMLLIElement>) => void;
57
+ /**
58
+ * Optional component to render instead of string
59
+ */
60
+ renderMenuContent?: () => JSX.Element;
61
+ /**
62
+ * Optionally provide a tabIndex for the underlying menu button
63
+ */
64
+ tabIndex?: number;
65
+ /**
66
+ * The children should be a series of `HeaderMenuItem` components.
67
+ */
68
+ children?: React.ReactNode;
69
+ }
70
+ interface HeaderMenuState {
71
+ expanded: boolean;
72
+ selectedIndex: number | null;
73
+ }
74
+ declare class HeaderMenu extends React.Component<HeaderMenuProps, HeaderMenuState> {
75
+ static propTypes: {
76
+ /**
77
+ * Optionally provide a custom class to apply to the underlying `<li>` node
78
+ */
79
+ className: PropTypes.Requireable<string>;
80
+ /**
81
+ * Provide a custom ref handler for the menu button
82
+ */
83
+ focusRef: PropTypes.Requireable<(...args: any[]) => any>;
84
+ /**
85
+ * Applies selected styles to the item if a user sets this to true and `aria-current !== 'page'`.
86
+ */
87
+ isActive: PropTypes.Requireable<boolean>;
88
+ /**
89
+ * Applies selected styles to the item if a user sets this to true and `aria-current !== 'page'`.
90
+ * @deprecated Please use `isActive` instead. This will be removed in the next major release.
91
+ */
92
+ isCurrentPage: (props: any, propName: any, componentName: any, ...rest: any[]) => any;
93
+ /**
94
+ * Provide a label for the link text
95
+ */
96
+ menuLinkName: PropTypes.Validator<string>;
97
+ /**
98
+ * Optionally provide an onBlur handler that is called when the underlying
99
+ * button fires it's onblur event
100
+ */
101
+ onBlur: PropTypes.Requireable<(...args: any[]) => any>;
102
+ /**
103
+ * Optionally provide an onClick handler that is called when the underlying
104
+ * button fires it's onclick event
105
+ */
106
+ onClick: PropTypes.Requireable<(...args: any[]) => any>;
107
+ /**
108
+ * Optionally provide an onKeyDown handler that is called when the underlying
109
+ * button fires it's onkeydown event
110
+ */
111
+ onKeyDown: PropTypes.Requireable<(...args: any[]) => any>;
112
+ /**
113
+ * Optional component to render instead of string
114
+ */
115
+ renderMenuContent: PropTypes.Requireable<(...args: any[]) => any>;
116
+ /**
117
+ * Optionally provide a tabIndex for the underlying menu button
118
+ */
119
+ tabIndex: PropTypes.Requireable<number>;
120
+ 0: string;
121
+ length: 1;
122
+ toString(): string;
123
+ toLocaleString(): string;
124
+ pop(): string | undefined;
125
+ push(...items: string[]): number;
126
+ concat(...items: ConcatArray<string>[]): string[];
127
+ concat(...items: (string | ConcatArray<string>)[]): string[];
128
+ join(separator?: string | undefined): string;
129
+ reverse(): string[];
130
+ shift(): string | undefined;
131
+ slice(start?: number | undefined, end?: number | undefined): string[];
132
+ sort(compareFn?: ((a: string, b: string) => number) | undefined): [key: string];
133
+ splice(start: number, deleteCount?: number | undefined): string[];
134
+ splice(start: number, deleteCount: number, ...items: string[]): string[];
135
+ unshift(...items: string[]): number;
136
+ indexOf(searchElement: string, fromIndex?: number | undefined): number;
137
+ lastIndexOf(searchElement: string, fromIndex?: number | undefined): number;
138
+ every<S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): this is S[];
139
+ every(predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): boolean;
140
+ some(predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): boolean;
141
+ forEach(callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any): void;
142
+ map<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any): U[];
143
+ filter<S_1 extends string>(predicate: (value: string, index: number, array: string[]) => value is S_1, thisArg?: any): S_1[];
144
+ filter(predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): string[];
145
+ reduce(callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string;
146
+ reduce(callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string;
147
+ reduce<U_1>(callbackfn: (previousValue: U_1, currentValue: string, currentIndex: number, array: string[]) => U_1, initialValue: U_1): U_1;
148
+ reduceRight(callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string;
149
+ reduceRight(callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string;
150
+ reduceRight<U_2>(callbackfn: (previousValue: U_2, currentValue: string, currentIndex: number, array: string[]) => U_2, initialValue: U_2): U_2;
151
+ find<S_2 extends string>(predicate: (this: void, value: string, index: number, obj: string[]) => value is S_2, thisArg?: any): S_2 | undefined;
152
+ find(predicate: (value: string, index: number, obj: string[]) => unknown, thisArg?: any): string | undefined;
153
+ findIndex(predicate: (value: string, index: number, obj: string[]) => unknown, thisArg?: any): number;
154
+ fill(value: string, start?: number | undefined, end?: number | undefined): [key: string];
155
+ copyWithin(target: number, start: number, end?: number | undefined): [key: string];
156
+ entries(): IterableIterator<[number, string]>;
157
+ keys(): IterableIterator<number>;
158
+ values(): IterableIterator<string>;
159
+ includes(searchElement: string, fromIndex?: number | undefined): boolean;
160
+ flatMap<U_3, This = undefined>(callback: (this: This, value: string, index: number, array: string[]) => U_3 | readonly U_3[], thisArg?: This | undefined): U_3[];
161
+ flat<A, D extends number = 1>(this: A, depth?: D | undefined): FlatArray<A, D>[];
162
+ at(index: number): string | undefined;
163
+ [Symbol.iterator](): IterableIterator<string>;
164
+ [Symbol.unscopables](): {
165
+ copyWithin: boolean;
166
+ entries: boolean;
167
+ fill: boolean;
168
+ find: boolean;
169
+ findIndex: boolean;
170
+ keys: boolean;
171
+ values: boolean;
172
+ };
173
+ };
174
+ static contextType: React.Context<string>;
175
+ _subMenus: React.RefObject<HTMLUListElement>;
176
+ private items;
177
+ private menuButtonRef;
178
+ constructor(props: any);
179
+ /**
180
+ * Toggle the expanded state of the menu on click.
181
+ */
182
+ handleOnClick: (e: any) => void;
183
+ /**
184
+ * Keyboard event handler for the entire menu.
185
+ */
186
+ handleOnKeyDown: (event: any) => void;
187
+ /**
188
+ * Handle our blur event from our underlying menuitems. Will mostly be used
189
+ * for closing our menu in response to a user clicking off or tabbing out of
190
+ * the menu or menubar.
191
+ */
192
+ handleOnBlur: (event: any) => void;
193
+ /**
194
+ * ref handler for our menu button. If we are supplied a `focusRef` prop, we also
195
+ * forward along the node.
196
+ *
197
+ * This is useful when this component is a child in a
198
+ * menu or menubar as it will allow the parent to explicitly focus the menu
199
+ * button node when that child should receive focus.
200
+ */
201
+ handleMenuButtonRef: (node: any) => void;
202
+ /**
203
+ * Handles individual menuitem refs. We assign them to a class instance
204
+ * property so that we can properly manage focus of our children.
205
+ */
206
+ handleItemRef: (index: any) => (node: any) => void;
207
+ handleMenuClose: (event: any) => void;
208
+ render(): import("react/jsx-runtime").JSX.Element;
209
+ /**
210
+ * We capture the `ref` for each child inside of `this.items` to properly
211
+ * manage focus. In addition to this focus management, all items receive a
212
+ * `tabIndex: -1` so the user won't hit a large number of items in their tab
213
+ * sequence when they might not want to go through all the items.
214
+ */
215
+ _renderMenuItem: (item: React.ReactNode, index: number) => React.ReactElement<any, string | React.JSXElementConstructor<any>> | undefined;
216
+ }
217
+ declare const HeaderMenuForwardRef: React.ForwardRefExoticComponent<React.RefAttributes<unknown>>;
218
+ export { HeaderMenu };
219
+ export default HeaderMenuForwardRef;
@@ -23,10 +23,13 @@ import { Enter, Space, Escape } from '../../internal/keyboard/keys.js';
23
23
  * with managing focus. It also passes along refs to each child so that it can
24
24
  * help manage focus state of its children.
25
25
  */
26
+
26
27
  class HeaderMenu extends React__default.Component {
27
28
  constructor(props) {
28
29
  super(props);
29
30
  _defineProperty(this, "_subMenus", /*#__PURE__*/React__default.createRef());
31
+ _defineProperty(this, "items", []);
32
+ _defineProperty(this, "menuButtonRef", null);
30
33
  /**
31
34
  * Toggle the expanded state of the menu on click.
32
35
  */
@@ -81,8 +84,12 @@ class HeaderMenu extends React__default.Component {
81
84
  * button node when that child should receive focus.
82
85
  */
83
86
  _defineProperty(this, "handleMenuButtonRef", node => {
84
- if (this.props.focusRef) {
85
- this.props.focusRef(node);
87
+ const {
88
+ focusRef
89
+ } = this.props;
90
+ // Check if focusRef is a function before calling it
91
+ if (typeof focusRef === 'function') {
92
+ focusRef(node);
86
93
  }
87
94
  this.menuButtonRef = node;
88
95
  });
@@ -104,7 +111,9 @@ class HeaderMenu extends React__default.Component {
104
111
  }));
105
112
 
106
113
  // Return focus to menu button when the user hits ESC.
107
- this.menuButtonRef.focus();
114
+ if (this.menuButtonRef !== null) {
115
+ this.menuButtonRef.focus();
116
+ }
108
117
  return;
109
118
  }
110
119
  });
@@ -141,23 +150,25 @@ class HeaderMenu extends React__default.Component {
141
150
  children,
142
151
  renderMenuContent: MenuContent,
143
152
  menuLinkName,
153
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
144
154
  focusRef,
145
- // eslint-disable-line no-unused-vars
146
155
  onBlur,
147
156
  onClick,
148
157
  onKeyDown,
149
158
  ...rest
150
159
  } = this.props;
151
- const hasActiveDescendant = childrenArg => React__default.Children.toArray(childrenArg).some(child => child.props && (child.props.isActive || child.props.isCurrentPage || child.props.children instanceof Array && hasActiveDescendant(child.props.children)));
160
+ const hasActiveDescendant = childrenArg => React__default.Children.toArray(childrenArg).some(child => /*#__PURE__*/React__default.isValidElement(child) && (
161
+ // This is the type guard
162
+ child.props.isActive || child.props.isCurrentPage || Array.isArray(child.props.children) && hasActiveDescendant(child.props.children)));
152
163
  const accessibilityLabel = {
153
164
  'aria-label': ariaLabel,
154
165
  'aria-labelledby': ariaLabelledBy
155
166
  };
156
167
  const itemClassName = cx({
157
168
  [`${prefix}--header__submenu`]: true,
158
- [customClassName]: !!customClassName
169
+ [`${customClassName}`]: !!customClassName
159
170
  });
160
- let isActivePage = isActive ? isActive : isCurrentPage;
171
+ const isActivePage = isActive ? isActive : isCurrentPage;
161
172
  const linkClassName = cx({
162
173
  [`${prefix}--header__menu-item`]: true,
163
174
  [`${prefix}--header__menu-title`]: true,
@@ -248,11 +259,12 @@ _defineProperty(HeaderMenu, "propTypes", {
248
259
  });
249
260
  _defineProperty(HeaderMenu, "contextType", PrefixContext);
250
261
  const HeaderMenuForwardRef = /*#__PURE__*/React__default.forwardRef((props, ref) => {
251
- return /*#__PURE__*/React__default.createElement(HeaderMenu, _extends({}, props, {
262
+ return /*#__PURE__*/React__default.createElement(HeaderMenu, _extends({
263
+ menuLinkName: "link"
264
+ }, props, {
252
265
  focusRef: ref
253
266
  }));
254
267
  });
255
268
  HeaderMenuForwardRef.displayName = 'HeaderMenu';
256
- var HeaderMenuForwardRef$1 = HeaderMenuForwardRef;
257
269
 
258
- export { HeaderMenu, HeaderMenuForwardRef$1 as default };
270
+ export { HeaderMenu, HeaderMenuForwardRef as default };
@@ -68,7 +68,7 @@ export interface ComposedModalProps extends HTMLAttributes<HTMLDivElement> {
68
68
  */
69
69
  selectorPrimaryFocus?: string;
70
70
  /** Specify the CSS selectors that match the floating menus. */
71
- selectorsFloatingMenus?: Array<string | null | undefined>;
71
+ selectorsFloatingMenus?: string[];
72
72
  size?: 'xs' | 'sm' | 'md' | 'lg';
73
73
  /**
74
74
  * **Experimental**: Provide a `Slug` component to be rendered inside the `ComposedModal` component
@@ -159,9 +159,9 @@ const ComposedModal = /*#__PURE__*/React__default["default"].forwardRef(function
159
159
  onKeyDown?.(event);
160
160
  }
161
161
  function handleMousedown(evt) {
162
+ const target = evt.target;
162
163
  evt.stopPropagation();
163
- const isInside = innerModal.current?.contains(evt.target);
164
- if (!isInside && !preventCloseOnClickOutside) {
164
+ if (!preventCloseOnClickOutside && !wrapFocus.elementOrParentIsFloatingMenu(target, selectorsFloatingMenus) && innerModal.current && !innerModal.current.contains(target)) {
165
165
  closeModal(evt);
166
166
  }
167
167
  }
@@ -347,7 +347,7 @@ ComposedModal.propTypes = {
347
347
  /**
348
348
  * Specify the CSS selectors that match the floating menus
349
349
  */
350
- selectorsFloatingMenus: PropTypes__default["default"].arrayOf(PropTypes__default["default"].string),
350
+ selectorsFloatingMenus: PropTypes__default["default"].arrayOf(PropTypes__default["default"].string.isRequired),
351
351
  /**
352
352
  * Specify the size variant.
353
353
  */
@@ -119,7 +119,7 @@ const Modal = /*#__PURE__*/React__default["default"].forwardRef(function Modal(_
119
119
  function handleMousedown(evt) {
120
120
  const target = evt.target;
121
121
  evt.stopPropagation();
122
- if (innerModal.current && !innerModal.current.contains(target) && !wrapFocus.elementOrParentIsFloatingMenu(target, selectorsFloatingMenus) && !preventCloseOnClickOutside) {
122
+ if (!preventCloseOnClickOutside && !wrapFocus.elementOrParentIsFloatingMenu(target, selectorsFloatingMenus) && innerModal.current && !innerModal.current.contains(target)) {
123
123
  onRequestClose(evt);
124
124
  }
125
125
  }
@@ -210,7 +210,8 @@ function TabList(_ref2) {
210
210
  // TODO: V12 - Remove this class
211
211
  [`${prefix}--layout--size-lg`]: iconSize === 'lg',
212
212
  [`${prefix}--tabs--tall`]: hasSecondaryLabelTabs,
213
- [`${prefix}--tabs--full-width`]: distributeWidth
213
+ [`${prefix}--tabs--full-width`]: distributeWidth,
214
+ [`${prefix}--tabs--dismissable`]: dismissable
214
215
  }, customClassName);
215
216
 
216
217
  // Previous Button
@@ -552,6 +553,25 @@ const Tab = /*#__PURE__*/React.forwardRef(function Tab(_ref5, forwardRef) {
552
553
  const handleClose = evt => {
553
554
  evt.stopPropagation();
554
555
  onTabCloseRequest?.(index);
556
+
557
+ // set focus after removing tab
558
+ if (tabRef.current && tabRef.current.parentElement) {
559
+ // determine number of tabs, excluding disabled
560
+ const tabCount = Array.from(tabRef.current.parentElement.childNodes).filter(node => {
561
+ const element = node;
562
+ return element.classList.contains('cds--tabs__nav-link') && !element.classList.contains('cds--tabs__nav-item--disabled');
563
+ }).length;
564
+
565
+ // if not removing last tab focus on next tab
566
+ if (tabRef.current && index + 1 !== tabCount) {
567
+ tabRef.current.focus();
568
+ }
569
+ // if removing last tab focus on previous tab
570
+ else {
571
+ const prevTabIndex = (tabCount - 2) * 2;
572
+ tabRef.current.parentElement.childNodes[prevTabIndex]?.focus();
573
+ }
574
+ }
555
575
  };
556
576
  const handleKeyDown = event => {
557
577
  if (dismissable && match.match(event, keys.Delete)) {
@@ -560,20 +580,31 @@ const Tab = /*#__PURE__*/React.forwardRef(function Tab(_ref5, forwardRef) {
560
580
  onKeyDown?.(event);
561
581
  };
562
582
  const DismissIcon = /*#__PURE__*/React__default["default"].createElement("div", {
563
- tabIndex: -1,
564
- "aria-hidden": true,
565
- className: cx__default["default"](`${prefix}--tabs__nav-item--close-icon`, {
566
- [`${prefix}--visually-hidden`]: !dismissable
583
+ className: cx__default["default"]({
584
+ [`${prefix}--tabs__nav-item--close`]: dismissable,
585
+ [`${prefix}--tabs__nav-item--close--hidden`]: !dismissable
586
+ })
587
+ }, /*#__PURE__*/React__default["default"].createElement("button", {
588
+ type: "button",
589
+ tabIndex: selectedIndex === index && dismissable ? 0 : -1,
590
+ "aria-disabled": disabled,
591
+ "aria-hidden": selectedIndex === index && dismissable ? 'false' : 'true',
592
+ disabled: disabled,
593
+ className: cx__default["default"]({
594
+ [`${prefix}--tabs__nav-item--close-icon`]: dismissable,
595
+ [`${prefix}--visually-hidden`]: !dismissable,
596
+ [`${prefix}--tabs__nav-item--close-icon--selected`]: selectedIndex === index,
597
+ [`${prefix}--tabs__nav-item--close-icon--disabled`]: disabled
567
598
  }),
568
599
  onClick: handleClose,
569
- title: "Close tab",
600
+ title: `Remove ${typeof children === 'string' ? children : ''} tab`,
570
601
  ref: dismissIconRef
571
602
  }, /*#__PURE__*/React__default["default"].createElement(iconsReact.Close, {
572
- "aria-hidden": dismissable ? 'false' : 'true',
573
- "aria-label": "Press delete to close tab"
574
- }));
603
+ "aria-hidden": selectedIndex === index && dismissable ? 'false' : 'true',
604
+ "aria-label": `Press delete to remove ${typeof children === 'string' ? children : ''} tab`
605
+ })));
575
606
  const hasIcon = Icon ?? dismissable;
576
- return /*#__PURE__*/React__default["default"].createElement(BaseComponent, _rollupPluginBabelHelpers["extends"]({}, rest, {
607
+ return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement(BaseComponent, _rollupPluginBabelHelpers["extends"]({}, rest, {
577
608
  "aria-controls": panelId,
578
609
  "aria-disabled": disabled,
579
610
  "aria-selected": selectedIndex === index,
@@ -600,17 +631,17 @@ const Tab = /*#__PURE__*/React.forwardRef(function Tab(_ref5, forwardRef) {
600
631
  size: 16
601
632
  })), /*#__PURE__*/React__default["default"].createElement(Text.Text, {
602
633
  className: `${prefix}--tabs__nav-item-label`
603
- }, children), /*#__PURE__*/React__default["default"].createElement("div", {
634
+ }, children), !dismissable && Icon && /*#__PURE__*/React__default["default"].createElement("div", {
604
635
  className: cx__default["default"](`${prefix}--tabs__nav-item--icon`, {
605
636
  [`${prefix}--visually-hidden`]: !hasIcon
606
637
  })
607
- }, DismissIcon, !dismissable && Icon && /*#__PURE__*/React__default["default"].createElement(Icon, {
638
+ }, !dismissable && Icon && /*#__PURE__*/React__default["default"].createElement(Icon, {
608
639
  size: 16
609
640
  }))), hasSecondaryLabel && secondaryLabel && /*#__PURE__*/React__default["default"].createElement(Text.Text, {
610
641
  as: "div",
611
642
  className: `${prefix}--tabs__nav-item-secondary-label`,
612
643
  title: secondaryLabel
613
- }, secondaryLabel));
644
+ }, secondaryLabel)), DismissIcon);
614
645
  });
615
646
  Tab.propTypes = {
616
647
  /**