Files
tolaria/src/hooks/useKeyboardNavigation.ts
Test 09f7498e86 fix: Cmd+Option+arrow navigation now follows the visible note list order
The keyboard navigation was computing its own note list with hardcoded
sortByModified, which didn't match the actual UI order (Inbox sorts by
createdAt, custom types use configurable sorts, etc.). Now the NoteList
component writes its sorted list to a shared ref that the navigation
hook reads directly, ensuring navigation always matches visual order.

Also fixes navigation not wrapping at list boundaries per spec.

Syncs CodeScene thresholds with current remote scores.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 10:29:27 +02:00

68 lines
2.2 KiB
TypeScript

import { useEffect, useRef } from 'react'
import type { VaultEntry } from '../types'
interface KeyboardNavigationOptions {
activeTabPath: string | null
visibleNotesRef: React.RefObject<VaultEntry[]>
onReplaceActiveTab: (entry: VaultEntry) => void
onSelectNote: (entry: VaultEntry) => void
}
function navigateNote(
visibleNotesRef: React.RefObject<VaultEntry[]>,
activeTabPathRef: React.RefObject<string | null>,
onReplace: React.RefObject<(entry: VaultEntry) => void>,
onSelect: React.RefObject<(entry: VaultEntry) => void>,
direction: 1 | -1,
) {
const notes = visibleNotesRef.current!
if (notes.length === 0) return
const currentPath = activeTabPathRef.current
const currentIndex = notes.findIndex((n) => n.path === currentPath)
const nextIndex = currentIndex === -1
? (direction === 1 ? 0 : notes.length - 1)
: currentIndex + direction
// Clamp to list bounds — don't wrap around
if (nextIndex < 0 || nextIndex >= notes.length) return
const nextNote = notes[nextIndex]
if (currentPath) {
onReplace.current!(nextNote)
} else {
onSelect.current!(nextNote)
}
}
function useLatestRef<T>(value: T): React.RefObject<T> {
const ref = useRef(value)
useEffect(() => { ref.current = value })
return ref
}
export function useKeyboardNavigation({
activeTabPath, visibleNotesRef,
onReplaceActiveTab, onSelectNote,
}: KeyboardNavigationOptions) {
const activeTabPathRef = useLatestRef(activeTabPath)
const onReplaceRef = useLatestRef(onReplaceActiveTab)
const onSelectNoteRef = useLatestRef(onSelectNote)
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
const mod = e.metaKey || e.ctrlKey
if (!mod) return
// Cmd+Alt+ArrowUp/Down: navigate notes in the current list
if (e.altKey && !e.shiftKey && (e.key === 'ArrowUp' || e.key === 'ArrowDown')) {
e.preventDefault()
const direction: 1 | -1 = e.key === 'ArrowDown' ? 1 : -1
navigateNote(visibleNotesRef, activeTabPathRef, onReplaceRef, onSelectNoteRef, direction)
}
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [activeTabPathRef, visibleNotesRef, onReplaceRef, onSelectNoteRef])
}