UNPKG

@clayui/shared

Version:
267 lines (264 loc) 12.1 kB
"use strict"; 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); } }