| 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,7 +17,6 @@ import { |
| 17 | type ReactNode, | 17 | type ReactNode, |
| 18 | type RefObject, | 18 | type RefObject, |
| 19 | useCallback, | 19 | useCallback, |
| 20 | useEffect, | ||
| 21 | useMemo, | 20 | useMemo, |
| 22 | useReducer, | 21 | useReducer, |
| 23 | useRef, | 22 | useRef, |
| ... | @@ -1340,28 +1339,6 @@ export function KeybindingsSettingsPanel() { | ... | @@ -1340,28 +1339,6 @@ export function KeybindingsSettingsPanel() { |
| 1340 | const commandOptions = useMemo(() => buildKeybindingCommandOptions(keybindings), [keybindings]); | 1339 | const commandOptions = useMemo(() => buildKeybindingCommandOptions(keybindings), [keybindings]); |
| 1341 | const whenVariables = useMemo(() => buildWhenVariableOptions(), []); | 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 | const openKeybindingsFile = useCallback(() => { | 1342 | const openKeybindingsFile = useCallback(() => { |
| 1366 | if ( | 1343 | if ( |
| 1367 | !keybindingsConfigPath || | 1344 | !keybindingsConfigPath || |
apps/web/src/components/settings/SettingsSidebarNav.tsx+18-7| ... | @@ -140,17 +140,28 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { | ... | @@ -140,17 +140,28 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { |
| 140 | 140 | ||
| 141 | useEffect(() => { | 141 | useEffect(() => { |
| 142 | const handleKeyDown = (event: globalThis.KeyboardEvent) => { | 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 | const target = event.target; | 153 | const target = event.target; |
| 146 | if ( | 154 | if ( |
| 147 | target instanceof HTMLElement && | 155 | target instanceof HTMLElement && |
| 148 | (target.tagName === "INPUT" || | 156 | ((isSlashShortcut && |
| 149 | target.tagName === "TEXTAREA" || | 157 | (target.tagName === "INPUT" || |
| 150 | target.isContentEditable || | 158 | target.tagName === "TEXTAREA" || |
| 151 | // Keep focus inside open dialogs and popups instead of escaping | 159 | target.isContentEditable)) || |
| 152 | // their focus trap into the sidebar search. | 160 | // Preserve dialog and popup focus traps, and let shortcut recorders |
| 153 | target.closest('[role="dialog"], [aria-modal="true"], [data-slot$="popup"]') !== null) | 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 | return; | 166 | return; |
| 156 | } | 167 | } |