All files / core/blong-browser/src/hooks useCommanderNav.ts

52.5% Statements 21/40
57.89% Branches 11/19
66.66% Functions 4/6
51.28% Lines 20/39

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94                                                                6x     64x 64x 64x   64x             64x 13x 3x 3x   3x 3x 2x   1x             1x               1x 1x 1x                             13x 13x     64x    
/**
 * useCommanderNav — keyboard handling for the commander explorer.
 *
 * Provides global shortcuts (F5 refresh, F2 open, Enter, Escape, `?` help,
 * Ctrl/Cmd+F search) and an active-pane focus model (tree | table | viewer)
 * so the commander can be driven entirely from the keyboard.
 *
 * Plain arrow keys are left to the focused widget (PrimeReact Tree/DataTable
 * already handle expand/collapse and cell navigation); the pane ring is
 * traversed with Tab / Shift+Tab via `movePane`.
 */
import {useCallback, useEffect, useRef, useState} from 'react';
 
export type CommanderPane = 'tree' | 'table' | 'viewer';
 
export interface ICommanderKeyHandlers {
    /** Enter pressed on the active pane. */
    onEnter?: () => void;
    /** Escape pressed (clear search / close help). */
    onEscape?: () => void;
    /** Backspace pressed (go up to parent / close the viewer). */
    onBackspace?: () => void;
    /** F5 pressed (refresh current branch). */
    onRefresh?: () => void;
    /** F2 pressed (open leaf). */
    onOpen?: () => void;
    /** Ctrl/Cmd+F pressed (focus search). */
    onSearch?: () => void;
    /** `?` pressed (toggle help overlay). */
    onHelp?: () => void;
}
 
const PANES: CommanderPane[] = ['tree', 'table', 'viewer'];
 
export function useCommanderNav(handlers: ICommanderKeyHandlers) {
    const [activePane, setActivePane] = useState<CommanderPane>('tree');
    const ref = useRef(handlers);
    ref.current = handlers;
 
    const movePane = useCallback((dir: 1 | -1) => {
        setActivePane(prev => {
            const idx = PANES.indexOf(prev);
            return PANES[(idx + dir + PANES.length) % PANES.length];
        });
    }, []);
 
    useEffect(() => {
        const onKeyDown = (e: KeyboardEvent) => {
            const h = ref.current;
            const target = e.target as HTMLElement | null;
            // Don't hijack keys while typing in an input/textarea/select.
            const tag = target?.tagName;
            if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || target?.isContentEditable) {
                return;
            }
            Iif (e.ctrlKey || e.metaKey) {
                if (e.key.toLowerCase() === 'f') {
                    e.preventDefault();
                    h.onSearch?.();
                }
                return;
            }
            switch (e.key) {
                case 'Enter':
                    h.onEnter?.();
                    break;
                case 'Escape':
                    h.onEscape?.();
                    break;
                case 'Backspace':
                    e.preventDefault();
                    h.onBackspace?.();
                    break;
                case 'F5':
                    e.preventDefault();
                    h.onRefresh?.();
                    break;
                case 'F2':
                    h.onOpen?.();
                    break;
                case '?':
                    h.onHelp?.();
                    break;
                default:
                    break;
            }
        };
        window.addEventListener('keydown', onKeyDown);
        return () => window.removeEventListener('keydown', onKeyDown);
    }, []);
 
    return {activePane, setActivePane, movePane};
}