@dhis2-ui/menu 9.9.1 → 9.10.1
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/build/cjs/menu/__tests__/menu.test.js +31 -1
- package/build/cjs/menu/helpers.js +12 -2
- package/build/cjs/menu/menu.js +7 -12
- package/build/cjs/menu/use-menu.js +5 -3
- package/build/cjs/menu-divider/menu-divider.js +0 -1
- package/build/es/menu/__tests__/menu.test.js +25 -1
- package/build/es/menu/helpers.js +12 -2
- package/build/es/menu/menu.js +8 -11
- package/build/es/menu/use-menu.js +5 -3
- package/build/es/menu-divider/menu-divider.js +0 -1
- package/package.json +8 -8
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
var _input = require("@dhis2-ui/input");
|
|
4
|
+
|
|
3
5
|
var _react = require("@testing-library/react");
|
|
4
6
|
|
|
5
7
|
var _userEvent = _interopRequireDefault(require("@testing-library/user-event"));
|
|
@@ -46,7 +48,7 @@ describe('Menu Component', () => {
|
|
|
46
48
|
expect(menuElement.prop('role')).toBe('menu');
|
|
47
49
|
expect(menuItem.childAt(0).props().role).toBe('menuitem');
|
|
48
50
|
expect(menuItem.childAt(0).prop('aria-label')).toBe('Menu item');
|
|
49
|
-
expect(menuDivider.
|
|
51
|
+
expect(menuDivider.find('[role="separator"]').exists()).toBe(true);
|
|
50
52
|
});
|
|
51
53
|
it('Empty menu has role menu', () => {
|
|
52
54
|
const menuDataTest = 'data-test-menu';
|
|
@@ -231,4 +233,32 @@ describe('Menu Component', () => {
|
|
|
231
233
|
|
|
232
234
|
expect(plainListItem.parentElement).not.toHaveFocus();
|
|
233
235
|
});
|
|
236
|
+
it('does not hijack input change value if space entered [bug]', () => {
|
|
237
|
+
const onChange = jest.fn();
|
|
238
|
+
const {
|
|
239
|
+
getByPlaceholderText
|
|
240
|
+
} = (0, _react.render)( /*#__PURE__*/_react2.default.createElement(_menu.Menu, {
|
|
241
|
+
dataTest: menuDataTest,
|
|
242
|
+
dense: false
|
|
243
|
+
}, /*#__PURE__*/_react2.default.createElement(_menuItem.MenuItem, {
|
|
244
|
+
value: "myValue",
|
|
245
|
+
label: "Click menu item"
|
|
246
|
+
}), /*#__PURE__*/_react2.default.createElement(_input.Input, {
|
|
247
|
+
onChange: onChange,
|
|
248
|
+
placeholder: "test"
|
|
249
|
+
})));
|
|
250
|
+
const inputField = getByPlaceholderText('test');
|
|
251
|
+
inputField.focus();
|
|
252
|
+
|
|
253
|
+
_userEvent.default.keyboard('t');
|
|
254
|
+
|
|
255
|
+
_userEvent.default.keyboard('e');
|
|
256
|
+
|
|
257
|
+
_userEvent.default.keyboard(' ');
|
|
258
|
+
|
|
259
|
+
_userEvent.default.keyboard('st');
|
|
260
|
+
|
|
261
|
+
expect(inputField.value).toBe('te st');
|
|
262
|
+
expect(onChange).toHaveBeenCalled();
|
|
263
|
+
});
|
|
234
264
|
});
|
|
@@ -14,8 +14,18 @@ const isValidMenuItemNode = node => {
|
|
|
14
14
|
return isValidMenuItemNode(node.firstElementChild);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const role = node.getAttribute('role');
|
|
18
|
-
|
|
17
|
+
const role = node.getAttribute('role'); // for h1 - h6 headings since their heading role is not explicitly set
|
|
18
|
+
// style elements do not have roles
|
|
19
|
+
|
|
20
|
+
if (node.nodeName.startsWith('H') || node.nodeName === 'STYLE') {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (role) {
|
|
25
|
+
return isMenuItem(role);
|
|
26
|
+
} else {
|
|
27
|
+
console.warn('Missing: role attribute on the menu child');
|
|
28
|
+
}
|
|
19
29
|
};
|
|
20
30
|
|
|
21
31
|
const getFocusableItemsIndices = elements => {
|
package/build/cjs/menu/menu.js
CHANGED
|
@@ -32,23 +32,20 @@ const Menu = _ref => {
|
|
|
32
32
|
menuRef,
|
|
33
33
|
focusedIndex
|
|
34
34
|
} = (0, _useMenu.useMenuNavigation)(children);
|
|
35
|
-
|
|
36
|
-
const childrenToRender = _react.Children.map(children, (child, index) => {
|
|
35
|
+
const childrenToRender = (0, _react.useMemo)(() => _react.Children.map(children, (child, index) => {
|
|
37
36
|
if (! /*#__PURE__*/(0, _react.isValidElement)(child)) {
|
|
38
37
|
return child;
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
const tabIndex = index === focusedIndex ? 0 : -1;
|
|
42
41
|
const childProps = { ...child.props
|
|
43
|
-
};
|
|
42
|
+
}; // this check is based on the type of child.
|
|
43
|
+
// if it is a native HTML element, like li, a, span, only apply its child props
|
|
44
|
+
// if it is a functional (React) component, it applies custom props, like dense, hideDivider, etc
|
|
44
45
|
|
|
45
46
|
if (typeof child.type === 'string') {
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
delete childProps.dense;
|
|
49
|
-
delete childProps.active; // all ul children must be li elements
|
|
50
|
-
// add tabindex for focus to those elements that are/contain a menuitem
|
|
51
|
-
|
|
47
|
+
// if the native HTML element child is not li, then wrap it in an li tag
|
|
48
|
+
// apply the tabindex prop if a child has the menuitem role to make it focusable
|
|
52
49
|
if (child.type === 'li') {
|
|
53
50
|
return (0, _helpers.hasMenuItemRole)(child.props.children[0]) ? /*#__PURE__*/(0, _react.cloneElement)(child, { ...childProps,
|
|
54
51
|
tabIndex
|
|
@@ -59,15 +56,13 @@ const Menu = _ref => {
|
|
|
59
56
|
}, /*#__PURE__*/(0, _react.cloneElement)(child, childProps));
|
|
60
57
|
}
|
|
61
58
|
} else {
|
|
62
|
-
// assign non-native props to custom elements
|
|
63
59
|
childProps.dense = typeof child.props.dense === 'boolean' ? child.props.dense : dense;
|
|
64
60
|
childProps.hideDivider = typeof child.props.hideDivider !== 'boolean' && index === 0 ? true : child.props.hideDivider;
|
|
65
61
|
return /*#__PURE__*/(0, _react.cloneElement)(child, { ...childProps,
|
|
66
62
|
tabIndex
|
|
67
63
|
});
|
|
68
64
|
}
|
|
69
|
-
});
|
|
70
|
-
|
|
65
|
+
}), [children, dense, focusedIndex]);
|
|
71
66
|
return /*#__PURE__*/_react.default.createElement("ul", {
|
|
72
67
|
"data-test": dataTest,
|
|
73
68
|
role: "menu",
|
|
@@ -13,6 +13,7 @@ const useMenuNavigation = children => {
|
|
|
13
13
|
const menuRef = (0, _react.useRef)(null);
|
|
14
14
|
const [focusableItemsIndices, setFocusableItemsIndices] = (0, _react.useState)(null);
|
|
15
15
|
const [activeItemIndex, setActiveItemIndex] = (0, _react.useState)(-1); // Initializes the indices for focusable items
|
|
16
|
+
// focusable items have the role of menuitem || menuitemcheckbox || menuitemradio
|
|
16
17
|
|
|
17
18
|
(0, _react.useEffect)(() => {
|
|
18
19
|
if (menuRef) {
|
|
@@ -51,10 +52,11 @@ const useMenuNavigation = children => {
|
|
|
51
52
|
|
|
52
53
|
case 'Enter':
|
|
53
54
|
case ' ':
|
|
54
|
-
event.preventDefault();
|
|
55
|
-
|
|
56
55
|
if (event.target.nodeName === 'LI') {
|
|
57
|
-
|
|
56
|
+
var _event$target$childre, _event$target$childre2;
|
|
57
|
+
|
|
58
|
+
event.preventDefault();
|
|
59
|
+
(_event$target$childre = event.target.children) === null || _event$target$childre === void 0 ? void 0 : (_event$target$childre2 = _event$target$childre[0]) === null || _event$target$childre2 === void 0 ? void 0 : _event$target$childre2.click();
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
break;
|
|
@@ -25,7 +25,6 @@ const MenuDivider = _ref => {
|
|
|
25
25
|
} = _ref;
|
|
26
26
|
return /*#__PURE__*/_react.default.createElement("li", {
|
|
27
27
|
"data-test": dataTest,
|
|
28
|
-
role: "separator",
|
|
29
28
|
className: _style.default.dynamic([["591815244", [_uiConstants.colors.white]]]) + " " + (className || "")
|
|
30
29
|
}, /*#__PURE__*/_react.default.createElement(_divider.Divider, {
|
|
31
30
|
dense: dense
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Input } from '@dhis2-ui/input';
|
|
1
2
|
import { render } from '@testing-library/react';
|
|
2
3
|
import userEvent from '@testing-library/user-event';
|
|
3
4
|
import { mount } from 'enzyme';
|
|
@@ -34,7 +35,7 @@ describe('Menu Component', () => {
|
|
|
34
35
|
expect(menuElement.prop('role')).toBe('menu');
|
|
35
36
|
expect(menuItem.childAt(0).props().role).toBe('menuitem');
|
|
36
37
|
expect(menuItem.childAt(0).prop('aria-label')).toBe('Menu item');
|
|
37
|
-
expect(menuDivider.
|
|
38
|
+
expect(menuDivider.find('[role="separator"]').exists()).toBe(true);
|
|
38
39
|
});
|
|
39
40
|
it('Empty menu has role menu', () => {
|
|
40
41
|
const menuDataTest = 'data-test-menu';
|
|
@@ -193,4 +194,27 @@ describe('Menu Component', () => {
|
|
|
193
194
|
|
|
194
195
|
expect(plainListItem.parentElement).not.toHaveFocus();
|
|
195
196
|
});
|
|
197
|
+
it('does not hijack input change value if space entered [bug]', () => {
|
|
198
|
+
const onChange = jest.fn();
|
|
199
|
+
const {
|
|
200
|
+
getByPlaceholderText
|
|
201
|
+
} = render( /*#__PURE__*/React.createElement(Menu, {
|
|
202
|
+
dataTest: menuDataTest,
|
|
203
|
+
dense: false
|
|
204
|
+
}, /*#__PURE__*/React.createElement(MenuItem, {
|
|
205
|
+
value: "myValue",
|
|
206
|
+
label: "Click menu item"
|
|
207
|
+
}), /*#__PURE__*/React.createElement(Input, {
|
|
208
|
+
onChange: onChange,
|
|
209
|
+
placeholder: "test"
|
|
210
|
+
})));
|
|
211
|
+
const inputField = getByPlaceholderText('test');
|
|
212
|
+
inputField.focus();
|
|
213
|
+
userEvent.keyboard('t');
|
|
214
|
+
userEvent.keyboard('e');
|
|
215
|
+
userEvent.keyboard(' ');
|
|
216
|
+
userEvent.keyboard('st');
|
|
217
|
+
expect(inputField.value).toBe('te st');
|
|
218
|
+
expect(onChange).toHaveBeenCalled();
|
|
219
|
+
});
|
|
196
220
|
});
|
package/build/es/menu/helpers.js
CHANGED
|
@@ -7,8 +7,18 @@ const isValidMenuItemNode = node => {
|
|
|
7
7
|
return isValidMenuItemNode(node.firstElementChild);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
const role = node.getAttribute('role');
|
|
11
|
-
|
|
10
|
+
const role = node.getAttribute('role'); // for h1 - h6 headings since their heading role is not explicitly set
|
|
11
|
+
// style elements do not have roles
|
|
12
|
+
|
|
13
|
+
if (node.nodeName.startsWith('H') || node.nodeName === 'STYLE') {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (role) {
|
|
18
|
+
return isMenuItem(role);
|
|
19
|
+
} else {
|
|
20
|
+
console.warn('Missing: role attribute on the menu child');
|
|
21
|
+
}
|
|
12
22
|
};
|
|
13
23
|
|
|
14
24
|
export const getFocusableItemsIndices = elements => {
|
package/build/es/menu/menu.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import _JSXStyle from "styled-jsx/style";
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import React, { Children, cloneElement, isValidElement } from 'react';
|
|
3
|
+
import React, { Children, cloneElement, isValidElement, useMemo } from 'react';
|
|
4
4
|
import { hasMenuItemRole } from './helpers.js';
|
|
5
5
|
import { useMenuNavigation } from './use-menu.js';
|
|
6
6
|
|
|
@@ -15,22 +15,20 @@ const Menu = _ref => {
|
|
|
15
15
|
menuRef,
|
|
16
16
|
focusedIndex
|
|
17
17
|
} = useMenuNavigation(children);
|
|
18
|
-
const childrenToRender = Children.map(children, (child, index) => {
|
|
18
|
+
const childrenToRender = useMemo(() => Children.map(children, (child, index) => {
|
|
19
19
|
if (! /*#__PURE__*/isValidElement(child)) {
|
|
20
20
|
return child;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const tabIndex = index === focusedIndex ? 0 : -1;
|
|
24
24
|
const childProps = { ...child.props
|
|
25
|
-
};
|
|
25
|
+
}; // this check is based on the type of child.
|
|
26
|
+
// if it is a native HTML element, like li, a, span, only apply its child props
|
|
27
|
+
// if it is a functional (React) component, it applies custom props, like dense, hideDivider, etc
|
|
26
28
|
|
|
27
29
|
if (typeof child.type === 'string') {
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
delete childProps.dense;
|
|
31
|
-
delete childProps.active; // all ul children must be li elements
|
|
32
|
-
// add tabindex for focus to those elements that are/contain a menuitem
|
|
33
|
-
|
|
30
|
+
// if the native HTML element child is not li, then wrap it in an li tag
|
|
31
|
+
// apply the tabindex prop if a child has the menuitem role to make it focusable
|
|
34
32
|
if (child.type === 'li') {
|
|
35
33
|
return hasMenuItemRole(child.props.children[0]) ? /*#__PURE__*/cloneElement(child, { ...childProps,
|
|
36
34
|
tabIndex
|
|
@@ -41,14 +39,13 @@ const Menu = _ref => {
|
|
|
41
39
|
}, /*#__PURE__*/cloneElement(child, childProps));
|
|
42
40
|
}
|
|
43
41
|
} else {
|
|
44
|
-
// assign non-native props to custom elements
|
|
45
42
|
childProps.dense = typeof child.props.dense === 'boolean' ? child.props.dense : dense;
|
|
46
43
|
childProps.hideDivider = typeof child.props.hideDivider !== 'boolean' && index === 0 ? true : child.props.hideDivider;
|
|
47
44
|
return /*#__PURE__*/cloneElement(child, { ...childProps,
|
|
48
45
|
tabIndex
|
|
49
46
|
});
|
|
50
47
|
}
|
|
51
|
-
});
|
|
48
|
+
}), [children, dense, focusedIndex]);
|
|
52
49
|
return /*#__PURE__*/React.createElement("ul", {
|
|
53
50
|
"data-test": dataTest,
|
|
54
51
|
role: "menu",
|
|
@@ -4,6 +4,7 @@ export const useMenuNavigation = children => {
|
|
|
4
4
|
const menuRef = useRef(null);
|
|
5
5
|
const [focusableItemsIndices, setFocusableItemsIndices] = useState(null);
|
|
6
6
|
const [activeItemIndex, setActiveItemIndex] = useState(-1); // Initializes the indices for focusable items
|
|
7
|
+
// focusable items have the role of menuitem || menuitemcheckbox || menuitemradio
|
|
7
8
|
|
|
8
9
|
useEffect(() => {
|
|
9
10
|
if (menuRef) {
|
|
@@ -42,10 +43,11 @@ export const useMenuNavigation = children => {
|
|
|
42
43
|
|
|
43
44
|
case 'Enter':
|
|
44
45
|
case ' ':
|
|
45
|
-
event.preventDefault();
|
|
46
|
-
|
|
47
46
|
if (event.target.nodeName === 'LI') {
|
|
48
|
-
|
|
47
|
+
var _event$target$childre, _event$target$childre2;
|
|
48
|
+
|
|
49
|
+
event.preventDefault();
|
|
50
|
+
(_event$target$childre = event.target.children) === null || _event$target$childre === void 0 ? void 0 : (_event$target$childre2 = _event$target$childre[0]) === null || _event$target$childre2 === void 0 ? void 0 : _event$target$childre2.click();
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
break;
|
|
@@ -12,7 +12,6 @@ const MenuDivider = _ref => {
|
|
|
12
12
|
} = _ref;
|
|
13
13
|
return /*#__PURE__*/React.createElement("li", {
|
|
14
14
|
"data-test": dataTest,
|
|
15
|
-
role: "separator",
|
|
16
15
|
className: _JSXStyle.dynamic([["591815244", [colors.white]]]) + " " + (className || "")
|
|
17
16
|
}, /*#__PURE__*/React.createElement(Divider, {
|
|
18
17
|
dense: dense
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dhis2-ui/menu",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.10.1",
|
|
4
4
|
"description": "UI Menu",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -33,13 +33,13 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@dhis2/prop-types": "^3.1.2",
|
|
36
|
-
"@dhis2-ui/card": "9.
|
|
37
|
-
"@dhis2-ui/divider": "9.
|
|
38
|
-
"@dhis2-ui/layer": "9.
|
|
39
|
-
"@dhis2-ui/popper": "9.
|
|
40
|
-
"@dhis2-ui/portal": "9.
|
|
41
|
-
"@dhis2/ui-constants": "9.
|
|
42
|
-
"@dhis2/ui-icons": "9.
|
|
36
|
+
"@dhis2-ui/card": "9.10.1",
|
|
37
|
+
"@dhis2-ui/divider": "9.10.1",
|
|
38
|
+
"@dhis2-ui/layer": "9.10.1",
|
|
39
|
+
"@dhis2-ui/popper": "9.10.1",
|
|
40
|
+
"@dhis2-ui/portal": "9.10.1",
|
|
41
|
+
"@dhis2/ui-constants": "9.10.1",
|
|
42
|
+
"@dhis2/ui-icons": "9.10.1",
|
|
43
43
|
"classnames": "^2.3.1",
|
|
44
44
|
"prop-types": "^15.7.2"
|
|
45
45
|
},
|