@geotab/zenith 3.10.0 → 3.11.0-beta.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.
Files changed (83) hide show
  1. package/README.md +6 -0
  2. package/dist/index.css +8 -1
  3. package/dist/menu/components/controlledMenuList/controlledMenuList.d.ts +27 -0
  4. package/dist/menu/components/controlledMenuList/controlledMenuList.js +123 -0
  5. package/dist/menu/components/createControlledMenuList.d.ts +37 -0
  6. package/dist/menu/components/createControlledMenuList.js +55 -0
  7. package/dist/menu/components/createMenuItem.d.ts +67 -0
  8. package/dist/menu/components/createMenuItem.js +97 -0
  9. package/dist/menu/components/menuButton.js +8 -2
  10. package/dist/menu/components/menuItem.d.ts +1 -2
  11. package/dist/menu/components/menuItem.js +20 -74
  12. package/dist/menu/contexts/usePathContext.d.ts +2 -0
  13. package/dist/menu/contexts/usePathContext.js +9 -0
  14. package/dist/menu/controlledMenu.js +8 -175
  15. package/dist/menu/utils/buildMenuContent.d.ts +2 -0
  16. package/dist/menu/utils/buildMenuContent.js +38 -0
  17. package/dist/menu/utils/findContent.d.ts +2 -2
  18. package/dist/menu/utils/findContent.js +4 -3
  19. package/dist/menu/utils/getItemLabel.d.ts +2 -0
  20. package/dist/menu/utils/getItemLabel.js +8 -0
  21. package/dist/menu/utils/getSafeRel.d.ts +1 -0
  22. package/dist/menu/utils/getSafeRel.js +14 -0
  23. package/dist/menu/utils/isMenuItem.d.ts +2 -0
  24. package/dist/menu/utils/isMenuItem.js +13 -0
  25. package/dist/menu/utils/isSafeHref.d.ts +1 -0
  26. package/dist/menu/utils/isSafeHref.js +10 -0
  27. package/dist/menu/utils/normalizeSeparators.d.ts +2 -0
  28. package/dist/menu/utils/normalizeSeparators.js +23 -0
  29. package/dist/menu/utils/resolveKeys.d.ts +12 -0
  30. package/dist/menu/utils/resolveKeys.js +22 -0
  31. package/dist/menu/utils/useLastValidSheet.d.ts +7 -0
  32. package/dist/menu/utils/useLastValidSheet.js +30 -0
  33. package/dist/menu/utils/useMenuItemCore.d.ts +31 -0
  34. package/dist/menu/utils/useMenuItemCore.js +51 -0
  35. package/dist/menu/utils/useMenuItemKeyboardNav.d.ts +2 -0
  36. package/dist/menu/utils/useMenuItemKeyboardNav.js +15 -0
  37. package/dist/menu/utils/useMenuListKeyboardNav.d.ts +12 -0
  38. package/dist/menu/utils/useMenuListKeyboardNav.js +77 -0
  39. package/dist/menu/utils/useMenuPath.d.ts +6 -0
  40. package/dist/menu/utils/useMenuPath.js +35 -0
  41. package/dist/nav/navItem/navItem.js +6 -4
  42. package/dist/nav/navSection/navSection.js +7 -5
  43. package/esm/menu/components/controlledMenuList/controlledMenuList.d.ts +27 -0
  44. package/esm/menu/components/controlledMenuList/controlledMenuList.js +120 -0
  45. package/esm/menu/components/createControlledMenuList.d.ts +37 -0
  46. package/esm/menu/components/createControlledMenuList.js +51 -0
  47. package/esm/menu/components/createMenuItem.d.ts +67 -0
  48. package/esm/menu/components/createMenuItem.js +93 -0
  49. package/esm/menu/components/menuButton.js +8 -2
  50. package/esm/menu/components/menuItem.d.ts +1 -2
  51. package/esm/menu/components/menuItem.js +20 -74
  52. package/esm/menu/contexts/usePathContext.d.ts +2 -0
  53. package/esm/menu/contexts/usePathContext.js +5 -0
  54. package/esm/menu/controlledMenu.js +10 -177
  55. package/esm/menu/utils/buildMenuContent.d.ts +2 -0
  56. package/esm/menu/utils/buildMenuContent.js +34 -0
  57. package/esm/menu/utils/findContent.d.ts +2 -2
  58. package/esm/menu/utils/findContent.js +4 -3
  59. package/esm/menu/utils/getItemLabel.d.ts +2 -0
  60. package/esm/menu/utils/getItemLabel.js +4 -0
  61. package/esm/menu/utils/getSafeRel.d.ts +1 -0
  62. package/esm/menu/utils/getSafeRel.js +10 -0
  63. package/esm/menu/utils/isMenuItem.d.ts +2 -0
  64. package/esm/menu/utils/isMenuItem.js +9 -0
  65. package/esm/menu/utils/isSafeHref.d.ts +1 -0
  66. package/esm/menu/utils/isSafeHref.js +6 -0
  67. package/esm/menu/utils/normalizeSeparators.d.ts +2 -0
  68. package/esm/menu/utils/normalizeSeparators.js +19 -0
  69. package/esm/menu/utils/resolveKeys.d.ts +12 -0
  70. package/esm/menu/utils/resolveKeys.js +18 -0
  71. package/esm/menu/utils/useLastValidSheet.d.ts +7 -0
  72. package/esm/menu/utils/useLastValidSheet.js +26 -0
  73. package/esm/menu/utils/useMenuItemCore.d.ts +31 -0
  74. package/esm/menu/utils/useMenuItemCore.js +47 -0
  75. package/esm/menu/utils/useMenuItemKeyboardNav.d.ts +2 -0
  76. package/esm/menu/utils/useMenuItemKeyboardNav.js +11 -0
  77. package/esm/menu/utils/useMenuListKeyboardNav.d.ts +12 -0
  78. package/esm/menu/utils/useMenuListKeyboardNav.js +73 -0
  79. package/esm/menu/utils/useMenuPath.d.ts +6 -0
  80. package/esm/menu/utils/useMenuPath.js +31 -0
  81. package/esm/nav/navItem/navItem.js +6 -4
  82. package/esm/nav/navSection/navSection.js +7 -5
  83. package/package.json +1 -1
