@carbon/ibm-products 2.43.2-canary.174 → 2.43.2-canary.177

Sign up to get free protection for your applications and to get access to all the features.
@@ -158,6 +158,8 @@ export interface NotificationsPanelProps {
158
158
  * Sets the yesterday label text
159
159
  */
160
160
  yesterdayLabel?: string;
161
+ /** Specify the CSS selectors that match the floating menus. */
162
+ selectorsFloatingMenus?: string[];
161
163
  }
162
164
  export declare let NotificationsPanel: React.ForwardRefExoticComponent<NotificationsPanelProps & React.RefAttributes<unknown>>;
163
165
  export {};
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, defineProperty as _defineProperty, extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js';
9
- import { Button, Toggle, Link, IconButton } from '@carbon/react';
9
+ import { usePrefix, Button, Toggle, Link, IconButton } from '@carbon/react';
10
10
  import { Settings, ErrorFilled, CheckmarkFilled, WarningAltFilled, InformationSquareFilled, Close, ChevronDown } from '@carbon/react/icons';
11
11
  import React__default, { useRef, useState, useEffect } from 'react';
12
12
  import PropTypes from '../../node_modules/prop-types/index.js';
@@ -16,12 +16,13 @@ import { pkg } from '../../settings.js';
16
16
  import { prepareProps } from '../../global/js/utils/props-helper.js';
17
17
  import { timeAgo } from './utils.js';
18
18
  import usePrefersReducedMotion from '../../global/js/hooks/usePrefersReducedMotion.js';
19
+ import wrapFocus from '../../global/js/utils/wrapFocus.js';
19
20
  import { usePreviousValue } from '../../global/js/hooks/usePreviousValue.js';
20
21
  import { useClickOutside } from '../../global/js/hooks/useClickOutside.js';
21
22
  import { NotificationsEmptyState } from '../EmptyStates/NotificationsEmptyState/NotificationsEmptyState.js';
22
23
 
23
24
  var _Close;
24
- var _excluded = ["className", "data", "daysAgoText", "dismissAllLabel", "dismissSingleNotificationIconDescription", "doNotDisturbDefaultToggled", "doNotDisturbLabel", "emptyStateLabel", "hourAgoText", "hoursAgoText", "minuteAgoText", "minutesAgoText", "monthAgoText", "monthsAgoText", "nowText", "onClickOutside", "onDismissAllNotifications", "onDismissSingleNotification", "onDoNotDisturbChange", "onSettingsClick", "onViewAllClick", "open", "previousLabel", "readLessLabel", "readMoreLabel", "secondsAgoText", "settingsIconDescription", "title", "todayLabel", "viewAllLabel", "yearAgoText", "yearsAgoText", "yesterdayAtText", "yesterdayLabel"];
25
+ var _excluded = ["className", "data", "daysAgoText", "dismissAllLabel", "dismissSingleNotificationIconDescription", "doNotDisturbDefaultToggled", "doNotDisturbLabel", "emptyStateLabel", "hourAgoText", "hoursAgoText", "minuteAgoText", "minutesAgoText", "monthAgoText", "monthsAgoText", "nowText", "onClickOutside", "onDismissAllNotifications", "onDismissSingleNotification", "onDoNotDisturbChange", "onSettingsClick", "onViewAllClick", "open", "previousLabel", "readLessLabel", "readMoreLabel", "secondsAgoText", "settingsIconDescription", "title", "todayLabel", "viewAllLabel", "yearAgoText", "yearsAgoText", "yesterdayAtText", "yesterdayLabel", "selectorsFloatingMenus"];
25
26
 
26
27
  // The block part of our conventional BEM class names (blockClass__E--M).
27
28
  var componentName = 'NotificationsPanel';
