@clayui/shared
Version:
ClayShared component
267 lines (264 loc) • 12.1 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getFocusableList = getFocusableList;
exports.isTypeahead = isTypeahead;
exports.useNavigation = useNavigation;
var _react = require("react");
var _Keys = require("./Keys");
var _useFocusManagement = require("./useFocusManagement");
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) { n[e] = r[e]; } return n; } /**
* SPDX-FileCopyrightText: © 2022 Liferay, Inc. <https://liferay.com>
* SPDX-License-Identifier: BSD-3-Clause
*/
// TODO: To avoid circular dependency we are just copying but we must remove this
// when moving this into the core package.
var verticalKeys = [_Keys.Keys.Up, _Keys.Keys.Down, _Keys.Keys.Home, _Keys.Keys.End];
var horizontalKeys = [_Keys.Keys.Left, _Keys.Keys.Right, _Keys.Keys.Home, _Keys.Keys.End];
function useNavigation(_ref) {
var _ref$activation = _ref.activation,
activation = _ref$activation === void 0 ? 'manual' : _ref$activation,
active = _ref.active,
collection = _ref.collection,
containerRef = _ref.containerRef,
_ref$focusableElement = _ref.focusableElements,
focusableElements = _ref$focusableElement === void 0 ? _useFocusManagement.FOCUSABLE_ELEMENTS : _ref$focusableElement,
_ref$loop = _ref.loop,
loop = _ref$loop === void 0 ? false : _ref$loop,
onNavigate = _ref.onNavigate,
_ref$orientation = _ref.orientation,
orientation = _ref$orientation === void 0 ? 'horizontal' : _ref$orientation,
_ref$typeahead = _ref.typeahead,
typeahead = _ref$typeahead === void 0 ? false : _ref$typeahead,
_ref$visible = _ref.visible,
visible = _ref$visible === void 0 ? false : _ref$visible;
var timeoutIdRef = (0, _react.useRef)();
var stringRef = (0, _react.useRef)('');
var prevIndexRef = (0, _react.useRef)(-1);
var matchIndexRef = (0, _react.useRef)(null);
// An event can be scheduled when the content is not visible in the DOM, it
// will be executed in sequence after the element is visible in the DOM.
var pendingEventStack = (0, _react.useRef)([]);
(0, _react.useEffect)(function () {
if (!visible) {
clearTimeout(timeoutIdRef.current);
matchIndexRef.current = null;
stringRef.current = '';
}
}, [visible]);
var accessibilityFocus = (0, _react.useCallback)(function (item, items) {
var index = items ? items.indexOf(item) : null;
var element = item instanceof HTMLElement ? item : document.getElementById(String(item));
if (onNavigate) {
onNavigate(item, index);
}
if (collection !== null && collection !== void 0 && collection.virtualize) {
var isEnd = collection.UNSAFE_virtualizer.options.count - 1 === index;
var isStart = index === 0;
collection.UNSAFE_virtualizer.scrollToIndex(index, {
align: 'auto',
behavior: isStart || isEnd ? 'auto' : 'smooth'
});
if (!onNavigate && !element) {
setTimeout(function () {
var nextFocus = containerRef.current.querySelector("[data-focus=\"".concat(item, "\"]"));
if (nextFocus) {
nextFocus.focus();
}
}, 20);
}
return;
}
var child = isScrollable(containerRef.current) ? containerRef.current : containerRef.current.firstElementChild;
if (isScrollable(child)) {
maintainScrollVisibility(element, child);
}
if (!isElementInView(element)) {
element.scrollIntoView({
behavior: 'smooth',
block: 'nearest'
});
}
}, []);
var onKeyDown = (0, _react.useCallback)(function (event) {
if (!containerRef.current) {
event.persist();
pendingEventStack.current.push(event);
return;
}
var keys = orientation === 'vertical' ? verticalKeys : horizontalKeys;
var alternativeKeys = orientation === 'vertical' ? horizontalKeys : verticalKeys;
if (keys.includes(event.key) || typeahead && !alternativeKeys.includes(event.key)) {
var items = collection ? collection.getItems() : getFocusableList(containerRef, focusableElements);
var item;
switch (event.key) {
case _Keys.Keys.Left:
case _Keys.Keys.Right:
case _Keys.Keys.Down:
case _Keys.Keys.Up:
{
var position;
var key = orientation === 'vertical' ? _Keys.Keys.Up : _Keys.Keys.Left;
if (collection && typeof active === 'string') {
position = items.indexOf(active);
} else if (collection) {
var activeElement = document.activeElement;
var focusKey = activeElement.getAttribute('data-focus');
position = items.indexOf(focusKey);
if (position === -1) {
item = event.key === key ? collection.getLastItem().key : collection.getFirstItem().key;
}
} else {
var _activeElement = document.activeElement;
position = items.indexOf(_activeElement);
if (typeof active === 'string') {
position = items.findIndex(function (element) {
return element.getAttribute('id') === active;
});
}
}
if (position === -1) {
break;
}
item = items[event.key === key ? position - 1 : position + 1];
if (loop && !item) {
item = items[event.key === key ? items.length - 1 : 0];
}
break;
}
case _Keys.Keys.Home:
case _Keys.Keys.End:
item = items[event.key === _Keys.Keys.Home ? 0 : items.length - 1];
break;
default:
{
var target = event.target;
if (!typeahead || target.tagName === 'INPUT' || event.key === _Keys.Keys.Tab) {
return;
}
if (event.currentTarget && !event.currentTarget.contains(target)) {
return;
}
if (stringRef.current.length > 0 && stringRef.current[0] !== _Keys.Keys.Spacebar) {
if (event.key === _Keys.Keys.Spacebar) {
event.preventDefault();
event.stopPropagation();
}
}
if (event.key.length !== 1 || event.ctrlKey || event.metaKey || event.altKey) {
return;
}
event.stopPropagation();
if (stringRef.current === event.key) {
stringRef.current = '';
prevIndexRef.current = matchIndexRef.current;
}
stringRef.current += event.key;
clearTimeout(timeoutIdRef.current);
timeoutIdRef.current = setTimeout(function () {
stringRef.current = '';
prevIndexRef.current = matchIndexRef.current;
}, 1000);
var prevIndex = prevIndexRef.current;
var orderedList = [].concat(_toConsumableArray(items.slice((prevIndex !== null && prevIndex !== void 0 ? prevIndex : 0) + 1)), _toConsumableArray(items.slice(0, (prevIndex !== null && prevIndex !== void 0 ? prevIndex : 0) + 1)));
item = orderedList.find(function (item) {
var _item$innerText;
var value = item instanceof HTMLElement ? (_item$innerText = item.innerText) !== null && _item$innerText !== void 0 ? _item$innerText : item.textContent : collection === null || collection === void 0 ? void 0 : collection.getItem(item).value;
return (value === null || value === void 0 ? void 0 : value.toLowerCase().indexOf(stringRef.current.toLocaleLowerCase())) === 0;
});
if (item) {
// @ts-ignore
matchIndexRef.current = items.indexOf(item);
}
break;
}
}
if (item) {
event.preventDefault();
var element = item instanceof HTMLElement ? item : document.getElementById(String(item));
if (onNavigate || !element) {
accessibilityFocus(item, items);
} else {
element.focus();
}
if (activation === 'automatic') {
element.click();
}
}
}
}, [active]);
(0, _react.useEffect)(function () {
// Moves the scroll to the element with visual "focus" if it exists.
if (visible && containerRef.current && active && onNavigate && !(collection !== null && collection !== void 0 && collection.virtualize)) {
var child = isScrollable(containerRef.current) ? containerRef.current : containerRef.current.firstElementChild;
var activeElement = document.getElementById(String(active));
if (activeElement && isScrollable(child)) {
maintainScrollVisibility(activeElement, child);
}
} else if (visible && active && collection !== null && collection !== void 0 && collection.virtualize) {
collection.UNSAFE_virtualizer.scrollToIndex(collection.getItem(active).index, {
align: 'center',
behavior: 'auto'
});
}
}, [visible]);
(0, _react.useEffect)(function () {
if (visible && pendingEventStack.current.length !== 0) {
for (var index = 0; index < pendingEventStack.current.length; index++) {
var event = pendingEventStack.current.shift();
onKeyDown(event);
}
}
}, [visible]);
var navigationProps = {
onKeyDown: onKeyDown
};
return {
accessibilityFocus: accessibilityFocus,
navigationProps: navigationProps
};
}
function getFocusableList(containeRef) {
var focusableElements = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _useFocusManagement.FOCUSABLE_ELEMENTS;
if (!containeRef.current) {
return [];
}
return Array.from(containeRef.current.querySelectorAll(focusableElements.join(','))).filter(function (element) {
return (0, _useFocusManagement.isFocusable)({
contentEditable: element.contentEditable,
disabled: element.getAttribute('disabled') !== null,
offsetParent: element.offsetParent,
tabIndex: 0,
tagName: element.tagName
});
});
}
function isTypeahead(event) {
return event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey;
}
function isElementInView(element) {
var bounding = element.getBoundingClientRect();
return bounding.top >= 0 && bounding.left >= 0 && bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) && bounding.right <= (window.innerWidth || document.documentElement.clientWidth);
}
function isScrollable(element) {
return element && element.clientHeight < element.scrollHeight;
}
function maintainScrollVisibility(activeElement, scrollParent) {
var offsetHeight = activeElement.offsetHeight,
offsetTop = activeElement.offsetTop;
var parentOffsetHeight = scrollParent.offsetHeight,
scrollTop = scrollParent.scrollTop;
var isAbove = offsetTop < scrollTop;
var isBelow = offsetTop + offsetHeight > scrollTop + parentOffsetHeight;
if (isAbove) {
scrollParent.scrollTo(0, offsetTop);
} else if (isBelow) {
scrollParent.scrollTo(0, offsetTop - parentOffsetHeight + offsetHeight);
}
}
;