@@ -0,0 +1,2 @@
1
+ import { RefObject } from "react";
2
+ export declare function useMenuItemKeyboardNav(isOpen: boolean, navigatedViaKeyboardRef: RefObject<boolean> | undefined): boolean;
@@ -0,0 +1,11 @@
1
+ import { useRef } from "react";
2
+ export function useMenuItemKeyboardNav(isOpen, navigatedViaKeyboardRef) {
3
+ const wasOpenRef = useRef(false);
4
+ const localOpenedViaKeyboardRef = useRef(false);
5
+ if (isOpen && !wasOpenRef.current && navigatedViaKeyboardRef) {
6
+ localOpenedViaKeyboardRef.current = navigatedViaKeyboardRef.current;
7
+ navigatedViaKeyboardRef.current = false;
8
+ }
9
+ wasOpenRef.current = isOpen;
10
+ return localOpenedViaKeyboardRef.current;
11
+ }
@@ -0,0 +1,12 @@
1
+ import { MutableRefObject } from "react";
2
+ export interface IKeyMap {
3
+ keyNext: string;
4
+ keyPrev: string;
5
+ keyOpenNested: string;
6
+ keyBack: string;
7
+ }
8
+ export declare const useMenuListKeyboardNav: (keyboardActiveRef: MutableRefObject<boolean>, navigatedViaKeyboardRef: MutableRefObject<boolean>, isHorizontal: boolean) => {
9
+ onKeyDown: (e: React.KeyboardEvent) => void;
10
+ onKeyDownVertical: (e: React.KeyboardEvent) => void;
11
+ onMouseDown: () => void;
12
+ };
@@ -0,0 +1,73 @@
1
+ import { useCallback } from "react";
2
+ import { findFirstFocusable } from "./findFirstFocusable";
3
+ import { findLastFocusable } from "./findLastFocusable";
4
+ import { findNextFocusable } from "./findNextFocusable";
5
+ import { findPrevFocusable } from "./findPrevFocusable";
6
+ import { isButton } from "./isButton";
7
+ import { isLink } from "./isLink";
8
+ import { resolveKeys, verticalKeys } from "./resolveKeys";
9
+ export const useMenuListKeyboardNav = (keyboardActiveRef, navigatedViaKeyboardRef, isHorizontal) => {
10
+ const handleNavigation = useCallback(
11
+ // eslint-disable-next-line complexity
12
+ (e, keyMap) => {
13
+ var _a, _b, _c, _d, _e, _f;
14
+ keyboardActiveRef.current = true;
15
+ const target = e.target;
16
+ const currentTarget = e.currentTarget;
17
+ if (!isButton(target) && !isLink(target)) {
18
+ if (target === currentTarget) {
19
+ const menuList = currentTarget.querySelector("ul");
20
+ if (menuList) {
21
+ if (e.key === keyMap.keyNext || e.key === "Home") {
22
+ e.preventDefault();
23
+ (_a = findFirstFocusable(menuList)) === null || _a === void 0 ? void 0 : _a.focus();
24
+ }
25
+ else if (e.key === keyMap.keyPrev || e.key === "End") {
26
+ e.preventDefault();
27
+ (_b = findLastFocusable(menuList)) === null || _b === void 0 ? void 0 : _b.focus();
28
+ }
29
+ }
30
+ }
31
+ return;
32
+ }
33
+ const isItemWithChildren = (e.key === keyMap.keyOpenNested || e.key === "Enter" || e.key === " ") &&
34
+ target.classList.contains("zen-menu-button__action--has-children");
35
+ const isBackButton = (e.key === keyMap.keyBack || e.key === "Enter" || e.key === " ") && target.dataset.id === "root";
36
+ if (e.key === keyMap.keyNext) {
37
+ e.preventDefault();
38
+ (_c = findNextFocusable(target)) === null || _c === void 0 ? void 0 : _c.focus();
39
+ return;
40
+ }
41
+ if (e.key === keyMap.keyPrev) {
42
+ e.preventDefault();
43
+ (_d = findPrevFocusable(target)) === null || _d === void 0 ? void 0 : _d.focus();
44
+ return;
45
+ }
46
+ if (e.key === "Home") {
47
+ e.preventDefault();
48
+ (_e = findFirstFocusable(target)) === null || _e === void 0 ? void 0 : _e.focus();
49
+ return;
50
+ }
51
+ if (e.key === "End") {
52
+ e.preventDefault();
53
+ (_f = findLastFocusable(target)) === null || _f === void 0 ? void 0 : _f.focus();
54
+ return;
55
+ }
56
+ if (isButton(target) && (isItemWithChildren || isBackButton)) {
57
+ e.preventDefault();
58
+ navigatedViaKeyboardRef.current = true;
59
+ target.click();
60
+ }
61
+ }, [keyboardActiveRef, navigatedViaKeyboardRef]);
62
+ const onKeyDown = useCallback((e) => {
63
+ handleNavigation(e, resolveKeys(e.target, isHorizontal));
64
+ }, [isHorizontal, handleNavigation]);
65
+ const onKeyDownVertical = useCallback((e) => {
66
+ handleNavigation(e, verticalKeys);
67
+ }, [handleNavigation]);
68
+ const onMouseDown = useCallback(() => {
69
+ keyboardActiveRef.current = false;
70
+ // eslint-disable-next-line react-hooks/exhaustive-deps
71
+ }, []);
72
+ return { onKeyDown, onKeyDownVertical, onMouseDown };
73
+ };
@@ -0,0 +1,6 @@
1
+ export declare const useMenuPath: (isOpen?: boolean) => {
2
+ path: string[];
3
+ onOpenBranch: (branchId?: string) => void;
4
+ closeBranch: () => void;
5
+ closeAll: () => void;
6
+ };
@@ -0,0 +1,31 @@
1
+ import { useState, useCallback, useEffect } from "react";
2
+ export const useMenuPath = (isOpen) => {
3
+ const [path, setPath] = useState([]);
4
+ useEffect(() => {
5
+ if (isOpen === false) {
6
+ setPath(v => (v.length ? [] : v));
7
+ }
8
+ }, [isOpen]);
9
+ const onOpenBranch = useCallback((branchId) => {
10
+ if (!branchId) {
11
+ return;
12
+ }
13
+ setPath(v => {
14
+ if (!v.includes(branchId)) {
15
+ return [...v, branchId];
16
+ }
17
+ const newPath = [...v];
18
+ newPath.pop();
19
+ return newPath;
20
+ });
21
+ }, []);
22
+ const closeBranch = useCallback(() => {
23
+ setPath(v => {
24
+ const newPath = [...v];
25
+ newPath.pop();
26
+ return newPath;
27
+ });
28
+ }, []);
29
+ const closeAll = useCallback(() => setPath([]), []);
30
+ return { path, onOpenBranch, closeBranch, closeAll };
31
+ };
@@ -91,17 +91,19 @@ const ButtonNavItem = (_a) => {
91
91
  }
