carbon-react 109.2.4 → 109.3.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 (91) hide show
  1. package/esm/__internal__/field-help/field-help.component.d.ts +10 -0
  2. package/esm/__internal__/field-help/field-help.component.js +12 -16
  3. package/esm/__internal__/field-help/field-help.style.d.ts +8 -0
  4. package/esm/__internal__/field-help/field-help.style.js +2 -10
  5. package/esm/__internal__/field-help/index.d.ts +2 -1
  6. package/esm/__internal__/focus-trap/focus-trap-utils.d.ts +1 -2
  7. package/esm/__internal__/focus-trap/focus-trap-utils.js +57 -8
  8. package/esm/__internal__/focus-trap/focus-trap.component.js +35 -25
  9. package/esm/__spec_helper__/index.d.ts +1 -0
  10. package/esm/__spec_helper__/index.js +4 -10
  11. package/esm/__spec_helper__/mock-match-media.d.ts +2 -2
  12. package/esm/__spec_helper__/mock-match-media.js +2 -2
  13. package/esm/__spec_helper__/mock-resize-observer.d.ts +2 -0
  14. package/esm/components/alert/alert.component.js +9 -0
  15. package/esm/components/dialog/dialog.component.js +9 -2
  16. package/esm/components/dialog/dialog.d.ts +2 -0
  17. package/esm/components/dialog-full-screen/dialog-full-screen.component.js +9 -2
  18. package/esm/components/dialog-full-screen/dialog-full-screen.d.ts +2 -0
  19. package/esm/components/grid/grid-container/grid-container.component.d.ts +8 -0
  20. package/esm/components/grid/grid-container/grid-container.component.js +1821 -21
  21. package/esm/components/grid/grid-container/grid-container.style.d.ts +3 -0
  22. package/esm/components/grid/grid-container/grid-container.style.js +2 -2
  23. package/esm/components/grid/grid-container/index.d.ts +2 -1
  24. package/esm/components/grid/grid-item/grid-item.component.d.ts +11 -0
  25. package/esm/components/grid/grid-item/grid-item.component.js +1221 -45
  26. package/esm/components/grid/grid-item/grid-item.style.d.ts +17 -0
  27. package/esm/components/grid/grid-item/grid-item.style.js +38 -62
  28. package/esm/components/grid/grid-item/index.d.ts +2 -1
  29. package/esm/components/grid/index.d.ts +2 -0
  30. package/esm/components/grid/index.js +2 -3
  31. package/esm/components/multi-action-button/multi-action-button.component.js +14 -84
  32. package/esm/components/select/utils/highlight-part-of-text.js +13 -1
  33. package/esm/components/select/utils/with-filter.hoc.js +3 -2
  34. package/esm/components/sidebar/sidebar.component.js +9 -2
  35. package/esm/components/sidebar/sidebar.d.ts +2 -0
  36. package/esm/components/split-button/split-button.component.js +15 -82
  37. package/esm/components/toast/toast.component.js +35 -9
  38. package/esm/components/toast/toast.d.ts +5 -1
  39. package/esm/hooks/__internal__/useMenuKeyboardNavigation/index.d.ts +1 -0
  40. package/esm/hooks/__internal__/useMenuKeyboardNavigation/index.js +1 -0
  41. package/esm/hooks/__internal__/useMenuKeyboardNavigation/useMenuKeyboardNavigation.d.ts +2 -0
  42. package/esm/hooks/__internal__/useMenuKeyboardNavigation/useMenuKeyboardNavigation.js +70 -0
  43. package/lib/__internal__/field-help/field-help.component.d.ts +10 -0
  44. package/lib/__internal__/field-help/field-help.component.js +12 -16
  45. package/lib/__internal__/field-help/field-help.style.d.ts +8 -0
  46. package/lib/__internal__/field-help/field-help.style.js +2 -13
  47. package/lib/__internal__/field-help/index.d.ts +2 -1
  48. package/lib/__internal__/focus-trap/focus-trap-utils.d.ts +1 -2
  49. package/lib/__internal__/focus-trap/focus-trap-utils.js +57 -10
  50. package/lib/__internal__/focus-trap/focus-trap.component.js +34 -24
  51. package/lib/__spec_helper__/index.d.ts +1 -0
  52. package/lib/__spec_helper__/index.js +3 -10
  53. package/lib/__spec_helper__/mock-match-media.d.ts +2 -2
  54. package/lib/__spec_helper__/mock-match-media.js +4 -4
  55. package/lib/__spec_helper__/mock-resize-observer.d.ts +2 -0
  56. package/lib/components/alert/alert.component.js +9 -0
  57. package/lib/components/dialog/dialog.component.js +9 -2
  58. package/lib/components/dialog/dialog.d.ts +2 -0
  59. package/lib/components/dialog-full-screen/dialog-full-screen.component.js +9 -2
  60. package/lib/components/dialog-full-screen/dialog-full-screen.d.ts +2 -0
  61. package/lib/components/grid/grid-container/grid-container.component.d.ts +8 -0
  62. package/lib/components/grid/grid-container/grid-container.component.js +1826 -22
  63. package/lib/components/grid/grid-container/grid-container.style.d.ts +3 -0
  64. package/lib/components/grid/grid-container/grid-container.style.js +2 -2
  65. package/lib/components/grid/grid-container/index.d.ts +2 -1
  66. package/lib/components/grid/grid-item/grid-item.component.d.ts +11 -0
  67. package/lib/components/grid/grid-item/grid-item.component.js +1221 -46
  68. package/lib/components/grid/grid-item/grid-item.style.d.ts +17 -0
  69. package/lib/components/grid/grid-item/grid-item.style.js +37 -67
  70. package/lib/components/grid/grid-item/index.d.ts +2 -1
  71. package/lib/components/grid/index.d.ts +2 -0
  72. package/lib/components/multi-action-button/multi-action-button.component.js +13 -83
  73. package/lib/components/select/utils/highlight-part-of-text.js +13 -1
  74. package/lib/components/select/utils/with-filter.hoc.js +3 -2
  75. package/lib/components/sidebar/sidebar.component.js +9 -2
  76. package/lib/components/sidebar/sidebar.d.ts +2 -0
  77. package/lib/components/split-button/split-button.component.js +14 -83
  78. package/lib/components/toast/toast.component.js +35 -7
  79. package/lib/components/toast/toast.d.ts +5 -1
  80. package/lib/hooks/__internal__/useMenuKeyboardNavigation/index.d.ts +1 -0
  81. package/lib/hooks/__internal__/useMenuKeyboardNavigation/index.js +15 -0
  82. package/lib/hooks/__internal__/useMenuKeyboardNavigation/package.json +6 -0
  83. package/lib/hooks/__internal__/useMenuKeyboardNavigation/useMenuKeyboardNavigation.d.ts +2 -0
  84. package/lib/hooks/__internal__/useMenuKeyboardNavigation/useMenuKeyboardNavigation.js +85 -0
  85. package/package.json +3 -4
  86. package/esm/__internal__/field-help/field-help.d.ts +0 -14
  87. package/esm/components/grid/grid-container/grid-container.d.ts +0 -18
  88. package/esm/components/grid/grid-item/grid-item.d.ts +0 -42
  89. package/lib/__internal__/field-help/field-help.d.ts +0 -14
  90. package/lib/components/grid/grid-container/grid-container.d.ts +0 -18
  91. package/lib/components/grid/grid-item/grid-item.d.ts +0 -42
