Skip to content

Commit

Permalink
Fixed FocusLock bug
Browse files Browse the repository at this point in the history
  • Loading branch information
Mil4n0r committed Dec 16, 2024
1 parent 1f6201c commit 4089918
Showing 1 changed file with 22 additions and 28 deletions.
50 changes: 22 additions & 28 deletions packages/lib/src/utils/FocusLock.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { KeyboardEvent, MutableRefObject, ReactNode, useCallback, useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";

const not = {
negTabIndex: ':not([tabindex^="-"])',
Expand All @@ -19,19 +19,15 @@ const focusableQuery = [
`[tabindex]${not.negTabIndex}${not.disabled}`,
].join(",");

const getFocusableElements = (container: HTMLElement | null): HTMLElement[] | null => {
if (container != null) {
return Array.prototype.slice
.call(container.querySelectorAll(focusableQuery))
.filter(
(element: HTMLElement) =>
element.getAttribute("aria-hidden") !== "true" &&
window.getComputedStyle(element).display !== "none" &&
window.getComputedStyle(element).visibility !== "hidden"
);
}
return null;
};
const getFocusableElements = (container: HTMLElement | null): HTMLElement[] =>
Array.prototype.slice
.call(container?.querySelectorAll(focusableQuery))
.filter(
(element: HTMLElement) =>
element.getAttribute("aria-hidden") !== "true" &&
window.getComputedStyle(element).display !== "none" &&
window.getComputedStyle(element).visibility !== "hidden"
);

/**
* This function will try to focus the element and return true if it was able to receive the focus.
Expand Down Expand Up @@ -60,10 +56,10 @@ const radixPortalContains = (activeElement: Node): boolean => {

/**
* Custom hook that returns an array of focusable elements inside a container.
* @param ref: MutableRefObject<HTMLDivElement>
* @param ref: React.MutableRefObject<HTMLDivElement>
* @returns
*/
const useFocusableElements = (ref: MutableRefObject<HTMLDivElement | null>): HTMLElement[] | null => {
const useFocusableElements = (ref: React.MutableRefObject<HTMLDivElement | null>): HTMLElement[] | null => {
const [focusableElements, setFocusableElements] = useState<HTMLElement[] | null>(null);

useEffect(() => {
Expand All @@ -73,11 +69,6 @@ const useFocusableElements = (ref: MutableRefObject<HTMLDivElement | null>): HTM
const observer = new MutationObserver(() => {
setFocusableElements(getFocusableElements(ref?.current));
});
observer.observe(ref.current, {
childList: true,
subtree: true,
attributes: true,
});
observer.observe(ref.current, { childList: true, subtree: true });
return () => {
observer.disconnect();
Expand All @@ -94,10 +85,10 @@ const useFocusableElements = (ref: MutableRefObject<HTMLDivElement | null>): HTM
* When the focus is on the last focusable element and the user tries to focus the next element, it will focus the first element.
* When the focus is on the first focusable element and the user tries to focus the previous element, it will focus the last element.
* The focus can't be moved outside the children unless the children are removed from the DOM (for example, a Dialog, a Modal, etc).
* @param children: ReactNode
* @param children: React.ReactNode
* @returns
*/
const FocusLock = ({ children }: { children: ReactNode }): JSX.Element => {
const FocusLock = ({ children }: { children: React.ReactNode }): JSX.Element => {
const childrenContainerRef = useRef<HTMLDivElement | null>(null);
const focusableElements = useFocusableElements(childrenContainerRef);
const initialFocus = useRef(false);
Expand All @@ -117,12 +108,14 @@ const FocusLock = ({ children }: { children: ReactNode }): JSX.Element => {
?.some((element) => attemptFocus(element));
};

const focusLock = (event: KeyboardEvent<HTMLDivElement>) => {
if (event.key === "Tab" && focusableElements && focusableElements.length === 0) {event.preventDefault();}
const focusLock = (event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "Tab" && focusableElements && focusableElements.length === 0) {
event.preventDefault();
}
};

useEffect(() => {
if (focusableElements !== undefined && !initialFocus.current) {
if (focusableElements != null && !initialFocus.current) {
initialFocus.current = true;
focusFirst();
}
Expand All @@ -141,8 +134,9 @@ const FocusLock = ({ children }: { children: ReactNode }): JSX.Element => {
container?.previousElementSibling?.contains(target) ||
radixPortalContains(target)
)
)
{focusFirst();}
) {
focusFirst();
}
};

document.addEventListener("focusout", focusGuardHandler);
Expand Down

0 comments on commit 4089918

Please sign in to comment.