Sheets: add "Data has header row" toggle to preserve headers during sort#495
Open
the-narwhal wants to merge 1 commit intoProtonMail:mainfrom
Open
Sheets: add "Data has header row" toggle to preserve headers during sort#495the-narwhal wants to merge 1 commit intoProtonMail:mainfrom
the-narwhal wants to merge 1 commit intoProtonMail:mainfrom
Conversation
Sorting a column via "Sort A to Z" / "Sort Z to A" was sorting the entire sheet including row 1, destroying column header labels. This adds an opt-in toggle under Data → Sort that, when enabled, excludes row 1 from all sort operations. Changes ------- ui-state.ts - Add `hasHeaderRow` boolean state (default false — existing behaviour unchanged unless the user explicitly enables the toggle) - Add `toggleHeaderRow` event setter (stable ref via `useEvent`) - Branch `sortAscending` / `sortDescending`: when `hasHeaderRow` is true, call `onSortRange` with a range starting at row 2 instead of calling `onSortColumn` for the full column - Expose `hasHeaderRow` and `toggleHeaderRow` on the `data` slice of the UI state so all sort entry points share a single source of truth sort-utils.ts (new) - Extract `makeSortRangeExcludingHeader(rowCount, columnCount)` as a pure function so the range-building logic can be unit-tested without importing @rowsncolumns/* packages sort-utils.test.ts (new) - 8 unit tests covering typical sheets, single-column sheets, header-only sheets (edge: empty range), and large sheets DataMenu.tsx - Add "Data has header row" menu item inside the Sort submenu with a checkmark indicator and proper i18n context string ContextMenu.tsx / LegacyContextMenu.tsx - Extract stable `sortAscending` / `sortDescending` refs at component top level (fixes implicit Rules-of-Hooks violation from inline hooks inside JSX event props) - Route "Sort A to Z" / "Sort Z to A" buttons through `useUI.$.data.sortAscending` / `sortDescending` so the header-row toggle is respected in both new and legacy UI modes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Due to the constraints of working outside the full Proton development environment, I was not able to build or run the application locally to verify this end-to-end. The implementation follows all established codebase patterns and passes TypeScript diagnostics, but it has not gone through a full build pipeline or staging environment test. This is intended as a reference implementation Proton is welcome to merge, incorporate, or iterate on it as needed.
Summary
Adds a "Data has header row" toggle to the Sort submenu (Data → Sort) in Proton Sheets. When enabled, row 1 is excluded from sort operations so column headers stay in place. When disabled (the default), sort behavior is identical to before, no regression for existing users.
This addresses a community feature request where using "Sort A to Z" or "Sort Z to A" on a column would sort the entire sheet including row 1, destroying header labels.
What changed
ui-state.tscore logichasHeaderRow: booleanstate (defaultfalse, so existing sort behaviour is completely unchanged unless the user explicitly enables the toggle)toggleHeaderRowa stable event ref viauseEvent()that flips the flagsortAscendingandsortDescending: whenhasHeaderRowistrue, callsonSortRangewith a range starting at row 2 instead ofonSortColumnon the full column; whenfalse, falls through to the original callhasHeaderRowandtoggleHeaderRowon thedataslice of the UI state so every sort entry point (menu bar, context menu, legacy context menu) shares a single source of truth without any duplicated logicsort-utils.ts(new file)Extracts
makeSortRangeExcludingHeader(rowCount, columnCount)as a pure function with no@rowsncolumns/*dependencies. This keeps the range-building logic independently testable and documents the intentional edge case (a sheet with only one row produces an empty range callers treat it as a no-op).sort-utils.test.ts(new file)8 unit tests covering:
startRowIndexis always 2 regardless of sheet sizestartColumnIndexis always 1endRowIndex/endColumnIndexreflect the provided dimensionsThese follow the existing test pattern in the codebase (pure utility functions only, no component harness needed).
DataMenu.tsxnew UI toggleselectedIndicator={hasHeaderRow}for the checkmark, matching the pattern used in the View menuleadingIndentkeeps it visually nested under the sort actionssheets_2025:Spreadsheet editor menubar data menu (sort submenu)contextContextMenu.tsxandLegacyContextMenu.tsxrouting + hooks fixsortAscendingandsortDescendingrefs at component top level (same pattern already used forinsertLinkin both files)onClickhandlers were inline arrow functions callinguseUI.$inside JSX, which is not valid under React's hooks rulesuseUI.$.data.sortAscending/sortDescending, so thehasHeaderRowcheck inui-state.tsis respected from every sort trigger in both the new and legacy UIBehaviour
onSortRangewithstartRowIndex: 2, keeping row 1 fixedThe toggle is session-local (resets on page reload). Persisting it to user preferences would be a natural follow-up.
What has and hasn't been tested
makeSortRangeExcludingHeaderunit tests (8 cases)node_modulesavailableSuggested manual verification steps