92
92
  };
93
93
  const handleKeyPress = evt => {
94
- if (evt.key !== "ArrowRight") {
94
+ if (hasNestedItems && (evt.key === "Enter" || evt.key === " ")) {
95
+ evt.preventDefault();
96
+ setMenuOpen(true);
95
97
  return;
96
98
  }
97
- if (hasNestedItems) {
98
- setMenuOpen(prevOpen => !prevOpen);
99
+ if (hasNestedItems && evt.key === "ArrowRight") {
100
+ setMenuOpen(true);
99
101
  }
100
102
  };
101
103
  const isMobile = useMobile();
102
104
  const isActive = active || (hasNestedItems && menuOpen);
103
105
  const triggerId = useId();
104
- const buttonElement = (_jsx("button", { id: triggerId, ref: triggerRef, "aria-label": title, title: title, tabIndex: tabIndex, role: isMenuItem ? "menuitem" : undefined, className: "zen-nav-item__main", onClick: handleClick, onKeyDown: handleKeyPress, children: _jsx(NavItemContent, Object.assign({ title: title, collapsed: collapsed, hasSubmenu: hasNestedItems, level: currentLevel }, rest, { children: _jsx("span", { className: "zen-nav-item__title-text", children: title }) })) }));
106
+ const buttonElement = (_jsx("button", { id: triggerId, ref: triggerRef, "aria-label": title, title: title, tabIndex: tabIndex, role: isMenuItem ? "menuitem" : undefined, "aria-haspopup": hasNestedItems ? "menu" : undefined, "aria-expanded": hasNestedItems ? menuOpen : undefined, className: "zen-nav-item__main", onClick: handleClick, onKeyDown: handleKeyPress, children: _jsx(NavItemContent, Object.assign({ title: title, collapsed: collapsed, hasSubmenu: hasNestedItems, level: currentLevel }, rest, { children: _jsx("span", { className: "zen-nav-item__title-text", children: title }) })) }));
105
107
  const trigger = collapsed ? (_jsx(Tooltip, { trigger: buttonElement, alignment: tooltipAlignment, children: title })) : (buttonElement);
106
108
  if (hasNestedItems) {
107
109
  // Process children to add appropriate level classes while allowing unlimited nesting
@@ -3,6 +3,7 @@ import { classNames } from "../../commonHelpers/classNames/classNames";
3
3
  import { Children, cloneElement, isValidElement, useCallback, useEffect, useMemo, useRef } from "react";
4
4
  import { useNavContext } from "../context/nav.context";
5
5
  import { NavItem } from "../navItem/navItem";
6
+ const getNavItemMain = (wrapper) => { var _a; return (_a = wrapper === null || wrapper === void 0 ? void 0 : wrapper.querySelector(".zen-nav-item__main")) !== null && _a !== void 0 ? _a : wrapper; };
6
7
  /**
7
8
  * @beta This component is not fully ready yet and may change in future releases.
8
9
  */
@@ -23,11 +24,11 @@ export const NavSection = ({ children, className }) => {
23
24
  if (!sectionRef.current) {
24
25
  return;
25
26
  }
26
- const navItemElements = sectionRef.current.querySelectorAll(".zen-nav-item");
27
+ const navItemElements = sectionRef.current.querySelectorAll(":scope > .zen-nav-item");
27
28
  childRefs.current = Array.from(navItemElements);
28
29
  }, [children]);
29
30
  const arrowClickHandler = useCallback(evt => {
30
- var _a, _b, _c;
31
+ var _a;
31
32
  const key = evt.key;
32
33
  if ((key !== "ArrowDown" && key !== "ArrowUp" && key !== "Home" && key !== "End" && key !== "PageUp" && key !== "PageDown") || // non-handled keys
33
34
  !children || // section is empty
@@ -36,7 +37,7 @@ export const NavSection = ({ children, className }) => {
36
37
  return;
37
38
  }
38
39
  evt.preventDefault();
39
- (_a = childRefs.current[focusedIndex.current]) === null || _a === void 0 ? void 0 : _a.setAttribute("tabindex", "-1");
40
+ (_a = getNavItemMain(childRefs.current[focusedIndex.current])) === null || _a === void 0 ? void 0 : _a.setAttribute("tabindex", "-1");
40
41
  if (key === "ArrowDown") {
41
42
  // get next in a loop
42
43
  focusedIndex.current = focusedIndex.current === Children.count(children) - 1 ? 0 : focusedIndex.current + 1;
@@ -51,8 +52,9 @@ export const NavSection = ({ children, className }) => {
51
52
  if (key === "End" || key === "PageDown") {
52
53
  focusedIndex.current = Children.count(children) - 1;
53
54
  }
54
- (_b = childRefs.current[focusedIndex.current]) === null || _b === void 0 ? void 0 : _b.setAttribute("tabindex", "0");
55
- (_c = childRefs.current[focusedIndex.current]) === null || _c === void 0 ? void 0 : _c.focus();
55
+ const nextMain = getNavItemMain(childRefs.current[focusedIndex.current]);
56
+ nextMain === null || nextMain === void 0 ? void 0 : nextMain.setAttribute("tabindex", "0");
57
+ nextMain === null || nextMain === void 0 ? void 0 : nextMain.focus();
56
58
  }, [children]);
57
59
  const cssClasses = classNames(["zen-nav-section", className || "", collapsed ? "zen-nav-section--collapsed" : ""]);
58
60
  return (_jsx("div", { ref: sectionRef, role: "menu", className: cssClasses, onKeyDown: arrowClickHandler, children: focusableChildren }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geotab/zenith",
3
- "version": "3.10.0",
3
+ "version": "3.11.0-beta.1",
4
4
  "description": "Zenith components library on React",
5
5
  "main": "dist/index.js",
6
6
  "types": "esm/index.d.ts",