| author | |
| committer | |
| log | f769e01e28c80745621a699ed5ee021cc78039b6 |
| tree | 3c306f5ca84630b39529dc3a145cf7c7d6564cef |
| parent | f0a47b3172c4a73e1932b23d37c3345481c5a67e |
| signature | Signed by PGP key B5690EEEBB952194 |
2 files changed, 18 insertions(+), 30 deletions(-)
apps/web/src/components/settings/KeybindingsSettings.tsx-23| ... | ... | @@ -17,7 +17,6 @@ import { |
| 17 | 17 | type ReactNode, |
| 18 | 18 | type RefObject, |
| 19 | 19 | useCallback, |
| 20 | useEffect, | |
| 21 | 20 | useMemo, |
| 22 | 21 | useReducer, |
| 23 | 22 | useRef, |
| ... | ... | @@ -1340,28 +1339,6 @@ export function KeybindingsSettingsPanel() { |
| 1340 | 1339 | const commandOptions = useMemo(() => buildKeybindingCommandOptions(keybindings), [keybindings]); |
| 1341 | 1340 | const whenVariables = useMemo(() => buildWhenVariableOptions(), []); |
| 1342 | 1341 | |
| 1343 | useEffect(() => { | |
| 1344 | const handleKeyDown = (event: globalThis.KeyboardEvent) => { | |
| 1345 | const isMod = event.metaKey || event.ctrlKey; | |
| 1346 | if (!isMod || event.altKey || event.key.toLowerCase() !== "f") return; | |
| 1347 | ||
| 1348 | const target = event.target; | |
| 1349 | if ( | |
| 1350 | target !== searchInputRef.current && | |
| 1351 | target instanceof HTMLElement && | |
| 1352 | (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) | |
| 1353 | ) { | |
| 1354 | return; | |
| 1355 | } | |
| 1356 | ||
| 1357 | event.preventDefault(); | |
| 1358 | searchInputRef.current?.focus(); | |
| 1359 | searchInputRef.current?.select(); | |
| 1360 | }; | |
| 1361 | window.addEventListener("keydown", handleKeyDown); | |
| 1362 | return () => window.removeEventListener("keydown", handleKeyDown); | |
| 1363 | }, []); | |
| 1364 | ||
| 1365 | 1342 | const openKeybindingsFile = useCallback(() => { |
| 1366 | 1343 | if ( |
| 1367 | 1344 | !keybindingsConfigPath || |
apps/web/src/components/settings/SettingsSidebarNav.tsx+18-7| ... | ... | @@ -140,17 +140,28 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { |
| 140 | 140 | |
| 141 | 141 | useEffect(() => { |
| 142 | 142 | const handleKeyDown = (event: globalThis.KeyboardEvent) => { |
| 143 | if (event.key !== "/" || event.metaKey || event.ctrlKey || event.altKey) return; | |
| 143 | if (event.defaultPrevented || event.isComposing) return; | |
| 144 | const isFindShortcut = | |
| 145 | event.key.toLowerCase() === "f" && | |
| 146 | (event.metaKey || event.ctrlKey) && | |
| 147 | !event.altKey && | |
| 148 | !event.shiftKey; | |
| 149 | const isSlashShortcut = | |
| 150 | event.key === "/" && !event.metaKey && !event.ctrlKey && !event.altKey; | |
| 151 | if (!isFindShortcut && !isSlashShortcut) return; | |
| 144 | 152 | |
| 145 | 153 | const target = event.target; |
| 146 | 154 | if ( |
| 147 | 155 | target instanceof HTMLElement && |
| 148 | (target.tagName === "INPUT" || | |
| 149 | target.tagName === "TEXTAREA" || | |
| 150 | target.isContentEditable || | |
| 151 | // Keep focus inside open dialogs and popups instead of escaping | |
| 152 | // their focus trap into the sidebar search. | |
| 153 | target.closest('[role="dialog"], [aria-modal="true"], [data-slot$="popup"]') !== null) | |
| 156 | ((isSlashShortcut && | |
| 157 | (target.tagName === "INPUT" || | |
| 158 | target.tagName === "TEXTAREA" || | |
| 159 | target.isContentEditable)) || | |
| 160 | // Preserve dialog and popup focus traps, and let shortcut recorders | |
| 161 | // capture the keypress without moving focus into the sidebar search. | |
| 162 | target.closest( | |
| 163 | '[role="dialog"], [aria-modal="true"], [data-slot$="popup"], [data-keybinding-capture]', | |
| 164 | ) !== null) | |
| 154 | 165 | ) { |
| 155 | 166 | return; |
| 156 | 167 | } |