carbon-react 114.12.3 → 114.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/esm/__internal__/input-icon-toggle/input-icon-toggle.component.js +2 -1
  2. package/esm/components/anchor-navigation/anchor-navigation.component.js +30 -33
  3. package/esm/components/anchor-navigation/anchor-navigation.style.js +4 -0
  4. package/esm/components/date/__internal__/date-picker/date-picker.component.js +11 -7
  5. package/esm/components/link/link.component.d.ts +2 -0
  6. package/esm/components/link/link.component.js +7 -1
  7. package/esm/components/menu/__internal__/keyboard-navigation/index.d.ts +4 -2
  8. package/esm/components/menu/__internal__/keyboard-navigation/index.js +16 -15
  9. package/esm/components/menu/__internal__/locators.d.ts +6 -0
  10. package/esm/components/menu/__internal__/locators.js +6 -0
  11. package/esm/components/menu/__internal__/submenu/submenu.component.js +109 -108
  12. package/esm/components/menu/menu-full-screen/menu-full-screen.component.js +1 -2
  13. package/esm/components/menu/menu-item/index.js +0 -1
  14. package/esm/components/menu/menu-item/menu-item.component.js +77 -51
  15. package/esm/components/menu/menu-item/menu-item.d.ts +7 -3
  16. package/esm/components/menu/menu.component.js +33 -37
  17. package/esm/components/menu/menu.context.d.ts +2 -2
  18. package/esm/components/menu/menu.context.js +2 -2
  19. package/esm/components/menu/scrollable-block/scrollable-block.component.js +6 -24
  20. package/lib/__internal__/input-icon-toggle/input-icon-toggle.component.js +2 -1
  21. package/lib/components/anchor-navigation/anchor-navigation.component.js +30 -32
  22. package/lib/components/anchor-navigation/anchor-navigation.style.js +4 -0
  23. package/lib/components/date/__internal__/date-picker/date-picker.component.js +11 -7
  24. package/lib/components/link/link.component.d.ts +2 -0
  25. package/lib/components/link/link.component.js +7 -1
  26. package/lib/components/menu/__internal__/keyboard-navigation/index.d.ts +4 -2
  27. package/lib/components/menu/__internal__/keyboard-navigation/index.js +16 -15
  28. package/lib/components/menu/__internal__/locators.d.ts +6 -0
  29. package/lib/components/menu/__internal__/locators.js +18 -0
  30. package/lib/components/menu/__internal__/submenu/submenu.component.js +111 -113
  31. package/lib/components/menu/menu-full-screen/menu-full-screen.component.js +1 -2
  32. package/lib/components/menu/menu-item/menu-item.component.js +76 -52
  33. package/lib/components/menu/menu-item/menu-item.d.ts +7 -3
  34. package/lib/components/menu/menu.component.js +33 -37
  35. package/lib/components/menu/menu.context.d.ts +2 -2
  36. package/lib/components/menu/menu.context.js +2 -2
  37. package/lib/components/menu/scrollable-block/scrollable-block.component.js +6 -25
  38. package/package.json +1 -1
@@ -59,7 +59,8 @@ const InputIconToggle = ({
59
59
  onFocus: onFocus,
60
60
  onBlur: onBlur,
61
61
  onMouseDown: onMouseDown,
62
- tabIndex: iconTabIndex
62
+ tabIndex: iconTabIndex,
63
+ "data-element": "input-icon-toggle"
63
64
  }, /*#__PURE__*/React.createElement(Icon, {
64
65
  type: type
65
66
  }));
@@ -1,8 +1,9 @@
1
- import React, { useState, useRef, useEffect, useMemo, useCallback, createRef } from "react";
1
+ import React, { useState, useRef, useEffect, useMemo, useCallback } from "react";
2
2
  import PropTypes from "prop-types";
3
3
  import { isFragment } from "react-is";
4
4
  import invariant from "invariant";
5
5
  import throttle from "lodash/throttle";
6
+ import { defaultFocusableSelectors } from "../../__internal__/focus-trap/focus-trap-utils";
6
7
  import Event from "../../__internal__/utils/helpers/events";
7
8
  import { StyledAnchorNavigation, StyledNavigation, StyledContent } from "./anchor-navigation.style";
8
9
  import AnchorNavigationItem from "./anchor-navigation-item/anchor-navigation-item.component";
