"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = useAccessibility; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var React = _interopRequireWildcard(require("react")); var _KeyCode = _interopRequireDefault(require("rc-util/lib/KeyCode")); var _raf = _interopRequireDefault(require("rc-util/lib/raf")); var _focus = require("rc-util/lib/Dom/focus"); var _IdContext = require("../context/IdContext"); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } // destruct to reduce minify size var LEFT = _KeyCode.default.LEFT, RIGHT = _KeyCode.default.RIGHT, UP = _KeyCode.default.UP, DOWN = _KeyCode.default.DOWN, ENTER = _KeyCode.default.ENTER, ESC = _KeyCode.default.ESC, HOME = _KeyCode.default.HOME, END = _KeyCode.default.END; var ArrowKeys = [UP, DOWN, LEFT, RIGHT]; function getOffset(mode, isRootLevel, isRtl, which) { var _inline, _horizontal, _vertical, _offsets; var prev = 'prev'; var next = 'next'; var children = 'children'; var parent = 'parent'; // Inline enter is special that we use unique operation if (mode === 'inline' && which === ENTER) { return { inlineTrigger: true }; } var inline = (_inline = {}, (0, _defineProperty2.default)(_inline, UP, prev), (0, _defineProperty2.default)(_inline, DOWN, next), _inline); var horizontal = (_horizontal = {}, (0, _defineProperty2.default)(_horizontal, LEFT, isRtl ? next : prev), (0, _defineProperty2.default)(_horizontal, RIGHT, isRtl ? prev : next), (0, _defineProperty2.default)(_horizontal, DOWN, children), (0, _defineProperty2.default)(_horizontal, ENTER, children), _horizontal); var vertical = (_vertical = {}, (0, _defineProperty2.default)(_vertical, UP, prev), (0, _defineProperty2.default)(_vertical, DOWN, next), (0, _defineProperty2.default)(_vertical, ENTER, children), (0, _defineProperty2.default)(_vertical, ESC, parent), (0, _defineProperty2.default)(_vertical, LEFT, isRtl ? children : parent), (0, _defineProperty2.default)(_vertical, RIGHT, isRtl ? parent : children), _vertical); var offsets = { inline: inline, horizontal: horizontal, vertical: vertical, inlineSub: inline, horizontalSub: vertical, verticalSub: vertical }; var type = (_offsets = offsets["".concat(mode).concat(isRootLevel ? '' : 'Sub')]) === null || _offsets === void 0 ? void 0 : _offsets[which]; switch (type) { case prev: return { offset: -1, sibling: true }; case next: return { offset: 1, sibling: true }; case parent: return { offset: -1, sibling: false }; case children: return { offset: 1, sibling: false }; default: return null; } } function findContainerUL(element) { var current = element; while (current) { if (current.getAttribute('data-menu-list')) { return current; } current = current.parentElement; } // Normally should not reach this line /* istanbul ignore next */ return null; } /** * Find focused element within element set provided */ function getFocusElement(activeElement, elements) { var current = activeElement || document.activeElement; while (current) { if (elements.has(current)) { return current; } current = current.parentElement; } return null; } /** * Get focusable elements from the element set under provided container */ function getFocusableElements(container, elements) { var list = (0, _focus.getFocusNodeList)(container, true); return list.filter(function (ele) { return elements.has(ele); }); } function getNextFocusElement(parentQueryContainer, elements, focusMenuElement) { var offset = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 1; // Key on the menu item will not get validate parent container if (!parentQueryContainer) { return null; } // List current level menu item elements var sameLevelFocusableMenuElementList = getFocusableElements(parentQueryContainer, elements); // Find next focus index var count = sameLevelFocusableMenuElementList.length; var focusIndex = sameLevelFocusableMenuElementList.findIndex(function (ele) { return focusMenuElement === ele; }); if (offset < 0) { if (focusIndex === -1) { focusIndex = count - 1; } else { focusIndex -= 1; } } else if (offset > 0) { focusIndex += 1; } focusIndex = (focusIndex + count) % count; // Focus menu item return sameLevelFocusableMenuElementList[focusIndex]; } function useAccessibility(mode, activeKey, isRtl, id, containerRef, getKeys, getKeyPath, triggerActiveKey, triggerAccessibilityOpen, originOnKeyDown) { var rafRef = React.useRef(); var activeRef = React.useRef(); activeRef.current = activeKey; var cleanRaf = function cleanRaf() { _raf.default.cancel(rafRef.current); }; React.useEffect(function () { return function () { cleanRaf(); }; }, []); return function (e) { var which = e.which; if ([].concat(ArrowKeys, [ENTER, ESC, HOME, END]).includes(which)) { // Convert key to elements var elements; var key2element; var element2key; // >>> Wrap as function since we use raf for some case var refreshElements = function refreshElements() { elements = new Set(); key2element = new Map(); element2key = new Map(); var keys = getKeys(); keys.forEach(function (key) { var element = document.querySelector("[data-menu-id='".concat((0, _IdContext.getMenuId)(id, key), "']")); if (element) { elements.add(element); element2key.set(element, key); key2element.set(key, element); } }); return elements; }; refreshElements(); // First we should find current focused MenuItem/SubMenu element var activeElement = key2element.get(activeKey); var focusMenuElement = getFocusElement(activeElement, elements); var focusMenuKey = element2key.get(focusMenuElement); var offsetObj = getOffset(mode, getKeyPath(focusMenuKey, true).length === 1, isRtl, which); // Some mode do not have fully arrow operation like inline if (!offsetObj && which !== HOME && which !== END) { return; } // Arrow prevent default to avoid page scroll if (ArrowKeys.includes(which) || [HOME, END].includes(which)) { e.preventDefault(); } var tryFocus = function tryFocus(menuElement) { if (menuElement) { var focusTargetElement = menuElement; // Focus to link instead of menu item if possible var link = menuElement.querySelector('a'); if (link !== null && link !== void 0 && link.getAttribute('href')) { focusTargetElement = link; } var targetKey = element2key.get(menuElement); triggerActiveKey(targetKey); /** * Do not `useEffect` here since `tryFocus` may trigger async * which makes React sync update the `activeKey` * that force render before `useRef` set the next activeKey */ cleanRaf(); rafRef.current = (0, _raf.default)(function () { if (activeRef.current === targetKey) { focusTargetElement.focus(); } }); } }; if ([HOME, END].includes(which) || offsetObj.sibling || !focusMenuElement) { // ========================== Sibling ========================== // Find walkable focus menu element container var parentQueryContainer; if (!focusMenuElement || mode === 'inline') { parentQueryContainer = containerRef.current; } else { parentQueryContainer = findContainerUL(focusMenuElement); } // Get next focus element var targetElement; var focusableElements = getFocusableElements(parentQueryContainer, elements); if (which === HOME) { targetElement = focusableElements[0]; } else if (which === END) { targetElement = focusableElements[focusableElements.length - 1]; } else { targetElement = getNextFocusElement(parentQueryContainer, elements, focusMenuElement, offsetObj.offset); } // Focus menu item tryFocus(targetElement); // ======================= InlineTrigger ======================= } else if (offsetObj.inlineTrigger) { // Inline trigger no need switch to sub menu item triggerAccessibilityOpen(focusMenuKey); // =========================== Level =========================== } else if (offsetObj.offset > 0) { triggerAccessibilityOpen(focusMenuKey, true); cleanRaf(); rafRef.current = (0, _raf.default)(function () { // Async should resync elements refreshElements(); var controlId = focusMenuElement.getAttribute('aria-controls'); var subQueryContainer = document.getElementById(controlId); // Get sub focusable menu item var targetElement = getNextFocusElement(subQueryContainer, elements); // Focus menu item tryFocus(targetElement); }, 5); } else if (offsetObj.offset < 0) { var keyPath = getKeyPath(focusMenuKey, true); var parentKey = keyPath[keyPath.length - 2]; var parentMenuElement = key2element.get(parentKey); // Focus menu item triggerAccessibilityOpen(parentKey, false); tryFocus(parentMenuElement); } } // Pass origin key down event originOnKeyDown === null || originOnKeyDown === void 0 ? void 0 : originOnKeyDown(e); }; }