@primer/react
Version:
An implementation of GitHub's Primer Design System using React
81 lines (70 loc) • 3.46 kB
JavaScript
;
var React = require('react');
var utils = require('@primer/behaviors/utils');
var useProvidedRefOrCreate = require('./useProvidedRefOrCreate.js');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var React__default = /*#__PURE__*/_interopDefault(React);
/*
* A mnemonic indicates to the user which key to press (single)
* to activate a command or navigate to a component
* typically appearing in a menu title, menu item, or the text of a button.
*/
const useMnemonics = (open, providedRef) => {
const containerRef = useProvidedRefOrCreate.useProvidedRefOrCreate(providedRef);
React__default.default.useEffect(function addAriaKeyshortcuts() {
if (!open || !containerRef.current) return;
const container = containerRef.current;
const focusableItems = [...utils.iterateFocusableElements(container)];
focusableItems.map(item => {
var _item$textContent;
// if item already has aria-keyshortcuts defined by user, skip
if (item.getAttribute('aria-keyshortcuts')) return;
const firstLetter = (_item$textContent = item.textContent) === null || _item$textContent === void 0 ? void 0 : _item$textContent.toLowerCase()[0];
if (firstLetter) item.setAttribute('aria-keyshortcuts', firstLetter);
});
}, [open, containerRef]);
React__default.default.useEffect(function handleKeyDown() {
if (!open || !containerRef.current) return;
const container = containerRef.current;
const handler = event => {
var _elementToFocus;
// skip if a TextInput has focus
const activeElement = document.activeElement;
if (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA') return;
// skip if used with modifier to preserve shortcuts like ⌘ + F
const hasModifier = event.ctrlKey || event.altKey || event.metaKey;
if (hasModifier) return;
// skip if it's not a alphabet key
if (!isAlphabetKey(event)) return;
// if this is a typeahead event, don't propagate outside of menu
event.stopPropagation();
const query = event.key.toLowerCase();
let elementToFocus;
const focusableItems = [...utils.iterateFocusableElements(container)];
const itemsMatchingKey = focusableItems.filter(item => {
var _item$getAttribute;
const keyshortcuts = (_item$getAttribute = item.getAttribute('aria-keyshortcuts')) === null || _item$getAttribute === void 0 ? void 0 : _item$getAttribute.split(' ').map(shortcut => shortcut.toLowerCase());
return keyshortcuts && keyshortcuts.includes(query);
});
const currentActiveIndex = itemsMatchingKey.indexOf(activeElement);
// If the last element is already selected, cycle through the list
if (currentActiveIndex === itemsMatchingKey.length - 1) {
elementToFocus = itemsMatchingKey[0];
} else {
elementToFocus = itemsMatchingKey.find((item, index) => {
return index > currentActiveIndex;
});
}
(_elementToFocus = elementToFocus) === null || _elementToFocus === void 0 ? void 0 : _elementToFocus.focus();
};
container.addEventListener('keydown', handler);
return () => container.removeEventListener('keydown', handler);
}, [open, containerRef]);
const isAlphabetKey = event => {
return event.key.length === 1 && /[a-z\d]/i.test(event.key);
};
return {
containerRef
};
};
exports.useMnemonics = useMnemonics;