@@ -23,9 +24,6 @@ const AnchorNavigation = ({
23
24
  !hasCorrectItemStructure ? process.env.NODE_ENV !== "production" ? invariant(false, `\`stickyNavigation\` prop in \`AnchorNavigation\` should be a React Fragment that only contains children of type \`${AnchorNavigationItem.displayName}\``) : invariant(false) : void 0;
24
25
  const [selectedIndex, setSelectedIndex] = useState(0);
25
26
  const sectionRefs = useRef(React.Children.map(stickyNavigation.props.children, child => child.props.target));
26
- const anchorRefs = useRef(Array.from({
27
- length: React.Children.count(stickyNavigation.props.children)
28
- }, () => /*#__PURE__*/createRef()));
29
27
  const contentRef = useRef(null);
30
28
  const navigationRef = useRef(null);
31
29
  const isUserScroll = useRef(true);
@@ -51,8 +49,13 @@ const AnchorNavigation = ({
51
49
  if (isUserScroll.current) {
52
50
  setSelectedAnchorBasedOnScroll();
53
51
  } else {
54
- if (isUserScrollTimer.current !== undefined) window.clearTimeout(isUserScrollTimer.current);
55
- isUserScrollTimer.current = setTimeout(() => {
52
+ if (isUserScrollTimer.current !== undefined) {
53
+ window.clearTimeout(isUserScrollTimer.current);
54
+ }
55
+
56
+ isUserScrollTimer.current = setTimeout(
57
+ /* istanbul ignore next */
58
+ () => {
56
59
  isUserScroll.current = true;
57
60
  }, SCROLL_THROTTLE + 50);
58
61
  }
@@ -62,23 +65,30 @@ const AnchorNavigation = ({
62
65
  return () => window.removeEventListener("scroll", scrollHandler, true);
63
66
  }, [scrollHandler]);
64
67
 
65
- const focusFirstFocusableChild = section => {
66
- const defaultFocusableSelectors = 'button, [href], input:not([type="hidden"]), select, textarea, [tabindex]:not([tabindex="-1"])';
67
- const firstFocusableElement = section.querySelector(defaultFocusableSelectors);
68
-
69
- if (firstFocusableElement) {
70
- firstFocusableElement.focus({
71
- preventScroll: true
72
- });
68
+ const focusSection = section => {
69
+ if (!section.matches(defaultFocusableSelectors)) {
70
+ section.setAttribute("tabindex", "-1");
73
71
  }
72
+
73
+ section.focus({
74
+ preventScroll: true
75
+ });
74
76
  };
75
77
 
76
78
  const scrollToSection = index => {
77
79
  const sectionToScroll = sectionRefs.current[index].current; // istanbul ignore if
78
80
  // function is called only after component is rendered, so ref cannot hold a null value
79
81
 
80
- if (sectionToScroll === null) return;
81
- focusFirstFocusableChild(sectionToScroll); // workaround due to preventScroll focus method option on firefox not working consistently
82
+ if (sectionToScroll === null) return; // ensure section has the appropriate element to remove the default focus styles.
83
+ // Can ignore else branch because there's no harm in setting this to "true" twice (it can't hold any other value),
84
+ // but it's probably more efficient not to.
85
+ // istanbul ignore else
86
+
87
+ if (!sectionToScroll.dataset.carbonAnchornavRef) {
88
+ sectionToScroll.dataset.carbonAnchornavRef = "true";
89
+ }
90
+
91
+ focusSection(sectionToScroll); // workaround due to preventScroll focus method option on firefox not working consistently
82
92
 
83
93
  window.setTimeout(() => {
84
94
  isUserScroll.current = false;
@@ -91,26 +101,13 @@ const AnchorNavigation = ({
91
101
  }, 10);
92
102
  };
93
103
 
94
- const focusNavItem = index => {
95
- var _anchorRefs$current$c;
96
-
97
- const noOfRefs = anchorRefs.current.length;
98
- (_anchorRefs$current$c = anchorRefs.current[(index % noOfRefs + noOfRefs) % noOfRefs].current) === null || _anchorRefs$current$c === void 0 ? void 0 : _anchorRefs$current$c.focus();
99
- };
100
-
101
104
  const handleClick = (event, index) => {
102
105
  event.preventDefault();
103
106
  scrollToSection(index);
104
107
  };
105
108
 
106
109
  const handleKeyDown = (event, index) => {
107
- event.preventDefault();
108
-
109
- if (Event.isUpKey(event)) {
110
- focusNavItem(index - 1);
111
- } else if (Event.isDownKey(event)) {
112
- focusNavItem(index + 1);
113
- } else if (Event.isEnterKey(event) || Event.isSpaceKey(event)) {
110
+ if (Event.isEnterKey(event)) {
114
111
  scrollToSection(index);
115
112
  }
116
113
  };
@@ -122,11 +119,11 @@ const AnchorNavigation = ({
122
119
  ref: navigationRef,
123
120
  "data-element": "anchor-sticky-navigation"
124
121
  }, React.Children.map(stickyNavigation.props.children, (child, index) => /*#__PURE__*/React.cloneElement(child, {
122
+ href: child.props.href || "#",
123
+ // need to pass an href to ensure the link is tabbable by default
125
124
  isSelected: index === selectedIndex,
126
- tabIndex: index === selectedIndex ? 0 : -1,
127
125
  onClick: event => handleClick(event, index),
128
- onKeyDown: event => handleKeyDown(event, index),
129
- ref: anchorRefs.current[index]
126
+ onKeyDown: event => handleKeyDown(event, index)
130
127
  }))), /*#__PURE__*/React.createElement(StyledContent, null, children));
131
128
  };
132
129
 
@@ -15,5 +15,9 @@ const StyledNavigation = styled.ul`
15
15
  const StyledContent = styled.div`
16
16
  flex: 1;
17
17
  margin-left: 32px;
18
+
19
+ [data-carbon-anchornav-ref="true"]:focus {
20
+ outline: none;
21
+ }
18
22
  `;
19
23
  export { StyledAnchorNavigation, StyledNavigation, StyledContent };
@@ -46,13 +46,17 @@ const DatePicker = /*#__PURE__*/React.forwardRef(({
46
46
  const weekdaysLong = useMemo(() => Array.from({
47
47
  length: 7
48
48
  }).map((_, i) => localize.day(i)), [localize]);
49
- const weekdaysShort = useMemo(() => Array.from({
50
- length: 7
51
- }).map((_, i) => localize.day(i, ["de", "pl"].filter(str => l.locale().includes(str)).length ? {
52
- width: "wide"
53
- } : {
54
- width: "abbreviated"
55
- }).substring(0, 3)), [l, localize]);
49
+ const weekdaysShort = useMemo(() => {
50
+ const isGivenLocale = str => l.locale().includes(str);
51
+
52
+ return Array.from({
53
+ length: 7
54
+ }).map((_, i) => localize.day(i, ["de", "pl"].some(isGivenLocale) ? {
55
+ width: "wide"
56
+ } : {
57
+ width: "abbreviated"
58
+ }).substring(0, isGivenLocale("de") ? 2 : 3));
59
+ }, [l, localize]);
56
60
 
57
61
  const handleDayClick = (date, {
58
62
  disabled
@@ -26,6 +26,8 @@ export interface LinkProps extends StyledLinkProps, React.AriaAttributes {
26
26
  ariaLabel?: string;
27
27
  /** allows to set rel property in <a> tag */
28
28
  rel?: string;
29
+ /** @ignore @private internal prop to be set when no href or onClick passed */
30
+ placeholderTabIndex?: boolean;
29
31
  }
30
32
  export declare const Link: React.ForwardRefExoticComponent<LinkProps & React.RefAttributes<HTMLButtonElement | HTMLLinkElement>>;
31
33
  export default Link;
@@ -25,6 +25,7 @@ const Link = /*#__PURE__*/React.forwardRef(({
25
25
  target,
26
26
  variant = "default",
27
27
  isDarkBackground,
28
+ placeholderTabIndex,
28
29
  ...rest
29
30
  }, ref) => {
30
31
  const l = useLocale();
@@ -84,7 +85,11 @@ const Link = /*#__PURE__*/React.forwardRef(({
84
85
  type = "button";
85
86
  }
86
87
 
87
- return /*#__PURE__*/React.createElement(type, componentProps, /*#__PURE__*/React.createElement(React.Fragment, null, renderLinkIcon(), /*#__PURE__*/React.createElement(StyledContent, null, isSkipLink ? l.link.skipLinkLabel() : children), renderLinkIcon("right")));
88
+ return /*#__PURE__*/React.createElement(type, { ...componentProps,
89
+ ...(placeholderTabIndex && href === undefined && !onClick && {
90
+ tabIndex: -1
91
+ })
92
+ }, /*#__PURE__*/React.createElement(React.Fragment, null, renderLinkIcon(), /*#__PURE__*/React.createElement(StyledContent, null, isSkipLink ? l.link.skipLinkLabel() : children), renderLinkIcon("right")));
88
93
  };
89
94
 
90
95
  return /*#__PURE__*/React.createElement(StyledLink, _extends({
@@ -160,6 +165,7 @@ Link.propTypes = {
160
165
  "onClick": PropTypes.func,
161
166
  "onKeyDown": PropTypes.func,
162
167
  "onMouseDown": PropTypes.func,
168
+ "placeholderTabIndex": PropTypes.bool,
163
169
  "rel": PropTypes.string,
164
170
  "target": PropTypes.string,
165
171
  "tooltipMessage": PropTypes.string,
@@ -1,2 +1,4 @@
1
- export function characterNavigation(inputString: any, focusableItems: any, currentFocusedIndex: any): any;
2
- export function menuKeyboardNavigation(event: any, focusableItems: any): number | undefined;
1
+ import React from "react";
2
+ declare function characterNavigation(inputString?: string, focusableItems?: Element[]): Element | undefined;
3
+ declare function menuKeyboardNavigation(event: React.KeyboardEvent, focusableItems: Element[]): number | undefined;
4
+ export { characterNavigation, menuKeyboardNavigation };
@@ -1,27 +1,28 @@
1
1
  import Events from "../../../../__internal__/utils/helpers/events";
2
- import MenuItem from "../../menu-item";
2
+ import { MENU_ITEM, SCROLLABLE_BLOCK_PARENT } from "../locators";
3
3
 
4
- function characterNavigation(inputString, focusableItems, currentFocusedIndex) {
5
- if (!inputString) return currentFocusedIndex;
4
+ function characterNavigation(inputString, focusableItems) {
5
+ if (!inputString || !focusableItems) return undefined;
6
6
 
7
- const getNodeText = node => {
8
- if (node instanceof Array) return node.map(getNodeText).join("");
9
- if (typeof node === "object" && node) return getNodeText(node.props.children);
10
- return node;
7
+ const getInnerText = element => {
8
+ var _element$textContent, _element$textContent$;
9
+
10
+ return element === null || element === void 0 ? void 0 : (_element$textContent = element.textContent) === null || _element$textContent === void 0 ? void 0 : (_element$textContent$ = _element$textContent.split("\n")) === null || _element$textContent$ === void 0 ? void 0 : _element$textContent$.map(text => text.trim()).join(" ");
11
11
  };
12
12
 
13
13
  const getMenuText = element => {
14
- if (element.submenu) {
15
- return element.submenu;
16
- }
14
+ var _getInnerText;
17
15
 
18
- return getNodeText(element.children);
16
+ return (_getInnerText = getInnerText(element)) === null || _getInnerText === void 0 ? void 0 : _getInnerText.toLowerCase();
19
17
  };
20
18
 
21
- const itemsList = focusableItems.map(item => item && item.type === MenuItem && getMenuText(item.props) && getMenuText(item.props).toLowerCase());
22
- const matchingItem = itemsList.find(item => item && item.startsWith(inputString));
23
- const matchingIndex = itemsList.indexOf(matchingItem);
24
- return matchingIndex === -1 ? currentFocusedIndex : matchingIndex;
19
+ const matchingItem = focusableItems.find(item => {
20
+ var _getMenuText;
21
+
22
+ if (!(item !== null && item !== void 0 && item.getAttribute("data-component"))) return false;
23
+ return [MENU_ITEM, SCROLLABLE_BLOCK_PARENT].includes(item.getAttribute("data-component")) && ((_getMenuText = getMenuText(item)) === null || _getMenuText === void 0 ? void 0 : _getMenuText.startsWith(inputString.toLowerCase()));
24
+ });
25
+ return matchingItem;
25
26
  }
26
27
 
27
28
  function menuKeyboardNavigation(event, focusableItems) {
@@ -0,0 +1,6 @@
1
+ export const MENU_ITEM: "menu-item";
2
+ export const SCROLLABLE_BLOCK: "submenu-scrollable-block";
3
+ export const SCROLLABLE_BLOCK_PARENT: "scrollable-block-parent";
4
+ export const ALL_CHILDREN_SELECTOR: string;
5
+ export const BLOCK_INDEX_SELECTOR: string;
6
+ export const MENU_ITEM_CHILDREN_LOCATOR: string;
@@ -0,0 +1,6 @@
1
+ export const MENU_ITEM = "menu-item";
2
+ export const SCROLLABLE_BLOCK = "submenu-scrollable-block";
3
+ export const SCROLLABLE_BLOCK_PARENT = "scrollable-block-parent";
4
+ export const ALL_CHILDREN_SELECTOR = `[data-component='${MENU_ITEM}'], [data-component='${SCROLLABLE_BLOCK_PARENT}']`;
5
+ export const BLOCK_INDEX_SELECTOR = `[data-component='${MENU_ITEM}'], [data-component='${SCROLLABLE_BLOCK}']`;
6
+ export const MENU_ITEM_CHILDREN_LOCATOR = `[data-component='${MENU_ITEM}']`;
@@ -1,6 +1,5 @@
1
1
  function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
2
 
3
- /* eslint-disable jsx-a11y/mouse-events-have-key-events */
4
3
  import React, { useCallback, useEffect, useContext, useMemo, useRef, useState } from "react";
5
4
  import PropTypes from "prop-types";
6
5
  import StyledMenuItemWrapper from "../../menu-item/menu-item.style";
@@ -8,11 +7,11 @@ import { StyledSubmenu, StyledSubmenuWrapper } from "./submenu.style";
8
7
  import Link from "../../../link";
9
8
  import Events from "../../../../__internal__/utils/helpers/events";
10
9
  import MenuContext from "../../menu.context";
11
- import MenuItem from "../../menu-item";
12
10
  import { characterNavigation } from "../keyboard-navigation";
13
- import ScrollableBlock from "../../scrollable-block";
14
11
  import SubmenuContext from "./submenu.context";
15
12
  import useClickAwayListener from "../../../../hooks/__internal__/useClickAwayListener";
13
+ import guid from "../../../../__internal__/utils/helpers/guid";
14
+ import { SCROLLABLE_BLOCK, SCROLLABLE_BLOCK_PARENT, BLOCK_INDEX_SELECTOR, ALL_CHILDREN_SELECTOR } from "../locators";
16
15
  const Submenu = /*#__PURE__*/React.forwardRef(({
17
16
  children,
18
17
  className,
@@ -29,46 +28,44 @@ const Submenu = /*#__PURE__*/React.forwardRef(({
29
28
  onSubmenuOpen,
30
29
  onSubmenuClose,
31
30
  onClick,
32
- indexInMenu,
33
31
  ...rest
34
32
  }, ref) => {
35
- const [blockDoubleFocus, setBlockDoubleFocus] = useState(false);
33
+ const submenuRef = useRef(null);
34
+ const submenuId = useRef(guid());
36
35
  const menuContext = useContext(MenuContext);
37
36
  const {
38
37
  inFullscreenView,
39
- openSubmenuIndex,
40
- setOpenSubmenuIndex
38
+ openSubmenuId,
39
+ setOpenSubmenuId
41
40
  } = menuContext;
42
41
  const [submenuOpen, setSubmenuOpen] = useState(false);
43
- const [submenuFocusIndex, setSubmenuFocusIndex] = useState(undefined);
42
+ const [submenuFocusId, setSubmenuFocusId] = useState(null);
43
+ const [submenuItemIds, setSubmenuItemIds] = useState([]);
44
44
  const [characterString, setCharacterString] = useState("");
45
- const formattedChildren = React.Children.map(children, child => {
46
- if (child.type === ScrollableBlock) {
47
- const blockChildren = [...child.props.children];
48
-
49
- if (child.props.parent) {
50
- blockChildren.unshift( /*#__PURE__*/React.createElement(MenuItem, null, child.props.parent));
51
- }
52
-
53
- return blockChildren;
54
- }
55
-
56
- return child;
57
- });
58
- const arrayOfFormattedChildren = React.Children.toArray(formattedChildren);
59
- const numberOfChildren = useMemo(() => React.Children.count(formattedChildren), [formattedChildren]);
45
+ const shiftTabPressed = useRef(false);
46
+ const registerItem = useCallback(id => {
47
+ setSubmenuItemIds(prevState => {
48
+ return [...prevState, id];
49
+ });
50
+ }, []);
51
+ const unregisterItem = useCallback(id => {
52
+ setSubmenuItemIds(prevState => {
53
+ return prevState.filter(itemId => itemId !== id);
54
+ });
55
+ }, []);
56
+ const numberOfChildren = submenuItemIds.length;
60
57
  const blockIndex = useMemo(() => {
61
- var _childrenArray$index;
62
-
63
- const childrenArray = React.Children.toArray(children);
64
- let index = childrenArray.findIndex(item => item.type === ScrollableBlock);
58
+ if (submenuOpen && numberOfChildren) {
59
+ var _submenuRef$current, _submenuRef$current2;
65
60
 
66
- if ((_childrenArray$index = childrenArray[index]) !== null && _childrenArray$index !== void 0 && _childrenArray$index.props.parent) {
67
- index += 1;
61
+ const childrenArray = Array.from((_submenuRef$current = submenuRef.current) === null || _submenuRef$current === void 0 ? void 0 : _submenuRef$current.querySelectorAll(BLOCK_INDEX_SELECTOR));
62
+ const scrollableBlock = (_submenuRef$current2 = submenuRef.current) === null || _submenuRef$current2 === void 0 ? void 0 : _submenuRef$current2.querySelector(`[data-component='${SCROLLABLE_BLOCK}']`);
63
+ const index = childrenArray.indexOf(scrollableBlock);
64
+ return scrollableBlock !== null && scrollableBlock !== void 0 && scrollableBlock.querySelector(`[data-component='${SCROLLABLE_BLOCK_PARENT}']`) ? index + 1 : index;
68
65
  }
69
66
 
70
- return index;
71
- }, [children]);
67
+ return -1;
68
+ }, [submenuOpen, numberOfChildren]);
72
69
  const characterTimer = useRef();
73
70
  const startCharacterTimeout = useCallback(() => {
74
71
  characterTimer.current = setTimeout(() => {
@@ -81,83 +78,88 @@ const Submenu = /*#__PURE__*/React.forwardRef(({
81
78
  }, [startCharacterTimeout]);
82
79
  const openSubmenu = useCallback(() => {
83
80
  setSubmenuOpen(true);
84
- setOpenSubmenuIndex(indexInMenu);
85
- if (onSubmenuOpen) onSubmenuOpen();
86
- }, [onSubmenuOpen, indexInMenu, setOpenSubmenuIndex]);
81
+ setOpenSubmenuId(submenuId.current);
82
+
83
+ if (onSubmenuOpen) {
84
+ onSubmenuOpen();
85
+ }
86
+ }, [onSubmenuOpen, setOpenSubmenuId]);
87
87
  const closeSubmenu = useCallback(() => {
88
+ shiftTabPressed.current = false;
88
89
  setSubmenuOpen(false);
90
+ setSubmenuFocusId(null);
89
91
 
90
- if (openSubmenuIndex === indexInMenu) {
91
- setOpenSubmenuIndex(null);
92
+ if (onSubmenuClose) {
93
+ onSubmenuClose();
92
94
  }
93
95
 
94
- if (onSubmenuClose) onSubmenuClose();
95
- setSubmenuFocusIndex(undefined);
96
- setBlockDoubleFocus(false);
97
96
  setCharacterString("");
98
- }, [onSubmenuClose, setOpenSubmenuIndex, indexInMenu, openSubmenuIndex]);
97
+ }, [onSubmenuClose]);
99
98
  useEffect(() => {
100
- if (openSubmenuIndex !== indexInMenu) {
99
+ if (openSubmenuId && openSubmenuId !== submenuId.current) {
101
100
  closeSubmenu();
102
101
  }
103
- }, [openSubmenuIndex, indexInMenu, closeSubmenu]);
104
- const handleKeyDown = useCallback((event, index = submenuFocusIndex) => {
102
+ }, [openSubmenuId, closeSubmenu]);
103
+ const findCurrentIndex = useCallback(id => {
104
+ const index = submenuItemIds.findIndex(itemId => itemId === id);
105
+ return index === -1 ? 0 : index;
106
+ }, [submenuItemIds]);
107
+ const handleKeyDown = useCallback(event => {
105
108
  if (!submenuOpen) {
106
109
  if (Events.isEnterKey(event) || Events.isSpaceKey(event) || Events.isDownKey(event) || Events.isUpKey(event)) {
107
110
  event.preventDefault();
108
111
  openSubmenu();
109
-
110
- if (!href) {
111
- setSubmenuFocusIndex(0);
112
- }
113
- }
114
-
115
- if (!event.defaultPrevented) {
116
- menuContext.handleKeyDown(event);
117
112
  }
118
113
  }
119
114
 
120
115
  if (submenuOpen) {
116
+ const index = findCurrentIndex(submenuFocusId);
121
117
  let nextIndex = index;
122
118
 
119
+ if (href && !submenuFocusId) {
120
+ if (Events.isDownKey(event) || Events.isUpKey(event) || Events.isTabKey(event) && !Events.isShiftKey(event)) {
121
+ event.preventDefault();
122
+ setSubmenuFocusId(submenuItemIds[0]);
123
+ return;
124
+ }
125
+ }
126
+
123
127
  if (Events.isTabKey(event) && !Events.isShiftKey(event)) {
124
- if (index === numberOfChildren - 1) {
128
+ if (nextIndex === numberOfChildren - 1) {
125
129
  closeSubmenu();
126
130
  return;
127
131
  }
128
132
 
129
- nextIndex = index + 1;
130
- setBlockDoubleFocus(true);
133
+ shiftTabPressed.current = false;
134
+ nextIndex += 1;
131
135
  }
132
136
 
133
137
  if (Events.isTabKey(event) && Events.isShiftKey(event)) {
134
- if (index === 0) {
138
+ if (nextIndex === 0) {
135
139
  closeSubmenu();
136
140
  return;
137
141
  }
138
142
 
139
- nextIndex = index - 1;
140
- setBlockDoubleFocus(true);
143
+ shiftTabPressed.current = true;
144
+ nextIndex -= 1;
141
145
  }
142
146
 
143
147
  if (Events.isDownKey(event)) {
144
148
  event.preventDefault();
149
+ shiftTabPressed.current = false;
145
150
 
146
- if (index < numberOfChildren - 1) {
147
- nextIndex = index + 1;
151
+ if (nextIndex < numberOfChildren - 1) {
152
+ nextIndex += 1;
148
153
  }
149
-
150
- setBlockDoubleFocus(false);
151
154
  }
152
155
 
153
156
  if (Events.isUpKey(event)) {
154
157
  event.preventDefault();
158
+ shiftTabPressed.current = false;
155
159
 
156
- if (index > 0) {
157
- nextIndex = index - 1;
160
+ if (nextIndex > 0) {
161
+ nextIndex -= 1;
158
162
  }
159
-
160
- setBlockDoubleFocus(false);
161
163
  }
162
164
 
163
165
  if (Events.isEscKey(event)) {
@@ -168,16 +170,19 @@ const Submenu = /*#__PURE__*/React.forwardRef(({
168
170
 
169
171
  if (Events.isHomeKey(event)) {
170
172
  event.preventDefault();
173
+ shiftTabPressed.current = false;
171
174
  nextIndex = 0;
172
175
  }
173
176
 
174
177
  if (Events.isEndKey(event)) {
175
178
  event.preventDefault();
179
+ shiftTabPressed.current = false;
176
180
  nextIndex = numberOfChildren - 1;
177
181
  }
178
182
 
179
183
  if (event.key.length === 1) {
180
184
  event.stopPropagation();
185
+ shiftTabPressed.current = false;
181
186
 
182
187
  if (characterTimer.current) {
183
188
  restartCharacterTimeout();
@@ -196,31 +201,21 @@ const Submenu = /*#__PURE__*/React.forwardRef(({
196
201
  setTimeout(() => closeSubmenu());
197
202
  }
198
203
 
199
- if (href && index === undefined) {
200
- if (Events.isEnterKey(event) || Events.isTabKey(event) && Events.isShiftKey(event)) {
201
- closeSubmenu();
202
- return;
203
- }
204
-
205
- if (Events.isSpaceKey(event) || Events.isDownKey(event) || Events.isUpKey(event) || Events.isTabKey(event)) {
206
- nextIndex = 0;
207
- }
208
- } // Defensive check in case an unhandled key event from a child component
209
- // has bubbled up
210
-
211
-
212
- if (!nextIndex && nextIndex !== 0) return; // Check that next index contains a MenuItem
213
- // If not, call handleKeyDown again
214
-
215
- const nextChild = arrayOfFormattedChildren[nextIndex];
204
+ if (href && Events.isEnterKey(event)) {
205
+ closeSubmenu();
206
+ return;
207
+ }
216
208
 
217
- if ((nextChild === null || nextChild === void 0 ? void 0 : nextChild.type) === MenuItem) {
218
- setSubmenuFocusIndex(nextIndex);
219
- } else {
220
- handleKeyDown(event, nextIndex);
209
+ if (nextIndex !== index) {
210
+ setSubmenuFocusId(submenuItemIds[nextIndex]);
221
211
  }
222
212
  }
223
- }, [submenuFocusIndex, submenuOpen, href, menuContext, arrayOfFormattedChildren, numberOfChildren, openSubmenu, closeSubmenu, onKeyDown, characterString, restartCharacterTimeout, startCharacterTimeout]);
213
+ }, [submenuItemIds, submenuOpen, href, numberOfChildren, submenuFocusId, findCurrentIndex, openSubmenu, closeSubmenu, onKeyDown, characterString, restartCharacterTimeout, startCharacterTimeout]);
214
+ useEffect(() => {
215
+ if (submenuOpen && !href && !submenuFocusId && submenuItemIds.length) {
216
+ setSubmenuFocusId(submenuItemIds[0]);
217
+ }
218
+ }, [submenuOpen, href, submenuFocusId, submenuItemIds]);
224
219
 
225
220
  const handleClickAway = () => {
226
221
  document.removeEventListener("click", handleClickAway);
@@ -237,11 +232,16 @@ const Submenu = /*#__PURE__*/React.forwardRef(({
237
232
 
238
233
  useEffect(() => {
239
234
  if (characterString !== "") {
240
- const nextIndex = characterNavigation(characterString, React.Children.toArray(formattedChildren), submenuFocusIndex);
241
- setSubmenuFocusIndex(nextIndex);
242
- } // eslint-disable-next-line react-hooks/exhaustive-deps
235
+ var _submenuRef$current3;
236
+
237
+ const submenuChildren = Array.from((_submenuRef$current3 = submenuRef.current) === null || _submenuRef$current3 === void 0 ? void 0 : _submenuRef$current3.querySelectorAll(ALL_CHILDREN_SELECTOR));
238
+ const nextItem = characterNavigation(characterString, submenuChildren);
243
239
 
244
- }, [characterString]);
240
+ if (nextItem) {
241
+ setSubmenuFocusId(nextItem.id);
242
+ }
243
+ }
244
+ }, [characterString, submenuItemIds]);
245
245
  const handleClickInside = useClickAwayListener(handleClickAway);
246
246
 
247
247
  if (inFullscreenView) {
@@ -265,15 +265,17 @@ const Submenu = /*#__PURE__*/React.forwardRef(({
265
265
  "data-component": "submenu",
266
266
  variant: variant,
267
267
  menuType: menuContext.menuType,
268
- inFullscreenView: inFullscreenView
269
- }, React.Children.map(children, (child, index) => /*#__PURE__*/React.createElement(SubmenuContext.Provider, {
268
+ inFullscreenView: inFullscreenView,
269
+ ref: submenuRef
270
+ }, /*#__PURE__*/React.createElement(SubmenuContext.Provider, {
270
271
  value: {
271
- isFocused: submenuFocusIndex === index,
272
- focusIndex: submenuFocusIndex,
273
272
  handleKeyDown,
274
- blockIndex
273
+ blockIndex,
274
+ registerItem,
275
+ unregisterItem,
276
+ updateFocusId: setSubmenuFocusId
275
277
  }
276
- }, child))));
278
+ }, children)));
277
279
  }
278
280
 
279
281
  return /*#__PURE__*/React.createElement(StyledSubmenuWrapper, {
@@ -281,7 +283,8 @@ const Submenu = /*#__PURE__*/React.forwardRef(({
281
283
  onMouseOver: !clickToOpen ? () => openSubmenu() : undefined,
282
284
  onMouseLeave: () => closeSubmenu(),
283
285
  isSubmenuOpen: submenuOpen,
284
- onClick: handleClickInside
286
+ onClick: handleClickInside,
287
+ ref: submenuRef
285
288
  }, /*#__PURE__*/React.createElement(StyledMenuItemWrapper, _extends({}, rest, {
286
289
  className: className,
287
290
  menuType: menuContext.menuType,
@@ -305,16 +308,17 @@ const Submenu = /*#__PURE__*/React.forwardRef(({
305
308
  variant: variant,
306
309
  menuType: menuContext.menuType,
307
310
  role: blockIndex === 0 ? "presentation" : "list"
308
- }, React.Children.map(children, (child, index) => /*#__PURE__*/React.createElement(SubmenuContext.Provider, {
311
+ }, /*#__PURE__*/React.createElement(SubmenuContext.Provider, {
309
312
  value: {
310
- isFocused: !blockDoubleFocus && submenuFocusIndex === index,
311
- focusIndex: submenuFocusIndex,
313
+ submenuFocusId,
312
314
  handleKeyDown,
313
315
  blockIndex,
314
- updateFocusIndex: setSubmenuFocusIndex,
315
- itemIndex: child.type === MenuItem ? index : undefined
316
+ registerItem,
317
+ unregisterItem,
318
+ updateFocusId: setSubmenuFocusId,
319
+ shiftTabPressed: shiftTabPressed.current
316
320
  }
317
- }, child))));
321
+ }, children)));
318
322
  });
319
323
  Submenu.propTypes = {
320
324
  /** Children elements */
@@ -364,9 +368,6 @@ Submenu.propTypes = {
364
368
  onSubmenuClose: PropTypes.func,
365
369
 
366
370
  /** Callback triggered when the top-level menu item is clicked */
367
- onClick: PropTypes.func,
368
-
369
- /** index of child in the parent menu */
370
- indexInMenu: PropTypes.number
371
+ onClick: PropTypes.func
371
372
  };
372
373
  export default Submenu;
@@ -85,8 +85,7 @@ const MenuFullscreen = ({
85
85
  value: {
86
86
  inFullscreenView: true,
87
87
  menuType,
88
- inMenu: true,
89
- setOpenSubmenuIndex: () => {}
88
+ inMenu: true
90
89
  }
91
90
  }, React.Children.map(children, (child, index) => /*#__PURE__*/React.createElement(React.Fragment, null, child, index < React.Children.count(children) - 1 && /*#__PURE__*/React.createElement(MenuDivider, null))))))))));
92
91
  };
@@ -1,2 +1 @@
1
- /* eslint-disable import/no-cycle */
2
1
  export { default } from "./menu-item.component";