UNPKG

@clayui/shared

Version:
286 lines (285 loc) 11 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var useNavigation_exports = {}; __export(useNavigation_exports, { getFocusableList: () => getFocusableList, isTypeahead: () => isTypeahead, useNavigation: () => useNavigation }); module.exports = __toCommonJS(useNavigation_exports); var import_react = require("react"); var import_Keys = require("./Keys"); var import_useFocusManagement = require("./useFocusManagement"); const verticalKeys = [import_Keys.Keys.Up, import_Keys.Keys.Down, import_Keys.Keys.Home, import_Keys.Keys.End]; const horizontalKeys = [import_Keys.Keys.Left, import_Keys.Keys.Right, import_Keys.Keys.Home, import_Keys.Keys.End]; function useNavigation({ activation = "manual", active, collection, containerRef, focusableElements = import_useFocusManagement.FOCUSABLE_ELEMENTS, loop = false, onNavigate, orientation = "horizontal", typeahead = false, visible = false }) { const [focusedElement, setFocusedElement] = (0, import_react.useState)( null ); const timeoutIdRef = (0, import_react.useRef)(); const stringRef = (0, import_react.useRef)(""); const prevIndexRef = (0, import_react.useRef)(-1); const matchIndexRef = (0, import_react.useRef)(null); const pendingEventStackRef = (0, import_react.useRef)([]); (0, import_react.useEffect)(() => { if (!visible) { clearTimeout(timeoutIdRef.current); matchIndexRef.current = null; stringRef.current = ""; } }, [visible]); const focusElement = (element) => { element.focus(); setFocusedElement(element); }; const accessibilityFocus = (0, import_react.useCallback)( (item, items) => { const index = items ? items.indexOf(item) : null; const element = item instanceof HTMLElement ? item : document.getElementById(String(item)); if (onNavigate) { onNavigate(item, index); } if (collection?.virtualize) { const isEnd = collection.UNSAFE_virtualizer.options.count - 1 === index; const isStart = index === 0; collection.UNSAFE_virtualizer.scrollToIndex(index, { align: "auto", behavior: isStart || isEnd ? "auto" : "smooth" }); if (!onNavigate && !element) { setTimeout(() => { const nextFocus = containerRef.current.querySelector( `[data-focus="${item}"]` ); if (nextFocus) { focusElement(nextFocus); } }, 20); } return; } const child = isScrollable(containerRef.current) ? containerRef.current : containerRef.current.firstElementChild; if (isScrollable(child)) { maintainScrollVisibility(element, child); } if (!isElementInView(element)) { element.scrollIntoView({ behavior: "smooth", block: "nearest" }); } }, [] ); const onKeyDown = (0, import_react.useCallback)( (event) => { if (!containerRef.current) { event.persist(); pendingEventStackRef.current.push(event); return; } const keys = orientation === "vertical" ? verticalKeys : horizontalKeys; const alternativeKeys = orientation === "vertical" ? horizontalKeys : verticalKeys; if (keys.includes(event.key) || typeahead && !alternativeKeys.includes(event.key)) { const items = collection ? collection.getItems() : getFocusableList(containerRef, focusableElements); let item; switch (event.key) { case import_Keys.Keys.Left: case import_Keys.Keys.Right: case import_Keys.Keys.Down: case import_Keys.Keys.Up: { let position; const key = orientation === "vertical" ? import_Keys.Keys.Up : import_Keys.Keys.Left; if (collection && typeof active === "string") { position = items.indexOf( active ); if (position === -1) { item = event.key === key ? collection.getLastItem().key : collection.getFirstItem().key; } } else if (collection) { const activeElement = document.activeElement; const focusKey = activeElement.getAttribute("data-focus"); position = items.indexOf( focusKey ); if (position === -1) { item = event.key === key ? collection.getLastItem().key : collection.getFirstItem().key; } } else { const activeElement = document.activeElement; position = items.indexOf( activeElement ); if (typeof active === "string") { position = items.findIndex( (element) => 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 import_Keys.Keys.Home: case import_Keys.Keys.End: item = items[event.key === import_Keys.Keys.Home ? 0 : items.length - 1]; break; default: { const target = event.target; if (!typeahead || target.tagName === "INPUT" || event.key === import_Keys.Keys.Tab) { return; } if (event.currentTarget && !event.currentTarget.contains(target)) { return; } if (!!stringRef.current.length && stringRef.current[0] !== import_Keys.Keys.Spacebar) { if (event.key === import_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(() => { stringRef.current = ""; prevIndexRef.current = matchIndexRef.current; }, 1e3); const prevIndex = prevIndexRef.current; const orderedList = [ ...items.slice((prevIndex ?? 0) + 1), ...items.slice(0, (prevIndex ?? 0) + 1) ]; item = orderedList.find((item2) => { const value = item2 instanceof HTMLElement ? item2.innerText ?? item2.textContent : collection?.getItem(item2).value; return value?.toLowerCase().indexOf( stringRef.current.toLocaleLowerCase() ) === 0; }); if (item) { matchIndexRef.current = items.indexOf(item); } break; } } if (item) { event.preventDefault(); const element = item instanceof HTMLElement ? item : document.getElementById(String(item)); if (onNavigate || !element) { accessibilityFocus(item, items); } else { focusElement(element); } if (activation === "automatic") { element.click(); } } } }, [active] ); (0, import_react.useEffect)(() => { if (visible && containerRef.current && active && onNavigate && !collection?.virtualize) { const child = isScrollable(containerRef.current) ? containerRef.current : containerRef.current.firstElementChild; const activeElement = document.getElementById(String(active)); if (activeElement && isScrollable(child)) { maintainScrollVisibility(activeElement, child); } } else if (visible && active && collection?.virtualize) { collection.UNSAFE_virtualizer.scrollToIndex( collection.getItem(active).index, { align: "center", behavior: "auto" } ); } }, [visible]); (0, import_react.useEffect)(() => { if (visible && pendingEventStackRef.current.length !== 0) { for (let index = 0; index < pendingEventStackRef.current.length; index++) { const event = pendingEventStackRef.current.shift(); onKeyDown(event); } } }, [visible]); return { accessibilityFocus, navigationFocusedElement: focusedElement, navigationProps: { onKeyDown } }; } function getFocusableList(containeRef, focusableElements = import_useFocusManagement.FOCUSABLE_ELEMENTS) { if (!containeRef.current) { return []; } return Array.from( containeRef.current.querySelectorAll(focusableElements.join(",")) ).filter( (element) => (0, import_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) { const 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) { const { offsetHeight, offsetTop } = activeElement; const { offsetHeight: parentOffsetHeight, scrollTop } = scrollParent; const isAbove = offsetTop < scrollTop; const isBelow = offsetTop + offsetHeight > scrollTop + parentOffsetHeight; if (isAbove) { scrollParent.scrollTo(0, offsetTop); } else if (isBelow) { scrollParent.scrollTo(0, offsetTop - parentOffsetHeight + offsetHeight); } }