From fa4053cd593b26d2f2435ec3b603440c295c17d8 Mon Sep 17 00:00:00 2001 From: Ali Waseem Date: Thu, 23 Apr 2026 11:40:43 -0600 Subject: [PATCH] fix(studio): preserve ignoreInputs across re-renders in useShortcut (#45174) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes [FE-3060](https://linear.app/supabase/issue/FE-3060/modshiftc-modshiftm-shortcuts-suppressed-when-a-table-editor-cell-is). `Mod+Shift+C` (Copy as CSV) and `Mod+Shift+M` (Copy as Markdown) — and any other Meta/Ctrl/Escape shortcut registered via `useShortcut` without an explicit `ignoreInputs` — stopped firing when focus landed on a `react-data-grid` cell in the table editor (or any other input/contenteditable). ## Fix In `apps/studio/state/shortcuts/useShortcut.tsx`, only include `ignoreInputs` in the options object when it's actually set, so TanStack's register-time default sticks across re-renders. Updated the `ignoreInputs resolution` test to assert that the key is omitted (rather than passed as `undefined`) when neither caller nor registry set it. ## Test plan - [x] SQL editor results: `Cmd+Shift+C`, `Cmd+Shift+M`, `Cmd+Shift+J`, `Cmd+Shift+D` still fire - [x] `Mod+ArrowUp` / `Mod+ArrowDown` / `Mod+ArrowLeft` / `Mod+ArrowRight` inside a focused cell editor still blocked (registry sets `ignoreInputs: true`) - [x] Unit tests: `pnpm vitest run state/shortcuts/useShortcut.test.tsx` ## Summary by CodeRabbit * **Bug Fixes** * Fixed keyboard shortcut handling to properly respect the hotkey library's default configuration values when custom options are not explicitly specified. --- apps/studio/state/shortcuts/useShortcut.test.tsx | 5 +++-- apps/studio/state/shortcuts/useShortcut.tsx | 11 ++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/studio/state/shortcuts/useShortcut.test.tsx b/apps/studio/state/shortcuts/useShortcut.test.tsx index 101ffb00ba8..9275dd10bf8 100644 --- a/apps/studio/state/shortcuts/useShortcut.test.tsx +++ b/apps/studio/state/shortcuts/useShortcut.test.tsx @@ -118,9 +118,10 @@ describe('useShortcut', () => { }) describe('ignoreInputs resolution', () => { - it('defaults to undefined when no registry default and no caller override', () => { + it('omits the key when no registry default and no caller override (library applies its per-hotkey default)', () => { renderHook(() => useShortcut(SHORTCUT_IDS.COMMAND_MENU_OPEN, vi.fn())) - expect(getLastHotkeyOptions().ignoreInputs).toBeUndefined() + const options = getLastHotkeyOptions() + expect('ignoreInputs' in options).toBe(false) }) it('uses the registry default when no caller override', () => { diff --git a/apps/studio/state/shortcuts/useShortcut.tsx b/apps/studio/state/shortcuts/useShortcut.tsx index 8b29baaf8fe..d5aad427798 100644 --- a/apps/studio/state/shortcuts/useShortcut.tsx +++ b/apps/studio/state/shortcuts/useShortcut.tsx @@ -60,7 +60,16 @@ export function useShortcut(id: ShortcutId, callback: () => void, options?: Shor const timeout = options?.timeout ?? def.options?.timeout ?? undefined const ignoreInputs = options?.ignoreInputs ?? def.options?.ignoreInputs - useHotkeySequence(def.sequence, callback, { enabled, timeout, ignoreInputs }) + // Only include `ignoreInputs` when set. The library resolves it to a concrete + // boolean at register time (false for Meta/Ctrl/Escape, true otherwise), but + // its setOptions does an object spread on every re-render — passing + // `ignoreInputs: undefined` would overwrite the resolved value and re-enable + // the input-focus guard for shortcuts that should always fire. + useHotkeySequence(def.sequence, callback, { + enabled, + timeout, + ...(ignoreInputs !== undefined && { ignoreInputs }), + }) // Handle overrides for command menu const enabledInCommandMenu = enabled && (options?.registerInCommandMenu ?? false)