fix(studio): preserve ignoreInputs across re-renders in useShortcut (#45174)

## 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`

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## 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.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Ali Waseem
2026-04-23 11:40:43 -06:00
committed by GitHub
parent 27afe9bbb4
commit fa4053cd59
2 changed files with 13 additions and 3 deletions
@@ -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', () => {
+10 -1
View File
@@ -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)