import React, { Component, createRef } from "react";
import type { ReactNode } from "react";
interface TabLoopProps {
enableTabLoop?: boolean;
children?: ReactNode | undefined;
}
const focusableElementsSelector =
"[tabindex], a, button, input, select, textarea";
const focusableFilter = (
node:
| HTMLButtonElement
| HTMLInputElement
| HTMLSelectElement
| HTMLTextAreaElement
| HTMLAnchorElement,
) => {
if (node instanceof HTMLAnchorElement) {
return node.tabIndex !== -1;
}
return !node.disabled && node.tabIndex !== -1;
};
/**
* `TabLoop` is a React component that manages tabbing behavior for its children.
*
* TabLoop prevents the user from tabbing outside of the popper
* It creates a tabindex loop so that "Tab" on the last element will focus the first element
* and "Shift Tab" on the first element will focus the last element
*
* @component
* @example
*
*
*
*
* @param props - The properties that define the `TabLoop` component.
* @param props.children - The child components.
* @param props.enableTabLoop - Whether to enable the tab loop.
*
* @returns The `TabLoop` component.
*/
export default class TabLoop extends Component {
static defaultProps = {
enableTabLoop: true,
};
constructor(props: TabLoopProps) {
super(props);
this.tabLoopRef = createRef();
}
private tabLoopRef: React.RefObject;
/**
* `getTabChildren` is a method of the `TabLoop` class that retrieves all tabbable children of the component.
*
* This method uses the `tabbable` library to find all tabbable elements within the `TabLoop` component.
* It then filters out any elements that are not visible.
*
* @returns An array of all tabbable and visible children of the `TabLoop` component.
*/
getTabChildren = () =>
Array.prototype.slice
.call(
this.tabLoopRef.current?.querySelectorAll(focusableElementsSelector),
1,
-1,
)
.filter(focusableFilter);
handleFocusStart = () => {
const tabChildren = this.getTabChildren();
tabChildren &&
tabChildren.length > 1 &&
tabChildren[tabChildren.length - 1].focus();
};
handleFocusEnd = () => {
const tabChildren = this.getTabChildren();
tabChildren && tabChildren.length > 1 && tabChildren[0].focus();
};
render() {
if (!(this.props.enableTabLoop ?? TabLoop.defaultProps.enableTabLoop)) {
return this.props.children;
}
return (
);
}
}