UNPKG

@atlaskit/navigation-system

Version:

The latest navigation system for Atlassian apps.

133 lines (127 loc) • 5.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useExpandSideNav = useExpandSideNav; var _react = require("react"); var _reactDom = require("react-dom"); var _bindEventListener = require("bind-event-listener"); var _fg = require("@atlaskit/platform-feature-flags/fg"); var _setSideNavVisibilityState = require("./set-side-nav-visibility-state"); var _useSideNavRef = require("./use-side-nav-ref"); /** * Moves focus to the first focusable item in the side nav, or the side nav element itself as a fallback. */ function focusFirstNavItem(sideNavElement) { var _firstNavItem$checkVi, _firstNavItem$checkVi2; /** * Try to get the first focusable item in the side nav. * The selector is not very broad, but should be appropriate for items from this package. */ var firstNavItem = sideNavElement.querySelector('a, button'); var isFirstNavItemVisible = firstNavItem !== null && ((_firstNavItem$checkVi = (_firstNavItem$checkVi2 = firstNavItem.checkVisibility) === null || _firstNavItem$checkVi2 === void 0 ? void 0 : _firstNavItem$checkVi2.call(firstNavItem)) !== null && _firstNavItem$checkVi !== void 0 ? _firstNavItem$checkVi : false); var itemToFocus = isFirstNavItemVisible ? firstNavItem : sideNavElement; if (itemToFocus === sideNavElement) { /** * Elements without an explicit `tabindex` attribute are not guaranteed to be focusable: * https://html.spec.whatwg.org/multipage/interaction.html#attr-tabindex * * Our slots are not interactive, so this is required. * * In the future we may want to check if there is an existing `tabindex` attribute, * as custom skip linked elements might already have one. */ sideNavElement.setAttribute('tabindex', '-1'); /** * Cleanup the `tabindex` attribute we set when the slot or custom target loses focus. * * This is preferable to always having `tabindex="-1"` because always applying the tab index can: * * - mess with click events * - potentially cause a focus ring to be always visible */ (0, _bindEventListener.bind)(sideNavElement, { type: 'blur', listener: function listener() { sideNavElement.removeAttribute('tabindex'); }, options: { once: true } }); } /** * Not using `focusVisible` option because we don't want clicks on the toggle button to show a focus ring. */ itemToFocus.focus(); } var triggersWithFocusOnExpand = new Set(['toggle-button', 'skip-link']); /** * __useExpandSideNav__ * * Returns a function that will expand the side nav when called. * * It will switch the appropriate internal desktop or mobile side nav visibility state based on the current screen size. * * If you need a function to toggle the side nav, use `useToggleSideNav` instead. */ function useExpandSideNav() { var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, _ref$trigger = _ref.trigger, trigger = _ref$trigger === void 0 ? 'programmatic' : _ref$trigger; var setSideNavState = (0, _react.useContext)(_setSideNavVisibilityState.SetSideNavVisibilityState); var sideNavRef = (0, _useSideNavRef.useSideNavRef)(); var expandSideNav = (0, _react.useCallback)(function () { var _window$matchMedia = window.matchMedia('(min-width: 64rem)'), isDesktop = _window$matchMedia.matches; var runUpdate = function runUpdate() { if (isDesktop) { setSideNavState(function (currentState) { // No-op if the side nav state has not been initialised yet // e.g. if the SideNav has not been mounted yet if (!currentState) { return null; } // Skip the re-render if it's a no-op change if (currentState.desktop === 'expanded' && currentState.flyout === 'closed') { return currentState; } return { mobile: currentState.mobile, desktop: 'expanded', flyout: 'closed', lastTrigger: trigger }; }); } else { setSideNavState(function (currentState) { // No-op if the side nav state has not been initialised yet // e.g. if the SideNav has not been mounted yet if (!currentState) { return null; } // Skip the re-render if it's a no-op change if (currentState.mobile === 'expanded' && currentState.flyout === 'closed') { return currentState; } return { desktop: currentState.desktop, mobile: 'expanded', flyout: 'closed', lastTrigger: trigger }; }); } }; if (triggersWithFocusOnExpand.has(trigger) && (0, _fg.fg)('platform_dst_nav4_skip_link_a11y_1')) { (0, _reactDom.flushSync)(runUpdate); var sideNavElement = sideNavRef.current; if (sideNavElement) { focusFirstNavItem(sideNavElement); } } else { runUpdate(); } }, [setSideNavState, sideNavRef, trigger]); return expandSideNav; }