(e: KeyboardEvent)
| 295 | useEffect(() => { |
| 296 | if (!vimMode) return |
| 297 | const handler = (e: KeyboardEvent): void => { |
| 298 | const state = useStore.getState() |
| 299 | const overrides = state.keymapOverrides |
| 300 | const leaderToken = getSequenceTokens(overrides, 'vim.leaderPrefix')[0] ?? 'Space' |
| 301 | const panePrefixToken = getSequenceTokens(overrides, 'vim.panePrefix')[0] ?? 'Ctrl+W' |
| 302 | |
| 303 | // Skip when modals / overlays are open |
| 304 | if ( |
| 305 | state.searchOpen || |
| 306 | state.vaultTextSearchOpen || |
| 307 | state.settingsOpen || |
| 308 | state.commandPaletteOpen || |
| 309 | state.bufferPaletteOpen |
| 310 | ) return |
| 311 | if ( |
| 312 | document.querySelector('[data-ctx-menu]') || |
| 313 | document.querySelector('[data-prompt-modal]') || |
| 314 | document.querySelector('[data-confirm-modal]') |
| 315 | ) return |
| 316 | |
| 317 | // Hint mode — handled entirely by HintOverlay's own listener |
| 318 | if (hintRef.current) return |
| 319 | |
| 320 | // `e.target` is only an HTMLElement for real DOM-dispatched events. |
| 321 | // Synthetic events fired at `window`/`document` (e.g. programmatic |
| 322 | // shortcuts) have a non-Element target, so narrow with `instanceof` |
| 323 | // before touching Element-only methods like `.closest()`. |
| 324 | const target = e.target instanceof HTMLElement ? e.target : null |
| 325 | const tag = target?.tagName |
| 326 | // Never steal keys from normal text-entry fields such as the |
| 327 | // inline note title, prompt inputs, or textarea-based controls. |
| 328 | if (tag === 'INPUT' || tag === 'TEXTAREA') return |
| 329 | // The selection format toolbar handles its own keyboard navigation |
| 330 | // (arrows / Enter / Esc) once focused — yield to it entirely. |
| 331 | if (target?.closest('[data-selection-toolbar]')) return |
| 332 | // The home view owns its own roving-focus navigation (↑/↓/j/k/Enter), but |
| 333 | // it does not handle the leader key — so the leader (and any pending leader |
| 334 | // sequence) must fall through to VimNav, or Space-as-leader is swallowed |
| 335 | // while the home view is focused (no note open). (#273) |
| 336 | if ( |
| 337 | shouldYieldToHomeNav( |
| 338 | target, |
| 339 | sequenceTokenFromEvent(e) === leaderToken, |
| 340 | !!leaderPending.current |
| 341 | ) |
| 342 | ) { |
| 343 | return |
| 344 | } |
| 345 | // The database/table view runs its own vim-style motion grid; yield to it |
| 346 | // so sidebar/note-list navigation doesn't steal j/k/h/l etc. — EXCEPT the |
| 347 | // pane prefix (Ctrl+W) and its pending direction key, so the grid can hand |
| 348 | // off to pane/tab navigation (Ctrl+W k → tabs) like every other surface. |
| 349 | if ( |
| 350 | target?.closest('[data-zen-db-grid]') && |
| 351 | !ctrlWPending.current && |
| 352 | sequenceTokenFromEvent(e) !== panePrefixToken |
| 353 | ) { |
| 354 | return |
nothing calls this directly
no test coverage detected