mirror of
https://github.com/supabase/supabase.git
synced 2026-05-19 19:37:22 +08:00
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Feature — a set of new keyboard shortcuts for the table editor, along with infrastructure to register, gate, and surface them. ## What is the current behavior? Clicking into the grid "traps" the keyboard: Escape doesn't pop out, there are no shortcuts for row selection / deletion / navigation, and the search-tables input grabs focus on page load. ## What is the new behavior? ### New shortcuts (all scoped to the table editor) | Keybind | Action | Surface | |---|---|---| | `Esc` | Exit grid selection — clears the highlighted cell and drops focus back to the page | hotkey | | `↑` / `↓` | Start grid navigation from the first cell when no cell is selected | hotkey | | `Shift+Space` | Toggle selection on the current row | hotkey + checkbox tooltip | | `Mod+A` | Toggle selection on all displayed rows (matches Excel) | hotkey + header-checkbox tooltip + Cmd+K | | `Mod+Shift+A` | Toggle selection on all rows in the table | hotkey + "Select all rows in table" button tooltip + Cmd+K | | `Mod+Backspace` | Delete selected rows | hotkey + delete-button tooltip + Cmd+K | ### Infrastructure - **Split registry** — table-editor shortcuts moved to `state/shortcuts/registry/table-editor.ts`, spread into `SHORTCUT_IDS`. Makes it easy to scope a runtime check to a specific surface. - **`eventMatchesAnyShortcut`** (`state/shortcuts/matchEvent.ts`) — queries the hotkey library's live `SequenceManager` so gated shortcuts (`enabled: false`) are correctly excluded. Covered by `matchEvent.test.ts`. - **`handleCellKeyDown`** now calls `event.preventGridDefault()` whenever the keystroke matches an active table-editor shortcut, so rdg's "start editing on key press" default doesn't compete with shortcut actions (e.g. typing `Shift+X` no longer opens edit mode with `X` as input). - **`<Shortcut>` / `<ShortcutTooltip>`** used on the header checkbox, the per-row checkbox, the "Select all rows in table" button, and the delete button — keybinds show up on hover (Linear-style) so users can discover them without reading docs. - **CSS** — `.rdg:not(:focus-within) .rdg-cell[aria-selected='true']` drops the selected-cell outline whenever focus leaves the grid, reinforcing the "you're out" feedback after `Esc`. - **`useShortcut`** wraps the Cmd+K-registered action to close the command menu after firing (previously menu stayed open after selecting an action). - **Search-tables input** no longer auto-focuses on load, so arrow shortcuts work immediately without clicking out first. ## Additional context Linear: FE-3057 ### Test plan - [x] Open any table → `↓` selects the first cell; subsequent arrows navigate rows - [x] `Esc` drops focus out of the grid and re-enables `↓` to re-enter - [x] Click a cell → `Shift+Space` toggles that row's selection (checkbox) - [x] `Mod+A` toggles all displayed rows - [x] With pagination + some rows selected → `Mod+Shift+A` toggles "Select all rows in table" - [x] With rows selected → `Mod+Backspace` deletes them (existing confirmation flow) - [x] Hover the header checkbox / per-row checkbox / delete button → keybind tooltip after ~500ms - [x] Cmd+K with selection → the relevant action shows up; selecting it closes the palette and runs <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added table editor keyboard shortcuts for navigation, row selection, and cell actions, with command-menu integration and visible shortcut tooltips. * **Improvements** * Better keyboard handling in grid cells allowing external shortcuts to override default behavior. * Select-all/deselect-all toggle and improved select-row UX; selected-cell styling no longer shows when grid loses focus. * Command menu now reliably closes before executing shortcut actions. * Removed autofocus on the table editor search input for consistent focus behavior. * **Tests** * Added unit tests covering shortcut matching and command-menu shortcut behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
39 lines
1.5 KiB
TypeScript
39 lines
1.5 KiB
TypeScript
import { getSequenceManager, matchesKeyboardEvent } from '@tanstack/react-hotkeys'
|
|
|
|
import { SHORTCUT_DEFINITIONS } from './registry'
|
|
import type { RegistryDefinations } from './types'
|
|
|
|
/**
|
|
* Returns true if the given keyboard event matches a shortcut that is both:
|
|
*
|
|
* 1. **In the target registry** (defaults to every known shortcut, but callers
|
|
* can pass a subset like `tableEditorRegistry` to scope the check)
|
|
* 2. **Currently active and enabled** — i.e. a `useShortcut` is mounted for it
|
|
* AND its `enabled` option is not `false`
|
|
*
|
|
* Chord sequences (e.g. `['G', 'T']`) match on any individual step, so
|
|
* pressing `G` counts as a match while the chord is in flight.
|
|
*
|
|
* Respecting the live `enabled` state matters: if a shortcut is registered but
|
|
* gated off (e.g. `enabled: !!snap.selectedCellPosition`), we must NOT suppress
|
|
* the default behavior on its behalf, because the shortcut won't actually fire.
|
|
*/
|
|
|
|
export function eventMatchesAnyShortcut(
|
|
event: KeyboardEvent,
|
|
registry: RegistryDefinations<string> = SHORTCUT_DEFINITIONS
|
|
): boolean {
|
|
const scopedSteps = new Set(Object.values(registry).flatMap((def) => def.sequence))
|
|
const activeRegistrations = getSequenceManager().registrations.state.values()
|
|
|
|
for (const view of activeRegistrations) {
|
|
if (view.options.enabled === false) continue
|
|
const matches = view.sequence.some(
|
|
(step) => scopedSteps.has(step) && matchesKeyboardEvent(event, step)
|
|
)
|
|
if (matches) return true
|
|
}
|
|
|
|
return false
|
|
}
|