@@ -141,8 +142,12 @@ var NotificationsPanel = /*#__PURE__*/React__default.forwardRef(function (_ref,
141
142
  yesterdayAtText = _ref$yesterdayAtText === void 0 ? defaults.yesterdayAtText : _ref$yesterdayAtText,
142
143
  _ref$yesterdayLabel = _ref.yesterdayLabel,
143
144
  yesterdayLabel = _ref$yesterdayLabel === void 0 ? defaults.yesterdayLabel : _ref$yesterdayLabel,
145
+ selectorsFloatingMenus = _ref.selectorsFloatingMenus,
144
146
  rest = _objectWithoutProperties(_ref, _excluded);
145
- var notificationPanelRef = useRef();
147
+ var notificationPanelRef = useRef(null);
148
+ var notificationPanelInnerRef = useRef(null);
149
+ var startSentinel = useRef(null);
150
+ var endSentinel = useRef(null);
146
151
  var _useState = useState(open),
147
152
  _useState2 = _slicedToArray(_useState, 2),
148
153
  shouldRender = _useState2[0],
@@ -155,6 +160,7 @@ var NotificationsPanel = /*#__PURE__*/React__default.forwardRef(function (_ref,
155
160
  open: open
156
161
  });
157
162
  var reducedMotion = usePrefersReducedMotion();
163
+ var carbonPrefix = usePrefix();
158
164
  useEffect(function () {
159
165
  // Set the notifications passed to the state within this component
160
166
  setAllNotifications(data);
@@ -166,12 +172,56 @@ var NotificationsPanel = /*#__PURE__*/React__default.forwardRef(function (_ref,
166
172
  // initialize the notification panel to open
167
173
  if (open) {
168
174
  setRender(true);
175
+ var observer = new MutationObserver(function () {
176
+ if (notificationPanelRef.current) {
177
+ var _querySelector;
178
+ var parentElement = notificationPanelRef.current;
179
+ parentElement === null || parentElement === void 0 || (_querySelector = parentElement.querySelector(".".concat(blockClass, "__dismiss-button"))) === null || _querySelector === void 0 || _querySelector.focus();
180
+ observer.disconnect();
181
+ }
182
+ });
183
+ if (notificationPanelRef.current) {
184
+ var parentElement = notificationPanelRef.current;
185
+ var button = parentElement === null || parentElement === void 0 ? void 0 : parentElement.querySelector(".".concat(blockClass, "__dismiss-button"));
186
+ button === null || button === void 0 || button.focus();
187
+ } else {
188
+ observer.observe(document.body, {
189
+ childList: true,
190
+ subtree: true
191
+ });
192
+ }
193
+ return function () {
194
+ return observer.disconnect();
195
+ };
169
196
  }
170
197
  }, [open]);
171
198
  var onAnimationEnd = function onAnimationEnd() {
172
199
  // initialize the notification panel to close
173
200
  !open && setRender(false);
174
201
  };
202
+ var handleBlur = function handleBlur(_ref2) {
203
+ var oldActiveNode = _ref2.target,
204
+ currentActiveNode = _ref2.relatedTarget;
205
+ if (open && currentActiveNode && oldActiveNode && notificationPanelInnerRef.current) {
206
+ var bodyNode = notificationPanelInnerRef.current;
207
+ var startSentinelNode = startSentinel.current;
208
+ var endSentinelNode = endSentinel.current;
209
+ wrapFocus({
210
+ bodyNode: bodyNode,
211
+ startTrapNode: startSentinelNode,
212
+ endTrapNode: endSentinelNode,
213
+ currentActiveNode: currentActiveNode,
214
+ oldActiveNode: oldActiveNode,
215
+ selectorsFloatingMenus: selectorsFloatingMenus === null || selectorsFloatingMenus === void 0 ? void 0 : selectorsFloatingMenus.filter(Boolean)
216
+ });
217
+ }
218
+ };
219
+ var handleKeydown = function handleKeydown(event) {
220
+ event.stopPropagation();
221
+ if (event.key === 'Escape') {
222
+ onClickOutside();
223
+ }
224
+ };
175
225
  useEffect(function () {
176
226
  if (!open && previousState !== null && previousState !== void 0 && previousState.open && reducedMotion) {
177
227
  setRender(false);
@@ -310,7 +360,17 @@ var NotificationsPanel = /*#__PURE__*/React__default.forwardRef(function (_ref,
310
360
  onDismissSingleNotification(notification);
311
361
  };
312
362
  var mainSectionClassName = cx(["".concat(blockClass, "__main-section"), _defineProperty({}, "".concat(blockClass, "__main-section-empty"), allNotifications && !allNotifications.length)]);
313
- return shouldRender ? /*#__PURE__*/React__default.createElement("div", _extends({}, rest, {
363
+ return shouldRender ? /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("button", {
364
+ type: "button",
365
+ className: "".concat(carbonPrefix, "--visually-hidden"),
366
+ ref: startSentinel
367
+ }, "Focus sentinel start"), /*#__PURE__*/React__default.createElement("div", _extends({
368
+ role: "dialog",
369
+ "aria-label": "Notification Panel",
370
+ onBlur: handleBlur,
371
+ tabIndex: 0,
372
+ onKeyDown: handleKeydown
373
+ }, rest, {
314
374
  id: blockClass,
315
375
  className: cx(blockClass, className, "".concat(blockClass, "__container")),
316
376
  style: {
@@ -319,6 +379,8 @@ var NotificationsPanel = /*#__PURE__*/React__default.forwardRef(function (_ref,
319
379
  onAnimationEnd: onAnimationEnd,
320
380
  ref: ref || notificationPanelRef
321
381
  }, getDevtoolsProps(componentName)), /*#__PURE__*/React__default.createElement("div", {
382
+ ref: notificationPanelInnerRef
383
+ }, /*#__PURE__*/React__default.createElement("div", {
322
384
  className: "".concat(blockClass, "__header-container")
323
385
  }, /*#__PURE__*/React__default.createElement("div", {
324
386
  className: "".concat(blockClass, "__header-flex")
@@ -382,7 +444,11 @@ var NotificationsPanel = /*#__PURE__*/React__default.forwardRef(function (_ref,
382
444
  onClick: function onClick() {
383
445
  return onSettingsClick();
384
446
  }
385
- })) : null) : null;
447
+ })) : null)), /*#__PURE__*/React__default.createElement("button", {
448
+ type: "button",
449
+ className: "".concat(carbonPrefix, "--visually-hidden"),
450
+ ref: endSentinel
451
+ }, "Focus sentinel end")) : null;
386
452
  });
387
453
 
388
454
  // Return a placeholder if not released and not enabled by feature flag
@@ -513,6 +579,10 @@ NotificationsPanel.propTypes = {
513
579
  * Sets the `seconds ago` label text
514
580
  */
515
581
  secondsAgoText: PropTypes.func,
582
+ /**
583
+ * Specify the CSS selectors that match the floating menus
584
+ */
585
+ selectorsFloatingMenus: PropTypes.arrayOf(PropTypes.string.isRequired),
516
586
  /**
517
587
  * Sets the settings icon description text
518
588
  */
@@ -4,7 +4,7 @@ export function useFocus(modalRef: any, selectorPrimaryFocus: any): {
4
4
  lastElement: any;
5
5
  allElements: any;
6
6
  specifiedElement: any;
7
- keyDownListener: (event: any) => void;
7
+ keyDownListener: (event: any) => Promise<void>;
8
8
  getFocusable: () => {
9
9
  first: any;
10
10
  last: any;
@@ -5,9 +5,11 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
+ import { asyncToGenerator as _asyncToGenerator, regeneratorRuntime as _regeneratorRuntime } from '../../../_virtual/_rollupPluginBabelHelpers.js';
8
9
  import { usePrefix } from '@carbon/react';
9
10
  import { pkg } from '../../../settings.js';
10
11
  import { useCallback, useEffect } from 'react';
12
+ import wait from '../utils/wait.js';
11
13
 
12
14
  var getSpecificElement = function getSpecificElement(parentEl, elementId) {
13
15
  var element = parentEl === null || parentEl === void 0 ? void 0 : parentEl.querySelector(elementId);
@@ -16,6 +18,7 @@ var getSpecificElement = function getSpecificElement(parentEl, elementId) {
16
18
  var useFocus = function useFocus(modalRef, selectorPrimaryFocus) {
17
19
  var carbonPrefix = usePrefix();
18
20
  var tearsheetBaseClass = "".concat(pkg.prefix, "--tearsheet");
21
+ var sidePanelBaseClass = "".concat(pkg.prefix, "--side-panel");
19
22
  // Querying focusable element in the modal
20
23
  // Query to exclude hidden elements in the modal from querySelectorAll() method
21
24
  // feel free to include more if needed :)
@@ -27,8 +30,9 @@ var useFocus = function useFocus(modalRef, selectorPrimaryFocus) {
27
30
  var queryTextarea = "textarea".concat(notQuery);
28
31
  var queryLink = "[href]".concat(notQuery);
29
32
  var queryTabIndex = "[tabindex=\"0\"]".concat(notQuery);
33
+ var querySidePanelScroll = ".".concat(sidePanelBaseClass, "--scrolls");
30
34
  // Final query
31
- var query = "".concat(queryButton, ",").concat(queryLink, ",").concat(queryInput, ",").concat(querySelect, ",").concat(queryTextarea, ", ").concat(queryTabIndex);
35
+ var query = "".concat(queryButton, ",").concat(queryLink, ",").concat(queryInput, ",").concat(querySelect, ",").concat(queryTextarea, ",").concat(queryTabIndex, ",").concat(querySidePanelScroll);
32
36
  var modalEl = modalRef === null || modalRef === void 0 ? void 0 : modalRef.current;
33
37
  var getFocusable = useCallback(function () {
34
38
  var _focusableElements, _focusableElements2, _focusableElements3, _focusableElements4;
@@ -54,30 +58,42 @@ var useFocus = function useFocus(modalRef, selectorPrimaryFocus) {
54
58
  useEffect(function () {
55
59
  getFocusable();
56
60
  }, [getFocusable]);
57
- var handleKeyDown = function handleKeyDown(event) {
58
- // Checking whether the key is tab or not
59
- if (event.key === 'Tab') {
60
- // updating the focusable elements list
61
- var _getFocusable = getFocusable(),
62
- first = _getFocusable.first,
63
- last = _getFocusable.last,
64
- all = _getFocusable.all;
65
- setTimeout(function () {
66
- var _document, _document2;
67
- if (event.shiftKey && !Array.prototype.includes.call(all, (_document = document) === null || _document === void 0 ? void 0 : _document.activeElement)) {
68
- // Prevents the default "Tab" behavior
69
- event.preventDefault();
70
- // if the user press shift+tab and the current element not in focusable items
71
- last === null || last === void 0 || last.focus();
72
- } else if (!Array.prototype.includes.call(all, (_document2 = document) === null || _document2 === void 0 ? void 0 : _document2.activeElement)) {
73
- event.preventDefault();
74
- // user pressing tab key only then
75
- // focusing the first element if the current element is not in focusable items
76
- first === null || first === void 0 || first.focus();
61
+ var handleKeyDown = /*#__PURE__*/function () {
62
+ var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(event) {
63
+ var _document, _document2, _getFocusable, first, last, all;
64
+ return _regeneratorRuntime().wrap(function _callee$(_context) {
65
+ while (1) switch (_context.prev = _context.next) {
66
+ case 0:
67
+ if (!(event.key === 'Tab')) {
68
+ _context.next = 5;
69
+ break;
70
+ }
71
+ // updating the focusable elements list
72
+ _getFocusable = getFocusable(), first = _getFocusable.first, last = _getFocusable.last, all = _getFocusable.all;
73
+ _context.next = 4;
74
+ return wait(1);
75
+ case 4:
76
+ if (event.shiftKey && !Array.prototype.includes.call(all, (_document = document) === null || _document === void 0 ? void 0 : _document.activeElement)) {
77
+ // Prevents the default "Tab" behavior
78
+ event.preventDefault();
79
+ // if the user press shift+tab and the current element not in focusable items
80
+ last === null || last === void 0 || last.focus();
81
+ } else if (!Array.prototype.includes.call(all, (_document2 = document) === null || _document2 === void 0 ? void 0 : _document2.activeElement)) {
82
+ event.preventDefault();
83
+ // user pressing tab key only then
84
+ // focusing the first element if the current element is not in focusable items
85
+ first === null || first === void 0 || first.focus();
86
+ }
87
+ case 5:
88
+ case "end":
89
+ return _context.stop();
77
90
  }
78
- }, 0);
79
- }
80
- };
91
+ }, _callee);
92
+ }));
93
+ return function handleKeyDown(_x) {
94
+ return _ref.apply(this, arguments);
95
+ };
96
+ }();
81
97
  return {
82
98
  firstElement: getFocusable().first,
83
99
  lastElement: getFocusable().last,
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2018
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
+ * Various utilities to help with a11y work
9
+ */
10
+ /**
11
+ * A flag `node.compareDocumentPosition(target)` returns,
12
+ * that indicates `target` is located earlier than `node` in the document or `target` contains `node`.
13
+ */
14
+ export const DOCUMENT_POSITION_BROAD_PRECEDING: number | false;
15
+ /**
16
+ * A flag `node.compareDocumentPosition(target)` returns,
17
+ * that indicates `target` is located later than `node` in the document or `node` contains `target`.
18
+ */
19
+ export const DOCUMENT_POSITION_BROAD_FOLLOWING: number | false;
20
+ /**
21
+ * CSS selector that selects major nodes that are sequential-focusable.
22
+ */
23
+ export const selectorTabbable: "\n a[href], area[href], input:not([disabled]):not([tabindex='-1']),\n button:not([disabled]):not([tabindex='-1']),select:not([disabled]):not([tabindex='-1']),\n textarea:not([disabled]):not([tabindex='-1']),\n iframe, object, embed, *[tabindex]:not([tabindex='-1']):not([disabled]), *[contenteditable=true]\n";
24
+ /**
25
+ * CSS selector that selects major nodes that are click focusable
26
+ */
27
+ export const selectorFocusable: "\n a[href], area[href], input:not([disabled]),\n button:not([disabled]),select:not([disabled]),\n textarea:not([disabled]),\n iframe, object, embed, *[tabindex]:not([disabled]), *[contenteditable=true]\n";
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Copyright IBM Corp. 2020, 2024
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
+ /**
9
+ * Various utilities to help with a11y work
10
+ */
11
+
12
+ /**
13
+ * A flag `node.compareDocumentPosition(target)` returns,
14
+ * that indicates `target` is located earlier than `node` in the document or `target` contains `node`.
15
+ */
16
+ var DOCUMENT_POSITION_BROAD_PRECEDING =
17
+ // Checks `typeof Node` for `react-docgen`
18
+ typeof Node !== 'undefined' &&
19
+ // eslint-disable-next-line ssr-friendly/no-dom-globals-in-module-scope
20
+ Node.DOCUMENT_POSITION_PRECEDING | Node.DOCUMENT_POSITION_CONTAINS;
21
+
22
+ /**
23
+ * A flag `node.compareDocumentPosition(target)` returns,
24
+ * that indicates `target` is located later than `node` in the document or `node` contains `target`.
25
+ */
26
+ var DOCUMENT_POSITION_BROAD_FOLLOWING =
27
+ // Checks `typeof Node` for `react-docgen`
28
+ typeof Node !== 'undefined' &&
29
+ // eslint-disable-next-line ssr-friendly/no-dom-globals-in-module-scope
30
+ Node.DOCUMENT_POSITION_FOLLOWING | Node.DOCUMENT_POSITION_CONTAINED_BY;
31
+
32
+ /**
33
+ * CSS selector that selects major nodes that are sequential-focusable.
34
+ */
35
+ var selectorTabbable = "\n a[href], area[href], input:not([disabled]):not([tabindex='-1']),\n button:not([disabled]):not([tabindex='-1']),select:not([disabled]):not([tabindex='-1']),\n textarea:not([disabled]):not([tabindex='-1']),\n iframe, object, embed, *[tabindex]:not([tabindex='-1']):not([disabled]), *[contenteditable=true]\n";
36
+
37
+ export { DOCUMENT_POSITION_BROAD_FOLLOWING, DOCUMENT_POSITION_BROAD_PRECEDING, selectorTabbable };
@@ -0,0 +1,25 @@
1
+ export default wrapFocus;
2
+ /**
3
+ * @param {Node} node A DOM node.
4
+ * @param {string[]} selectorsFloatingMenus The CSS selectors that matches floating menus.
5
+ * @returns {boolean} `true` of the given `node` is in a floating menu.
6
+ */
7
+ export function elementOrParentIsFloatingMenu(node: Node, selectorsFloatingMenus?: string[]): boolean;
8
+ /**
9
+ * Ensures the focus is kept in the given `modalNode`, implementing "focus-wrap" behavior.
10
+ * @param {object} options The options.
11
+ * @param {Node|null} options.bodyNode The DOM node of the inner modal.
12
+ * @param {Node|null} options.startTrapNode The DOM node of the focus sentinel the is placed earlier next to `modalNode`.
13
+ * @param {Node|null} options.endTrapNode The DOM node of the focus sentinel the is placed next to `modalNode`.
14
+ * @param {Node} options.currentActiveNode The DOM node that has focus.
15
+ * @param {Node} options.oldActiveNode The DOM node that previously had focus.
16
+ * @param {string[]} [options.selectorsFloatingMenus] The CSS selectors that matches floating menus
17
+ */
18
+ declare function wrapFocus({ bodyNode, startTrapNode, endTrapNode, currentActiveNode, oldActiveNode, selectorsFloatingMenus, }: {
19
+ bodyNode: Node | null;
20
+ startTrapNode: Node | null;
21
+ endTrapNode: Node | null;
22
+ currentActiveNode: Node;
23
+ oldActiveNode: Node;
24
+ selectorsFloatingMenus?: string[] | undefined;
25
+ }): void;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Copyright IBM Corp. 2020, 2024
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
+ import { DOCUMENT_POSITION_BROAD_PRECEDING, selectorTabbable, DOCUMENT_POSITION_BROAD_FOLLOWING } from './keyboardNavigation.js';
9
+ import { carbon } from '../../../settings.js';
10
+
11
+ /**
12
+ * @param {Node} node A DOM node.
13
+ * @param {string[]} selectorsFloatingMenus The CSS selectors that matches floating menus.
14
+ * @returns {boolean} `true` of the given `node` is in a floating menu.
15
+ */
16
+ function elementOrParentIsFloatingMenu(node) {
17
+ var selectorsFloatingMenus = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [".".concat(carbon.prefix, "--overflow-menu-options"), ".".concat(carbon.prefix, "--tooltip"), '.flatpickr-calendar'];
18
+ if (node && typeof node.closest === 'function') {
19
+ return selectorsFloatingMenus.some(function (selector) {
20
+ return node.closest(selector);
21
+ });
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Ensures the focus is kept in the given `modalNode`, implementing "focus-wrap" behavior.
27
+ * @param {object} options The options.
28
+ * @param {Node|null} options.bodyNode The DOM node of the inner modal.
29
+ * @param {Node|null} options.startTrapNode The DOM node of the focus sentinel the is placed earlier next to `modalNode`.
30
+ * @param {Node|null} options.endTrapNode The DOM node of the focus sentinel the is placed next to `modalNode`.
31
+ * @param {Node} options.currentActiveNode The DOM node that has focus.
32
+ * @param {Node} options.oldActiveNode The DOM node that previously had focus.
33
+ * @param {string[]} [options.selectorsFloatingMenus] The CSS selectors that matches floating menus
34
+ */
35
+ function wrapFocus(_ref) {
36
+ var bodyNode = _ref.bodyNode,
37
+ startTrapNode = _ref.startTrapNode,
38
+ endTrapNode = _ref.endTrapNode,
39
+ currentActiveNode = _ref.currentActiveNode,
40
+ oldActiveNode = _ref.oldActiveNode,
41
+ selectorsFloatingMenus = _ref.selectorsFloatingMenus;
42
+ if (bodyNode && currentActiveNode && oldActiveNode && !bodyNode.contains(currentActiveNode) && !elementOrParentIsFloatingMenu(currentActiveNode, selectorsFloatingMenus)) {
43
+ var comparisonResult = oldActiveNode.compareDocumentPosition(currentActiveNode);
44
+ if (currentActiveNode === startTrapNode || comparisonResult & DOCUMENT_POSITION_BROAD_PRECEDING) {
45
+ var arrayNodes = Array.from(bodyNode.querySelectorAll(selectorTabbable));
46
+ arrayNodes.reverse();
47
+ var tabbable = arrayNodes.find(function (elem) {
48
+ return Boolean(elem.offsetParent);
49
+ });
50
+ if (tabbable) {
51
+ tabbable.focus();
52
+ } else if (bodyNode !== oldActiveNode) {
53
+ bodyNode.focus();
54
+ }
55
+ } else if (currentActiveNode === endTrapNode || comparisonResult & DOCUMENT_POSITION_BROAD_FOLLOWING) {
56
+ var _tabbable = Array.prototype.find.call(bodyNode.querySelectorAll(selectorTabbable), function (elem) {
57
+ return Boolean(elem.offsetParent);
58
+ });
59
+ if (_tabbable) {
60
+ _tabbable.focus();
61
+ } else if (bodyNode !== oldActiveNode) {
62
+ bodyNode.focus();
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ export { wrapFocus as default, elementOrParentIsFloatingMenu };
@@ -158,6 +158,8 @@ export interface NotificationsPanelProps {
158
158
  * Sets the yesterday label text
159
159
  */
160
160
  yesterdayLabel?: string;
161
+ /** Specify the CSS selectors that match the floating menus. */
162
+ selectorsFloatingMenus?: string[];
161
163
  }
162
164
  export declare let NotificationsPanel: React.ForwardRefExoticComponent<NotificationsPanelProps & React.RefAttributes<unknown>>;
163
165
  export {};
@@ -20,6 +20,7 @@ var settings = require('../../settings.js');
20
20
  var propsHelper = require('../../global/js/utils/props-helper.js');
21
21
  var utils = require('./utils.js');
22
22
  var usePrefersReducedMotion = require('../../global/js/hooks/usePrefersReducedMotion.js');
23
+ var wrapFocus = require('../../global/js/utils/wrapFocus.js');
23
24
  var usePreviousValue = require('../../global/js/hooks/usePreviousValue.js');
24
25
  var useClickOutside = require('../../global/js/hooks/useClickOutside.js');
25
26
  var NotificationsEmptyState = require('../EmptyStates/NotificationsEmptyState/NotificationsEmptyState.js');
@@ -30,7 +31,7 @@ var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
30
31
  var cx__default = /*#__PURE__*/_interopDefaultLegacy(cx);
31
32
 
32
33
  var _Close;
33
- var _excluded = ["className", "data", "daysAgoText", "dismissAllLabel", "dismissSingleNotificationIconDescription", "doNotDisturbDefaultToggled", "doNotDisturbLabel", "emptyStateLabel", "hourAgoText", "hoursAgoText", "minuteAgoText", "minutesAgoText", "monthAgoText", "monthsAgoText", "nowText", "onClickOutside", "onDismissAllNotifications", "onDismissSingleNotification", "onDoNotDisturbChange", "onSettingsClick", "onViewAllClick", "open", "previousLabel", "readLessLabel", "readMoreLabel", "secondsAgoText", "settingsIconDescription", "title", "todayLabel", "viewAllLabel", "yearAgoText", "yearsAgoText", "yesterdayAtText", "yesterdayLabel"];
34
+ var _excluded = ["className", "data", "daysAgoText", "dismissAllLabel", "dismissSingleNotificationIconDescription", "doNotDisturbDefaultToggled", "doNotDisturbLabel", "emptyStateLabel", "hourAgoText", "hoursAgoText", "minuteAgoText", "minutesAgoText", "monthAgoText", "monthsAgoText", "nowText", "onClickOutside", "onDismissAllNotifications", "onDismissSingleNotification", "onDoNotDisturbChange", "onSettingsClick", "onViewAllClick", "open", "previousLabel", "readLessLabel", "readMoreLabel", "secondsAgoText", "settingsIconDescription", "title", "todayLabel", "viewAllLabel", "yearAgoText", "yearsAgoText", "yesterdayAtText", "yesterdayLabel", "selectorsFloatingMenus"];
34
35
 
35
36
  // The block part of our conventional BEM class names (blockClass__E--M).
36
37
  var componentName = 'NotificationsPanel';
@@ -150,8 +151,12 @@ exports.NotificationsPanel = /*#__PURE__*/React__default["default"].forwardRef(f
150
151
  yesterdayAtText = _ref$yesterdayAtText === void 0 ? defaults.yesterdayAtText : _ref$yesterdayAtText,
151
152
  _ref$yesterdayLabel = _ref.yesterdayLabel,
152
153
  yesterdayLabel = _ref$yesterdayLabel === void 0 ? defaults.yesterdayLabel : _ref$yesterdayLabel,
154
+ selectorsFloatingMenus = _ref.selectorsFloatingMenus,
153
155
  rest = _rollupPluginBabelHelpers.objectWithoutProperties(_ref, _excluded);
154
- var notificationPanelRef = React.useRef();
156
+ var notificationPanelRef = React.useRef(null);
157
+ var notificationPanelInnerRef = React.useRef(null);
158
+ var startSentinel = React.useRef(null);
159
+ var endSentinel = React.useRef(null);
155
160
  var _useState = React.useState(open),
156
161
  _useState2 = _rollupPluginBabelHelpers.slicedToArray(_useState, 2),
157
162
  shouldRender = _useState2[0],
@@ -164,6 +169,7 @@ exports.NotificationsPanel = /*#__PURE__*/React__default["default"].forwardRef(f
164
169
  open: open
165
170
  });
166
171
  var reducedMotion = usePrefersReducedMotion["default"]();
172
+ var carbonPrefix = react.usePrefix();
167
173
  React.useEffect(function () {
168
174
  // Set the notifications passed to the state within this component
169
175
  setAllNotifications(data);
@@ -175,12 +181,56 @@ exports.NotificationsPanel = /*#__PURE__*/React__default["default"].forwardRef(f
175
181
  // initialize the notification panel to open
176
182
  if (open) {
177
183
  setRender(true);
184
+ var observer = new MutationObserver(function () {
185
+ if (notificationPanelRef.current) {
186
+ var _querySelector;
187
+ var parentElement = notificationPanelRef.current;
188
+ parentElement === null || parentElement === void 0 || (_querySelector = parentElement.querySelector(".".concat(blockClass, "__dismiss-button"))) === null || _querySelector === void 0 || _querySelector.focus();
189
+ observer.disconnect();
190
+ }
191
+ });
192
+ if (notificationPanelRef.current) {
193
+ var parentElement = notificationPanelRef.current;
194
+ var button = parentElement === null || parentElement === void 0 ? void 0 : parentElement.querySelector(".".concat(blockClass, "__dismiss-button"));
195
+ button === null || button === void 0 || button.focus();
196
+ } else {
197
+ observer.observe(document.body, {
198
+ childList: true,
199
+ subtree: true
200
+ });
201
+ }
202
+ return function () {
203
+ return observer.disconnect();
204
+ };
178
205
  }
179
206
  }, [open]);
180
207
  var onAnimationEnd = function onAnimationEnd() {
181
208
  // initialize the notification panel to close
182
209
  !open && setRender(false);
183
210
  };
211
+ var handleBlur = function handleBlur(_ref2) {
212
+ var oldActiveNode = _ref2.target,
213
+ currentActiveNode = _ref2.relatedTarget;
214
+ if (open && currentActiveNode && oldActiveNode && notificationPanelInnerRef.current) {
215
+ var bodyNode = notificationPanelInnerRef.current;
216
+ var startSentinelNode = startSentinel.current;
217
+ var endSentinelNode = endSentinel.current;
218
+ wrapFocus["default"]({
219
+ bodyNode: bodyNode,
220
+ startTrapNode: startSentinelNode,
221
+ endTrapNode: endSentinelNode,
222
+ currentActiveNode: currentActiveNode,
223
+ oldActiveNode: oldActiveNode,
224
+ selectorsFloatingMenus: selectorsFloatingMenus === null || selectorsFloatingMenus === void 0 ? void 0 : selectorsFloatingMenus.filter(Boolean)
225
+ });
226
+ }
227
+ };
228
+ var handleKeydown = function handleKeydown(event) {
229
+ event.stopPropagation();
230
+ if (event.key === 'Escape') {
231
+ onClickOutside();
232
+ }
233
+ };
184
234
  React.useEffect(function () {
185
235
  if (!open && previousState !== null && previousState !== void 0 && previousState.open && reducedMotion) {
186
236
  setRender(false);
@@ -319,7 +369,17 @@ exports.NotificationsPanel = /*#__PURE__*/React__default["default"].forwardRef(f
319
369
  onDismissSingleNotification(notification);
320
370
  };
321
371
  var mainSectionClassName = cx__default["default"](["".concat(blockClass, "__main-section"), _rollupPluginBabelHelpers.defineProperty({}, "".concat(blockClass, "__main-section-empty"), allNotifications && !allNotifications.length)]);
322
- return shouldRender ? /*#__PURE__*/React__default["default"].createElement("div", _rollupPluginBabelHelpers["extends"]({}, rest, {
372
+ return shouldRender ? /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement("button", {
373
+ type: "button",
374
+ className: "".concat(carbonPrefix, "--visually-hidden"),
375
+ ref: startSentinel
376
+ }, "Focus sentinel start"), /*#__PURE__*/React__default["default"].createElement("div", _rollupPluginBabelHelpers["extends"]({
377
+ role: "dialog",
378
+ "aria-label": "Notification Panel",
379
+ onBlur: handleBlur,
380
+ tabIndex: 0,
381
+ onKeyDown: handleKeydown
382
+ }, rest, {
323
383
  id: blockClass,
324
384
  className: cx__default["default"](blockClass, className, "".concat(blockClass, "__container")),
325
385
  style: {
@@ -328,6 +388,8 @@ exports.NotificationsPanel = /*#__PURE__*/React__default["default"].forwardRef(f
328
388
  onAnimationEnd: onAnimationEnd,
329
389
  ref: ref || notificationPanelRef
330
390
  }, devtools.getDevtoolsProps(componentName)), /*#__PURE__*/React__default["default"].createElement("div", {
391
+ ref: notificationPanelInnerRef
392
+ }, /*#__PURE__*/React__default["default"].createElement("div", {
331
393
  className: "".concat(blockClass, "__header-container")
332
394
  }, /*#__PURE__*/React__default["default"].createElement("div", {
333
395
  className: "".concat(blockClass, "__header-flex")
@@ -391,7 +453,11 @@ exports.NotificationsPanel = /*#__PURE__*/React__default["default"].forwardRef(f
391
453
  onClick: function onClick() {
392
454
  return onSettingsClick();
393
455
  }
394
- })) : null) : null;
456
+ })) : null)), /*#__PURE__*/React__default["default"].createElement("button", {
457
+ type: "button",
458
+ className: "".concat(carbonPrefix, "--visually-hidden"),
459
+ ref: endSentinel
460
+ }, "Focus sentinel end")) : null;
395
461
  });
396
462
 
397
463
  // Return a placeholder if not released and not enabled by feature flag
@@ -522,6 +588,10 @@ exports.NotificationsPanel.propTypes = {
522
588
  * Sets the `seconds ago` label text
523
589
  */
524
590
  secondsAgoText: index["default"].func,
591
+ /**
592
+ * Specify the CSS selectors that match the floating menus
593
+ */
594
+ selectorsFloatingMenus: index["default"].arrayOf(index["default"].string.isRequired),
525
595
  /**
526
596
  * Sets the settings icon description text
527
597
  */
@@ -4,7 +4,7 @@ export function useFocus(modalRef: any, selectorPrimaryFocus: any): {
4
4
  lastElement: any;
5
5
  allElements: any;
6
6
  specifiedElement: any;
7
- keyDownListener: (event: any) => void;
7
+ keyDownListener: (event: any) => Promise<void>;
8
8
  getFocusable: () => {
9
9
  first: any;
10
10
  last: any;
@@ -9,9 +9,11 @@
9
9
 
10
10
  Object.defineProperty(exports, '__esModule', { value: true });
11
11
 
12
+ var _rollupPluginBabelHelpers = require('../../../_virtual/_rollupPluginBabelHelpers.js');
12
13
  var react = require('@carbon/react');
13
14
  var settings = require('../../../settings.js');
14
15
  var React = require('react');
16
+ var wait = require('../utils/wait.js');
15
17
 
16
18
  var getSpecificElement = function getSpecificElement(parentEl, elementId) {
17
19
  var element = parentEl === null || parentEl === void 0 ? void 0 : parentEl.querySelector(elementId);
@@ -20,6 +22,7 @@ var getSpecificElement = function getSpecificElement(parentEl, elementId) {
20
22
  var useFocus = function useFocus(modalRef, selectorPrimaryFocus) {
21
23
  var carbonPrefix = react.usePrefix();
22
24
  var tearsheetBaseClass = "".concat(settings.pkg.prefix, "--tearsheet");
25
+ var sidePanelBaseClass = "".concat(settings.pkg.prefix, "--side-panel");
23
26
  // Querying focusable element in the modal
24
27
  // Query to exclude hidden elements in the modal from querySelectorAll() method
25
28
  // feel free to include more if needed :)
@@ -31,8 +34,9 @@ var useFocus = function useFocus(modalRef, selectorPrimaryFocus) {
31
34
  var queryTextarea = "textarea".concat(notQuery);
32
35
  var queryLink = "[href]".concat(notQuery);
33
36
  var queryTabIndex = "[tabindex=\"0\"]".concat(notQuery);
37
+ var querySidePanelScroll = ".".concat(sidePanelBaseClass, "--scrolls");
34
38
  // Final query
35
- var query = "".concat(queryButton, ",").concat(queryLink, ",").concat(queryInput, ",").concat(querySelect, ",").concat(queryTextarea, ", ").concat(queryTabIndex);
39
+ var query = "".concat(queryButton, ",").concat(queryLink, ",").concat(queryInput, ",").concat(querySelect, ",").concat(queryTextarea, ",").concat(queryTabIndex, ",").concat(querySidePanelScroll);
36
40
  var modalEl = modalRef === null || modalRef === void 0 ? void 0 : modalRef.current;
37
41
  var getFocusable = React.useCallback(function () {
38
42
  var _focusableElements, _focusableElements2, _focusableElements3, _focusableElements4;
@@ -58,30 +62,42 @@ var useFocus = function useFocus(modalRef, selectorPrimaryFocus) {
58
62
  React.useEffect(function () {
59
63
  getFocusable();
60
64
  }, [getFocusable]);
61
- var handleKeyDown = function handleKeyDown(event) {
62
- // Checking whether the key is tab or not
63
- if (event.key === 'Tab') {
64
- // updating the focusable elements list
65
- var _getFocusable = getFocusable(),
66
- first = _getFocusable.first,
67
- last = _getFocusable.last,
68
- all = _getFocusable.all;
69
- setTimeout(function () {
70
- var _document, _document2;
71
- if (event.shiftKey && !Array.prototype.includes.call(all, (_document = document) === null || _document === void 0 ? void 0 : _document.activeElement)) {
72
- // Prevents the default "Tab" behavior
73
- event.preventDefault();
74
- // if the user press shift+tab and the current element not in focusable items
75
- last === null || last === void 0 || last.focus();
76
- } else if (!Array.prototype.includes.call(all, (_document2 = document) === null || _document2 === void 0 ? void 0 : _document2.activeElement)) {
77
- event.preventDefault();
78
- // user pressing tab key only then
79
- // focusing the first element if the current element is not in focusable items
80
- first === null || first === void 0 || first.focus();
65
+ var handleKeyDown = /*#__PURE__*/function () {
66
+ var _ref = _rollupPluginBabelHelpers.asyncToGenerator( /*#__PURE__*/_rollupPluginBabelHelpers.regeneratorRuntime().mark(function _callee(event) {
67
+ var _document, _document2, _getFocusable, first, last, all;
68
+ return _rollupPluginBabelHelpers.regeneratorRuntime().wrap(function _callee$(_context) {
69
+ while (1) switch (_context.prev = _context.next) {
70
+ case 0:
71
+ if (!(event.key === 'Tab')) {
72
+ _context.next = 5;
73
+ break;
74
+ }
75
+ // updating the focusable elements list
76
+ _getFocusable = getFocusable(), first = _getFocusable.first, last = _getFocusable.last, all = _getFocusable.all;
77
+ _context.next = 4;
78
+ return wait["default"](1);
79
+ case 4:
80
+ if (event.shiftKey && !Array.prototype.includes.call(all, (_document = document) === null || _document === void 0 ? void 0 : _document.activeElement)) {
81
+ // Prevents the default "Tab" behavior
82
+ event.preventDefault();
83
+ // if the user press shift+tab and the current element not in focusable items
84
+ last === null || last === void 0 || last.focus();
85
+ } else if (!Array.prototype.includes.call(all, (_document2 = document) === null || _document2 === void 0 ? void 0 : _document2.activeElement)) {
86
+ event.preventDefault();
87
+ // user pressing tab key only then
88
+ // focusing the first element if the current element is not in focusable items
89
+ first === null || first === void 0 || first.focus();
90
+ }
91
+ case 5:
92
+ case "end":
93
+ return _context.stop();
81
94
  }
82
- }, 0);
83
- }
84
- };
95
+ }, _callee);
96
+ }));
97
+ return function handleKeyDown(_x) {
98
+ return _ref.apply(this, arguments);
99
+ };
100
+ }();
85
101
  return {
86
102
  firstElement: getFocusable().first,
87
103
  lastElement: getFocusable().last,
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2018
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
+ * Various utilities to help with a11y work
9
+ */
10
+ /**
11
+ * A flag `node.compareDocumentPosition(target)` returns,
12
+ * that indicates `target` is located earlier than `node` in the document or `target` contains `node`.
13
+ */
14
+ export const DOCUMENT_POSITION_BROAD_PRECEDING: number | false;
15
+ /**
16
+ * A flag `node.compareDocumentPosition(target)` returns,
17
+ * that indicates `target` is located later than `node` in the document or `node` contains `target`.
18
+ */
19
+ export const DOCUMENT_POSITION_BROAD_FOLLOWING: number | false;
20
+ /**
21
+ * CSS selector that selects major nodes that are sequential-focusable.
22
+ */
23
+ export const selectorTabbable: "\n a[href], area[href], input:not([disabled]):not([tabindex='-1']),\n button:not([disabled]):not([tabindex='-1']),select:not([disabled]):not([tabindex='-1']),\n textarea:not([disabled]):not([tabindex='-1']),\n iframe, object, embed, *[tabindex]:not([tabindex='-1']):not([disabled]), *[contenteditable=true]\n";
24
+ /**
25
+ * CSS selector that selects major nodes that are click focusable
26
+ */
27
+ export const selectorFocusable: "\n a[href], area[href], input:not([disabled]),\n button:not([disabled]),select:not([disabled]),\n textarea:not([disabled]),\n iframe, object, embed, *[tabindex]:not([disabled]), *[contenteditable=true]\n";
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Copyright IBM Corp. 2020, 2024
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
+ 'use strict';
9
+
10
+ Object.defineProperty(exports, '__esModule', { value: true });
11
+
12
+ /**
13
+ * Various utilities to help with a11y work
14
+ */
15
+
16
+ /**
17
+ * A flag `node.compareDocumentPosition(target)` returns,
18
+ * that indicates `target` is located earlier than `node` in the document or `target` contains `node`.
19
+ */
20
+ var DOCUMENT_POSITION_BROAD_PRECEDING =
21
+ // Checks `typeof Node` for `react-docgen`
22
+ typeof Node !== 'undefined' &&
23
+ // eslint-disable-next-line ssr-friendly/no-dom-globals-in-module-scope
24
+ Node.DOCUMENT_POSITION_PRECEDING | Node.DOCUMENT_POSITION_CONTAINS;
25
+
26
+ /**
27
+ * A flag `node.compareDocumentPosition(target)` returns,
28
+ * that indicates `target` is located later than `node` in the document or `node` contains `target`.
29
+ */
30
+ var DOCUMENT_POSITION_BROAD_FOLLOWING =
31
+ // Checks `typeof Node` for `react-docgen`
32
+ typeof Node !== 'undefined' &&
33
+ // eslint-disable-next-line ssr-friendly/no-dom-globals-in-module-scope
34
+ Node.DOCUMENT_POSITION_FOLLOWING | Node.DOCUMENT_POSITION_CONTAINED_BY;
35
+
36
+ /**
37
+ * CSS selector that selects major nodes that are sequential-focusable.
38
+ */
39
+ var selectorTabbable = "\n a[href], area[href], input:not([disabled]):not([tabindex='-1']),\n button:not([disabled]):not([tabindex='-1']),select:not([disabled]):not([tabindex='-1']),\n textarea:not([disabled]):not([tabindex='-1']),\n iframe, object, embed, *[tabindex]:not([tabindex='-1']):not([disabled]), *[contenteditable=true]\n";
40
+
41
+ exports.DOCUMENT_POSITION_BROAD_FOLLOWING = DOCUMENT_POSITION_BROAD_FOLLOWING;
42
+ exports.DOCUMENT_POSITION_BROAD_PRECEDING = DOCUMENT_POSITION_BROAD_PRECEDING;
43
+ exports.selectorTabbable = selectorTabbable;
@@ -0,0 +1,25 @@
1
+ export default wrapFocus;
2
+ /**
3
+ * @param {Node} node A DOM node.
4
+ * @param {string[]} selectorsFloatingMenus The CSS selectors that matches floating menus.
5
+ * @returns {boolean} `true` of the given `node` is in a floating menu.
6
+ */
7
+ export function elementOrParentIsFloatingMenu(node: Node, selectorsFloatingMenus?: string[]): boolean;
8
+ /**
9
+ * Ensures the focus is kept in the given `modalNode`, implementing "focus-wrap" behavior.
10
+ * @param {object} options The options.
11
+ * @param {Node|null} options.bodyNode The DOM node of the inner modal.
12
+ * @param {Node|null} options.startTrapNode The DOM node of the focus sentinel the is placed earlier next to `modalNode`.
13
+ * @param {Node|null} options.endTrapNode The DOM node of the focus sentinel the is placed next to `modalNode`.
14
+ * @param {Node} options.currentActiveNode The DOM node that has focus.
15
+ * @param {Node} options.oldActiveNode The DOM node that previously had focus.
16
+ * @param {string[]} [options.selectorsFloatingMenus] The CSS selectors that matches floating menus
17
+ */
18
+ declare function wrapFocus({ bodyNode, startTrapNode, endTrapNode, currentActiveNode, oldActiveNode, selectorsFloatingMenus, }: {
19
+ bodyNode: Node | null;
20
+ startTrapNode: Node | null;
21
+ endTrapNode: Node | null;
22
+ currentActiveNode: Node;
23
+ oldActiveNode: Node;
24
+ selectorsFloatingMenus?: string[] | undefined;
25
+ }): void;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Copyright IBM Corp. 2020, 2024
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
+ 'use strict';
9
+
10
+ Object.defineProperty(exports, '__esModule', { value: true });
11
+
12
+ var keyboardNavigation = require('./keyboardNavigation.js');
13
+ var settings = require('../../../settings.js');
14
+
15
+ /**
16
+ * @param {Node} node A DOM node.
17
+ * @param {string[]} selectorsFloatingMenus The CSS selectors that matches floating menus.
18
+ * @returns {boolean} `true` of the given `node` is in a floating menu.
19
+ */
20
+ function elementOrParentIsFloatingMenu(node) {
21
+ var selectorsFloatingMenus = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [".".concat(settings.carbon.prefix, "--overflow-menu-options"), ".".concat(settings.carbon.prefix, "--tooltip"), '.flatpickr-calendar'];
22
+ if (node && typeof node.closest === 'function') {
23
+ return selectorsFloatingMenus.some(function (selector) {
24
+ return node.closest(selector);
25
+ });
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Ensures the focus is kept in the given `modalNode`, implementing "focus-wrap" behavior.
31
+ * @param {object} options The options.
32
+ * @param {Node|null} options.bodyNode The DOM node of the inner modal.
33
+ * @param {Node|null} options.startTrapNode The DOM node of the focus sentinel the is placed earlier next to `modalNode`.
34
+ * @param {Node|null} options.endTrapNode The DOM node of the focus sentinel the is placed next to `modalNode`.
35
+ * @param {Node} options.currentActiveNode The DOM node that has focus.
36
+ * @param {Node} options.oldActiveNode The DOM node that previously had focus.
37
+ * @param {string[]} [options.selectorsFloatingMenus] The CSS selectors that matches floating menus
38
+ */
39
+ function wrapFocus(_ref) {
40
+ var bodyNode = _ref.bodyNode,
41
+ startTrapNode = _ref.startTrapNode,
42
+ endTrapNode = _ref.endTrapNode,
43
+ currentActiveNode = _ref.currentActiveNode,
44
+ oldActiveNode = _ref.oldActiveNode,
45
+ selectorsFloatingMenus = _ref.selectorsFloatingMenus;
46
+ if (bodyNode && currentActiveNode && oldActiveNode && !bodyNode.contains(currentActiveNode) && !elementOrParentIsFloatingMenu(currentActiveNode, selectorsFloatingMenus)) {
47
+ var comparisonResult = oldActiveNode.compareDocumentPosition(currentActiveNode);
48
+ if (currentActiveNode === startTrapNode || comparisonResult & keyboardNavigation.DOCUMENT_POSITION_BROAD_PRECEDING) {
49
+ var arrayNodes = Array.from(bodyNode.querySelectorAll(keyboardNavigation.selectorTabbable));
50
+ arrayNodes.reverse();
51
+ var tabbable = arrayNodes.find(function (elem) {
52
+ return Boolean(elem.offsetParent);
53
+ });
54
+ if (tabbable) {
55
+ tabbable.focus();
56
+ } else if (bodyNode !== oldActiveNode) {
57
+ bodyNode.focus();
58
+ }
59
+ } else if (currentActiveNode === endTrapNode || comparisonResult & keyboardNavigation.DOCUMENT_POSITION_BROAD_FOLLOWING) {
60
+ var _tabbable = Array.prototype.find.call(bodyNode.querySelectorAll(keyboardNavigation.selectorTabbable), function (elem) {
61
+ return Boolean(elem.offsetParent);
62
+ });
63
+ if (_tabbable) {
64
+ _tabbable.focus();
65
+ } else if (bodyNode !== oldActiveNode) {
66
+ bodyNode.focus();
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ exports["default"] = wrapFocus;
73
+ exports.elementOrParentIsFloatingMenu = elementOrParentIsFloatingMenu;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@carbon/ibm-products",
3
3
  "description": "Carbon for IBM Products",
4
- "version": "2.43.2-canary.174+91733fb43",
4
+ "version": "2.43.2-canary.177+bf1741045",
5
5
  "license": "Apache-2.0",
6
6
  "main": "lib/index.js",
7
7
  "module": "es/index.js",
@@ -96,7 +96,7 @@
96
96
  "dependencies": {
97
97
  "@babel/runtime": "^7.23.9",
98
98
  "@carbon/feature-flags": "^0.20.0",
99
- "@carbon/ibm-products-styles": "^2.39.1-canary.184+91733fb43",
99
+ "@carbon/ibm-products-styles": "^2.44.0-rc.0",
100
100
  "@carbon/telemetry": "^0.1.0",
101
101
  "@dnd-kit/core": "^6.0.8",
102
102
  "@dnd-kit/modifiers": "^7.0.0",
@@ -120,5 +120,5 @@
120
120
  "react": "^16.8.6 || ^17.0.1 || ^18.2.0",
121
121
  "react-dom": "^16.8.6 || ^17.0.1 || ^18.2.0"
122
122
  },
123
- "gitHead": "91733fb43157eab26c885f0652adaf9276f372d4"
123
+ "gitHead": "bf1741045997b784c98068c618260dfbc7a79dc6"
124
124
  }