* feat: keyboard shortcuts, click-to-select, pulse/glow, smooth orbit resume * feat: add camera controls and enhance keyboard shortcuts help; improve flight card accessibility * feat: enhance flight layers and keyboard shortcuts; improve airline data structure
74 lines
1.6 KiB
TypeScript
74 lines
1.6 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef } from "react";
|
|
|
|
type ShortcutActions = {
|
|
onNorthUp: () => void;
|
|
onResetView: () => void;
|
|
onToggleOrbit: () => void;
|
|
onOpenSearch: () => void;
|
|
onToggleHelp: () => void;
|
|
onDeselect: () => void;
|
|
};
|
|
|
|
const INPUT_TAGS = new Set(["INPUT", "TEXTAREA", "SELECT"]);
|
|
|
|
export function useKeyboardShortcuts(actions: ShortcutActions) {
|
|
const ref = useRef(actions);
|
|
|
|
useEffect(() => {
|
|
ref.current = actions;
|
|
}, [actions]);
|
|
|
|
useEffect(() => {
|
|
function handler(e: KeyboardEvent) {
|
|
const target = e.target as HTMLElement;
|
|
if (INPUT_TAGS.has(target.tagName) || target.isContentEditable) return;
|
|
|
|
const dialogOpen = !!document.querySelector(
|
|
'[role="dialog"][aria-modal="true"]',
|
|
);
|
|
|
|
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
|
|
const a = ref.current;
|
|
|
|
if (e.key === "Escape") {
|
|
if (!dialogOpen) a.onDeselect();
|
|
return;
|
|
}
|
|
|
|
if (dialogOpen) return;
|
|
|
|
switch (e.key) {
|
|
case "n":
|
|
case "N":
|
|
e.preventDefault();
|
|
a.onNorthUp();
|
|
break;
|
|
case "r":
|
|
case "R":
|
|
e.preventDefault();
|
|
a.onResetView();
|
|
break;
|
|
case "o":
|
|
case "O":
|
|
e.preventDefault();
|
|
a.onToggleOrbit();
|
|
break;
|
|
case "/":
|
|
e.preventDefault();
|
|
a.onOpenSearch();
|
|
break;
|
|
case "?":
|
|
e.preventDefault();
|
|
a.onToggleHelp();
|
|
break;
|
|
}
|
|
}
|
|
|
|
window.addEventListener("keydown", handler);
|
|
return () => window.removeEventListener("keydown", handler);
|
|
}, []);
|
|
}
|