carbon-react 106.6.10 → 106.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/__internal__/focus-trap/focus-trap-utils.js +25 -1
- package/esm/__internal__/focus-trap/focus-trap.component.d.ts +3 -1
- package/esm/__internal__/focus-trap/focus-trap.component.js +44 -12
- package/esm/components/advanced-color-picker/advanced-color-picker.component.js +5 -5
- package/esm/components/dialog/dialog.component.js +4 -3
- package/esm/components/dialog-full-screen/dialog-full-screen.component.js +4 -3
- package/esm/components/menu/menu-full-screen/menu-full-screen.component.js +4 -18
- package/esm/components/menu/menu-full-screen/menu-full-screen.style.js +1 -0
- package/esm/components/sidebar/__internal__/sidebar-header/sidebar-header.component.d.ts +3 -1
- package/esm/components/sidebar/__internal__/sidebar-header/sidebar-header.component.js +7 -2
- package/esm/components/sidebar/sidebar.component.js +10 -3
- package/lib/__internal__/focus-trap/focus-trap-utils.js +25 -1
- package/lib/__internal__/focus-trap/focus-trap.component.d.ts +3 -1
- package/lib/__internal__/focus-trap/focus-trap.component.js +46 -12
- package/lib/components/advanced-color-picker/advanced-color-picker.component.js +5 -5
- package/lib/components/dialog/dialog.component.js +4 -3
- package/lib/components/dialog-full-screen/dialog-full-screen.component.js +4 -3
- package/lib/components/menu/menu-full-screen/menu-full-screen.component.js +3 -17
- package/lib/components/menu/menu-full-screen/menu-full-screen.style.js +1 -0
- package/lib/components/sidebar/__internal__/sidebar-header/sidebar-header.component.d.ts +3 -1
- package/lib/components/sidebar/__internal__/sidebar-header/sidebar-header.component.js +7 -2
- package/lib/components/sidebar/sidebar.component.js +11 -3
- package/package.json +1 -1
|
@@ -1,11 +1,35 @@
|
|
|
1
1
|
const defaultFocusableSelectors = 'button:not([disabled]), [href], input:not([type="hidden"]):not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]';
|
|
2
2
|
|
|
3
|
+
const waitForVisibleAndFocus = element => {
|
|
4
|
+
const INTERVAL = 10;
|
|
5
|
+
const MAX_TIME = 100;
|
|
6
|
+
let timeSoFar = 0;
|
|
7
|
+
|
|
8
|
+
const stylesMatch = () => {
|
|
9
|
+
const actualStyles = window.getComputedStyle(element);
|
|
10
|
+
return actualStyles.visibility === "visible";
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const check = () => {
|
|
14
|
+
/* istanbul ignore else */
|
|
15
|
+
if (stylesMatch()) {
|
|
16
|
+
element.focus();
|
|
17
|
+
} else if (timeSoFar < MAX_TIME) {
|
|
18
|
+
setTimeout(check, INTERVAL);
|
|
19
|
+
timeSoFar += INTERVAL;
|
|
20
|
+
} // just "fail" silently if maxTime exceeded - callback will never be called
|
|
21
|
+
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
check();
|
|
25
|
+
};
|
|
26
|
+
|
|
3
27
|
function setElementFocus(element) {
|
|
4
28
|
if (typeof element === "function") {
|
|
5
29
|
element();
|
|
6
30
|
} else {
|
|
7
31
|
const el = element.current || element;
|
|
8
|
-
el
|
|
32
|
+
waitForVisibleAndFocus(el);
|
|
9
33
|
}
|
|
10
34
|
}
|
|
11
35
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
export default FocusTrap;
|
|
2
|
-
declare function FocusTrap({ children, autoFocus, focusFirstElement, bespokeTrap, wrapperRef, }: {
|
|
2
|
+
declare function FocusTrap({ children, autoFocus, focusFirstElement, bespokeTrap, wrapperRef, isOpen, }: {
|
|
3
3
|
children: any;
|
|
4
4
|
autoFocus?: boolean | undefined;
|
|
5
5
|
focusFirstElement: any;
|
|
6
6
|
bespokeTrap: any;
|
|
7
7
|
wrapperRef: any;
|
|
8
|
+
isOpen: any;
|
|
8
9
|
}): JSX.Element;
|
|
9
10
|
declare namespace FocusTrap {
|
|
10
11
|
namespace propTypes {
|
|
@@ -17,6 +18,7 @@ declare namespace FocusTrap {
|
|
|
17
18
|
const wrapperRef: PropTypes.Requireable<PropTypes.InferProps<{
|
|
18
19
|
current: PropTypes.Requireable<any>;
|
|
19
20
|
}>>;
|
|
21
|
+
const isOpen: PropTypes.Requireable<boolean>;
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
import PropTypes from "prop-types";
|
|
@@ -2,22 +2,23 @@ import React, { useCallback, useContext, useEffect, useLayoutEffect, useRef, use
|
|
|
2
2
|
import PropTypes from "prop-types";
|
|
3
3
|
import { defaultFocusableSelectors, nextNonRadioElementIndex, isRadio, setElementFocus } from "./focus-trap-utils";
|
|
4
4
|
import { ModalContext } from "../../components/modal/modal.component";
|
|
5
|
+
import usePrevious from "../../hooks/__internal__/usePrevious";
|
|
5
6
|
|
|
6
7
|
const FocusTrap = ({
|
|
7
8
|
children,
|
|
8
9
|
autoFocus = true,
|
|
9
10
|
focusFirstElement,
|
|
10
11
|
bespokeTrap,
|
|
11
|
-
wrapperRef
|
|
12
|
+
wrapperRef,
|
|
13
|
+
isOpen
|
|
12
14
|
}) => {
|
|
13
15
|
const trapRef = useRef(null);
|
|
14
|
-
const firstOpen = useRef(true);
|
|
15
16
|
const [focusableElements, setFocusableElements] = useState();
|
|
16
17
|
const [firstElement, setFirstElement] = useState();
|
|
17
18
|
const [lastElement, setLastElement] = useState();
|
|
18
19
|
const [currentFocusedElement, setCurrentFocusedElement] = useState();
|
|
19
20
|
const {
|
|
20
|
-
isAnimationComplete,
|
|
21
|
+
isAnimationComplete = true,
|
|
21
22
|
triggerRefocusFlag
|
|
22
23
|
} = useContext(ModalContext);
|
|
23
24
|
const hasNewInputs = useCallback(candidate => {
|
|
@@ -53,12 +54,13 @@ const FocusTrap = ({
|
|
|
53
54
|
useLayoutEffect(() => {
|
|
54
55
|
updateFocusableElements();
|
|
55
56
|
}, [children, updateFocusableElements]);
|
|
57
|
+
const shouldSetFocus = autoFocus && isOpen && isAnimationComplete && (focusFirstElement || (wrapperRef === null || wrapperRef === void 0 ? void 0 : wrapperRef.current));
|
|
58
|
+
const prevShouldSetFocus = usePrevious(shouldSetFocus);
|
|
56
59
|
useEffect(() => {
|
|
57
|
-
if (
|
|
58
|
-
setElementFocus(focusFirstElement ||
|
|
59
|
-
firstOpen.current = false;
|
|
60
|
+
if (shouldSetFocus && !prevShouldSetFocus) {
|
|
61
|
+
setElementFocus(focusFirstElement || (wrapperRef === null || wrapperRef === void 0 ? void 0 : wrapperRef.current));
|
|
60
62
|
}
|
|
61
|
-
}, [
|
|
63
|
+
}, [shouldSetFocus, prevShouldSetFocus, focusFirstElement, wrapperRef]);
|
|
62
64
|
useEffect(() => {
|
|
63
65
|
const trapFn = ev => {
|
|
64
66
|
if (bespokeTrap) {
|
|
@@ -76,7 +78,7 @@ const FocusTrap = ({
|
|
|
76
78
|
ev.preventDefault();
|
|
77
79
|
} else if (ev.shiftKey) {
|
|
78
80
|
/* shift + tab */
|
|
79
|
-
if (activeElement === firstElement) {
|
|
81
|
+
if (activeElement === firstElement || activeElement === wrapperRef.current) {
|
|
80
82
|
lastElement.focus();
|
|
81
83
|
ev.preventDefault();
|
|
82
84
|
} // If current element is radio button -
|
|
@@ -99,7 +101,7 @@ const FocusTrap = ({
|
|
|
99
101
|
return function cleanup() {
|
|
100
102
|
document.removeEventListener("keydown", trapFn);
|
|
101
103
|
};
|
|
102
|
-
}, [firstElement, lastElement, focusableElements, bespokeTrap]);
|
|
104
|
+
}, [firstElement, lastElement, focusableElements, bespokeTrap, wrapperRef]);
|
|
103
105
|
const updateCurrentFocusedElement = useCallback(() => {
|
|
104
106
|
const element = focusableElements === null || focusableElements === void 0 ? void 0 : focusableElements.find(el => el === document.activeElement);
|
|
105
107
|
|
|
@@ -114,22 +116,49 @@ const FocusTrap = ({
|
|
|
114
116
|
};
|
|
115
117
|
}, [updateCurrentFocusedElement]);
|
|
116
118
|
const refocusTrap = useCallback(() => {
|
|
119
|
+
var _wrapperRef$current;
|
|
120
|
+
|
|
117
121
|
/* istanbul ignore else */
|
|
118
122
|
if (currentFocusedElement && !currentFocusedElement.hasAttribute("disabled")) {
|
|
119
123
|
// the trap breaks if it tries to refocus a disabled element
|
|
120
124
|
setElementFocus(currentFocusedElement);
|
|
125
|
+
} else if (wrapperRef !== null && wrapperRef !== void 0 && (_wrapperRef$current = wrapperRef.current) !== null && _wrapperRef$current !== void 0 && _wrapperRef$current.hasAttribute("tabindex")) {
|
|
126
|
+
setElementFocus(wrapperRef.current);
|
|
121
127
|
} else if (firstElement) {
|
|
122
128
|
setElementFocus(firstElement);
|
|
123
129
|
}
|
|
124
|
-
}, [currentFocusedElement, firstElement]);
|
|
130
|
+
}, [currentFocusedElement, firstElement, wrapperRef]);
|
|
125
131
|
useEffect(() => {
|
|
126
132
|
if (triggerRefocusFlag) {
|
|
127
133
|
refocusTrap();
|
|
128
134
|
}
|
|
129
135
|
}, [triggerRefocusFlag, refocusTrap]);
|
|
136
|
+
const [tabIndex, setTabIndex] = useState(0);
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
// issue in cypress prevents setting tabIndex to -1, instead tabIndex is set to 0 and removed on blur.
|
|
139
|
+
if (!isOpen) {
|
|
140
|
+
setTabIndex(0);
|
|
141
|
+
}
|
|
142
|
+
}, [isOpen]);
|
|
143
|
+
|
|
144
|
+
const onBlur = () => {
|
|
145
|
+
/* istanbul ignore else */
|
|
146
|
+
if (isOpen) {
|
|
147
|
+
setTabIndex(undefined);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const focusProps = {
|
|
152
|
+
tabIndex,
|
|
153
|
+
onBlur
|
|
154
|
+
}; // passes focusProps if no tabindex has been explicitly set on the wrapper
|
|
155
|
+
|
|
156
|
+
const clonedChildren = React.Children.map(children, child => {
|
|
157
|
+
return child.props.tabIndex === undefined ? /*#__PURE__*/React.cloneElement(child, focusProps) : child;
|
|
158
|
+
});
|
|
130
159
|
return /*#__PURE__*/React.createElement("div", {
|
|
131
160
|
ref: trapRef
|
|
132
|
-
},
|
|
161
|
+
}, clonedChildren);
|
|
133
162
|
};
|
|
134
163
|
|
|
135
164
|
FocusTrap.propTypes = {
|
|
@@ -149,6 +178,9 @@ FocusTrap.propTypes = {
|
|
|
149
178
|
/** a ref to the container wrapping the focusable elements */
|
|
150
179
|
wrapperRef: PropTypes.shape({
|
|
151
180
|
current: PropTypes.any
|
|
152
|
-
})
|
|
181
|
+
}),
|
|
182
|
+
|
|
183
|
+
/* whether the modal (etc.) component that the focus trap is inside is open or not */
|
|
184
|
+
isOpen: PropTypes.bool
|
|
153
185
|
};
|
|
154
186
|
export default FocusTrap;
|
|
@@ -48,17 +48,17 @@ const AdvancedColorPicker = ({
|
|
|
48
48
|
setSelectedColorRef(selected.ref.current);
|
|
49
49
|
}
|
|
50
50
|
}, [colors, currentColor, dialogOpen, isOpen]);
|
|
51
|
-
const handleFocus = useCallback((e, firstFocusableElement
|
|
51
|
+
const handleFocus = useCallback((e, firstFocusableElement) => {
|
|
52
52
|
if (e.key === "Tab") {
|
|
53
53
|
/* istanbul ignore else */
|
|
54
54
|
if (e.shiftKey) {
|
|
55
55
|
/* istanbul ignore else */
|
|
56
|
-
if (document.activeElement ===
|
|
57
|
-
|
|
56
|
+
if (document.activeElement === firstFocusableElement) {
|
|
57
|
+
selectedColorRef.focus();
|
|
58
58
|
e.preventDefault();
|
|
59
59
|
}
|
|
60
|
-
} else if (document.activeElement ===
|
|
61
|
-
|
|
60
|
+
} else if (document.activeElement === selectedColorRef) {
|
|
61
|
+
firstFocusableElement.focus();
|
|
62
62
|
e.preventDefault();
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -155,7 +155,8 @@ const Dialog = ({
|
|
|
155
155
|
autoFocus: !disableAutoFocus,
|
|
156
156
|
focusFirstElement: focusFirstElement,
|
|
157
157
|
bespokeTrap: bespokeFocusTrap,
|
|
158
|
-
wrapperRef: dialogRef
|
|
158
|
+
wrapperRef: dialogRef,
|
|
159
|
+
isOpen: open
|
|
159
160
|
}, /*#__PURE__*/React.createElement(DialogStyle, _extends({
|
|
160
161
|
"aria-modal": true,
|
|
161
162
|
ref: dialogRef,
|
|
@@ -165,9 +166,9 @@ const Dialog = ({
|
|
|
165
166
|
"data-element": "dialog",
|
|
166
167
|
"data-role": rest["data-role"],
|
|
167
168
|
role: role
|
|
168
|
-
}, contentPadding), dialogTitle(), /*#__PURE__*/React.createElement(DialogContentStyle, contentPadding, /*#__PURE__*/React.createElement(DialogInnerContentStyle, _extends({
|
|
169
|
+
}, contentPadding), dialogTitle(), closeIcon(), /*#__PURE__*/React.createElement(DialogContentStyle, contentPadding, /*#__PURE__*/React.createElement(DialogInnerContentStyle, _extends({
|
|
169
170
|
ref: innerContentRef
|
|
170
|
-
}, contentPadding), children))
|
|
171
|
+
}, contentPadding), children)))));
|
|
171
172
|
};
|
|
172
173
|
|
|
173
174
|
Dialog.propTypes = {
|
|
@@ -85,7 +85,8 @@ const DialogFullScreen = ({
|
|
|
85
85
|
}, componentTags), /*#__PURE__*/React.createElement(FocusTrap, {
|
|
86
86
|
autoFocus: !disableAutoFocus,
|
|
87
87
|
focusFirstElement: focusFirstElement,
|
|
88
|
-
wrapperRef: dialogRef
|
|
88
|
+
wrapperRef: dialogRef,
|
|
89
|
+
isOpen: open
|
|
89
90
|
}, /*#__PURE__*/React.createElement(StyledDialogFullScreen, _extends({
|
|
90
91
|
"aria-modal": role === "dialog" ? true : undefined
|
|
91
92
|
}, ariaProps, {
|
|
@@ -93,12 +94,12 @@ const DialogFullScreen = ({
|
|
|
93
94
|
"data-element": "dialog-full-screen",
|
|
94
95
|
pagesStyling: pagesStyling,
|
|
95
96
|
role: role
|
|
96
|
-
}), dialogTitle(), /*#__PURE__*/React.createElement(StyledContent, {
|
|
97
|
+
}), dialogTitle(), closeIcon(), /*#__PURE__*/React.createElement(StyledContent, {
|
|
97
98
|
hasHeader: title !== undefined,
|
|
98
99
|
"data-element": "content",
|
|
99
100
|
ref: contentRef,
|
|
100
101
|
disableContentPadding: disableContentPadding
|
|
101
|
-
}, children)
|
|
102
|
+
}, children))));
|
|
102
103
|
};
|
|
103
104
|
|
|
104
105
|
DialogFullScreen.defaultProps = {
|
|
@@ -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, { useContext,
|
|
3
|
+
import React, { useContext, useRef } from "react";
|
|
4
4
|
import PropTypes from "prop-types";
|
|
5
5
|
import { StyledMenuFullscreen, StyledMenuFullscreenHeader } from "./menu-full-screen.style";
|
|
6
6
|
import { StyledMenuWrapper } from "../menu.style";
|
|
@@ -33,19 +33,6 @@ const MenuFullscreen = ({
|
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
-
useLayoutEffect(() => {
|
|
37
|
-
const checkTransitionEnd = () => {
|
|
38
|
-
menuContentRef.current.focus();
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const wrapperRef = menuWrapperRef.current;
|
|
42
|
-
|
|
43
|
-
if (isOpen) {
|
|
44
|
-
wrapperRef.addEventListener("transitionend", checkTransitionEnd);
|
|
45
|
-
} else {
|
|
46
|
-
wrapperRef.removeEventListener("transitionend", checkTransitionEnd);
|
|
47
|
-
}
|
|
48
|
-
}, [isOpen]);
|
|
49
36
|
const scrollVariants = {
|
|
50
37
|
light: "light",
|
|
51
38
|
dark: "dark",
|
|
@@ -61,8 +48,8 @@ const MenuFullscreen = ({
|
|
|
61
48
|
return /*#__PURE__*/React.createElement("li", {
|
|
62
49
|
"aria-label": "menu-fullscreen"
|
|
63
50
|
}, /*#__PURE__*/React.createElement(Portal, null, /*#__PURE__*/React.createElement(FocusTrap, {
|
|
64
|
-
|
|
65
|
-
|
|
51
|
+
wrapperRef: menuWrapperRef,
|
|
52
|
+
isOpen: isOpen
|
|
66
53
|
}, /*#__PURE__*/React.createElement(StyledMenuFullscreen, _extends({
|
|
67
54
|
"data-component": "menu-fullscreen",
|
|
68
55
|
ref: menuWrapperRef,
|
|
@@ -93,8 +80,7 @@ const MenuFullscreen = ({
|
|
|
93
80
|
display: "flex",
|
|
94
81
|
flexDirection: "column",
|
|
95
82
|
role: "list",
|
|
96
|
-
inFullscreenView: true
|
|
97
|
-
tabIndex: -1
|
|
83
|
+
inFullscreenView: true
|
|
98
84
|
}, React.Children.map(children, (child, index) => /*#__PURE__*/React.createElement(MenuContext.Provider, {
|
|
99
85
|
value: {
|
|
100
86
|
inFullscreenView: true,
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
export default SidebarHeader;
|
|
2
|
-
declare function SidebarHeader({ className, children, ...props }: {
|
|
2
|
+
declare function SidebarHeader({ className, children, id, ...props }: {
|
|
3
3
|
[x: string]: any;
|
|
4
4
|
className: any;
|
|
5
5
|
children: any;
|
|
6
|
+
id: any;
|
|
6
7
|
}): JSX.Element;
|
|
7
8
|
declare namespace SidebarHeader {
|
|
8
9
|
namespace propTypes {
|
|
9
10
|
const children: PropTypes.Requireable<PropTypes.ReactNodeLike>;
|
|
10
11
|
const className: PropTypes.Requireable<string>;
|
|
12
|
+
const id: PropTypes.Requireable<string>;
|
|
11
13
|
}
|
|
12
14
|
}
|
|
13
15
|
import PropTypes from "prop-types";
|
|
@@ -8,9 +8,11 @@ import SidebarHeaderStyle from "./sidebar-header.style";
|
|
|
8
8
|
const SidebarHeader = ({
|
|
9
9
|
className,
|
|
10
10
|
children,
|
|
11
|
+
id,
|
|
11
12
|
...props
|
|
12
13
|
}) => /*#__PURE__*/React.createElement(SidebarHeaderStyle, _extends({
|
|
13
|
-
className: className
|
|
14
|
+
className: className,
|
|
15
|
+
id: id
|
|
14
16
|
}, tagComponent("sidebar-header", props)), children);
|
|
15
17
|
|
|
16
18
|
SidebarHeader.propTypes = {
|
|
@@ -18,6 +20,9 @@ SidebarHeader.propTypes = {
|
|
|
18
20
|
children: PropTypes.node,
|
|
19
21
|
|
|
20
22
|
/** A custom class name. */
|
|
21
|
-
className: PropTypes.string
|
|
23
|
+
className: PropTypes.string,
|
|
24
|
+
|
|
25
|
+
/** A custom id. */
|
|
26
|
+
id: PropTypes.string
|
|
22
27
|
};
|
|
23
28
|
export default SidebarHeader;
|
|
@@ -10,6 +10,7 @@ import FocusTrap from "../../__internal__/focus-trap";
|
|
|
10
10
|
import SidebarHeader from "./__internal__/sidebar-header";
|
|
11
11
|
import Box from "../box";
|
|
12
12
|
import { SIDEBAR_SIZES, SIDEBAR_ALIGNMENTS } from "./sidebar.config";
|
|
13
|
+
import createGuid from "../../__internal__/utils/helpers/guid";
|
|
13
14
|
import useLocale from "../../hooks/__internal__/useLocale";
|
|
14
15
|
export const SidebarContext = /*#__PURE__*/React.createContext({});
|
|
15
16
|
const Sidebar = /*#__PURE__*/React.forwardRef(({
|
|
@@ -28,6 +29,9 @@ const Sidebar = /*#__PURE__*/React.forwardRef(({
|
|
|
28
29
|
...rest
|
|
29
30
|
}, ref) => {
|
|
30
31
|
const locale = useLocale();
|
|
32
|
+
const {
|
|
33
|
+
current: titleId
|
|
34
|
+
} = useRef(createGuid());
|
|
31
35
|
let sidebarRef = useRef();
|
|
32
36
|
if (ref) sidebarRef = ref;
|
|
33
37
|
|
|
@@ -51,14 +55,16 @@ const Sidebar = /*#__PURE__*/React.forwardRef(({
|
|
|
51
55
|
"aria-modal": !enableBackgroundUI,
|
|
52
56
|
"aria-describedby": ariaDescribedBy,
|
|
53
57
|
"aria-label": ariaLabel,
|
|
54
|
-
"aria-labelledby": ariaLabelledBy,
|
|
58
|
+
"aria-labelledby": !ariaLabelledBy && !ariaLabel ? titleId : ariaLabelledBy,
|
|
55
59
|
ref: sidebarRef,
|
|
56
60
|
position: position,
|
|
57
61
|
size: size,
|
|
58
62
|
"data-element": "sidebar",
|
|
59
63
|
onCancel: onCancel,
|
|
60
64
|
role: role
|
|
61
|
-
},
|
|
65
|
+
}, header && /*#__PURE__*/React.createElement(SidebarHeader, {
|
|
66
|
+
id: titleId
|
|
67
|
+
}, header), closeIcon(), /*#__PURE__*/React.createElement(Box, {
|
|
62
68
|
"data-element": "sidebar-content",
|
|
63
69
|
p: 4,
|
|
64
70
|
pt: "27px",
|
|
@@ -77,7 +83,8 @@ const Sidebar = /*#__PURE__*/React.forwardRef(({
|
|
|
77
83
|
enableBackgroundUI: enableBackgroundUI,
|
|
78
84
|
className: "carbon-sidebar"
|
|
79
85
|
}, componentTags), enableBackgroundUI ? sidebar : /*#__PURE__*/React.createElement(FocusTrap, {
|
|
80
|
-
wrapperRef: sidebarRef
|
|
86
|
+
wrapperRef: sidebarRef,
|
|
87
|
+
isOpen: open
|
|
81
88
|
}, sidebar));
|
|
82
89
|
});
|
|
83
90
|
Sidebar.propTypes = {
|
|
@@ -8,12 +8,36 @@ exports.isRadio = exports.nextNonRadioElementIndex = exports.defaultFocusableSel
|
|
|
8
8
|
const defaultFocusableSelectors = 'button:not([disabled]), [href], input:not([type="hidden"]):not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]';
|
|
9
9
|
exports.defaultFocusableSelectors = defaultFocusableSelectors;
|
|
10
10
|
|
|
11
|
+
const waitForVisibleAndFocus = element => {
|
|
12
|
+
const INTERVAL = 10;
|
|
13
|
+
const MAX_TIME = 100;
|
|
14
|
+
let timeSoFar = 0;
|
|
15
|
+
|
|
16
|
+
const stylesMatch = () => {
|
|
17
|
+
const actualStyles = window.getComputedStyle(element);
|
|
18
|
+
return actualStyles.visibility === "visible";
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const check = () => {
|
|
22
|
+
/* istanbul ignore else */
|
|
23
|
+
if (stylesMatch()) {
|
|
24
|
+
element.focus();
|
|
25
|
+
} else if (timeSoFar < MAX_TIME) {
|
|
26
|
+
setTimeout(check, INTERVAL);
|
|
27
|
+
timeSoFar += INTERVAL;
|
|
28
|
+
} // just "fail" silently if maxTime exceeded - callback will never be called
|
|
29
|
+
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
check();
|
|
33
|
+
};
|
|
34
|
+
|
|
11
35
|
function setElementFocus(element) {
|
|
12
36
|
if (typeof element === "function") {
|
|
13
37
|
element();
|
|
14
38
|
} else {
|
|
15
39
|
const el = element.current || element;
|
|
16
|
-
el
|
|
40
|
+
waitForVisibleAndFocus(el);
|
|
17
41
|
}
|
|
18
42
|
}
|
|
19
43
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
export default FocusTrap;
|
|
2
|
-
declare function FocusTrap({ children, autoFocus, focusFirstElement, bespokeTrap, wrapperRef, }: {
|
|
2
|
+
declare function FocusTrap({ children, autoFocus, focusFirstElement, bespokeTrap, wrapperRef, isOpen, }: {
|
|
3
3
|
children: any;
|
|
4
4
|
autoFocus?: boolean | undefined;
|
|
5
5
|
focusFirstElement: any;
|
|
6
6
|
bespokeTrap: any;
|
|
7
7
|
wrapperRef: any;
|
|
8
|
+
isOpen: any;
|
|
8
9
|
}): JSX.Element;
|
|
9
10
|
declare namespace FocusTrap {
|
|
10
11
|
namespace propTypes {
|
|
@@ -17,6 +18,7 @@ declare namespace FocusTrap {
|
|
|
17
18
|
const wrapperRef: PropTypes.Requireable<PropTypes.InferProps<{
|
|
18
19
|
current: PropTypes.Requireable<any>;
|
|
19
20
|
}>>;
|
|
21
|
+
const isOpen: PropTypes.Requireable<boolean>;
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
import PropTypes from "prop-types";
|
|
@@ -13,6 +13,8 @@ var _focusTrapUtils = require("./focus-trap-utils");
|
|
|
13
13
|
|
|
14
14
|
var _modal = require("../../components/modal/modal.component");
|
|
15
15
|
|
|
16
|
+
var _usePrevious = _interopRequireDefault(require("../../hooks/__internal__/usePrevious"));
|
|
17
|
+
|
|
16
18
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
17
19
|
|
|
18
20
|
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
|
|
@@ -24,16 +26,16 @@ const FocusTrap = ({
|
|
|
24
26
|
autoFocus = true,
|
|
25
27
|
focusFirstElement,
|
|
26
28
|
bespokeTrap,
|
|
27
|
-
wrapperRef
|
|
29
|
+
wrapperRef,
|
|
30
|
+
isOpen
|
|
28
31
|
}) => {
|
|
29
32
|
const trapRef = (0, _react.useRef)(null);
|
|
30
|
-
const firstOpen = (0, _react.useRef)(true);
|
|
31
33
|
const [focusableElements, setFocusableElements] = (0, _react.useState)();
|
|
32
34
|
const [firstElement, setFirstElement] = (0, _react.useState)();
|
|
33
35
|
const [lastElement, setLastElement] = (0, _react.useState)();
|
|
34
36
|
const [currentFocusedElement, setCurrentFocusedElement] = (0, _react.useState)();
|
|
35
37
|
const {
|
|
36
|
-
isAnimationComplete,
|
|
38
|
+
isAnimationComplete = true,
|
|
37
39
|
triggerRefocusFlag
|
|
38
40
|
} = (0, _react.useContext)(_modal.ModalContext);
|
|
39
41
|
const hasNewInputs = (0, _react.useCallback)(candidate => {
|
|
@@ -69,12 +71,13 @@ const FocusTrap = ({
|
|
|
69
71
|
(0, _react.useLayoutEffect)(() => {
|
|
70
72
|
updateFocusableElements();
|
|
71
73
|
}, [children, updateFocusableElements]);
|
|
74
|
+
const shouldSetFocus = autoFocus && isOpen && isAnimationComplete && (focusFirstElement || (wrapperRef === null || wrapperRef === void 0 ? void 0 : wrapperRef.current));
|
|
75
|
+
const prevShouldSetFocus = (0, _usePrevious.default)(shouldSetFocus);
|
|
72
76
|
(0, _react.useEffect)(() => {
|
|
73
|
-
if (
|
|
74
|
-
(0, _focusTrapUtils.setElementFocus)(focusFirstElement ||
|
|
75
|
-
firstOpen.current = false;
|
|
77
|
+
if (shouldSetFocus && !prevShouldSetFocus) {
|
|
78
|
+
(0, _focusTrapUtils.setElementFocus)(focusFirstElement || (wrapperRef === null || wrapperRef === void 0 ? void 0 : wrapperRef.current));
|
|
76
79
|
}
|
|
77
|
-
}, [
|
|
80
|
+
}, [shouldSetFocus, prevShouldSetFocus, focusFirstElement, wrapperRef]);
|
|
78
81
|
(0, _react.useEffect)(() => {
|
|
79
82
|
const trapFn = ev => {
|
|
80
83
|
if (bespokeTrap) {
|
|
@@ -92,7 +95,7 @@ const FocusTrap = ({
|
|
|
92
95
|
ev.preventDefault();
|
|
93
96
|
} else if (ev.shiftKey) {
|
|
94
97
|
/* shift + tab */
|
|
95
|
-
if (activeElement === firstElement) {
|
|
98
|
+
if (activeElement === firstElement || activeElement === wrapperRef.current) {
|
|
96
99
|
lastElement.focus();
|
|
97
100
|
ev.preventDefault();
|
|
98
101
|
} // If current element is radio button -
|
|
@@ -115,7 +118,7 @@ const FocusTrap = ({
|
|
|
115
118
|
return function cleanup() {
|
|
116
119
|
document.removeEventListener("keydown", trapFn);
|
|
117
120
|
};
|
|
118
|
-
}, [firstElement, lastElement, focusableElements, bespokeTrap]);
|
|
121
|
+
}, [firstElement, lastElement, focusableElements, bespokeTrap, wrapperRef]);
|
|
119
122
|
const updateCurrentFocusedElement = (0, _react.useCallback)(() => {
|
|
120
123
|
const element = focusableElements === null || focusableElements === void 0 ? void 0 : focusableElements.find(el => el === document.activeElement);
|
|
121
124
|
|
|
@@ -130,22 +133,50 @@ const FocusTrap = ({
|
|
|
130
133
|
};
|
|
131
134
|
}, [updateCurrentFocusedElement]);
|
|
132
135
|
const refocusTrap = (0, _react.useCallback)(() => {
|
|
136
|
+
var _wrapperRef$current;
|
|
137
|
+
|
|
133
138
|
/* istanbul ignore else */
|
|
134
139
|
if (currentFocusedElement && !currentFocusedElement.hasAttribute("disabled")) {
|
|
135
140
|
// the trap breaks if it tries to refocus a disabled element
|
|
136
141
|
(0, _focusTrapUtils.setElementFocus)(currentFocusedElement);
|
|
142
|
+
} else if (wrapperRef !== null && wrapperRef !== void 0 && (_wrapperRef$current = wrapperRef.current) !== null && _wrapperRef$current !== void 0 && _wrapperRef$current.hasAttribute("tabindex")) {
|
|
143
|
+
(0, _focusTrapUtils.setElementFocus)(wrapperRef.current);
|
|
137
144
|
} else if (firstElement) {
|
|
138
145
|
(0, _focusTrapUtils.setElementFocus)(firstElement);
|
|
139
146
|
}
|
|
140
|
-
}, [currentFocusedElement, firstElement]);
|
|
147
|
+
}, [currentFocusedElement, firstElement, wrapperRef]);
|
|
141
148
|
(0, _react.useEffect)(() => {
|
|
142
149
|
if (triggerRefocusFlag) {
|
|
143
150
|
refocusTrap();
|
|
144
151
|
}
|
|
145
152
|
}, [triggerRefocusFlag, refocusTrap]);
|
|
153
|
+
const [tabIndex, setTabIndex] = (0, _react.useState)(0);
|
|
154
|
+
(0, _react.useEffect)(() => {
|
|
155
|
+
// issue in cypress prevents setting tabIndex to -1, instead tabIndex is set to 0 and removed on blur.
|
|
156
|
+
if (!isOpen) {
|
|
157
|
+
setTabIndex(0);
|
|
158
|
+
}
|
|
159
|
+
}, [isOpen]);
|
|
160
|
+
|
|
161
|
+
const onBlur = () => {
|
|
162
|
+
/* istanbul ignore else */
|
|
163
|
+
if (isOpen) {
|
|
164
|
+
setTabIndex(undefined);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const focusProps = {
|
|
169
|
+
tabIndex,
|
|
170
|
+
onBlur
|
|
171
|
+
}; // passes focusProps if no tabindex has been explicitly set on the wrapper
|
|
172
|
+
|
|
173
|
+
const clonedChildren = _react.default.Children.map(children, child => {
|
|
174
|
+
return child.props.tabIndex === undefined ? /*#__PURE__*/_react.default.cloneElement(child, focusProps) : child;
|
|
175
|
+
});
|
|
176
|
+
|
|
146
177
|
return /*#__PURE__*/_react.default.createElement("div", {
|
|
147
178
|
ref: trapRef
|
|
148
|
-
},
|
|
179
|
+
}, clonedChildren);
|
|
149
180
|
};
|
|
150
181
|
|
|
151
182
|
FocusTrap.propTypes = {
|
|
@@ -165,7 +196,10 @@ FocusTrap.propTypes = {
|
|
|
165
196
|
/** a ref to the container wrapping the focusable elements */
|
|
166
197
|
wrapperRef: _propTypes.default.shape({
|
|
167
198
|
current: _propTypes.default.any
|
|
168
|
-
})
|
|
199
|
+
}),
|
|
200
|
+
|
|
201
|
+
/* whether the modal (etc.) component that the focus trap is inside is open or not */
|
|
202
|
+
isOpen: _propTypes.default.bool
|
|
169
203
|
};
|
|
170
204
|
var _default = FocusTrap;
|
|
171
205
|
exports.default = _default;
|
|
@@ -68,17 +68,17 @@ const AdvancedColorPicker = ({
|
|
|
68
68
|
setSelectedColorRef(selected.ref.current);
|
|
69
69
|
}
|
|
70
70
|
}, [colors, currentColor, dialogOpen, isOpen]);
|
|
71
|
-
const handleFocus = (0, _react.useCallback)((e, firstFocusableElement
|
|
71
|
+
const handleFocus = (0, _react.useCallback)((e, firstFocusableElement) => {
|
|
72
72
|
if (e.key === "Tab") {
|
|
73
73
|
/* istanbul ignore else */
|
|
74
74
|
if (e.shiftKey) {
|
|
75
75
|
/* istanbul ignore else */
|
|
76
|
-
if (document.activeElement ===
|
|
77
|
-
|
|
76
|
+
if (document.activeElement === firstFocusableElement) {
|
|
77
|
+
selectedColorRef.focus();
|
|
78
78
|
e.preventDefault();
|
|
79
79
|
}
|
|
80
|
-
} else if (document.activeElement ===
|
|
81
|
-
|
|
80
|
+
} else if (document.activeElement === selectedColorRef) {
|
|
81
|
+
firstFocusableElement.focus();
|
|
82
82
|
e.preventDefault();
|
|
83
83
|
}
|
|
84
84
|
}
|
|
@@ -179,7 +179,8 @@ const Dialog = ({
|
|
|
179
179
|
autoFocus: !disableAutoFocus,
|
|
180
180
|
focusFirstElement: focusFirstElement,
|
|
181
181
|
bespokeTrap: bespokeFocusTrap,
|
|
182
|
-
wrapperRef: dialogRef
|
|
182
|
+
wrapperRef: dialogRef,
|
|
183
|
+
isOpen: open
|
|
183
184
|
}, /*#__PURE__*/_react.default.createElement(_dialog.DialogStyle, _extends({
|
|
184
185
|
"aria-modal": true,
|
|
185
186
|
ref: dialogRef,
|
|
@@ -189,9 +190,9 @@ const Dialog = ({
|
|
|
189
190
|
"data-element": "dialog",
|
|
190
191
|
"data-role": rest["data-role"],
|
|
191
192
|
role: role
|
|
192
|
-
}, contentPadding), dialogTitle(), /*#__PURE__*/_react.default.createElement(_dialog.DialogContentStyle, contentPadding, /*#__PURE__*/_react.default.createElement(_dialog.DialogInnerContentStyle, _extends({
|
|
193
|
+
}, contentPadding), dialogTitle(), closeIcon(), /*#__PURE__*/_react.default.createElement(_dialog.DialogContentStyle, contentPadding, /*#__PURE__*/_react.default.createElement(_dialog.DialogInnerContentStyle, _extends({
|
|
193
194
|
ref: innerContentRef
|
|
194
|
-
}, contentPadding), children))
|
|
195
|
+
}, contentPadding), children)))));
|
|
195
196
|
};
|
|
196
197
|
|
|
197
198
|
Dialog.propTypes = {
|
|
@@ -109,7 +109,8 @@ const DialogFullScreen = ({
|
|
|
109
109
|
}, componentTags), /*#__PURE__*/_react.default.createElement(_focusTrap.default, {
|
|
110
110
|
autoFocus: !disableAutoFocus,
|
|
111
111
|
focusFirstElement: focusFirstElement,
|
|
112
|
-
wrapperRef: dialogRef
|
|
112
|
+
wrapperRef: dialogRef,
|
|
113
|
+
isOpen: open
|
|
113
114
|
}, /*#__PURE__*/_react.default.createElement(_dialogFullScreen.default, _extends({
|
|
114
115
|
"aria-modal": role === "dialog" ? true : undefined
|
|
115
116
|
}, ariaProps, {
|
|
@@ -117,12 +118,12 @@ const DialogFullScreen = ({
|
|
|
117
118
|
"data-element": "dialog-full-screen",
|
|
118
119
|
pagesStyling: pagesStyling,
|
|
119
120
|
role: role
|
|
120
|
-
}), dialogTitle(), /*#__PURE__*/_react.default.createElement(_content.default, {
|
|
121
|
+
}), dialogTitle(), closeIcon(), /*#__PURE__*/_react.default.createElement(_content.default, {
|
|
121
122
|
hasHeader: title !== undefined,
|
|
122
123
|
"data-element": "content",
|
|
123
124
|
ref: contentRef,
|
|
124
125
|
disableContentPadding: disableContentPadding
|
|
125
|
-
}, children)
|
|
126
|
+
}, children))));
|
|
126
127
|
};
|
|
127
128
|
|
|
128
129
|
DialogFullScreen.defaultProps = {
|
|
@@ -57,19 +57,6 @@ const MenuFullscreen = ({
|
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
(0, _react.useLayoutEffect)(() => {
|
|
61
|
-
const checkTransitionEnd = () => {
|
|
62
|
-
menuContentRef.current.focus();
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const wrapperRef = menuWrapperRef.current;
|
|
66
|
-
|
|
67
|
-
if (isOpen) {
|
|
68
|
-
wrapperRef.addEventListener("transitionend", checkTransitionEnd);
|
|
69
|
-
} else {
|
|
70
|
-
wrapperRef.removeEventListener("transitionend", checkTransitionEnd);
|
|
71
|
-
}
|
|
72
|
-
}, [isOpen]);
|
|
73
60
|
const scrollVariants = {
|
|
74
61
|
light: "light",
|
|
75
62
|
dark: "dark",
|
|
@@ -85,8 +72,8 @@ const MenuFullscreen = ({
|
|
|
85
72
|
return /*#__PURE__*/_react.default.createElement("li", {
|
|
86
73
|
"aria-label": "menu-fullscreen"
|
|
87
74
|
}, /*#__PURE__*/_react.default.createElement(_portal.default, null, /*#__PURE__*/_react.default.createElement(_focusTrap.default, {
|
|
88
|
-
|
|
89
|
-
|
|
75
|
+
wrapperRef: menuWrapperRef,
|
|
76
|
+
isOpen: isOpen
|
|
90
77
|
}, /*#__PURE__*/_react.default.createElement(_menuFullScreen.StyledMenuFullscreen, _extends({
|
|
91
78
|
"data-component": "menu-fullscreen",
|
|
92
79
|
ref: menuWrapperRef,
|
|
@@ -117,8 +104,7 @@ const MenuFullscreen = ({
|
|
|
117
104
|
display: "flex",
|
|
118
105
|
flexDirection: "column",
|
|
119
106
|
role: "list",
|
|
120
|
-
inFullscreenView: true
|
|
121
|
-
tabIndex: -1
|
|
107
|
+
inFullscreenView: true
|
|
122
108
|
}, _react.default.Children.map(children, (child, index) => /*#__PURE__*/_react.default.createElement(_menu2.default.Provider, {
|
|
123
109
|
value: {
|
|
124
110
|
inFullscreenView: true,
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
export default SidebarHeader;
|
|
2
|
-
declare function SidebarHeader({ className, children, ...props }: {
|
|
2
|
+
declare function SidebarHeader({ className, children, id, ...props }: {
|
|
3
3
|
[x: string]: any;
|
|
4
4
|
className: any;
|
|
5
5
|
children: any;
|
|
6
|
+
id: any;
|
|
6
7
|
}): JSX.Element;
|
|
7
8
|
declare namespace SidebarHeader {
|
|
8
9
|
namespace propTypes {
|
|
9
10
|
const children: PropTypes.Requireable<PropTypes.ReactNodeLike>;
|
|
10
11
|
const className: PropTypes.Requireable<string>;
|
|
12
|
+
const id: PropTypes.Requireable<string>;
|
|
11
13
|
}
|
|
12
14
|
}
|
|
13
15
|
import PropTypes from "prop-types";
|
|
@@ -20,9 +20,11 @@ function _extends() { _extends = Object.assign || function (target) { for (var i
|
|
|
20
20
|
const SidebarHeader = ({
|
|
21
21
|
className,
|
|
22
22
|
children,
|
|
23
|
+
id,
|
|
23
24
|
...props
|
|
24
25
|
}) => /*#__PURE__*/_react.default.createElement(_sidebarHeader.default, _extends({
|
|
25
|
-
className: className
|
|
26
|
+
className: className,
|
|
27
|
+
id: id
|
|
26
28
|
}, (0, _tags.default)("sidebar-header", props)), children);
|
|
27
29
|
|
|
28
30
|
SidebarHeader.propTypes = {
|
|
@@ -30,7 +32,10 @@ SidebarHeader.propTypes = {
|
|
|
30
32
|
children: _propTypes.default.node,
|
|
31
33
|
|
|
32
34
|
/** A custom class name. */
|
|
33
|
-
className: _propTypes.default.string
|
|
35
|
+
className: _propTypes.default.string,
|
|
36
|
+
|
|
37
|
+
/** A custom id. */
|
|
38
|
+
id: _propTypes.default.string
|
|
34
39
|
};
|
|
35
40
|
var _default = SidebarHeader;
|
|
36
41
|
exports.default = _default;
|
|
@@ -25,6 +25,8 @@ var _box = _interopRequireDefault(require("../box"));
|
|
|
25
25
|
|
|
26
26
|
var _sidebar2 = require("./sidebar.config");
|
|
27
27
|
|
|
28
|
+
var _guid = _interopRequireDefault(require("../../__internal__/utils/helpers/guid"));
|
|
29
|
+
|
|
28
30
|
var _useLocale = _interopRequireDefault(require("../../hooks/__internal__/useLocale"));
|
|
29
31
|
|
|
30
32
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
@@ -55,6 +57,9 @@ const Sidebar = /*#__PURE__*/_react.default.forwardRef(({
|
|
|
55
57
|
...rest
|
|
56
58
|
}, ref) => {
|
|
57
59
|
const locale = (0, _useLocale.default)();
|
|
60
|
+
const {
|
|
61
|
+
current: titleId
|
|
62
|
+
} = (0, _react.useRef)((0, _guid.default)());
|
|
58
63
|
let sidebarRef = (0, _react.useRef)();
|
|
59
64
|
if (ref) sidebarRef = ref;
|
|
60
65
|
|
|
@@ -79,14 +84,16 @@ const Sidebar = /*#__PURE__*/_react.default.forwardRef(({
|
|
|
79
84
|
"aria-modal": !enableBackgroundUI,
|
|
80
85
|
"aria-describedby": ariaDescribedBy,
|
|
81
86
|
"aria-label": ariaLabel,
|
|
82
|
-
"aria-labelledby": ariaLabelledBy,
|
|
87
|
+
"aria-labelledby": !ariaLabelledBy && !ariaLabel ? titleId : ariaLabelledBy,
|
|
83
88
|
ref: sidebarRef,
|
|
84
89
|
position: position,
|
|
85
90
|
size: size,
|
|
86
91
|
"data-element": "sidebar",
|
|
87
92
|
onCancel: onCancel,
|
|
88
93
|
role: role
|
|
89
|
-
},
|
|
94
|
+
}, header && /*#__PURE__*/_react.default.createElement(_sidebarHeader.default, {
|
|
95
|
+
id: titleId
|
|
96
|
+
}, header), closeIcon(), /*#__PURE__*/_react.default.createElement(_box.default, {
|
|
90
97
|
"data-element": "sidebar-content",
|
|
91
98
|
p: 4,
|
|
92
99
|
pt: "27px",
|
|
@@ -106,7 +113,8 @@ const Sidebar = /*#__PURE__*/_react.default.forwardRef(({
|
|
|
106
113
|
enableBackgroundUI: enableBackgroundUI,
|
|
107
114
|
className: "carbon-sidebar"
|
|
108
115
|
}, componentTags), enableBackgroundUI ? sidebar : /*#__PURE__*/_react.default.createElement(_focusTrap.default, {
|
|
109
|
-
wrapperRef: sidebarRef
|
|
116
|
+
wrapperRef: sidebarRef,
|
|
117
|
+
isOpen: open
|
|
110
118
|
}, sidebar));
|
|
111
119
|
});
|
|
112
120
|
|