@clayui/shared
Version:
ClayShared component
286 lines (285 loc) • 11 kB
JavaScript
;
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);
}
}