@hitachivantara/uikit-react-core
Version:
UI Kit Core React components.
250 lines (249 loc) • 8.8 kB
JavaScript
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 };