You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
InternshipProject/node_modules/rc-menu/lib/hooks/useAccessibility.js

306 lines
9.7 KiB

"use strict";
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
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");
// 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 ? 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);
};
}