@doyourjob/gravity-ui-page-constructor-addons 2.1.45 → 2.1.47

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 (43) hide show
  1. package/build/cjs/components/Navigation/containers/Footer/GroupLinks/GroupLinks.js +3 -4
  2. package/build/cjs/components/NewHeader/NewHeader.css +5 -0
  3. package/build/cjs/components/NewHeader/NewHeader.js +3 -2
  4. package/build/cjs/components/NewHeader/components/NHLoginButton/NHLoginButton.css +18 -0
  5. package/build/cjs/components/NewHeader/components/NHLoginButton/NHLoginButton.js +18 -6
  6. package/build/cjs/components/NewHeader/components/NHMobileNavigation/NHMobileNavigation.css +10 -0
  7. package/build/cjs/components/NewHeader/components/NHMobileNavigation/NHMobileNavigation.js +8 -3
  8. package/build/cjs/components/NewHeader/components/NHMobileNavigationItem/NHMobileNavigationItem.css +6 -0
  9. package/build/cjs/components/NewHeader/components/NHMobileNavigationItem/NHMobileNavigationItem.js +2 -1
  10. package/build/cjs/components/NewHeader/components/NHMobileNavigationPopup/NHMobileNavigationPopup.d.ts +2 -1
  11. package/build/cjs/components/NewHeader/components/NHMobileNavigationPopup/NHMobileNavigationPopup.js +2 -2
  12. package/build/cjs/components/NewHeader/components/NHNavigation/NHNavigation.d.ts +4 -1
  13. package/build/cjs/components/NewHeader/components/NHNavigation/NHNavigation.js +72 -9
  14. package/build/cjs/components/NewHeader/components/NHNavigationItem/NHNavigationItem.css +5 -0
  15. package/build/cjs/components/NewHeader/components/NHNavigationItem/NHNavigationItem.d.ts +3 -0
  16. package/build/cjs/components/NewHeader/components/NHNavigationItem/NHNavigationItem.js +12 -3
  17. package/build/cjs/components/NewHeader/components/NHNavigationPopup/NHNavigationPopup.css +13 -2
  18. package/build/cjs/components/NewHeader/components/NHNavigationPopup/NHNavigationPopup.js +58 -6
  19. package/build/cjs/components/NewHeader/components/NHNavigationPopup/keyboardNavigation.d.ts +4 -0
  20. package/build/cjs/components/NewHeader/components/NHNavigationPopup/keyboardNavigation.js +43 -0
  21. package/build/cjs/components/NewHeader/components/NHPopupItem/NHPopupItem.css +5 -0
  22. package/build/esm/components/Navigation/containers/Footer/GroupLinks/GroupLinks.js +3 -4
  23. package/build/esm/components/NewHeader/NewHeader.css +5 -0
  24. package/build/esm/components/NewHeader/NewHeader.js +3 -2
  25. package/build/esm/components/NewHeader/components/NHLoginButton/NHLoginButton.css +18 -0
  26. package/build/esm/components/NewHeader/components/NHLoginButton/NHLoginButton.js +19 -7
  27. package/build/esm/components/NewHeader/components/NHMobileNavigation/NHMobileNavigation.css +10 -0
  28. package/build/esm/components/NewHeader/components/NHMobileNavigation/NHMobileNavigation.js +9 -4
  29. package/build/esm/components/NewHeader/components/NHMobileNavigationItem/NHMobileNavigationItem.css +6 -0
  30. package/build/esm/components/NewHeader/components/NHMobileNavigationItem/NHMobileNavigationItem.js +2 -1
  31. package/build/esm/components/NewHeader/components/NHMobileNavigationPopup/NHMobileNavigationPopup.d.ts +2 -1
  32. package/build/esm/components/NewHeader/components/NHMobileNavigationPopup/NHMobileNavigationPopup.js +2 -2
  33. package/build/esm/components/NewHeader/components/NHNavigation/NHNavigation.d.ts +4 -1
  34. package/build/esm/components/NewHeader/components/NHNavigation/NHNavigation.js +72 -9
  35. package/build/esm/components/NewHeader/components/NHNavigationItem/NHNavigationItem.css +5 -0
  36. package/build/esm/components/NewHeader/components/NHNavigationItem/NHNavigationItem.d.ts +3 -0
  37. package/build/esm/components/NewHeader/components/NHNavigationItem/NHNavigationItem.js +12 -3
  38. package/build/esm/components/NewHeader/components/NHNavigationPopup/NHNavigationPopup.css +13 -2
  39. package/build/esm/components/NewHeader/components/NHNavigationPopup/NHNavigationPopup.js +58 -6
  40. package/build/esm/components/NewHeader/components/NHNavigationPopup/keyboardNavigation.d.ts +4 -0
  41. package/build/esm/components/NewHeader/components/NHNavigationPopup/keyboardNavigation.js +38 -0
  42. package/build/esm/components/NewHeader/components/NHPopupItem/NHPopupItem.css +5 -0
  43. package/package.json +1 -1
@@ -7,9 +7,8 @@ const EnrichedLink_1 = require("../../../../EnrichedLink/EnrichedLink");
7
7
  const b = (0, cn_1.block)('group-links');
8
8
  const GroupLinks = ({ columnGroup: { title, items }, className }) => {
9
9
  return (react_1.default.createElement("div", { className: b(null, className), key: title },
10
- react_1.default.createElement("ul", { className: b('group') },
11
- title && react_1.default.createElement("h5", { className: b('group-title') }, title),
12
- items.map((item) => (react_1.default.createElement("li", { key: item.title, className: b('item-wrapper') },
13
- react_1.default.createElement(EnrichedLink_1.EnrichedLink, Object.assign({ className: b('item') }, item))))))));
10
+ title && react_1.default.createElement("h5", { className: b('group-title') }, title),
11
+ react_1.default.createElement("ul", { className: b('group'), "aria-label": title }, items.map((item) => (react_1.default.createElement("li", { key: item.title, className: b('item-wrapper') },
12
+ react_1.default.createElement(EnrichedLink_1.EnrichedLink, Object.assign({ className: b('item') }, item))))))));
14
13
  };