@@ -0,0 +1,17 @@
1
+ import { PaddingProps, GridAreaProps, GridColumnProps, GridRowProps } from "styled-system";
2
+ export declare function getSpacing(prop?: PaddingProps[keyof PaddingProps]): string;
3
+ interface GridProperties extends PaddingProps {
4
+ alignSelf?: string;
5
+ justifySelf?: string;
6
+ gridColumn?: GridColumnProps["gridColumn"];
7
+ gridRow?: GridRowProps["gridRow"];
8
+ gridArea?: GridAreaProps["gridArea"];
9
+ }
10
+ interface ResponsiveSettings extends GridProperties {
11
+ maxWidth?: string;
12
+ }
13
+ export interface StyledGridItemProps extends GridProperties {
14
+ responsiveSettings?: ResponsiveSettings[];
15
+ }
16
+ declare const StyledGridItem: import("styled-components").StyledComponent<"div", any, StyledGridItemProps, never>;
17
+ export default StyledGridItem;
@@ -1,42 +1,5 @@
1
1
  import styled, { css } from "styled-components";
2
- import { grid, flexbox } from "styled-system";
3
- import styledSystemPropTypes from "@styled-system/prop-types";
4
- import { padding } from "@styled-system/space";
5
- import PropTypes from "prop-types";
6
- import { filterStyledSystemPaddingProps } from "../../../style/utils";
7
-
8
- function responsiveGridItem(responsiveSettings) {
9
- return responsiveSettings.map(setting => {
10
- const {
11
- alignSelf,
12
- gridArea,
13
- gridColumn,
14
- gridRow,
15
- maxWidth,
16
- justifySelf,
17
- p,
18
- pl,
19
- pr,
20
- pt,
21
- pb
22
- } = setting;
23
- return css`
24
- @media screen and (max-width: ${maxWidth}) {
25
- align-self: ${alignSelf || "stretch"};
26
- justify-self: ${justifySelf || "stretch"};
27
- grid-area: ${gridArea};
28
- grid-column: ${gridColumn};
29
- grid-row: ${gridRow};
30
- padding: ${getSpacing(p)};
31
- padding-left: ${getSpacing(pl)};
32
- padding-right: ${getSpacing(pr)};
33
- padding-top: ${getSpacing(pt)};
34
- padding-bottom: ${getSpacing(pb)};
35
- }
36
- `;
37
- });
38
- }
39
-
2
+ import { grid, flexbox, padding } from "styled-system";
40
3
  export function getSpacing(prop) {
41
4
  if (typeof prop === "number") {
42
5
  switch (prop) {
@@ -78,17 +41,49 @@ export function getSpacing(prop) {
78
41
  }
79
42
  }
80
43
 
81
- return prop;
44
+ return String(prop);
82
45
  }
83
- const paddingPropTypes = filterStyledSystemPaddingProps(styledSystemPropTypes.space);
84
- const GridItemStyle = styled.div`
46
+
47
+ function responsiveGridItem(responsiveSettings) {
48
+ return responsiveSettings.map(setting => {
49
+ const {
50
+ alignSelf,
51
+ gridArea,
52
+ gridColumn,
53
+ gridRow,
54
+ maxWidth,
55
+ justifySelf,
56
+ p,
57
+ pl,
58
+ pr,
59
+ pt,
60
+ pb
61
+ } = setting;
62
+ return css`
63
+ @media screen and (max-width: ${maxWidth}) {
64
+ align-self: ${alignSelf || "stretch"};
65
+ justify-self: ${justifySelf || "stretch"};
66
+ grid-area: ${gridArea};
67
+ grid-column: ${gridColumn};
68
+ grid-row: ${gridRow};
69
+ padding: ${getSpacing(p)};
70
+ padding-left: ${getSpacing(pl)};
71
+ padding-right: ${getSpacing(pr)};
72
+ padding-top: ${getSpacing(pt)};
73
+ padding-bottom: ${getSpacing(pb)};
74
+ }
75
+ `;
76
+ });
77
+ }
78
+
79
+ const StyledGridItem = styled.div`
85
80
  margin: 0;
86
81
  ${padding}
87
82
 
88
83
  ${({
89
84
  alignSelf,
90
85
  justifySelf,
91
- gridArea,
86
+ gridArea = "auto / 1 / auto / 13",
92
87
  gridColumn,
93
88
  gridRow,
94
89
  responsiveSettings
@@ -108,23 +103,4 @@ const GridItemStyle = styled.div`
108
103
  `}
109
104
  `}
110
105
  `;
111
- GridItemStyle.propTypes = {
112
- alignSelf: PropTypes.string,
113
- gridColumn: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
114
- gridRow: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
115
- justifySelf: PropTypes.string,
116
- ...paddingPropTypes,
117
- responsiveSettings: PropTypes.arrayOf(PropTypes.shape({
118
- alignSelf: PropTypes.string,
119
- gridArea: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
120
- gridColumn: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
121
- gridRow: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
122
- justifySelf: PropTypes.string,
123
- maxWidth: PropTypes.string,
124
- ...paddingPropTypes
125
- }))
126
- };
127
- GridItemStyle.defaultProps = {
128
- gridArea: "auto / 1 / auto / 13"
129
- };
130
- export default GridItemStyle;
106
+ export default StyledGridItem;
@@ -1 +1,2 @@
1
- export { default } from "./grid-item";
1
+ export { default } from "./grid-item.component";
2
+ export type { GridItemProps } from "./grid-item.component";
@@ -1,2 +1,4 @@
1
1
  export { default as GridContainer } from "./grid-container";
2
+ export type { GridContainerProps } from "./grid-container";
2
3
  export { default as GridItem } from "./grid-item";
4
+ export type { GridItemProps } from "./grid-item";
@@ -1,3 +1,2 @@
1
- import GridContainer from "./grid-container";
2
- import GridItem from "./grid-item";
3
- export { GridContainer, GridItem };
1
+ export { default as GridContainer } from "./grid-container";
2
+ export { default as GridItem } from "./grid-item";
@@ -1,6 +1,6 @@
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
- import React, { useCallback, useEffect, useState, useRef, useMemo } from "react";
3
+ import React, { useCallback, useState, useRef, useMemo } from "react";
4
4
  import PropTypes from "prop-types";
5
5
  import useClickAwayListener from "../../hooks/__internal__/useClickAwayListener";
6
6
  import { StyledMultiActionButton, StyledButtonChildrenContainer } from "./multi-action-button.style";
@@ -8,7 +8,7 @@ import Button from "../button";
8
8
  import Events from "../../__internal__/utils/helpers/events";
9
9
  import Popover from "../../__internal__/popover";
10
10
  import { filterStyledSystemMarginProps, filterOutStyledSystemSpacingProps } from "../../style/utils";
11
- import { defaultFocusableSelectors } from "../../__internal__/focus-trap/focus-trap-utils";
11
+ import useMenuKeyboardNavigation from "../../hooks/__internal__/useMenuKeyboardNavigation";
12
12
 
13
13
  const MultiActionButton = ({
14
14
  align = "left",
@@ -26,14 +26,11 @@ const MultiActionButton = ({
26
26
  const buttonRef = useRef(null);
27
27
  const buttonContainer = useRef(null);
28
28
  const buttonChildren = useMemo(() => React.Children.toArray(children), [children]);
29
- const additionalButtons = useRef(buttonChildren.map(() => /*#__PURE__*/React.createRef()));
30
- const listening = useRef(false);
31
- const isMainButtonFocused = useRef(false);
32
- const isFocusedAfterClosing = useRef(false);
29
+ const buttonChildrenRefs = useMemo(() => buttonChildren.map(() => /*#__PURE__*/React.createRef()), [buttonChildren]);
33
30
  const [showAdditionalButtons, setShowAdditionalButtons] = useState(false);
34
31
  const [minWidth, setMinWidth] = useState(0);
35
32
  const hideButtons = useCallback(() => {
36
- if (isMainButtonFocused.current) return;
33
+ if (buttonRef.current === document.activeElement) return;
37
34
  setShowAdditionalButtons(false);
38
35
  }, []);
39
36
 
@@ -55,15 +52,13 @@ const MultiActionButton = ({
55
52
  const props = {
56
53
  key: index.toString(),
57
54
  role: "menuitem",
58
- ref: additionalButtons.current[index],
55
+ ref: buttonChildrenRefs[index],
59
56
  tabIndex: -1,
60
57
  onClick: ev => {
61
58
  var _buttonRef$current;
62
59
 
63
60
  if (child.props.onClick) child.props.onClick(ev);
64
- isMainButtonFocused.current = false;
65
61
  hideButtons();
66
- isFocusedAfterClosing.current = true;
67
62
  (_buttonRef$current = buttonRef.current) === null || _buttonRef$current === void 0 ? void 0 : _buttonRef$current.focus();
68
63
  }
69
64
  };
@@ -71,62 +66,10 @@ const MultiActionButton = ({
71
66
  });
72
67
  };
73
68
 
74
- const handleKeyDown = useCallback(ev => {
75
- const currentIndex = additionalButtons.current.findIndex(node => node.current === document.activeElement);
76
- let nextIndex = -1;
77
-
78
- if (Events.isUpKey(ev)) {
79
- nextIndex = currentIndex > 0 ? currentIndex - 1 : buttonChildren.length - 1;
80
- ev.preventDefault();
81
- }
82
-
83
- if (Events.isDownKey(ev)) {
84
- nextIndex = currentIndex < buttonChildren.length - 1 ? currentIndex + 1 : 0;
85
- ev.preventDefault();
86
- }
87
-
88
- if (Events.isTabKey(ev)) {
89
- var _elements$indexOf;
90
-
91
- const elements = Array.from(document.querySelectorAll(defaultFocusableSelectors)).filter(el => Number(el.tabIndex) !== -1);
92
- const indexOf = elements.indexOf(buttonRef.current);
93
- (_elements$indexOf = elements[indexOf]) === null || _elements$indexOf === void 0 ? void 0 : _elements$indexOf.focus(); // timeout enforces that the "hideButtons" method will be run after browser focuses on the next element
94
-
95
- setTimeout(hideButtons, 0);
96
- }
97
-
98
- if (nextIndex > -1) {
99
- var _additionalButtons$cu;
100
-
101
- (_additionalButtons$cu = additionalButtons.current[nextIndex].current) === null || _additionalButtons$cu === void 0 ? void 0 : _additionalButtons$cu.focus();
102
- }
103
- }, [buttonChildren, hideButtons]);
104
- const addListeners = useCallback(() => {
105
- /* istanbul ignore else */
106
- if (!listening.current) {
107
- document.addEventListener("keydown", handleKeyDown);
108
- listening.current = true;
109
- }
110
- }, [handleKeyDown]);
111
- const removeListeners = useCallback(() => {
112
- /* istanbul ignore else */
113
- if (listening.current) {
114
- document.removeEventListener("keydown", handleKeyDown);
115
- listening.current = false;
116
- }
117
- }, [handleKeyDown]);
118
- useEffect(() => {
119
- if (showAdditionalButtons) {
120
- addListeners();
121
- }
122
-
123
- return () => {
124
- removeListeners();
125
- };
126
- }, [showAdditionalButtons, addListeners, removeListeners]);
69
+ const handleKeyDown = useMenuKeyboardNavigation(buttonRef, buttonChildrenRefs, hideButtons);
127
70
 
128
71
  const handleMainButtonKeyDown = ev => {
129
- if (Events.isEnterKey(ev) || Events.isSpaceKey(ev) || Events.isDownKey(ev)) {
72
+ if (Events.isEnterKey(ev) || Events.isSpaceKey(ev) || Events.isDownKey(ev) || Events.isUpKey(ev)) {
130
73
  ev.preventDefault();
131
74
 
132
75
  if (!showAdditionalButtons) {
@@ -135,34 +78,20 @@ const MultiActionButton = ({
135
78
 
136
79
 
137
80
  setTimeout(() => {
138
- var _additionalButtons$cu2, _additionalButtons$cu3;
81
+ var _buttonChildrenRefs$, _buttonChildrenRefs$$;
139
82
 
140
- (_additionalButtons$cu2 = additionalButtons.current[0]) === null || _additionalButtons$cu2 === void 0 ? void 0 : (_additionalButtons$cu3 = _additionalButtons$cu2.current) === null || _additionalButtons$cu3 === void 0 ? void 0 : _additionalButtons$cu3.focus();
83
+ (_buttonChildrenRefs$ = buttonChildrenRefs[0]) === null || _buttonChildrenRefs$ === void 0 ? void 0 : (_buttonChildrenRefs$$ = _buttonChildrenRefs$.current) === null || _buttonChildrenRefs$$ === void 0 ? void 0 : _buttonChildrenRefs$$.focus();
141
84
  }, 0);
85
+ } else if (Events.isEscKey(ev)) {
86
+ ev.preventDefault();
87
+ setShowAdditionalButtons(false);
142
88
  }
143
89
  };
144
90
 
145
- const focusMainButton = () => {
146
- isMainButtonFocused.current = true;
147
-
148
- if (isFocusedAfterClosing.current) {
149
- isFocusedAfterClosing.current = false;
150
- return;
151
- }
152
-
153
- showButtons();
154
- };
155
-
156
- const blurMainButton = () => {
157
- isMainButtonFocused.current = false;
158
- };
159
-
160
91
  const mainButtonProps = {
161
92
  disabled,
162
93
  displayed: showAdditionalButtons,
163
94
  onTouchStart: showButtons,
164
- onFocus: focusMainButton,
165
- onBlur: blurMainButton,
166
95
  onKeyDown: handleMainButtonKeyDown,
167
96
  buttonType,
168
97
  size,
@@ -182,7 +111,8 @@ const MultiActionButton = ({
182
111
  "data-element": "additional-buttons",
183
112
  align: align,
184
113
  minWidth: minWidth,
185
- ref: buttonContainer
114
+ ref: buttonContainer,
115
+ onKeyDown: handleKeyDown
186
116
  }, childrenWithProps()));
187
117
 
188
118
  useClickAwayListener([ref], hideButtons);
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
  import MatchingText from "./matching-text.style";
3
- export default function highlightPartOfText(text, partToHighlight) {
3
+
4
+ function highlightPartOfText(text, partToHighlight) {
4
5
  if (!partToHighlight || !partToHighlight.length || !text) return text;
5
6
  const lowercaseText = text.toLowerCase();
6
7
  const lowercasePart = partToHighlight.toLowerCase();
@@ -26,4 +27,15 @@ export default function highlightPartOfText(text, partToHighlight) {
26
27
  key: "following"
27
28
  }, followingText)];
28
29
  return newValue;
30
+ }
31
+
32
+ export default function highlightPartOfTextRecursive(child, partToHighlight) {
33
+ if (typeof child === "string") {
34
+ return highlightPartOfText(child, partToHighlight);
35
+ }
36
+
37
+ const highlightedChildren = React.Children.map(child.props.children, grandChild => highlightPartOfTextRecursive(grandChild, partToHighlight));
38
+ return /*#__PURE__*/React.cloneElement(child, {
39
+ children: highlightedChildren
40
+ });
29
41
  }
@@ -29,9 +29,10 @@ const filterOptionRows = (optionRow, filterText) => {
29
29
  const processedText = cell.props.children.toLowerCase();
30
30
  const processedValue = filterText.toLowerCase();
31
31
  return processedText.includes(processedValue);
32
- }
32
+ } // filter recursively based on children
33
+
33
34
 
34
- return false;
35
+ return filterOptionRows(cell, filterText);
35
36
  });
36
37
 
37
38
  if (hasText) {
@@ -26,6 +26,7 @@ const Sidebar = /*#__PURE__*/React.forwardRef(({
26
26
  children,
27
27
  onCancel,
28
28
  role = "dialog",
29
+ focusableContainers,
29
30
  ...rest
30
31
  }, ref) => {
31
32
  const locale = useLocale();
@@ -84,7 +85,8 @@ const Sidebar = /*#__PURE__*/React.forwardRef(({
84
85
  className: "carbon-sidebar"
85
86
  }, componentTags), enableBackgroundUI ? sidebar : /*#__PURE__*/React.createElement(FocusTrap, {
86
87
  wrapperRef: sidebarRef,
87
- isOpen: open
88
+ isOpen: open,
89
+ additionalWrapperRefs: focusableContainers
88
90
  }, sidebar));
89
91
  });
90
92
  Sidebar.propTypes = {
@@ -122,7 +124,12 @@ Sidebar.propTypes = {
122
124
  header: PropTypes.node,
123
125
 
124
126
  /** The ARIA role to be applied to the container */
125
- role: PropTypes.string
127
+ role: PropTypes.string,
128
+
129
+ /** an optional array of refs to containers whose content should also be reachable by tabbing from the sidebar */
130
+ focusableContainers: PropTypes.arrayOf(PropTypes.shape({
131
+ current: PropTypes.any
132
+ }))
126
133
  };
127
134
  Sidebar.defaultProps = {
128
135
  position: "right",
@@ -42,6 +42,8 @@ export interface SidebarProps {
42
42
  | "medium-large"
43
43
  | "large"
44
44
  | "extra-large";
45
+ /** an optional array of refs to containers whose content should also be reachable by tabbing from the sidebar */
46
+ focusableContainers?: React.MutableRefObject<HTMLElement>[];
45
47
  }
46
48
 
47
49
  declare const SidebarContext: React.Context<SidebarContextProps>;
@@ -1,6 +1,6 @@
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
- import React, { useRef, useState, useContext, useCallback, useEffect } from "react";
3
+ import React, { useContext, useCallback, useMemo, useRef, useState } from "react";
4
4
  import { ThemeContext } from "styled-components";
5
5
  import PropTypes from "prop-types";
6
6
  import styledSystemPropTypes from "@styled-system/prop-types";
@@ -15,7 +15,7 @@ import guid from "../../__internal__/utils/helpers/guid";
15
15
  import Popover from "../../__internal__/popover";
16
16
  import { filterStyledSystemMarginProps, filterOutStyledSystemSpacingProps } from "../../style/utils";
17
17
  import { baseTheme } from "../../style/themes";
18
- import { defaultFocusableSelectors } from "../../__internal__/focus-trap/focus-trap-utils";
18
+ import useMenuKeyboardNavigation from "../../hooks/__internal__/useMenuKeyboardNavigation";
19
19
  const marginPropTypes = filterStyledSystemMarginProps(styledSystemPropTypes.space);
20
20
  const CONTENT_WIDTH_RATIO = 0.75;
21
21
 
@@ -35,73 +35,19 @@ const SplitButton = ({
35
35
  ...rest
36
36
  }) => {
37
37
  const theme = useContext(ThemeContext) || baseTheme;
38
- const isToggleButtonFocused = useRef(false);
39
- const isFocusedAfterClosing = useRef(false);
40
38
  const buttonLabelId = useRef(guid());
41
- const buttonChildren = React.Children.toArray(children);
42
- const additionalButtons = useRef(buttonChildren.map(() => /*#__PURE__*/React.createRef()));
39
+ const buttonChildren = useMemo(() => React.Children.toArray(children), [children]);
40
+ const buttonChildrenRefs = useMemo(() => buttonChildren.map(() => /*#__PURE__*/React.createRef()), [buttonChildren]);
43
41
  const splitButtonNode = useRef();
44
42
  const toggleButton = useRef();
45
43
  const buttonContainer = useRef();
46
44
  const [showAdditionalButtons, setShowAdditionalButtons] = useState(false);
47
45
  const [minWidth, setMinWidth] = useState(0);
48
- const listening = useRef(false);
49
46
  const hideButtons = useCallback(() => {
50
- if (isToggleButtonFocused.current) return;
47
+ if (toggleButton.current === document.activeElement) return;
51
48
  setShowAdditionalButtons(false);
52
49
  }, []);
53
- const handleKeyDown = useCallback(ev => {
54
- const numOfChildren = children.length - 1;
55
- const currentIndex = additionalButtons.current.findIndex(node => node.current === document.activeElement);
56
- let nextIndex = -1;
57
-
58
- if (Events.isUpKey(ev)) {
59
- nextIndex = currentIndex > 0 ? currentIndex - 1 : numOfChildren;
60
- ev.preventDefault();
61
- }
62
-
63
- if (Events.isDownKey(ev)) {
64
- nextIndex = currentIndex < numOfChildren ? currentIndex + 1 : 0;
65
- ev.preventDefault();
66
- }
67
-
68
- if (Events.isTabKey(ev)) {
69
- var _elements$indexOf;
70
-
71
- const elements = Array.from(document.querySelectorAll(defaultFocusableSelectors)).filter(el => Number(el.tabIndex) !== -1);
72
- const indexOf = elements.indexOf(toggleButton.current);
73
- (_elements$indexOf = elements[indexOf]) === null || _elements$indexOf === void 0 ? void 0 : _elements$indexOf.focus(); // timeout enforces that the "hideButtons" method will be run after browser focuses on the next element
74
-
75
- setTimeout(hideButtons, 0);
76
- }
77
-
78
- if (nextIndex > -1) {
79
- additionalButtons.current[nextIndex].current.focus();
80
- }
81
- }, [hideButtons, children]);
82
- const addListeners = useCallback(() => {
83
- /* istanbul ignore else */
84
- if (!listening.current) {
85
- document.addEventListener("keydown", handleKeyDown);
86
- listening.current = true;
87
- }
88
- }, [handleKeyDown]);
89
- const removeListeners = useCallback(() => {
90
- /* istanbul ignore else */
91
- if (listening.current) {
92
- document.removeEventListener("keydown", handleKeyDown);
93
- listening.current = false;
94
- }
95
- }, [handleKeyDown]);
96
- useEffect(() => {
97
- if (showAdditionalButtons) {
98
- addListeners();
99
- }
100
-
101
- return () => {
102
- removeListeners();
103
- };
104
- }, [showAdditionalButtons, addListeners, removeListeners]);
50
+ const handleKeyDown = useMenuKeyboardNavigation(toggleButton, buttonChildrenRefs, hideButtons);
105
51
 
106
52
  function mainButtonProps() {
107
53
  return {
@@ -124,10 +70,6 @@ const SplitButton = ({
124
70
  disabled,
125
71
  displayed: showAdditionalButtons,
126
72
  onTouchStart: showButtons,
127
- onFocus: focusToggleButton,
128
- onBlur: () => {
129
- isToggleButtonFocused.current = false;
130
- },
131
73
  onKeyDown: handleToggleButtonKeyDown,
132
74
  buttonType,
133
75
  size
@@ -184,7 +126,7 @@ const SplitButton = ({
184
126
  }
185
127
 
186
128
  function handleToggleButtonKeyDown(ev) {
187
- if (Events.isEnterKey(ev) || Events.isSpaceKey(ev) || Events.isDownKey(ev)) {
129
+ if (Events.isEnterKey(ev) || Events.isSpaceKey(ev) || Events.isDownKey(ev) || Events.isUpKey(ev)) {
188
130
  ev.preventDefault();
189
131
 
190
132
  if (!showAdditionalButtons) {
@@ -192,10 +134,13 @@ const SplitButton = ({
192
134
  }
193
135
 
194
136
  setTimeout(() => {
195
- var _additionalButtons$cu, _additionalButtons$cu2;
137
+ var _buttonChildrenRefs$, _buttonChildrenRefs$$;
196
138
 
197
- (_additionalButtons$cu = additionalButtons.current[0]) === null || _additionalButtons$cu === void 0 ? void 0 : (_additionalButtons$cu2 = _additionalButtons$cu.current) === null || _additionalButtons$cu2 === void 0 ? void 0 : _additionalButtons$cu2.focus();
139
+ (_buttonChildrenRefs$ = buttonChildrenRefs[0]) === null || _buttonChildrenRefs$ === void 0 ? void 0 : (_buttonChildrenRefs$$ = _buttonChildrenRefs$.current) === null || _buttonChildrenRefs$$ === void 0 ? void 0 : _buttonChildrenRefs$$.focus();
198
140
  }, 0);
141
+ } else if (Events.isEscKey(ev)) {
142
+ setShowAdditionalButtons(false);
143
+ ev.preventDefault();
199
144
  }
200
145
  }
201
146
 
@@ -205,15 +150,13 @@ const SplitButton = ({
205
150
  const childProps = {
206
151
  key: index.toString(),
207
152
  role: "menuitem",
208
- ref: additionalButtons.current[index],
153
+ ref: buttonChildrenRefs[index],
209
154
  tabIndex: -1,
210
155
  onClick: ev => {
211
156
  var _toggleButton$current;
212
157
 
213
158
  if (child.props.onClick) child.props.onClick(ev);
214
- isToggleButtonFocused.current = false;
215
159
  hideButtons();
216
- isFocusedAfterClosing.current = true;
217
160
  (_toggleButton$current = toggleButton.current) === null || _toggleButton$current === void 0 ? void 0 : _toggleButton$current.focus();
218
161
  }
219
162
  };
@@ -221,17 +164,6 @@ const SplitButton = ({
221
164
  });
222
165
  }
223
166
 
224
- function focusToggleButton() {
225
- isToggleButtonFocused.current = true;
226
-
227
- if (isFocusedAfterClosing.current) {
228
- isFocusedAfterClosing.current = false;
229
- return;
230
- }
231
-
232
- showButtons();
233
- }
234
-
235
167
  function renderAdditionalButtons() {
236
168
  if (!showAdditionalButtons) return null;
237
169
  return /*#__PURE__*/React.createElement(Popover, {
@@ -243,7 +175,8 @@ const SplitButton = ({
243
175
  "data-element": "additional-buttons",
244
176
  align: align,
245
177
  minWidth: minWidth,
246
- ref: buttonContainer
178
+ ref: buttonContainer,
179
+ onKeyDown: handleKeyDown
247
180
  }, childrenWithProps()));
248
181
  }
249
182
 
@@ -11,8 +11,7 @@ import IconButton from "../icon-button";
11
11
  import Events from "../../__internal__/utils/helpers/events";
12
12
  import useLocale from "../../hooks/__internal__/useLocale";
13
13
  import useModalManager from "../../hooks/__internal__/useModalManager";
14
-
15
- const Toast = ({
14
+ const Toast = /*#__PURE__*/React.forwardRef(({
16
15
  children,
17
16
  className,
18
17
  id,
@@ -23,12 +22,16 @@ const Toast = ({
23
22
  targetPortalId,
24
23
  timeout,
25
24
  variant,
25
+ disableAutoFocus,
26
26
  ...restProps
27
- }) => {
27
+ }, ref) => {
28
28
  const locale = useLocale();
29
29
  const toastRef = useRef();
30
30
  const timer = useRef();
31
31
  const toastContentNodeRef = useRef();
32
+ const closeIconRef = useRef();
33
+ const focusedElementBeforeOpening = useRef();
34
+ const refToPass = ref || toastRef;
32
35
  const componentClasses = useMemo(() => {
33
36
  return classNames(className);
34
37
  }, [className]);
@@ -38,7 +41,7 @@ const Toast = ({
38
41
  onDismiss(ev);
39
42
  }
40
43
  }, [onDismiss]);
41
- useModalManager(open, dismissToast, toastRef);
44
+ useModalManager(open, dismissToast, refToPass);
42
45
  useEffect(() => {
43
46
  clearTimeout(timer.current);
44
47
 
@@ -48,13 +51,34 @@ const Toast = ({
48
51
 
49
52
  timer.current = setTimeout(() => onDismiss(), timeout);
50
53
  }, [onDismiss, open, timeout]);
54
+ useEffect(() => {
55
+ if (onDismiss && !disableAutoFocus) {
56
+ if (open) {
57
+ var _closeIconRef$current;
58
+
59
+ focusedElementBeforeOpening.current = document.activeElement;
60
+ (_closeIconRef$current = closeIconRef.current) === null || _closeIconRef$current === void 0 ? void 0 : _closeIconRef$current.focus();
61
+ } else if (focusedElementBeforeOpening.current) {
62
+ focusedElementBeforeOpening.current.focus();
63
+ focusedElementBeforeOpening.current = undefined;
64
+ }
65
+ }
66
+ }, [open, onDismiss, disableAutoFocus]);
67
+ useEffect(() => {
68
+ return () => {
69
+ if (focusedElementBeforeOpening.current) {
70
+ focusedElementBeforeOpening.current.focus();
71
+ }
72
+ };
73
+ }, []);
51
74
 
52
75
  function renderCloseIcon() {
53
76
  if (!onDismiss) return null;
54
77
  return /*#__PURE__*/React.createElement(IconButton, {
55
78
  "aria-label": locale.toast.ariaLabels.close(),
56
79
  "data-element": "close",
57
- onAction: onDismiss
80
+ onAction: onDismiss,
81
+ ref: closeIconRef
58
82
  }, /*#__PURE__*/React.createElement(Icon, {
59
83
  type: "close"
60
84
  }));
@@ -96,10 +120,9 @@ const Toast = ({
96
120
  isCenter: isCenter
97
121
  }, /*#__PURE__*/React.createElement(ToastWrapper, {
98
122
  isCenter: isCenter,
99
- ref: toastRef
123
+ ref: refToPass
100
124
  }, /*#__PURE__*/React.createElement(TransitionGroup, null, renderToastContent())));
101
- };
102
-
125
+ });
103
126
  Toast.propTypes = {
104
127
  /** Customizes the appearance in the DLS theme */
105
128
  variant: PropTypes.oneOf(["error", "info", "success", "warning"]),
@@ -132,6 +155,9 @@ Toast.propTypes = {
132
155
  targetPortalId: PropTypes.string,
133
156
 
134
157
  /** Maximum toast width */
135
- maxWidth: PropTypes.string
158
+ maxWidth: PropTypes.string,
159
+
160
+ /** Disables auto focus functionality when the Toast has a close icon */
161
+ disableAutoFocus: PropTypes.bool
136
162
  };
137
163
  export default Toast;