@dhis2-ui/menu 9.9.1 → 9.10.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.
@@ -46,7 +46,7 @@ describe('Menu Component', () => {
46
46
  expect(menuElement.prop('role')).toBe('menu');
47
47
  expect(menuItem.childAt(0).props().role).toBe('menuitem');
48
48
  expect(menuItem.childAt(0).prop('aria-label')).toBe('Menu item');
49
- expect(menuDivider.prop('role')).toBe('separator');
49
+ expect(menuDivider.find('[role="separator"]').exists()).toBe(true);
50
50
  });
51
51
  it('Empty menu has role menu', () => {
52
52
  const menuDataTest = 'data-test-menu';
@@ -14,8 +14,18 @@ const isValidMenuItemNode = node => {
14
14
  return isValidMenuItemNode(node.firstElementChild);
15
15
  }
16
16
 
17
- const role = node.getAttribute('role');
18
- return role && isMenuItem(role);
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 => {
@@ -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
- // remove non-native props from native HTML elements
47
- delete childProps.hideDivider;
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) {
@@ -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
@@ -34,7 +34,7 @@ describe('Menu Component', () => {
34
34
  expect(menuElement.prop('role')).toBe('menu');
35
35
  expect(menuItem.childAt(0).props().role).toBe('menuitem');
36
36
  expect(menuItem.childAt(0).prop('aria-label')).toBe('Menu item');
37
- expect(menuDivider.prop('role')).toBe('separator');
37
+ expect(menuDivider.find('[role="separator"]').exists()).toBe(true);
38
38
  });
39
39
  it('Empty menu has role menu', () => {
40
40
  const menuDataTest = 'data-test-menu';
@@ -7,8 +7,18 @@ const isValidMenuItemNode = node => {
7
7
  return isValidMenuItemNode(node.firstElementChild);
8
8
  }
9
9
 
10
- const role = node.getAttribute('role');
11
- return role && isMenuItem(role);
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 => {
@@ -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
- // remove non-native props from native HTML elements
29
- delete childProps.hideDivider;
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) {
@@ -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.9.1",
3
+ "version": "9.10.0",
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.9.1",
37
- "@dhis2-ui/divider": "9.9.1",
38
- "@dhis2-ui/layer": "9.9.1",
39
- "@dhis2-ui/popper": "9.9.1",
40
- "@dhis2-ui/portal": "9.9.1",
41
- "@dhis2/ui-constants": "9.9.1",
42
- "@dhis2/ui-icons": "9.9.1",
36
+ "@dhis2-ui/card": "9.10.0",
37
+ "@dhis2-ui/divider": "9.10.0",
38
+ "@dhis2-ui/layer": "9.10.0",
39
+ "@dhis2-ui/popper": "9.10.0",
40
+ "@dhis2-ui/portal": "9.10.0",
41
+ "@dhis2/ui-constants": "9.10.0",
42
+ "@dhis2/ui-icons": "9.10.0",
43
43
  "classnames": "^2.3.1",
44
44
  "prop-types": "^15.7.2"
45
45
  },