114 lines
3.3 KiB
TypeScript
114 lines
3.3 KiB
TypeScript
import { useProcesses } from "contexts/process";
|
|
import { useSession } from "contexts/session";
|
|
import { useCallback, useLayoutEffect, useMemo } from "react";
|
|
import { FOCUSABLE_ELEMENT, PREVENT_SCROLL } from "utils/constants";
|
|
|
|
type Events = {
|
|
onBlurCapture: (event: React.FocusEvent<HTMLElement>) => void;
|
|
onClickCapture: (event?: React.MouseEvent<HTMLElement>) => void;
|
|
onFocusCapture: (event?: React.FocusEvent<HTMLElement>) => void;
|
|
};
|
|
|
|
type Focusable = Events &
|
|
typeof FOCUSABLE_ELEMENT & {
|
|
zIndex: number;
|
|
};
|
|
|
|
const useFocusable = (
|
|
id: string,
|
|
callbackEvents?: Partial<Events>
|
|
): Focusable => {
|
|
const {
|
|
foregroundId,
|
|
prependToStack,
|
|
setForegroundId,
|
|
stackOrder = [],
|
|
} = useSession();
|
|
const {
|
|
processes: { [id]: process },
|
|
} = useProcesses();
|
|
const { closing, componentWindow, minimized, taskbarEntry, url } =
|
|
process || {};
|
|
const zIndex = useMemo(
|
|
() => stackOrder.length + (minimized ? 1 : -stackOrder.indexOf(id)) + 1,
|
|
[id, minimized, stackOrder]
|
|
);
|
|
const onBlurCapture: React.FocusEventHandler<HTMLElement> = useCallback(
|
|
(event) => {
|
|
const { relatedTarget } = event;
|
|
const focusedElement = relatedTarget as HTMLElement | null;
|
|
const focusedOnTaskbarEntry = relatedTarget === taskbarEntry;
|
|
const focusedOnTaskbarPeek =
|
|
focusedElement &&
|
|
taskbarEntry?.previousSibling?.contains(focusedElement);
|
|
const focusedOnInsideWindow =
|
|
focusedElement && componentWindow?.contains(focusedElement);
|
|
|
|
setForegroundId((currentForegroundId) => {
|
|
if (
|
|
currentForegroundId === id &&
|
|
!focusedOnTaskbarEntry &&
|
|
!focusedOnInsideWindow
|
|
) {
|
|
if (focusedOnTaskbarPeek) {
|
|
componentWindow?.focus(PREVENT_SCROLL);
|
|
} else {
|
|
callbackEvents?.onBlurCapture?.(event);
|
|
}
|
|
|
|
return focusedOnTaskbarPeek ? currentForegroundId : "";
|
|
}
|
|
|
|
return currentForegroundId;
|
|
});
|
|
},
|
|
[callbackEvents, componentWindow, id, setForegroundId, taskbarEntry]
|
|
);
|
|
const moveToFront = useCallback(
|
|
(event?: React.FocusEvent<HTMLElement> | React.MouseEvent<HTMLElement>) => {
|
|
const { relatedTarget } = event || {};
|
|
|
|
if (componentWindow?.contains(document.activeElement)) {
|
|
prependToStack(id);
|
|
setForegroundId(id);
|
|
} else if (!relatedTarget || document.activeElement === taskbarEntry) {
|
|
componentWindow?.focus(PREVENT_SCROLL);
|
|
callbackEvents?.onFocusCapture?.(
|
|
event as React.FocusEvent<HTMLElement>
|
|
);
|
|
}
|
|
},
|
|
[
|
|
callbackEvents,
|
|
componentWindow,
|
|
id,
|
|
prependToStack,
|
|
setForegroundId,
|
|
taskbarEntry,
|
|
]
|
|
);
|
|
|
|
useLayoutEffect(() => {
|
|
if (id === foregroundId) moveToFront();
|
|
}, [foregroundId, id, moveToFront]);
|
|
|
|
useLayoutEffect(() => {
|
|
if (componentWindow && !closing && !minimized) {
|
|
setForegroundId(id);
|
|
}
|
|
}, [closing, componentWindow, id, minimized, setForegroundId, url]);
|
|
|
|
return useMemo(
|
|
() => ({
|
|
onBlurCapture,
|
|
onClickCapture: moveToFront,
|
|
onFocusCapture: moveToFront,
|
|
zIndex,
|
|
...FOCUSABLE_ELEMENT,
|
|
}),
|
|
[moveToFront, onBlurCapture, zIndex]
|
|
);
|
|
};
|
|
|
|
export default useFocusable;
|