diff --git a/base/src/user_model/ui.rs b/base/src/user_model/ui.rs index bfb3509..1b9238c 100644 --- a/base/src/user_model/ui.rs +++ b/base/src/user_model/ui.rs @@ -2,7 +2,11 @@ use serde::{Deserialize, Serialize}; -use crate::expressions::utils::{is_valid_column_number, is_valid_row}; +use crate::{ + constants::LAST_ROW, + expressions::utils::{is_valid_column_number, is_valid_row}, + worksheet::NavigationDirection, +}; use super::common::UserModel; @@ -472,7 +476,7 @@ impl UserModel { // if the row is not fully visible we 'scroll' down until it is let mut height = 0.0; let mut row = view.top_row; - while row <= new_row + 1 { + while row <= new_row + 1 && row <= LAST_ROW { height += self.model.get_row_height(sheet, row)?; row += 1; } @@ -682,4 +686,94 @@ impl UserModel { Ok(()) } + + /// User navigates to the edge in the given direction + pub fn on_navigate_to_edge_in_direction( + &mut self, + direction: NavigationDirection, + ) -> Result<(), String> { + let (sheet, window_height, window_width) = + if let Some(view) = self.model.workbook.views.get(&self.model.view_id) { + (view.sheet, view.window_height, view.window_width) + } else { + return Err("View not found".to_string()); + }; + let worksheet = match self.model.workbook.worksheet(sheet) { + Ok(s) => s, + Err(_) => return Err("Worksheet not found".to_string()), + }; + let view = match worksheet.views.get(&self.model.view_id) { + Some(s) => s, + None => return Err("View not found".to_string()), + }; + let row = view.row; + let column = view.column; + if !is_valid_row(row) || !is_valid_column_number(column) { + return Err("Invalid row or column".to_string()); + } + let (new_row, new_column) = + worksheet.navigate_to_edge_in_direction(row, column, direction)?; + if !is_valid_row(new_row) || !is_valid_column_number(new_column) { + return Err("Invalid row or column after navigation".to_string()); + } + if new_row == row && new_column == column { + return Ok(()); // No change in selection + } + + let mut top_row = view.top_row; + let mut left_column = view.left_column; + + match direction { + NavigationDirection::Left | NavigationDirection::Right => { + // If the new column is not fully visible we 'scroll' until it is + // We need to check two conditions: + // 1. new_column > view.left_column + // 2. right_column < new_column + if new_column < view.left_column { + left_column = new_column; + } else { + let mut c = new_column; + let mut width = self.model.get_column_width(sheet, c)?; + while c > 1 && width <= window_width as f64 { + c -= 1; + width += self.model.get_column_width(sheet, c)?; + } + if c > view.left_column { + left_column = c; + } + } + } + NavigationDirection::Up | NavigationDirection::Down => { + // If the new row is not fully visible we 'scroll' until it is + // We need to check two conditions: + // 1. new_row > view.top_row + // 2. bottom_row < new_row + if new_row < view.top_row { + top_row = new_row; + } else { + let mut r = new_row; + let mut height = self.model.get_row_height(sheet, r)?; + while r > 1 && height <= window_height as f64 { + r -= 1; + height += self.model.get_row_height(sheet, r)?; + } + if r > view.top_row { + top_row = r; + } + } + } + } + + if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) { + if let Some(view) = worksheet.views.get_mut(&self.model.view_id) { + view.row = new_row; + view.column = new_column; + view.range = [new_row, new_column, new_row, new_column]; + + view.top_row = top_row; + view.left_column = left_column; + } + } + Ok(()) + } } diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 0c0b6cb..c8b3b83 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -7,6 +7,7 @@ use wasm_bindgen::{ use ironcalc_base::{ expressions::{lexer::util::get_tokens as tokenizer, types::Area, utils::number_to_column}, types::{CellType, Style}, + worksheet::NavigationDirection, BorderArea, ClipboardData, UserModel as BaseModel, }; @@ -530,6 +531,20 @@ impl Model { self.model.on_page_up().map_err(to_js_error) } + #[wasm_bindgen(js_name = "onNavigateToEdgeInDirection")] + pub fn on_navigate_to_edge_in_direction(&mut self, direction: &str) -> Result<(), JsError> { + let direction = match direction { + "ArrowLeft" => NavigationDirection::Left, + "ArrowRight" => NavigationDirection::Right, + "ArrowUp" => NavigationDirection::Up, + "ArrowDown" => NavigationDirection::Down, + _ => return Err(JsError::new(&format!("Invalid direction: {direction}"))), + }; + self.model + .on_navigate_to_edge_in_direction(direction) + .map_err(to_js_error) + } + #[wasm_bindgen(js_name = "setWindowWidth")] pub fn set_window_width(&mut self, window_width: f64) { self.model.set_window_width(window_width); diff --git a/webapp/IronCalc/src/components/Workbook/Workbook.tsx b/webapp/IronCalc/src/components/Workbook/Workbook.tsx index 70db123..24d0212 100644 --- a/webapp/IronCalc/src/components/Workbook/Workbook.tsx +++ b/webapp/IronCalc/src/components/Workbook/Workbook.tsx @@ -249,8 +249,8 @@ const Workbook = (props: { model: Model; workbookState: WorkbookState }) => { onToggleUnderline(!value); }, onNavigationToEdge: (direction: NavigationKey): void => { - console.log(direction); - throw new Error("Function not implemented."); + model.onNavigateToEdgeInDirection(direction); + setRedrawId((id) => id + 1); }, onPageDown: (): void => { model.onPageDown(); diff --git a/webapp/IronCalc/src/components/Workbook/useKeyboardNavigation.ts b/webapp/IronCalc/src/components/Workbook/useKeyboardNavigation.ts index b03d3e4..dd8cbde 100644 --- a/webapp/IronCalc/src/components/Workbook/useKeyboardNavigation.ts +++ b/webapp/IronCalc/src/components/Workbook/useKeyboardNavigation.ts @@ -74,7 +74,11 @@ const useKeyboardNavigation = ( if (event.metaKey || event.ctrlKey) { switch (key) { case "z": { - options.onUndo(); + if (event.shiftKey) { + options.onRedo(); + } else { + options.onUndo(); + } event.stopPropagation(); event.preventDefault();