pstream is dead; long live pstream
taciturnaxolotl.github.io/pstream-ng/
1import { useCallback, useEffect, useRef } from "react";
2
3import { useOverlayStack } from "@/stores/interface/overlayStack";
4
5/**
6 * Global keyboard event handler that works across the entire application.
7 * Handles Escape key to close modals and other global shortcuts.
8 */
9export function useGlobalKeyboardEvents() {
10 const { getTopModal, hideModal, showModal } = useOverlayStack();
11 const holdTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>();
12 const isKeyHeldRef = useRef<boolean>(false);
13
14 const showKeyboardCommands = useCallback(() => {
15 showModal("keyboard-commands");
16 }, [showModal]);
17
18 const hideKeyboardCommands = useCallback(() => {
19 hideModal("keyboard-commands");
20 }, [hideModal]);
21
22 useEffect(() => {
23 const handleKeyDown = (event: KeyboardEvent) => {
24 // Don't handle keyboard events if user is typing in an input
25 if (
26 event.target &&
27 (event.target as HTMLInputElement).nodeName === "INPUT"
28 ) {
29 return;
30 }
31
32 // Cancel if command or alt is pressed
33 if (event.metaKey || event.altKey) return;
34
35 // Handle backtick (`) key hold for keyboard commands
36 if (event.key === "`") {
37 // Prevent default browser behavior (console opening in some browsers)
38 event.preventDefault();
39
40 if (!isKeyHeldRef.current) {
41 isKeyHeldRef.current = true;
42
43 // Show modal after 500ms hold
44 holdTimeoutRef.current = setTimeout(() => {
45 showKeyboardCommands();
46 }, 150);
47 }
48 }
49
50 // Handle Escape key to close modals
51 if (event.key === "Escape") {
52 const topModal = getTopModal();
53 if (topModal) {
54 hideModal(topModal);
55 }
56 }
57 };
58
59 const handleKeyUp = (event: KeyboardEvent) => {
60 if (event.key === "`") {
61 // Clear the hold timeout if key is released before modal shows
62 if (holdTimeoutRef.current) {
63 clearTimeout(holdTimeoutRef.current);
64 holdTimeoutRef.current = undefined;
65 }
66
67 // Hide modal if it was shown
68 if (isKeyHeldRef.current) {
69 hideKeyboardCommands();
70 }
71
72 isKeyHeldRef.current = false;
73 }
74 };
75
76 // Add event listeners to document for global coverage
77 document.addEventListener("keydown", handleKeyDown);
78 document.addEventListener("keyup", handleKeyUp);
79
80 return () => {
81 document.removeEventListener("keydown", handleKeyDown);
82 document.removeEventListener("keyup", handleKeyUp);
83
84 // Clean up any pending timeouts
85 if (holdTimeoutRef.current) {
86 clearTimeout(holdTimeoutRef.current);
87 }
88 };
89 }, [getTopModal, hideModal, showKeyboardCommands, hideKeyboardCommands]);
90}