* feat: add session quick wins (#51-#55) - Session timer showing elapsed time in header (#51) - Tab keyboard shortcut to focus notes textarea (#52) - Repeat Last Session button on tree library page (#53) - Auto-recovery banner for incomplete sessions (#54) - Copy individual step to clipboard on session detail (#55) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add missing delete button to table and list tree views The onDeleteTree prop was accepted but never used in TreeTableView and TreeListView. Now both views show a trash icon (permission-gated) matching the existing grid view behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
98 lines
2.4 KiB
TypeScript
98 lines
2.4 KiB
TypeScript
import { useEffect, useCallback } from 'react'
|
|
|
|
interface ShortcutConfig {
|
|
key: string
|
|
ctrl?: boolean
|
|
shift?: boolean
|
|
alt?: boolean
|
|
handler: () => void
|
|
enabled?: boolean
|
|
}
|
|
|
|
export function useKeyboardShortcuts(shortcuts: ShortcutConfig[]) {
|
|
const handleKeyDown = useCallback(
|
|
(e: KeyboardEvent) => {
|
|
// Don't trigger shortcuts when typing in inputs
|
|
const target = e.target as HTMLElement
|
|
if (
|
|
target.tagName === 'INPUT' ||
|
|
target.tagName === 'TEXTAREA' ||
|
|
target.isContentEditable
|
|
) {
|
|
return
|
|
}
|
|
|
|
for (const shortcut of shortcuts) {
|
|
if (shortcut.enabled === false) continue
|
|
|
|
const keyMatch = e.key === shortcut.key || e.key === shortcut.key.toLowerCase()
|
|
const ctrlMatch = !!shortcut.ctrl === (e.ctrlKey || e.metaKey)
|
|
const shiftMatch = !!shortcut.shift === e.shiftKey
|
|
const altMatch = !!shortcut.alt === e.altKey
|
|
|
|
if (keyMatch && ctrlMatch && shiftMatch && altMatch) {
|
|
e.preventDefault()
|
|
shortcut.handler()
|
|
return
|
|
}
|
|
}
|
|
},
|
|
[shortcuts]
|
|
)
|
|
|
|
useEffect(() => {
|
|
document.addEventListener('keydown', handleKeyDown)
|
|
return () => document.removeEventListener('keydown', handleKeyDown)
|
|
}, [handleKeyDown])
|
|
}
|
|
|
|
// Convenience hook for tree navigation specifically
|
|
export interface TreeNavigationShortcutsConfig {
|
|
onSelectOption: (index: number) => void
|
|
onGoBack: () => void
|
|
onContinue: () => void
|
|
optionCount: number
|
|
canGoBack: boolean
|
|
canContinue: boolean
|
|
}
|
|
|
|
export function useTreeNavigationShortcuts({
|
|
onSelectOption,
|
|
onGoBack,
|
|
onContinue,
|
|
optionCount,
|
|
canGoBack,
|
|
canContinue,
|
|
}: TreeNavigationShortcutsConfig) {
|
|
const shortcuts: ShortcutConfig[] = [
|
|
// Number keys 1-9 for options
|
|
...Array.from({ length: Math.min(optionCount, 9) }, (_, i) => ({
|
|
key: String(i + 1),
|
|
handler: () => onSelectOption(i),
|
|
})),
|
|
// Escape to go back
|
|
{
|
|
key: 'Escape',
|
|
handler: onGoBack,
|
|
enabled: canGoBack,
|
|
},
|
|
// Enter to continue (for action nodes)
|
|
{
|
|
key: 'Enter',
|
|
handler: onContinue,
|
|
enabled: canContinue,
|
|
},
|
|
// Tab to focus notes
|
|
{
|
|
key: 'Tab',
|
|
handler: () => {
|
|
document.getElementById('session-notes')?.focus()
|
|
},
|
|
},
|
|
]
|
|
|
|
useKeyboardShortcuts(shortcuts)
|
|
}
|
|
|
|
export default useKeyboardShortcuts
|