UNPKG

@hitachivantara/uikit-react-core

Version:
250 lines (249 loc) 8.8 kB
import { isKey, isOneOfKeys } from "../utils/keyboardUtils.js"; import { staticClasses, useClasses } from "./Focus.styles.js"; import { getFocusableChildren, isBrowser, setFocusTo } from "./utils.js"; import { cloneElement, useRef, useState } from "react"; //#region src/Focus/Focus.tsx /** @deprecated internal use only. this component has navigation issues. */ var HvFocus = ({ classes: classesProp, children, configuration = {}, disabledClass = false, selected = false, disabled = false, rootRef = void 0, focusOnClick = false, focusDisabled = true, strategy = "listbox", filterClass, navigationJump = 4 }) => { const [childFocus, setChildFocus] = useState(); const [hasRunConfig, setHasRunConfig] = useState(false); const { classes, cx } = useClasses(classesProp); const hasFocusVisible = useRef(false); const getFocuses = () => { return rootRef?.current ? Array.from(rootRef.current.getElementsByClassName(filterClass || staticClasses.root || "root")) : []; }; const setTabIndex = (el, tabIndex = 0) => { if (!el) return; const elChildFocus = getFocusableChildren(el)[0]; if (elChildFocus) { el.tabIndex = -1; elChildFocus.tabIndex = tabIndex; } else el.tabIndex = tabIndex; }; const setSelectedTabIndex = () => { const focuses = getFocuses(); const firstSelected = focuses.find((focus) => focus.classList.contains(classes.selected || "selected")); if (!firstSelected) return; focuses.forEach((focus) => setTabIndex(focus, -1)); setTabIndex(firstSelected, 0); }; const clearTabSiblings = (el) => { getFocuses().forEach((focus) => setTabIndex(focus, -1)); setTabIndex(el, 0); }; const onFocusStrategy = (evt) => { if (strategy === "listbox") clearTabSiblings(evt.currentTarget); }; const onBlurStrategy = () => { if (strategy === "listbox" && rootRef && rootRef.current && !rootRef.current.contains(document.activeElement)) setTimeout(() => { setSelectedTabIndex(); }, 10); }; const config = (el) => { const { tabIndex } = configuration; if (!el || hasRunConfig) return; if (strategy === "card") { setChildFocus(children); return; } if (strategy === "grid") return; const focusableChildren = getFocusableChildren(el); if (focusableChildren.length) { focusableChildren.forEach((child) => setTabIndex(child, -1)); setChildFocus(focusableChildren[0]); } if (tabIndex != null) setTabIndex(el, tabIndex); setHasRunConfig(true); }; const addFocusClass = (evt) => { if (focusOnClick) classes.focused.split(" ").forEach((c) => evt.currentTarget.classList.add(c)); else classes.focusedVisible.split(" ").forEach((c) => evt.currentTarget.classList.add(c)); evt.currentTarget.classList.add("HvIsFocused"); classes?.focus?.split(" ").forEach((c) => evt.currentTarget.classList.add(c)); }; const removeFocusClass = () => { getFocuses().forEach((element) => { classes.focused.split(" ").forEach((c) => element.classList.remove(c)); classes.focusedVisible.split(" ").forEach((c) => element.classList.remove(c)); element.classList.remove("HvIsFocused"); classes?.focus?.split(" ").forEach((c) => element.classList.remove(c)); }); }; const onFocus = (evt) => { hasFocusVisible.current = evt.currentTarget.matches(":focus-visible"); addFocusClass(evt); childFocus?.focus?.(); onFocusStrategy(evt); }; const onBlur = () => { removeFocusClass(); onBlurStrategy(); }; const onMouseDown = (evt) => { const hasCard = !!evt.currentTarget?.querySelector(".HvIsCardGridElement"); if (strategy === "grid" && hasCard) return; setFocusTo(evt.currentTarget); setTabIndex(evt.currentTarget, 0); }; const focusAndUpdateIndex = (nextFocus, previousFocus, focusesList) => { if (focusesList?.includes(previousFocus)) setTabIndex(previousFocus, -1); setTabIndex(nextFocus, 0); setFocusTo(nextFocus); }; const getEnabledKeys = (currentFocusIndex, jump, listSize) => ({ right: (currentFocusIndex + 1) % jump === 0 || currentFocusIndex + 1 > listSize - 1, left: currentFocusIndex % jump === 0, up: currentFocusIndex - jump < 0, down: currentFocusIndex + jump > listSize || currentFocusIndex + jump > listSize - 1 }); const onGridKeyDownHandler = (evt, focuses, focusesList, currentFocusIndex, jump) => { const childFocusIsInput = childFocus && childFocus.nodeName === "INPUT"; if (!isOneOfKeys(evt, [ "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Home", "End", "Space", "Enter" ]) || childFocusIsInput && isKey(evt, "Enter")) return; if (!isOneOfKeys(evt, ["Enter", "Space"])) { evt.preventDefault(); evt.stopPropagation(); } if (!hasFocusVisible.current) { hasFocusVisible.current = true; if (evt.code === "Space" || evt.code === "Enter") { focusAndUpdateIndex(focuses.current || focuses.first, evt.current, focusesList); return; } if (focuses.current === focuses.first) { focuses.previous = void 0; focuses.next = void 0; focuses.jump = void 0; focuses.fall = void 0; } } const blockedKeys = getEnabledKeys(currentFocusIndex, jump, focusesList.length); switch (evt.code) { case "Space": case "Enter": if (isBrowser("firefox")) evt.target.click(); else evt.currentTarget.click(); break; case "ArrowUp": if (!blockedKeys.up) focusAndUpdateIndex(focuses.jump || focuses.last, evt.current, focusesList); break; case "ArrowDown": if (!blockedKeys.down) focusAndUpdateIndex(focuses.fall || focuses.first, evt.current, focusesList); break; case "ArrowLeft": if (!blockedKeys.left) focusAndUpdateIndex(focuses.previous || focuses.last, evt.current, focusesList); break; case "ArrowRight": if (!blockedKeys.right) focusAndUpdateIndex(focuses.next || focuses.first, evt.current, focusesList); break; case "Home": focusAndUpdateIndex(focuses.first, evt.current, focusesList); break; case "End": focusAndUpdateIndex(focuses.last, evt.current, focusesList); break; default: } }; const onVerticalArrangementHandler = (evt, focuses, focusesList) => { const childFocusIsInput = childFocus && childFocus.nodeName === "INPUT"; if (!isOneOfKeys(evt, [ "ArrowUp", "ArrowDown", "Home", "End", "Space", "Enter" ]) || childFocusIsInput && isKey(evt, "Enter")) return; evt.preventDefault(); evt.stopPropagation(); if (!hasFocusVisible.current) { hasFocusVisible.current = true; if (evt.code === "Space" || evt.code === "Enter") { focusAndUpdateIndex(focuses.current || focuses.first, evt.current, focusesList); return; } if (focuses.current === focuses.first) { focuses.previous = void 0; focuses.next = void 0; } } switch (evt.code) { case "Space": case "Enter": evt.target.click(); break; case "ArrowUp": focusAndUpdateIndex(focuses.previous || focuses.last, evt.current, focusesList); break; case "ArrowDown": focusAndUpdateIndex(focuses.next || focuses.first, evt.current, focusesList); break; case "Home": focusAndUpdateIndex(focuses.first, evt.current, focusesList); break; case "End": focusAndUpdateIndex(focuses.last, evt.current, focusesList); break; default: } }; const onSingleHandler = (evt) => { const childFocusIsInput = childFocus && childFocus.nodeName === "INPUT"; if (!isOneOfKeys(evt, ["Space", "Enter"]) || childFocusIsInput && isKey(evt, "Enter")) return; evt.preventDefault(); evt.stopPropagation(); evt.currentTarget.click(); }; const onKeyDown = (evt) => { if (rootRef?.current == null) { onSingleHandler(evt); return; } const isDisabledFocusable = strategy === "menu"; const focusesList = getFocuses().filter((el) => isDisabledFocusable || !el.classList.contains(classes?.disabled)); const currentFocus = focusesList.indexOf(evt.currentTarget); const focuses = { first: focusesList[0], last: focusesList[focusesList.length - 1], previous: focusesList[currentFocus - 1], current: focusesList[currentFocus], next: focusesList[currentFocus + 1], fall: focusesList[currentFocus + navigationJump], jump: focusesList[currentFocus - navigationJump] }; if (strategy === "grid") { onGridKeyDownHandler(evt, focuses, focusesList, currentFocus, navigationJump); return; } onVerticalArrangementHandler(evt, focuses, focusesList); }; const onKeyUp = (evt) => { if (isBrowser("firefox")) evt.preventDefault(); }; if (disabled) return children; return cloneElement(children, { className: cx([classes.root, filterClass], { [classes.selected]: selected, [classes.disabled]: disabledClass, [classes.focusDisabled]: focusDisabled }, children.props.className), ref: config, onFocus, onBlur, onMouseDown, onKeyDown, onKeyUp, selected }); }; //#endregion export { HvFocus };