15
14
  exports.default = GroupLinks;
@@ -172,6 +172,11 @@ unpredictable css rules order in build */
172
172
  border-color: rgba(5, 43, 66, 0.25);
173
173
  }
174
174
 
175
+ .pc-addons-new-header__button:focus-visible {
176
+ outline: 2px solid var(--g-color-line-focus);
177
+ outline-offset: 2px;
178
+ }
179
+
175
180
  @media (max-width: 1081px) {
176
181
  .pc-addons-new-header__left {
177
182
  margin-right: 0;
@@ -19,6 +19,7 @@ const NewHeader = ({ data, setupRouteChangeHandler, renderSearch, className, scr
19
19
  const [isSearchMode, setIsSearchMode] = (0, react_1.useState)(false);
20
20
  const [isMobileNavigationOpen, setIsMobileNavigationOpen] = (0, react_1.useState)(false);
21
21
  const [pageHasScroll, setPageHasScroll] = (0, react_1.useState)(false);
22
+ const [activeNavigationId, setActiveNavigationId] = (0, react_1.useState)(null);
22
23
  const showButtonsContainer = Boolean(buttons);
23
24
  const toggleSearch = (0, react_1.useCallback)((isActive) => setIsSearchMode(isActive), []);
24
25
  const toggleMobileNavigationPopup = (0, react_1.useCallback)((isOpened) => {
@@ -58,10 +59,10 @@ const NewHeader = ({ data, setupRouteChangeHandler, renderSearch, className, scr
58
59
  react_1.default.createElement("div", { className: b('left') },
59
60
  logo && react_1.default.createElement(NHLogo_1.NHLogo, { data: logo }),
60
61
  left ? (react_1.default.createElement("div", { className: b('navigation') },
61
- react_1.default.createElement(NHNavigation_1.NHNavigation, { data: left, headerRef: headerRef }))) : null),
62
+ react_1.default.createElement(NHNavigation_1.NHNavigation, { activeNavigationId: activeNavigationId, data: left, headerRef: headerRef, navigationId: "left", setActiveNavigationId: setActiveNavigationId }))) : null),
62
63
  react_1.default.createElement("div", { className: b('right') },
63
64
  right ? (react_1.default.createElement("div", { className: b('navigation') },
64
- react_1.default.createElement(NHNavigation_1.NHNavigation, { data: right, headerRef: headerRef }))) : null,
65
+ react_1.default.createElement(NHNavigation_1.NHNavigation, { activeNavigationId: activeNavigationId, data: right, headerRef: headerRef, navigationId: "right", setActiveNavigationId: setActiveNavigationId }))) : null,
65
66
  react_1.default.createElement("div", { className: b('wrap') },
66
67
  renderSearch && (react_1.default.createElement("div", { className: b('icons-container') }, renderSearch({ onActiveToggle: toggleSearch }))),
67
68
  react_1.default.createElement("div", { className: b('buttons') },
@@ -1,6 +1,19 @@
1
1
  /* use this for style redefinitions to awoid problems with
2
2
  unpredictable css rules order in build */
3
+ .pc-addons-nh-login-button__wrapper {
4
+ display: flex;
5
+ }
6
+
3
7
  .pc-addons-nh-login-button {
8
+ display: inline-block;
9
+ margin: 0;
10
+ padding: 0;
11
+ font: inherit;
12
+ border: none;
13
+ outline: none;
14
+ color: inherit;
15
+ background: none;
16
+ cursor: pointer;
4
17
  display: flex;
5
18
  align-items: center;
6
19
  height: 36px;
@@ -17,4 +30,9 @@ unpredictable css rules order in build */
17
30
 
18
31
  .pc-addons-nh-login-button:hover {
19
32
  background: var(--g-color-base-brand-hover);
33
+ }
34
+
35
+ .pc-addons-nh-login-button:focus-visible {
36
+ outline: 2px solid var(--g-color-line-focus);
37
+ outline-offset: 2px;
20
38
  }
@@ -15,19 +15,30 @@ const NHLoginButton = ({ data, headerRef, setupRouteChangeHandler }) => {
15
15
  const [isActive, setIsActive] = (0, react_1.useState)(false);
16
16
  const [pretendentActiveTab, setPretendentAciveTab] = (0, react_1.useState)(false);
17
17
  const [previouslyFocusedElement, setPreviouslyFocusedElement] = (0, react_1.useState)(null);
18
+ const popupId = (0, uikit_1.useUniqId)();
18
19
  const handleActiveTab = (0, react_1.useCallback)((val) => {
19
20
  setPreviouslyFocusedElement(document.activeElement);
20
21
  setPretendentAciveTab(val);
21
22
  }, []);
22
23
  const onEscapeKeyDown = (0, react_1.useCallback)((event) => {
23
- if (event.key === 'Escape') {
24
+ if (event.key === 'Escape' && isActive) {
24
25
  setIsActive(false);
25
26
  setPretendentAciveTab(false);
26
27
  previouslyFocusedElement === null || previouslyFocusedElement === void 0 ? void 0 : previouslyFocusedElement.focus({ preventScroll: true });
27
28
  }
28
- }, [previouslyFocusedElement]);
29
+ }, [isActive, previouslyFocusedElement]);
29
30
  const handleMouseEnter = (0, react_1.useCallback)(() => handleActiveTab(true), [handleActiveTab]);
30
31
  const handleMouseLeave = (0, react_1.useCallback)(() => handleActiveTab(false), [handleActiveTab]);
32
+ const handleToggle = (0, react_1.useCallback)(() => {
33
+ const focusedElement = document.activeElement;
34
+ const nextIsActive = !isActive;
35
+ setPreviouslyFocusedElement(focusedElement);
36
+ setPretendentAciveTab(nextIsActive);
37
+ setIsActive(nextIsActive);
38
+ if (!nextIsActive) {
39
+ focusedElement === null || focusedElement === void 0 ? void 0 : focusedElement.focus({ preventScroll: true });
40
+ }
41
+ }, [isActive]);
31
42
  (0, react_1.useEffect)(() => {
32
43
  const timerId = setTimeout(() => {
33
44
  setIsActive(pretendentActiveTab);
@@ -43,10 +54,11 @@ const NHLoginButton = ({ data, headerRef, setupRouteChangeHandler }) => {
43
54
  (0, react_1.useEffect)(() => setupRouteChangeHandler === null || setupRouteChangeHandler === void 0 ? void 0 : setupRouteChangeHandler(() => {
44
55
  handleMouseLeave();
45
56
  }), [handleMouseLeave, setupRouteChangeHandler]);
46
- return (react_1.default.createElement("div", { className: b(), onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave },
47
- data.text,
48
- react_1.default.createElement(uikit_1.Icon, { data: isActive ? ChevronUp_1.ChevronUp : ChevronDown_1.ChevronDown, size: 16 }),
49
- isActive && (react_1.default.createElement(NHNavigationPopup_1.NHNavigationPopup, { headerRef: headerRef },
57
+ return (react_1.default.createElement("div", { className: b('wrapper'), onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave },
58
+ react_1.default.createElement("button", { className: b(), type: "button", onClick: handleToggle, "aria-expanded": isActive, "aria-controls": popupId },
59
+ data.text,
60
+ react_1.default.createElement(uikit_1.Icon, { data: isActive ? ChevronUp_1.ChevronUp : ChevronDown_1.ChevronDown, size: 16 })),
61
+ isActive && (react_1.default.createElement(NHNavigationPopup_1.NHNavigationPopup, { headerRef: headerRef, id: popupId },
50
62
  react_1.default.createElement(NHLoginPopup_1.NHLoginPopup, Object.assign({}, data))))));
51
63
  };
52
64
  exports.NHLoginButton = NHLoginButton;
@@ -29,6 +29,11 @@ unpredictable css rules order in build */
29
29
  display: none;
30
30
  }
31
31
 
32
+ .pc-addons-nh-mobile-navigation__icon:focus-visible {
33
+ outline: 2px solid var(--g-color-line-focus);
34
+ outline-offset: 2px;
35
+ }
36
+
32
37
  .pc-addons-nh-mobile-navigation__header {
33
38
  position: sticky;
34
39
  top: 0;
@@ -48,6 +53,11 @@ unpredictable css rules order in build */
48
53
  gap: 8px;
49
54
  }
50
55
 
56
+ .pc-addons-nh-mobile-navigation__close-button:focus-visible {
57
+ outline: 2px solid var(--g-color-line-focus);
58
+ outline-offset: 2px;
59
+ }
60
+
51
61
  .pc-addons-nh-mobile-navigation__search-container {
52
62
  display: flex;
53
63
  align-items: center;
@@ -14,17 +14,22 @@ const NHPopupItem_1 = require("../NHPopupItem/NHPopupItem");
14
14
  const b = (0, cn_1.block)('nh-mobile-navigation');
15
15
  const NHMobileNavigation = ({ isOpened, toogleOpen, isSearchOpen, data, onMenuScroll, }) => {
16
16
  var _a, _b;
17
+ const popupId = (0, uikit_1.useUniqId)();
17
18
  return (react_1.default.createElement("div", { className: b() },
18
- react_1.default.createElement(uikit_1.Button, { view: "flat", size: "l", className: b('icon', { hidden: isSearchOpen }), onClick: (e) => {
19
+ react_1.default.createElement(uikit_1.Button, { view: "flat", size: "l", className: b('icon', { hidden: isSearchOpen }), extraProps: {
20
+ 'aria-label': isOpened ? 'Close navigation menu' : 'Open navigation menu',
21
+ 'aria-expanded': isOpened,
22
+ 'aria-controls': popupId,
23
+ }, onClick: (e) => {
19
24
  e.stopPropagation();
20
25
  toogleOpen(!isOpened);
21
26
  } },
22
27
  react_1.default.createElement(uikit_1.Icon, { data: isOpened ? icons_1.Xmark : icons_1.Bars, size: 24 })),
23
- react_1.default.createElement(NHMobileNavigationPopup_1.NHMobileNavigationPopup, { isOpened: isOpened, onClose: () => toogleOpen(false), onMenuScroll: onMenuScroll },
28
+ react_1.default.createElement(NHMobileNavigationPopup_1.NHMobileNavigationPopup, { id: popupId, isOpened: isOpened, onClose: () => toogleOpen(false), onMenuScroll: onMenuScroll },
24
29
  react_1.default.createElement("div", { className: b('header') },
25
30
  (data === null || data === void 0 ? void 0 : data.logo) && react_1.default.createElement(NHLogo_1.NHLogo, { data: data.logo }),
26
31
  react_1.default.createElement("div", { className: b('header-right') },
27
- react_1.default.createElement(uikit_1.Button, { view: "flat", size: "l", className: b('close-button'), onClick: () => toogleOpen(false) },
32
+ react_1.default.createElement(uikit_1.Button, { view: "flat", size: "l", className: b('close-button'), extraProps: { 'aria-label': 'Close navigation menu' }, onClick: () => toogleOpen(false) },
28
33
  react_1.default.createElement(uikit_1.Icon, { data: icons_1.Xmark, size: 24 })))),
29
34
  react_1.default.createElement("nav", { className: b('nav') },
30
35
  react_1.default.createElement("ul", { className: b('list') }, (_a = data === null || data === void 0 ? void 0 : data.left) === null || _a === void 0 ? void 0 : _a.map((item, index) => (react_1.default.createElement("li", { className: b('item'), key: index },
@@ -8,6 +8,11 @@ unpredictable css rules order in build */
8
8
  align-items: center;
9
9
  }
10
10
 
11
+ .pc-addons-nh-mobile-navigation-item:focus-visible {
12
+ outline: 2px solid var(--g-color-line-focus);
13
+ outline-offset: 2px;
14
+ }
15
+
11
16
  .pc-addons-nh-mobile-navigation-item_type_link {
12
17
  color: inherit;
13
18
  text-decoration: none;
@@ -27,6 +32,7 @@ unpredictable css rules order in build */
27
32
 
28
33
  .pc-addons-nh-mobile-navigation-item__arrow {
29
34
  color: var(--g-color-text-secondary);
35
+ display: flex;
30
36
  }
31
37
 
32
38
  .pc-addons-nh-mobile-navigation-item__content {
@@ -15,6 +15,7 @@ const NHMobileNavigationItem = ({ item }) => {
15
15
  }, [isOpened]);
16
16
  const handleKeyDown = (0, react_1.useCallback)((e) => {
17
17
  if (e.key === 'Enter' || e.key === ' ') {
18
+ e.preventDefault();
18
19
  toggleOpen();
19
20
  }
20
21
  }, [toggleOpen]);
@@ -23,7 +24,7 @@ const NHMobileNavigationItem = ({ item }) => {
23
24
  return (react_1.default.createElement("a", Object.assign({ href: item.data.url, className: b({ type: 'link' }) }, (0, gravity_ui_page_constructor_1.getLinkProps)(item.data.url || '', undefined, item.data.target)), item.title));
24
25
  }
25
26
  return (react_1.default.createElement(react_1.Fragment, null,
26
- react_1.default.createElement("div", { className: b(), onClick: toggleOpen, role: "button", tabIndex: 0, onKeyDown: handleKeyDown },
27
+ react_1.default.createElement("div", { className: b(), onClick: toggleOpen, role: "button", tabIndex: 0, "aria-expanded": isOpened, onKeyDown: handleKeyDown },
27
28
  react_1.default.createElement("div", { className: b('text') }, item.title),
28
29
  react_1.default.createElement("div", { className: b('arrow') },
29
30
  react_1.default.createElement(gravity_ui_page_constructor_1.ToggleArrow, { size: 12, type: "vertical", open: isOpened, iconType: "navigation" }))),
@@ -1,9 +1,10 @@
1
1
  import React from 'react';
2
2
  interface MobileNavigationPopupProps {
3
+ id?: string;
3
4
  isOpened: boolean;
4
5
  onClose: () => void;
5
6
  children: React.ReactNode;
6
7
  onMenuScroll: (scrollTop: number) => void;
7
8
  }
8
- export declare const NHMobileNavigationPopup: ({ isOpened, onClose, children, onMenuScroll, }: MobileNavigationPopupProps) => React.ReactPortal | null;
9
+ export declare const NHMobileNavigationPopup: ({ id, isOpened, onClose, children, onMenuScroll, }: MobileNavigationPopupProps) => React.ReactPortal | null;
9
10
  export {};
@@ -10,7 +10,7 @@ const react_transition_group_1 = require("react-transition-group");
10
10
  const cn_1 = require("../../../../utils/cn");
11
11
  const b = (0, cn_1.block)('nh-mobile-navigation-popup');
12
12
  const TRANSITION_TIME = 400;
13
- const NHMobileNavigationPopup = ({ isOpened, onClose, children, onMenuScroll, }) => {
13
+ const NHMobileNavigationPopup = ({ id, isOpened, onClose, children, onMenuScroll, }) => {
14
14
  const [body, setBody] = (0, react_1.useState)();
15
15
  const ref = (0, react_1.useRef)(null);
16
16
  const handleScroll = (0, react_1.useCallback)(() => {
@@ -38,6 +38,6 @@ const NHMobileNavigationPopup = ({ isOpened, onClose, children, onMenuScroll, })
38
38
  }
39
39
  return react_dom_1.default.createPortal(react_1.default.createElement(react_transition_group_1.CSSTransition, { in: isOpened, classNames: b('transition'), unmountOnExit: true, timeout: TRANSITION_TIME },
40
40
  react_1.default.createElement(gravity_ui_page_constructor_1.OutsideClick, { className: b(), onOutsideClick: onClose },
41
- react_1.default.createElement("div", { ref: ref, className: b('container') }, children))), body);
41
+ react_1.default.createElement("div", { ref: ref, className: b('container'), id: id }, children))), body);
42
42
  };
43
43
  exports.NHMobileNavigationPopup = NHMobileNavigationPopup;
@@ -2,9 +2,12 @@ import type { RefObject } from 'react';
2
2
  import React from 'react';
3
3
  import { NHNavigationItemData, SetupRouteChangeHandler } from '../../models';
4
4
  interface NavigationProps {
5
+ activeNavigationId: string | null;
5
6
  data: NHNavigationItemData[];
6
7
  headerRef?: RefObject<HTMLDivElement>;
8
+ navigationId: string;
7
9
  setupRouteChangeHandler?: SetupRouteChangeHandler;
10
+ setActiveNavigationId: (navigationId: string | null) => void;
8
11
  }
9
- export declare const NHNavigation: ({ data, headerRef, setupRouteChangeHandler }: NavigationProps) => React.JSX.Element;
12
+ export declare const NHNavigation: ({ activeNavigationId, data, headerRef, navigationId, setupRouteChangeHandler, setActiveNavigationId, }: NavigationProps) => React.JSX.Element;
10
13
  export {};
@@ -9,6 +9,7 @@ const models_1 = require("../../models");
9
9
  const NHDefaultPopup_1 = require("../NHDefaultPopup/NHDefaultPopup");
10
10
  const NHNavigationItem_1 = require("../NHNavigationItem/NHNavigationItem");
11
11
  const NHNavigationPopup_1 = require("../NHNavigationPopup/NHNavigationPopup");
12
+ const keyboardNavigation_1 = require("../NHNavigationPopup/keyboardNavigation");
12
13
  const b = (0, cn_1.block)('nh-navigation');
13
14
  const tooltipPrefixId = 'navigation-item-key';
14
15
  const getPopupContent = (sectionData) => {
@@ -19,37 +20,99 @@ const getPopupContent = (sectionData) => {
19
20
  return null;
20
21
  }
21
22
  };
22
- const NHNavigation = ({ data, headerRef, setupRouteChangeHandler }) => {
23
+ const NHNavigation = ({ activeNavigationId, data, headerRef, navigationId, setupRouteChangeHandler, setActiveNavigationId, }) => {
23
24
  const [activeTab, setActiveTab] = (0, react_1.useState)(constants_1.NO_MENU_TAB_SELECTED);
24
25
  const [pretendentActiveTab, setPretendentAciveTab] = (0, react_1.useState)(constants_1.NO_MENU_TAB_SELECTED);
25
26
  const [previouslyFocusedElement, setPreviouslyFocusedElement] = (0, react_1.useState)(null);
27
+ const [popupTabToFocus, setPopupTabToFocus] = (0, react_1.useState)(null);
28
+ const setNavigationTab = (0, react_1.useCallback)((currentIndex) => {
29
+ setPretendentAciveTab(currentIndex);
30
+ setActiveTab(currentIndex);
31
+ setPopupTabToFocus(null);
32
+ setActiveNavigationId(currentIndex === constants_1.NO_MENU_TAB_SELECTED ? null : navigationId);
33
+ }, [navigationId, setActiveNavigationId]);
26
34
  const handleActiveTab = (0, react_1.useCallback)((currentIndex) => {
27
35
  setPreviouslyFocusedElement(document.activeElement);
28
36
  setPretendentAciveTab(currentIndex);
29
37
  }, []);
38
+ const handleToggleTab = (0, react_1.useCallback)((currentIndex) => {
39
+ const focusedElement = document.activeElement;
40
+ const nextActiveTab = activeTab === currentIndex ? constants_1.NO_MENU_TAB_SELECTED : currentIndex;
41
+ setPreviouslyFocusedElement(focusedElement);
42
+ setNavigationTab(nextActiveTab);
43
+ if (nextActiveTab === constants_1.NO_MENU_TAB_SELECTED) {
44
+ focusedElement === null || focusedElement === void 0 ? void 0 : focusedElement.focus({ preventScroll: true });
45
+ }
46
+ }, [activeTab, setNavigationTab]);
47
+ const handleFocusTabPopup = (0, react_1.useCallback)((currentIndex) => {
48
+ setPreviouslyFocusedElement(document.activeElement);
49
+ setPretendentAciveTab(currentIndex);
50
+ setActiveTab(currentIndex);
51
+ setPopupTabToFocus(currentIndex);
52
+ setActiveNavigationId(navigationId);
53
+ }, [navigationId, setActiveNavigationId]);
54
+ const handleFocusTab = (0, react_1.useCallback)((currentIndex) => {
55
+ if (activeNavigationId === null) {
56
+ return;
57
+ }
58
+ setPreviouslyFocusedElement(document.activeElement);
59
+ setNavigationTab(currentIndex);
60
+ }, [activeNavigationId, setNavigationTab]);
61
+ const handleCloseTab = (0, react_1.useCallback)(() => {
62
+ setNavigationTab(constants_1.NO_MENU_TAB_SELECTED);
63
+ previouslyFocusedElement === null || previouslyFocusedElement === void 0 ? void 0 : previouslyFocusedElement.focus({ preventScroll: true });
64
+ }, [previouslyFocusedElement, setNavigationTab]);
65
+ const handleBlur = (0, react_1.useCallback)((event) => {
66
+ if (activeTab === constants_1.NO_MENU_TAB_SELECTED) {
67
+ return;
68
+ }
69
+ const nextFocusedElement = event.relatedTarget;
70
+ const popup = document.getElementById(`${tooltipPrefixId}-${activeTab}`);
71
+ if (nextFocusedElement &&
72
+ (event.currentTarget.contains(nextFocusedElement) ||
73
+ (popup === null || popup === void 0 ? void 0 : popup.contains(nextFocusedElement)))) {
74
+ return;
75
+ }
76
+ setNavigationTab(constants_1.NO_MENU_TAB_SELECTED);
77
+ }, [activeTab, setNavigationTab]);
30
78
  const onEscapeKeyDown = (0, react_1.useCallback)((event) => {
31
- if (event.key === 'Escape') {
32
- setActiveTab(constants_1.NO_MENU_TAB_SELECTED);
33
- setPretendentAciveTab(constants_1.NO_MENU_TAB_SELECTED);
34
- previouslyFocusedElement === null || previouslyFocusedElement === void 0 ? void 0 : previouslyFocusedElement.focus({ preventScroll: true });
79
+ if (event.key === 'Escape' && activeTab !== constants_1.NO_MENU_TAB_SELECTED) {
80
+ handleCloseTab();
35
81
  }
36
- }, [previouslyFocusedElement]);
82
+ }, [activeTab, handleCloseTab]);
37
83
  (0, react_1.useEffect)(() => setupRouteChangeHandler === null || setupRouteChangeHandler === void 0 ? void 0 : setupRouteChangeHandler(() => {
38
84
  handleActiveTab(constants_1.NO_MENU_TAB_SELECTED);
39
85
  }), [setupRouteChangeHandler, handleActiveTab]);
40
86
  (0, react_1.useEffect)(() => {
41
87
  const timerId = setTimeout(() => {
42
88
  setActiveTab(pretendentActiveTab);
89
+ setActiveNavigationId(pretendentActiveTab === constants_1.NO_MENU_TAB_SELECTED ? null : navigationId);
43
90
  }, constants_1.SWITCH_MENU_TAB_TIMEOUT);
44
91
  return () => clearTimeout(timerId);
45
- }, [activeTab, pretendentActiveTab]);
92
+ }, [activeTab, navigationId, pretendentActiveTab, setActiveNavigationId]);
93
+ (0, react_1.useEffect)(() => {
94
+ if (activeNavigationId !== navigationId && activeTab !== constants_1.NO_MENU_TAB_SELECTED) {
95
+ setActiveTab(constants_1.NO_MENU_TAB_SELECTED);
96
+ setPretendentAciveTab(constants_1.NO_MENU_TAB_SELECTED);
97
+ setPopupTabToFocus(null);
98
+ }
99
+ }, [activeNavigationId, activeTab, navigationId]);
100
+ (0, react_1.useEffect)(() => {
101
+ if (popupTabToFocus === null || activeTab !== popupTabToFocus) {
102
+ return;
103
+ }
104
+ const popup = document.getElementById(`${tooltipPrefixId}-${popupTabToFocus}`);
105
+ const [firstFocusableElement] = (0, keyboardNavigation_1.getFocusableElements)(popup);
106
+ (0, keyboardNavigation_1.focusFromKeyboard)(firstFocusableElement, popup);
107
+ setPopupTabToFocus(null);
108
+ }, [activeTab, popupTabToFocus]);
46
109
  (0, react_1.useEffect)(() => {
47
110
  document.addEventListener('keydown', onEscapeKeyDown);
48
111
  return () => {
49
112
  document.removeEventListener('keydown', onEscapeKeyDown);
50
113
  };
51
114
  }, [onEscapeKeyDown]);
52
- return (react_1.default.createElement("nav", null,
53
- react_1.default.createElement("ul", { className: b() }, data.map((item, i) => (react_1.default.createElement(NHNavigationItem_1.NHNavigationItem, { key: i, item: item, handleActiveTab: handleActiveTab, index: i, isActive: activeTab === i, tooltipId: `${tooltipPrefixId}-${i}` }, activeTab === i && item.type !== models_1.NHNavigationItemType.Link && (react_1.default.createElement(NHNavigationPopup_1.NHNavigationPopup, { headerRef: headerRef, id: `${tooltipPrefixId}-${i}` }, getPopupContent(item)))))))));
115
+ return (react_1.default.createElement("nav", { onBlur: handleBlur },
116
+ react_1.default.createElement("ul", { className: b() }, data.map((item, i) => (react_1.default.createElement(NHNavigationItem_1.NHNavigationItem, { key: i, item: item, handleActiveTab: handleActiveTab, handleFocusTabPopup: handleFocusTabPopup, handleFocusTab: handleFocusTab, handleToggleTab: handleToggleTab, index: i, isActive: activeTab === i, tooltipId: `${tooltipPrefixId}-${i}` }, activeTab === i && item.type !== models_1.NHNavigationItemType.Link && (react_1.default.createElement(NHNavigationPopup_1.NHNavigationPopup, { headerRef: headerRef, id: `${tooltipPrefixId}-${i}` }, getPopupContent(item)))))))));
54
117
  };
55
118
  exports.NHNavigation = NHNavigation;
@@ -55,6 +55,11 @@ unpredictable css rules order in build */
55
55
  cursor: default;
56
56
  }
57
57
 
58
+ .pc-addons-nh-navigation-item__text:focus-visible {
59
+ outline: 2px solid var(--g-color-line-focus);
60
+ outline-offset: 2px;
61
+ }
62
+
58
63
  .pc-addons-nh-navigation-item:first-child .pc-addons-nh-navigation-item__text {
59
64
  margin-left: 0;
60
65
  }
@@ -5,6 +5,9 @@ interface NavigationItemOwnProps {
5
5
  item: NHNavigationItemData;
6
6
  isActive: boolean;
7
7
  handleActiveTab: (currentIndex: number) => void;
8
+ handleFocusTab: (currentIndex: number) => void;
9
+ handleFocusTabPopup: (currentIndex: number) => void;
10
+ handleToggleTab: (currentIndex: number) => void;
8
11
  children?: ReactNode;
9
12
  tooltipId?: string;
10
13
  }
@@ -12,21 +12,30 @@ const models_1 = require("../../models");
12
12
  const ChevronDown_1 = require("../ChevronDown");
13
13
  const ChevronUp_1 = require("../ChevronUp");
14
14
  const b = (0, cn_1.block)('nh-navigation-item');
15
- const NHNavigationItem = ({ item, isActive, handleActiveTab, index, children, tooltipId, }) => {
15
+ const NHNavigationItem = ({ item, isActive, handleActiveTab, handleFocusTab, handleFocusTabPopup, handleToggleTab, index, children, tooltipId, }) => {
16
16
  var _a, _b, _c, _d;
17
17
  const setupRouteChangeHandler = (0, react_1.useContext)(route_change_1.RouteChangeHandlerContext);
18
18
  const handleMouseEnter = (0, react_1.useCallback)(() => handleActiveTab(index), [handleActiveTab, index]);
19
19
  const handleMouseLeave = (0, react_1.useCallback)(() => handleActiveTab(constants_1.NO_MENU_TAB_SELECTED), [handleActiveTab]);
20
+ const handleToggle = (0, react_1.useCallback)(() => handleToggleTab(index), [handleToggleTab, index]);
21
+ const handlePopupFocus = (0, react_1.useCallback)(() => handleFocusTab(index), [handleFocusTab, index]);
22
+ const handleLinkFocus = (0, react_1.useCallback)(() => handleFocusTab(constants_1.NO_MENU_TAB_SELECTED), [handleFocusTab]);
23
+ const handleKeyDown = (0, react_1.useCallback)((event) => {
24
+ if (event.key === 'ArrowDown') {
25
+ event.preventDefault();
26
+ handleFocusTabPopup(index);
27
+ }
28
+ }, [handleFocusTabPopup, index]);
20
29
  (0, react_1.useEffect)(() => setupRouteChangeHandler === null || setupRouteChangeHandler === void 0 ? void 0 : setupRouteChangeHandler(() => {
21
30
  handleMouseLeave();
22
31
  }), [handleMouseLeave, setupRouteChangeHandler]);
23
32
  if (item.type === models_1.NHNavigationItemType.Link) {
24
33
  return (react_1.default.createElement("li", { className: b({ disable: !((_a = item.data) === null || _a === void 0 ? void 0 : _a.url) }), onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave },
25
- react_1.default.createElement("a", Object.assign({ className: b('text', { active: isActive }), href: (_b = item.data) === null || _b === void 0 ? void 0 : _b.url }, (0, gravity_ui_page_constructor_1.getLinkProps)(((_c = item.data) === null || _c === void 0 ? void 0 : _c.url) || '', undefined, (_d = item.data) === null || _d === void 0 ? void 0 : _d.target)), item.title),
34
+ react_1.default.createElement("a", Object.assign({ className: b('text', { active: isActive }), href: (_b = item.data) === null || _b === void 0 ? void 0 : _b.url, onFocus: handleLinkFocus }, (0, gravity_ui_page_constructor_1.getLinkProps)(((_c = item.data) === null || _c === void 0 ? void 0 : _c.url) || '', undefined, (_d = item.data) === null || _d === void 0 ? void 0 : _d.target)), item.title),
26
35
  children));
27
36
  }
28
37
  return (react_1.default.createElement("li", { className: b({}), onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave },
29
- react_1.default.createElement("button", { className: b('text', { active: isActive, cursor: 'default' }), onClick: handleMouseEnter, "aria-expanded": isActive, "aria-controls": tooltipId },
38
+ react_1.default.createElement("button", { className: b('text', { active: isActive, cursor: 'default' }), type: "button", onClick: handleToggle, onFocus: handlePopupFocus, onKeyDown: handleKeyDown, "aria-expanded": isActive, "aria-controls": tooltipId },
30
39
  item.title,
31
40
  react_1.default.createElement(uikit_1.Icon, { className: b('icon'), data: isActive ? ChevronUp_1.ChevronUp : ChevronDown_1.ChevronDown, size: 16 })),
32
41
  children));
@@ -8,6 +8,17 @@
8
8
  z-index: 10;
9
9
  }
10
10
 
11
- .pc-addons-nh-navigation-popup:focus {
12
- outline: 0;
11
+ .pc-addons-nh-navigation-popup a:focus-visible, .pc-addons-nh-navigation-popup a[data-keyboard-focus=true],
12
+ .pc-addons-nh-navigation-popup button:focus-visible,
13
+ .pc-addons-nh-navigation-popup button[data-keyboard-focus=true],
14
+ .pc-addons-nh-navigation-popup input:focus-visible,
15
+ .pc-addons-nh-navigation-popup input[data-keyboard-focus=true],
16
+ .pc-addons-nh-navigation-popup select:focus-visible,
17
+ .pc-addons-nh-navigation-popup select[data-keyboard-focus=true],
18
+ .pc-addons-nh-navigation-popup textarea:focus-visible,
19
+ .pc-addons-nh-navigation-popup textarea[data-keyboard-focus=true],
20
+ .pc-addons-nh-navigation-popup [tabindex]:not([tabindex="-1"]):focus-visible,
21
+ .pc-addons-nh-navigation-popup [tabindex]:not([tabindex="-1"])[data-keyboard-focus=true] {
22
+ outline: 2px solid var(--g-color-line-focus);
23
+ outline-offset: 4px;
13
24
  }
@@ -2,17 +2,69 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.NHNavigationPopup = void 0;
4
4
  const tslib_1 = require("tslib");
5
- const react_1 = tslib_1.__importDefault(require("react"));
5
+ const react_1 = tslib_1.__importStar(require("react"));
6
6
  const react_dom_1 = tslib_1.__importDefault(require("react-dom"));
7
- const FocusTrap_1 = require("../../../../utils/FocusTrap");
8
7
  const cn_1 = require("../../../../utils/cn");
8
+ const keyboardNavigation_1 = require("./keyboardNavigation");
9
9
  const b = (0, cn_1.block)('nh-navigation-popup');
10
+ const nextKeys = new Set(['ArrowDown', 'ArrowRight']);
11
+ const previousKeys = new Set(['ArrowUp', 'ArrowLeft']);
12
+ const getNextIndex = (currentIndex, offset, elementsCount) => {
13
+ if (currentIndex === -1) {
14
+ return offset > 0 ? 0 : elementsCount - 1;
15
+ }
16
+ return (currentIndex + offset + elementsCount) % elementsCount;
17
+ };
10
18
  const NHNavigationPopup = ({ headerRef, children, id }) => {
19
+ const popupRef = (0, react_1.useRef)(null);
20
+ const focusRelativeElement = (0, react_1.useCallback)((offset) => {
21
+ const popup = popupRef.current;
22
+ const focusableElements = (0, keyboardNavigation_1.getFocusableElements)(popup);
23
+ if (!popup || !focusableElements.length) {
24
+ return;
25
+ }
26
+ const activeElement = popup.ownerDocument.activeElement;
27
+ const currentIndex = activeElement ? focusableElements.indexOf(activeElement) : -1;
28
+ const nextIndex = getNextIndex(currentIndex, offset, focusableElements.length);
29
+ (0, keyboardNavigation_1.focusFromKeyboard)(focusableElements[nextIndex], popup);
30
+ }, []);
31
+ (0, react_1.useEffect)(() => {
32
+ const popup = popupRef.current;
33
+ if (!popup) {
34
+ return undefined;
35
+ }
36
+ const handleKeyDown = (event) => {
37
+ if (event.key === 'Tab') {
38
+ event.preventDefault();
39
+ focusRelativeElement(event.shiftKey ? -1 : 1);
40
+ }
41
+ else if (nextKeys.has(event.key)) {
42
+ event.preventDefault();
43
+ focusRelativeElement(1);
44
+ }
45
+ else if (previousKeys.has(event.key)) {
46
+ event.preventDefault();
47
+ focusRelativeElement(-1);
48
+ }
49
+ else if (event.key === 'Home') {
50
+ event.preventDefault();
51
+ const popupElement = popupRef.current;
52
+ (0, keyboardNavigation_1.focusFromKeyboard)((0, keyboardNavigation_1.getFocusableElements)(popupElement)[0], popupElement);
53
+ }
54
+ else if (event.key === 'End') {
55
+ event.preventDefault();
56
+ const popupElement = popupRef.current;
57
+ const focusableElements = (0, keyboardNavigation_1.getFocusableElements)(popupElement);
58
+ (0, keyboardNavigation_1.focusFromKeyboard)(focusableElements[focusableElements.length - 1], popupElement);
59
+ }
60
+ };
61
+ popup.addEventListener('keydown', handleKeyDown);
62
+ return () => {
63
+ popup.removeEventListener('keydown', handleKeyDown);
64
+ };
65
+ }, [focusRelativeElement]);
11
66
  return (headerRef === null || headerRef === void 0 ? void 0 : headerRef.current)
12
- ? react_dom_1.default.createPortal(react_1.default.createElement(FocusTrap_1.FocusTrap, { enabled: true },
13
- react_1.default.createElement("div", {
14
- // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
15
- tabIndex: 0, className: b(), id: id }, children)), headerRef.current)
67
+ ? react_dom_1.default.createPortal(react_1.default.createElement("div", { className: b(), id: id, ref: popupRef }, children), headerRef.current)
16
68
  : null;
17
69
  };
18
70
  exports.NHNavigationPopup = NHNavigationPopup;
@@ -0,0 +1,4 @@
1
+ export declare const keyboardFocusAttribute = "data-keyboard-focus";
2
+ export declare const focusableElementSelector: string;
3
+ export declare const getFocusableElements: (root?: HTMLElement | null) => HTMLElement[];
4
+ export declare const focusFromKeyboard: (element?: HTMLElement | null, root?: ParentNode | null) => void;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.focusFromKeyboard = exports.getFocusableElements = exports.focusableElementSelector = exports.keyboardFocusAttribute = void 0;
4
+ exports.keyboardFocusAttribute = 'data-keyboard-focus';
5
+ exports.focusableElementSelector = [
6
+ 'a[href]',
7
+ 'button:not([disabled])',
8
+ 'input:not([disabled])',
9
+ 'select:not([disabled])',
10
+ 'textarea:not([disabled])',
11
+ '[tabindex]:not([tabindex="-1"])',
12
+ ].join(',');
13
+ const keyboardFocusSelector = `[${exports.keyboardFocusAttribute}='true']`;
14
+ const blurHandlers = new WeakMap();
15
+ const clearKeyboardFocus = (root) => {
16
+ root.querySelectorAll(keyboardFocusSelector).forEach((element) => {
17
+ const blurHandler = blurHandlers.get(element);
18
+ if (blurHandler) {
19
+ element.removeEventListener('blur', blurHandler);
20
+ blurHandlers.delete(element);
21
+ }
22
+ element.removeAttribute(exports.keyboardFocusAttribute);
23
+ });
24
+ };
25
+ const getFocusableElements = (root) => Array.from((root === null || root === void 0 ? void 0 : root.querySelectorAll(exports.focusableElementSelector)) || []);
26
+ exports.getFocusableElements = getFocusableElements;
27
+ const focusFromKeyboard = (element, root) => {
28
+ if (!element) {
29
+ return;
30
+ }
31
+ const focusRoot = root || element.ownerDocument;
32
+ const handleBlur = () => {
33
+ element.removeAttribute(exports.keyboardFocusAttribute);
34
+ element.removeEventListener('blur', handleBlur);
35
+ blurHandlers.delete(element);
36
+ };
37
+ clearKeyboardFocus(focusRoot);
38
+ element.setAttribute(exports.keyboardFocusAttribute, 'true');
39
+ element.addEventListener('blur', handleBlur);
40
+ blurHandlers.set(element, handleBlur);
41
+ element.focus({ preventScroll: true });
42
+ };
43
+ exports.focusFromKeyboard = focusFromKeyboard;
@@ -57,6 +57,11 @@ unpredictable css rules order in build */
57
57
  opacity: 1;
58
58
  }
59
59
 
60
+ .pc-addons-nh-navigation-popup-item:focus, .pc-addons-nh-navigation-popup-item:focus-visible, .pc-addons-nh-navigation-popup-item[data-keyboard-focus=true] {
61
+ outline: 2px solid var(--g-color-line-focus);
62
+ outline-offset: 4px;
63
+ }
64
+
60
65
  .pc-addons-nh-navigation-popup-item_disable {
61
66
  pointer-events: none;
62
67
  }
@@ -5,9 +5,8 @@ import './GroupLinks.css';
5
5
  const b = block('group-links');
6
6
  const GroupLinks = ({ columnGroup: { title, items }, className }) => {
7
7
  return (React.createElement("div", { className: b(null, className), key: title },
8
- React.createElement("ul", { className: b('group') },
9
- title && React.createElement("h5", { className: b('group-title') }, title),
10
- items.map((item) => (React.createElement("li", { key: item.title, className: b('item-wrapper') },
11
- React.createElement(EnrichedLink, Object.assign({ className: b('item') }, item))))))));
8
+ title && React.createElement("h5", { className: b('group-title') }, title),
9
+ React.createElement("ul", { className: b('group'), "aria-label": title }, items.map((item) => (React.createElement("li", { key: item.title, className: b('item-wrapper') },
10
+ React.createElement(EnrichedLink, Object.assign({ className: b('item') }, item))))))));
12
11
  };
13
12
  export default GroupLinks;
@@ -172,6 +172,11 @@ unpredictable css rules order in build */
172
172
  border-color: rgba(5, 43, 66, 0.25);
173
173
  }
174
174
 
175
+ .pc-addons-new-header__button:focus-visible {
176
+ outline: 2px solid var(--g-color-line-focus);
177
+ outline-offset: 2px;
178
+ }
179
+
175
180
  @media (max-width: 1081px) {
176
181
  .pc-addons-new-header__left {
177
182
  margin-right: 0;