UPDATE: Add keyboard shortcuts for move column/row

Also clean up a bit of the  keyboard shortcuts mess
This commit is contained in:
Nicolás Hatcher
2025-07-24 19:40:15 +02:00
committed by Nicolás Hatcher Andrés
parent caf26194df
commit fb7886ca9e
5 changed files with 125 additions and 50 deletions

View File

@@ -607,7 +607,7 @@ impl Model {
.set_cell_style(target_row, c, style_idx)?;
}
let worksheet = &mut self.workbook.worksheets[sheet as usize];
let worksheet = &mut self.workbook.worksheet_mut(sheet)?;
let mut new_rows = Vec::new();
for r in worksheet.rows.iter() {
if r.r == row {

View File

@@ -3,7 +3,7 @@
use serde::{Deserialize, Serialize};
use crate::{
constants::LAST_ROW,
constants::{LAST_COLUMN, LAST_ROW},
expressions::utils::{is_valid_column_number, is_valid_row},
worksheet::NavigationDirection,
};
@@ -114,7 +114,7 @@ impl UserModel {
Ok(())
}
/// Sets the selected range. Note that the selected cell must be in one of the corners.
/// Sets the selected range. Note that the selected cell must be in the selected range.
pub fn set_selected_range(
&mut self,
start_row: i32,
@@ -148,17 +148,33 @@ impl UserModel {
if let Some(view) = worksheet.views.get_mut(&0) {
let selected_row = view.row;
let selected_column = view.column;
if start_row == 1 && end_row == LAST_ROW {
// full row selected. The cell must be at the top or the bottom of the range
if selected_column != start_column && selected_column != end_column {
return Err(format!(
"The selected cell is not the column edge. Column '{selected_column}' and column range '({start_column}, {end_column})'"
));
}
} else if start_column == 1 && end_column == LAST_COLUMN {
// full column selected. The cell must be at the left or the right of the range
if selected_row != start_row && selected_row != end_row {
return Err(format!(
"The selected cell is not in the row edge. Row: '{selected_row}' and row range '({start_row}, {end_row})'"
));
}
} else {
// The selected cells must be on one of the corners of the selected range:
if selected_row != start_row && selected_row != end_row {
return Err(format!(
"The selected cells is not in one of the corners. Row: '{selected_row}' and row range '({start_row}, {end_row})'"
"The selected cell is not in one of the corners. Row: '{selected_row}' and row range '({start_row}, {end_row})'"
));
}
if selected_column != start_column && selected_column != end_column {
return Err(format!(
"The selected cells is not in one of the corners. Column '{selected_column}' and column range '({start_column}, {end_column})'"
"The selected cell is not in one of the corners. Column '{selected_column}' and column range '({start_column}, {end_column})'"
));
}
}
view.range = [start_row, start_column, end_row, end_column];
}
}
@@ -194,6 +210,15 @@ impl UserModel {
return Ok(());
};
let [row_start, column_start, row_end, column_end] = range;
if ["ArrowUp", "ArrowDown"].contains(&key) && row_start == 1 && row_end == LAST_ROW {
// full column selected, nothing to do
return Ok(());
}
if ["ArrowRight", "ArrowLeft"].contains(&key) && column_start == 1 && column_end == LAST_COLUMN
{
// full row selected, nothing to do
return Ok(());
}
match key {
"ArrowRight" => {

View File

@@ -13,6 +13,7 @@ import Worksheet from "../Worksheet/Worksheet";
import {
COLUMN_WIDTH_SCALE,
LAST_COLUMN,
LAST_ROW,
ROW_HEIGH_SCALE,
} from "../WorksheetCanvas/constants";
import type WorksheetCanvas from "../WorksheetCanvas/worksheetCanvas";
@@ -318,6 +319,16 @@ const Workbook = (props: { model: Model; workbookState: WorkbookState }) => {
workbookState.clearCutRange();
setRedrawId((id) => id + 1);
},
onSelectColumn: (): void => {
const { column } = model.getSelectedView();
model.setSelectedRange(1, column, LAST_ROW, column);
setRedrawId((id) => id + 1);
},
onSelectRow: (): void => {
const { row } = model.getSelectedView();
model.setSelectedRange(row, 1, row, LAST_COLUMN);
setRedrawId((id) => id + 1);
},
root: rootRef,
});

View File

@@ -32,6 +32,8 @@ interface Options {
onNextSheet: () => void;
onPreviousSheet: () => void;
onEscape: () => void;
onSelectColumn: () => void;
onSelectRow: () => void;
root: RefObject<HTMLDivElement | null>;
}
@@ -51,6 +53,16 @@ interface Options {
// * Ctrl+u/i/b: style
// * Ctrl+z/y: undo/redo
// * F2: start editing
// * Ctrl+Space: select column
// * Shift+Space: select row
//
// # Not implemented yet:
// * Ctrl+a: select all (continuous area around the selection, if it exists,
// otherwise select whole sheet)
// * Ctrl+Shift+Arrows: select to edge
// * Ctrl+Shift+Home/End: select to end
// * Ctrl+Shift++: (after selecting) insert row/column (also Alt+I, R or C)
// * Ctrl+-: (after selecting) delete row/column
// References:
// In Google Sheets: Ctrl+/ shows the list of keyboard shortcuts
@@ -63,6 +75,7 @@ const useKeyboardNavigation = (
const onKeyDown = useCallback(
(event: KeyboardEvent) => {
const { key } = event;
const lowerKey = key.toLowerCase();
const { root } = options;
// Silence the linter
if (!root.current) {
@@ -71,45 +84,40 @@ const useKeyboardNavigation = (
if (event.target !== root.current) {
return;
}
if (event.metaKey || event.ctrlKey) {
switch (key) {
const isCtrl = event.metaKey || event.ctrlKey;
const isShift = event.shiftKey;
const isAlt = event.altKey;
if (isCtrl && !isShift && !isAlt) {
// Ctrl+...
switch (lowerKey) {
case "z": {
if (event.shiftKey) {
options.onRedo();
} else {
options.onUndo();
}
event.stopPropagation();
event.preventDefault();
break;
}
case "y": {
options.onRedo();
event.stopPropagation();
event.preventDefault();
break;
}
case "b": {
options.onBold();
event.stopPropagation();
event.preventDefault();
break;
}
case "i": {
options.onItalic();
event.stopPropagation();
event.preventDefault();
break;
}
case "u": {
options.onUnderline();
event.stopPropagation();
event.preventDefault();
break;
}
case "a": {
@@ -119,18 +127,61 @@ const useKeyboardNavigation = (
event.preventDefault();
break;
}
case " ": {
options.onSelectColumn();
event.stopPropagation();
event.preventDefault();
break;
}
// No default
}
if (isNavigationKey(key)) {
// Ctrl+Arrows, Ctrl+Home/End
options.onNavigationToEdge(key);
// navigate_to_edge_in_direction
event.stopPropagation();
event.preventDefault();
}
return;
}
if (event.altKey) {
if (isCtrl && isShift && !isAlt) {
// Ctrl+Shift+...
switch (lowerKey) {
case "z": {
options.onRedo();
event.stopPropagation();
event.preventDefault();
break;
}
}
return;
}
if (isShift && !isAlt && !isCtrl) {
// Shift+...
switch (key) {
case " ": {
options.onSelectRow();
event.stopPropagation();
event.preventDefault();
break;
}
case "ArrowRight":
case "ArrowLeft":
case "ArrowUp":
case "ArrowDown": {
options.onExpandAreaSelectedKeyboard(key);
break;
}
case "Tab": {
options.onArrowLeft();
event.stopPropagation();
event.preventDefault();
break;
}
}
return;
}
if (isAlt && !isCtrl && !isShift) {
// Alt+...
switch (key) {
case "ArrowDown": {
// select next sheet
@@ -147,6 +198,12 @@ const useKeyboardNavigation = (
break;
}
}
return;
}
// At this point we know that no modifier keys are pressed
if (isCtrl || isShift || isAlt) {
// If any modifier key is pressed, we do not handle the key
return;
}
if (key === "F2") {
options.onCellEditStart();
@@ -162,67 +219,43 @@ const useKeyboardNavigation = (
return;
}
// Worksheet Navigation
if (event.shiftKey) {
if (
key === "ArrowRight" ||
key === "ArrowLeft" ||
key === "ArrowUp" ||
key === "ArrowDown"
) {
options.onExpandAreaSelectedKeyboard(key);
} else if (key === "Tab") {
options.onArrowLeft();
event.stopPropagation();
event.preventDefault();
}
return;
}
switch (key) {
case "ArrowRight":
case "Tab": {
options.onArrowRight();
break;
}
case "ArrowLeft": {
options.onArrowLeft();
break;
}
case "ArrowDown":
case "Enter": {
options.onArrowDown();
break;
}
case "ArrowUp": {
options.onArrowUp();
break;
}
case "End": {
options.onKeyEnd();
break;
}
case "Home": {
options.onKeyHome();
break;
}
case "Delete": {
options.onCellsDeleted();
break;
}
case "PageDown": {
options.onPageDown();
break;
}
case "PageUp": {
options.onPageUp();
break;
}
case "Escape": {

View File

@@ -118,7 +118,13 @@
"delete_column": "Delete column '{{column}}'",
"freeze": "Freeze",
"insert_row": "Insert row",
"insert_column": "Insert column"
"insert_column": "Insert column",
"move_row": "Move row",
"move_column": "Move column",
"move_row_up": "Move row up",
"move_row_down": "Move row down",
"move_column_left": "Move column left",
"move_column_right": "Move column right"
},
"color_picker": {
"apply": "Add color",