UPDATE: Add keyboard shortcuts for move column/row
Also clean up a bit of the keyboard shortcuts mess
This commit is contained in:
committed by
Nicolás Hatcher Andrés
parent
caf26194df
commit
fb7886ca9e
@@ -607,7 +607,7 @@ impl Model {
|
|||||||
.set_cell_style(target_row, c, style_idx)?;
|
.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();
|
let mut new_rows = Vec::new();
|
||||||
for r in worksheet.rows.iter() {
|
for r in worksheet.rows.iter() {
|
||||||
if r.r == row {
|
if r.r == row {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::LAST_ROW,
|
constants::{LAST_COLUMN, LAST_ROW},
|
||||||
expressions::utils::{is_valid_column_number, is_valid_row},
|
expressions::utils::{is_valid_column_number, is_valid_row},
|
||||||
worksheet::NavigationDirection,
|
worksheet::NavigationDirection,
|
||||||
};
|
};
|
||||||
@@ -114,7 +114,7 @@ impl UserModel {
|
|||||||
Ok(())
|
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(
|
pub fn set_selected_range(
|
||||||
&mut self,
|
&mut self,
|
||||||
start_row: i32,
|
start_row: i32,
|
||||||
@@ -148,16 +148,32 @@ impl UserModel {
|
|||||||
if let Some(view) = worksheet.views.get_mut(&0) {
|
if let Some(view) = worksheet.views.get_mut(&0) {
|
||||||
let selected_row = view.row;
|
let selected_row = view.row;
|
||||||
let selected_column = view.column;
|
let selected_column = view.column;
|
||||||
// The selected cells must be on one of the corners of the selected range:
|
if start_row == 1 && end_row == LAST_ROW {
|
||||||
if selected_row != start_row && selected_row != end_row {
|
// full row selected. The cell must be at the top or the bottom of the range
|
||||||
return Err(format!(
|
if selected_column != start_column && selected_column != end_column {
|
||||||
"The selected cells is not in one of the corners. Row: '{selected_row}' and row range '({start_row}, {end_row})'"
|
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 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 {
|
if selected_column != start_column && selected_column != end_column {
|
||||||
return Err(format!(
|
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];
|
view.range = [start_row, start_column, end_row, end_column];
|
||||||
}
|
}
|
||||||
@@ -194,6 +210,15 @@ impl UserModel {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
let [row_start, column_start, row_end, column_end] = range;
|
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 {
|
match key {
|
||||||
"ArrowRight" => {
|
"ArrowRight" => {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import Worksheet from "../Worksheet/Worksheet";
|
|||||||
import {
|
import {
|
||||||
COLUMN_WIDTH_SCALE,
|
COLUMN_WIDTH_SCALE,
|
||||||
LAST_COLUMN,
|
LAST_COLUMN,
|
||||||
|
LAST_ROW,
|
||||||
ROW_HEIGH_SCALE,
|
ROW_HEIGH_SCALE,
|
||||||
} from "../WorksheetCanvas/constants";
|
} from "../WorksheetCanvas/constants";
|
||||||
import type WorksheetCanvas from "../WorksheetCanvas/worksheetCanvas";
|
import type WorksheetCanvas from "../WorksheetCanvas/worksheetCanvas";
|
||||||
@@ -318,6 +319,16 @@ const Workbook = (props: { model: Model; workbookState: WorkbookState }) => {
|
|||||||
workbookState.clearCutRange();
|
workbookState.clearCutRange();
|
||||||
setRedrawId((id) => id + 1);
|
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,
|
root: rootRef,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ interface Options {
|
|||||||
onNextSheet: () => void;
|
onNextSheet: () => void;
|
||||||
onPreviousSheet: () => void;
|
onPreviousSheet: () => void;
|
||||||
onEscape: () => void;
|
onEscape: () => void;
|
||||||
|
onSelectColumn: () => void;
|
||||||
|
onSelectRow: () => void;
|
||||||
root: RefObject<HTMLDivElement | null>;
|
root: RefObject<HTMLDivElement | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +53,16 @@ interface Options {
|
|||||||
// * Ctrl+u/i/b: style
|
// * Ctrl+u/i/b: style
|
||||||
// * Ctrl+z/y: undo/redo
|
// * Ctrl+z/y: undo/redo
|
||||||
// * F2: start editing
|
// * 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:
|
// References:
|
||||||
// In Google Sheets: Ctrl+/ shows the list of keyboard shortcuts
|
// In Google Sheets: Ctrl+/ shows the list of keyboard shortcuts
|
||||||
@@ -63,6 +75,7 @@ const useKeyboardNavigation = (
|
|||||||
const onKeyDown = useCallback(
|
const onKeyDown = useCallback(
|
||||||
(event: KeyboardEvent) => {
|
(event: KeyboardEvent) => {
|
||||||
const { key } = event;
|
const { key } = event;
|
||||||
|
const lowerKey = key.toLowerCase();
|
||||||
const { root } = options;
|
const { root } = options;
|
||||||
// Silence the linter
|
// Silence the linter
|
||||||
if (!root.current) {
|
if (!root.current) {
|
||||||
@@ -71,45 +84,40 @@ const useKeyboardNavigation = (
|
|||||||
if (event.target !== root.current) {
|
if (event.target !== root.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.metaKey || event.ctrlKey) {
|
const isCtrl = event.metaKey || event.ctrlKey;
|
||||||
switch (key) {
|
const isShift = event.shiftKey;
|
||||||
|
const isAlt = event.altKey;
|
||||||
|
if (isCtrl && !isShift && !isAlt) {
|
||||||
|
// Ctrl+...
|
||||||
|
switch (lowerKey) {
|
||||||
case "z": {
|
case "z": {
|
||||||
if (event.shiftKey) {
|
options.onUndo();
|
||||||
options.onRedo();
|
|
||||||
} else {
|
|
||||||
options.onUndo();
|
|
||||||
}
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "y": {
|
case "y": {
|
||||||
options.onRedo();
|
options.onRedo();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "b": {
|
case "b": {
|
||||||
options.onBold();
|
options.onBold();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "i": {
|
case "i": {
|
||||||
options.onItalic();
|
options.onItalic();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "u": {
|
case "u": {
|
||||||
options.onUnderline();
|
options.onUnderline();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "a": {
|
case "a": {
|
||||||
@@ -119,18 +127,61 @@ const useKeyboardNavigation = (
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case " ": {
|
||||||
|
options.onSelectColumn();
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
}
|
||||||
// No default
|
// No default
|
||||||
}
|
}
|
||||||
if (isNavigationKey(key)) {
|
if (isNavigationKey(key)) {
|
||||||
// Ctrl+Arrows, Ctrl+Home/End
|
// Ctrl+Arrows, Ctrl+Home/End
|
||||||
options.onNavigationToEdge(key);
|
options.onNavigationToEdge(key);
|
||||||
// navigate_to_edge_in_direction
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
return;
|
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) {
|
switch (key) {
|
||||||
case "ArrowDown": {
|
case "ArrowDown": {
|
||||||
// select next sheet
|
// select next sheet
|
||||||
@@ -147,6 +198,12 @@ const useKeyboardNavigation = (
|
|||||||
break;
|
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") {
|
if (key === "F2") {
|
||||||
options.onCellEditStart();
|
options.onCellEditStart();
|
||||||
@@ -162,67 +219,43 @@ const useKeyboardNavigation = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Worksheet Navigation
|
// 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) {
|
switch (key) {
|
||||||
case "ArrowRight":
|
case "ArrowRight":
|
||||||
case "Tab": {
|
case "Tab": {
|
||||||
options.onArrowRight();
|
options.onArrowRight();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "ArrowLeft": {
|
case "ArrowLeft": {
|
||||||
options.onArrowLeft();
|
options.onArrowLeft();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "ArrowDown":
|
case "ArrowDown":
|
||||||
case "Enter": {
|
case "Enter": {
|
||||||
options.onArrowDown();
|
options.onArrowDown();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "ArrowUp": {
|
case "ArrowUp": {
|
||||||
options.onArrowUp();
|
options.onArrowUp();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "End": {
|
case "End": {
|
||||||
options.onKeyEnd();
|
options.onKeyEnd();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "Home": {
|
case "Home": {
|
||||||
options.onKeyHome();
|
options.onKeyHome();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "Delete": {
|
case "Delete": {
|
||||||
options.onCellsDeleted();
|
options.onCellsDeleted();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "PageDown": {
|
case "PageDown": {
|
||||||
options.onPageDown();
|
options.onPageDown();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "PageUp": {
|
case "PageUp": {
|
||||||
options.onPageUp();
|
options.onPageUp();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "Escape": {
|
case "Escape": {
|
||||||
|
|||||||
@@ -118,7 +118,13 @@
|
|||||||
"delete_column": "Delete column '{{column}}'",
|
"delete_column": "Delete column '{{column}}'",
|
||||||
"freeze": "Freeze",
|
"freeze": "Freeze",
|
||||||
"insert_row": "Insert row",
|
"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": {
|
"color_picker": {
|
||||||
"apply": "Add color",
|
"apply": "Add color",
|
||||||
|
|||||||
Reference in New Issue
Block a user