authorgravatar for 118054752+extoci@users.noreply.github.comExotic <118054752+extoci@users.noreply.github.com> 2026-10-11 08:47:53+03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-11 00:47:53-05:00
logf769e01e28c80745621a699ed5ee021cc78039b6
tree3c306f5ca84630b39529dc3a145cf7c7d6564cef
parentf0a47b3172c4a73e1932b23d37c3345481c5a67e
signature Signed by PGP key B5690EEEBB952194

fix(web): focus settings search with command-f (#17859)


2 files changed, 18 insertions(+), 30 deletions(-)

apps/web/src/components/settings/KeybindingsSettings.tsx-23
......@@ -17,7 +17,6 @@ import {
1717 type ReactNode,
1818 type RefObject,
1919 useCallback,
20 useEffect,
2120 useMemo,
2221 useReducer,
2322 useRef,
......@@ -1340,28 +1339,6 @@ export function KeybindingsSettingsPanel() {
13401339 const commandOptions = useMemo(() => buildKeybindingCommandOptions(keybindings), [keybindings]);
13411340 const whenVariables = useMemo(() => buildWhenVariableOptions(), []);
13421341
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
13651342 const openKeybindingsFile = useCallback(() => {
13661343 if (
13671344 !keybindingsConfigPath ||
apps/web/src/components/settings/SettingsSidebarNav.tsx+18-7
......@@ -140,17 +140,28 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) {
140140
141141 useEffect(() => {
142142 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;
144152
145153 const target = event.target;
146154 if (
147155 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)
154165 ) {
155166 return;
156167 }