Skip to content

feat(sheets): add keyboard shortcut parity with other spreadsheets#497

Open
the-narwhal wants to merge 4638 commits into
ProtonMail:mainfrom
the-narwhal:More-keyboard-shortcuts
Open

feat(sheets): add keyboard shortcut parity with other spreadsheets#497
the-narwhal wants to merge 4638 commits into
ProtonMail:mainfrom
the-narwhal:More-keyboard-shortcuts

Conversation

@the-narwhal
Copy link
Copy Markdown

Keyboard shortcut parity with Excel / Google Sheets

Note: The @rowsncolumns/spreadsheet and @rowsncolumns/grid vendor packages live in a vendor/ subtree that is absent from this checkout, so this could not be built or run locally. The implementation is based on a thorough reading of the surrounding application code (ui-state.ts, state.ts, ui-store.tsx, LegacyGrid.tsx) and is intended as a reference or starting point for Proton to review, test, and iterate on internally.


Problem

Several common Shift-based selection shortcuts were missing from the spreadsheet editor. The most reported example was Shift+Home having no effect, when users expect it to extend the selection to column A — consistent with both Excel and Google Sheets behaviour. Because the keyboard handling is entirely internal to the CanvasGrid from @rowsncolumns/spreadsheet, missing shortcuts cannot be added by configuring the component — they have to be intercepted externally.

Approach

A new hook, useGridKeyboardShortcuts, is registered in LegacyGrid and attaches a single keydown listener to document in the capture phase. This ensures our handler fires before the library's bubble-phase listeners. stopPropagation() is called only for the specific shortcuts this hook owns — every other keystroke passes through to the library completely unchanged.

Two guards prevent the hook from acting in the wrong context:

  1. document.activeElement must be inside the grid's container element (no effect when a menu, toolbar, or dialog has focus)
  2. The focused element must not be an <input>, <textarea>, or contenteditable — i.e. the user is not mid-edit inside a cell or the formula bar

All mutable values (activeCell, activeSheetId, rowCount, columnCount, setter functions) are read through refs that are refreshed every render, so the effect's empty dependency array never captures stale state.

Shortcuts added

Keys Behaviour
Shift + Home Extend selection to column A of the current row
Ctrl/ + Shift + Home Extend selection to A1
Shift + End Extend selection to the last column of the current row
Shift + Space Select the entire row
Ctrl + Space Select the entire column
Ctrl + Shift + Space Select all cells (anchor moves to A1)

Shift+End intentionally selects to the last allocated column (sheet edge), which matches Google Sheets behaviour — it is distinct from Ctrl+Shift+End, which is supposed to find the last cell with actual data.

Not yet implemented — and why

Ctrl+End / Ctrl+Shift+End — these are meant to navigate/extend to the last cell with data, not the last row/column of the allocated sheet. Using rowCount/columnCount directly would land on row 1000, column Z in an empty sheet, which would be actively wrong. Implementing these correctly requires knowing the signature of getDataRowCount (or an equivalent) from the vendor package.

Home / Ctrl+Home (navigation without Shift) — the library almost certainly handles these already. Overriding them in the capture phase without first confirming they are broken risks silently replacing library-provided scroll animation and visual feedback (cell flash, etc.). Worth verifying against the vendor source before adding.

