73 lines
2.5 KiB
TypeScript
73 lines
2.5 KiB
TypeScript
import { useEffect } from 'react';
|
|
|
|
/**
|
|
* Горячие клавиши inbox (когда фокус не в input/textarea/select/contenteditable).
|
|
* j/k — соседний элемент, 1/2 — действия, Esc — снять фокус списка (опционально).
|
|
*/
|
|
export function useInboxHotkeys(options: {
|
|
enabled?: boolean;
|
|
onNext: () => void;
|
|
onPrev: () => void;
|
|
onPrimary?: () => void;
|
|
onSecondary?: () => void;
|
|
onOpen?: () => void;
|
|
}) {
|
|
const { enabled = true, onNext, onPrev, onPrimary, onSecondary, onOpen } = options;
|
|
|
|
useEffect(() => {
|
|
if (!enabled) return;
|
|
|
|
const isTypingTarget = (el: EventTarget | null) => {
|
|
if (!(el instanceof HTMLElement)) return false;
|
|
const tag = el.tagName;
|
|
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || el.isContentEditable;
|
|
};
|
|
|
|
const handler = (e: KeyboardEvent) => {
|
|
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
if (isTypingTarget(e.target)) return;
|
|
|
|
const key = e.key.toLowerCase();
|
|
if (key === 'j' || key === 'arrowdown') {
|
|
e.preventDefault();
|
|
onNext();
|
|
} else if (key === 'k' || key === 'arrowup') {
|
|
e.preventDefault();
|
|
onPrev();
|
|
} else if (key === 'enter' && onOpen) {
|
|
e.preventDefault();
|
|
onOpen();
|
|
} else if (key === '1' && onPrimary) {
|
|
e.preventDefault();
|
|
onPrimary();
|
|
} else if (key === '2' && onSecondary) {
|
|
e.preventDefault();
|
|
onSecondary();
|
|
}
|
|
};
|
|
|
|
window.addEventListener('keydown', handler);
|
|
return () => window.removeEventListener('keydown', handler);
|
|
}, [enabled, onNext, onPrev, onPrimary, onSecondary, onOpen]);
|
|
}
|
|
|
|
export function selectNeighborId(
|
|
ids: string[],
|
|
currentId: string,
|
|
direction: 1 | -1
|
|
): string | null {
|
|
if (ids.length === 0) return null;
|
|
const idx = ids.indexOf(currentId);
|
|
if (idx < 0) return ids[0] ?? null;
|
|
const next = idx + direction;
|
|
if (next < 0 || next >= ids.length) return currentId;
|
|
return ids[next];
|
|
}
|
|
|
|
/** Следующий после текущего; если конец — предыдущий. */
|
|
export function selectAfterRemove(ids: string[], currentId: string): string | null {
|
|
const idx = ids.indexOf(currentId);
|
|
if (idx < 0) return ids[0] ?? null;
|
|
return ids[idx + 1] ?? ids[idx - 1] ?? null;
|
|
}
|