Files changed

  • Spreadsheet/useGridKeyboardShortcuts.ts (new) — the hook; self-contained with inline documentation of every shortcut, the ref pattern, and the two open TODOs
  • Spreadsheet/components/legacy/LegacyGrid.tsx — one line added: useGridKeyboardShortcuts() at the top of `LegacyGrid

Raphael Rychetsky and others added 30 commits April 21, 2026 09:15
Prevent concurrent referral spotlight feature PUTs

See merge request web/clients!24282
VPNPLG-58: Add payload and telemetry when forking the session

See merge request web/clients!24135
fix: fix icon alignment in cancellation flow

See merge request web/clients!24315
Add offer card UI components

See merge request web/clients!24302
added temporary need content and config

See merge request web/clients!24307
feat: workspace premium cancelation flow

See merge request web/clients!24319
Add news subscription for Meet

See merge request web/clients!24209
use the same setting menu for extension as for web

See merge request web/clients!24241
INWEB-867: Add common setup for Slack API in the CI

See merge request web/clients!24271
Add new permissions modals and stop requesting permissions automatically

See merge request web/clients!24248
DRVWEB: Search blobs encryption/decryption

See merge request web/clients!23888
[IDTEAM-5613] Add telemetry for sharing

See merge request web/clients!24221
Limit sharing settings visibility only to admins on parent share

See merge request web/clients!24326
[DRVWEB-4974] Album listing sdk

See merge request web/clients!23946
Enable telemetry for Meet

See merge request web/clients!24331
flavienbonvin and others added 28 commits April 24, 2026 08:47
Category view focus ring and tooltip fix

See merge request web/clients!24430
Idteam 5873 fix safari too small popup

See merge request web/clients!24390
Add `justify-start-when-stacked` table helper

See merge request web/clients!24428
Prevent missing navigator.mediaDevices to throw

See merge request web/clients!24429
Update category counts when labelling a message from a conversation

See merge request web/clients!24325
Update openSubscriptionModal usage

See merge request web/clients!24107
Fix a bug storing feature flags in cookie

See merge request web/clients!24454
[DRVWEB-4967] delete legacy drive view

See merge request web/clients!24263
NOISSUE: Adapt telemetry for tv flow

See merge request web/clients!24405
Defer update prompt if user is in meeting

See merge request web/clients!24450
Mail BYOE: hide lock icons in sent messages when `X-Pm-Byoe` header is present

See merge request web/clients!24000
PostQuantumOptInModal: prompt for auth before committing the opt-in

See merge request web/clients!24457
Add LoginLink member creation mode

See merge request web/clients!24448
Several Shift-based selection shortcuts were missing entirely from the
spreadsheet editor. The most visible symptom was Shift+Home having no
effect when users expected it to extend the selection to column A.

The underlying CanvasGrid (from @rowsncolumns/spreadsheet) handles
many keys internally, but the Shift-selection family and row/column/
all-select shortcuts are absent. Because the vendor package source is
not directly modifiable here, we intercept these specific key
combinations at the document level in the capture phase, which fires
before the library's bubble-phase listeners. stopPropagation() is
called only for the shortcuts this hook owns, so every other keystroke
passes through to the library unchanged.

Shortcuts added
---------------
  Shift+Home            Extend selection to column A of the current row
  Ctrl/⌘+Shift+Home     Extend selection to A1
  Shift+End             Extend selection to the last column of the row
                        (matches Google Sheets: selects to sheet edge,
                        not just the last cell with data)
  Shift+Space           Select the entire row
  Ctrl+Space            Select the entire column
  Ctrl+Shift+Space      Select all cells (anchor moves to A1)

Implementation notes
--------------------
- All mutable values (activeCell, activeSheetId, rowCount, columnCount,
  setters) are read through refs that are refreshed on every render, so
  the single useEffect with an empty dependency array never captures
  stale state.
- Two guards prevent the hook from stealing keystrokes in the wrong
  context: (1) document.activeElement must be inside the grid container,
  and (2) the focused element must not be an input, textarea, or
  contenteditable (i.e. the user is not mid-edit in a cell).
- Alt/Option combinations are passed through untouched to avoid
  conflicting with OS-level shortcuts.
- Shortcut branches are ordered most-specific-first (Ctrl+Shift before
  Shift, etc.) so modifier checks are mutually exclusive.

Not yet implemented
-------------------
- Ctrl+End / Ctrl+Shift+End: these need the actual last row/column with
  data, not the allocated sheet dimensions (rowCount/columnCount). Using
  the latter would land on row 1000/col Z in an empty sheet. Blocked on
  knowing the getDataRowCount signature from the vendor package.
- Home / Ctrl+Home (navigation without Shift): the library likely
  handles these already. Adding capture-phase overrides without first
  confirming they are broken risks replacing library scroll animation
  and cell-flash feedback for no gain.
@the-narwhal the-narwhal marked this pull request as ready for review April 26, 2026 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.