UPDATE: Add rows/column style APIs

This commit is contained in:
Nicolás Hatcher
2024-12-18 17:22:49 +01:00
committed by Nicolás Hatcher Andrés
parent 7e54cb6aa2
commit 23ab5dfef2
20 changed files with 909 additions and 141 deletions

View File

@@ -1872,12 +1872,29 @@ impl Model {
} }
/// Returns the style for cell (`sheet`, `row`, `column`) /// Returns the style for cell (`sheet`, `row`, `column`)
/// If the cell does not have a style defined we check the row, otherwise the column and finally a default
pub fn get_style_for_cell(&self, sheet: u32, row: i32, column: i32) -> Result<Style, String> { pub fn get_style_for_cell(&self, sheet: u32, row: i32, column: i32) -> Result<Style, String> {
let style_index = self.get_cell_style_index(sheet, row, column)?; let style_index = self.get_cell_style_index(sheet, row, column)?;
let style = self.workbook.styles.get_style(style_index)?; let style = self.workbook.styles.get_style(style_index)?;
Ok(style) Ok(style)
} }
/// Returns the style defined in a cell if any.
pub fn get_cell_style_or_none(
&self,
sheet: u32,
row: i32,
column: i32,
) -> Result<Option<Style>, String> {
let style = self
.workbook
.worksheet(sheet)?
.cell(row, column)
.map(|c| self.workbook.styles.get_style(c.get_style()))
.transpose();
style
}
/// Returns an internal binary representation of the workbook /// Returns an internal binary representation of the workbook
/// ///
/// See also: /// See also:
@@ -2161,6 +2178,73 @@ impl Model {
Err("Defined name not found".to_string()) Err("Defined name not found".to_string())
} }
} }
/// Returns the style object of a column, if any
pub fn get_column_style(&self, sheet: u32, column: i32) -> Result<Option<Style>, String> {
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
let cols = &worksheet.cols;
for col in cols {
if column >= col.min && column <= col.max {
if let Some(style_index) = col.style {
let style = self.workbook.styles.get_style(style_index)?;
return Ok(Some(style));
}
return Ok(None);
}
}
Ok(None)
} else {
Err("Invalid sheet".to_string())
}
}
/// Returns the style object of a row, if any
pub fn get_row_style(&self, sheet: u32, row: i32) -> Result<Option<Style>, String> {
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
let rows = &worksheet.rows;
for r in rows {
if row == r.r {
let style = self.workbook.styles.get_style(r.s)?;
return Ok(Some(style));
}
}
Ok(None)
} else {
Err("Invalid sheet".to_string())
}
}
/// Sets a column with style
pub fn set_column_style(
&mut self,
sheet: u32,
column: i32,
style: &Style,
) -> Result<(), String> {
let style_index = self.workbook.styles.get_style_index_or_create(style);
self.workbook
.worksheet_mut(sheet)?
.set_column_style(column, style_index)
}
/// Sets a row with style
pub fn set_row_style(&mut self, sheet: u32, row: i32, style: &Style) -> Result<(), String> {
let style_index = self.workbook.styles.get_style_index_or_create(style);
self.workbook
.worksheet_mut(sheet)?
.set_row_style(row, style_index)
}
/// Deletes the style of a column if the is any
pub fn delete_column_style(&mut self, sheet: u32, column: i32) -> Result<(), String> {
self.workbook
.worksheet_mut(sheet)?
.delete_column_style(column)
}
/// Deletes the style of a row if there is any
pub fn delete_row_style(&mut self, sheet: u32, row: i32) -> Result<(), String> {
self.workbook.worksheet_mut(sheet)?.delete_row_style(row)
}
} }
#[cfg(test)] #[cfg(test)]

View File

@@ -301,7 +301,7 @@ impl Model {
}; };
if sheet_index >= sheet_count { if sheet_index >= sheet_count {
return Err("Sheet index too large".to_string()); return Err("Sheet index too large".to_string());
} };
self.workbook.worksheets.remove(sheet_index as usize); self.workbook.worksheets.remove(sheet_index as usize);
self.reset_parsed_structures(); self.reset_parsed_structures();
Ok(()) Ok(())

View File

@@ -4,8 +4,6 @@ use crate::{
types::{Border, CellStyles, CellXfs, Fill, Font, NumFmt, Style, Styles}, types::{Border, CellStyles, CellXfs, Fill, Font, NumFmt, Style, Styles},
}; };
// TODO: Move Styles and all related types from crate::types here
// Not doing it right now to not have conflicts with exporter branch
impl Styles { impl Styles {
fn get_font_index(&self, font: &Font) -> Option<i32> { fn get_font_index(&self, font: &Font) -> Option<i32> {
for (font_index, item) in self.fonts.iter().enumerate() { for (font_index, item) in self.fonts.iter().enumerate() {

View File

@@ -37,6 +37,7 @@ mod test_model_cell_clear_all;
mod test_model_is_empty_cell; mod test_model_is_empty_cell;
mod test_move_formula; mod test_move_formula;
mod test_quote_prefix; mod test_quote_prefix;
mod test_row_column_styles;
mod test_set_user_input; mod test_set_user_input;
mod test_sheet_markup; mod test_sheet_markup;
mod test_sheets; mod test_sheets;

View File

@@ -0,0 +1,32 @@
#![allow(clippy::unwrap_used)]
use crate::{constants::DEFAULT_COLUMN_WIDTH, test::util::new_empty_model};
#[test]
fn test_model_set_cells_with_values_styles() {
let mut model = new_empty_model();
let style_base = model.get_style_for_cell(0, 1, 1).unwrap();
let mut style = style_base.clone();
style.font.b = true;
model.set_column_style(0, 10, &style).unwrap();
assert!(model.get_style_for_cell(0, 21, 10).unwrap().font.b);
model.delete_column_style(0, 10).unwrap();
// There are no styles in the column
assert!(model.workbook.worksheets[0].cols.is_empty());
// lets change the column width and check it does not affect the style
model
.set_column_width(0, 10, DEFAULT_COLUMN_WIDTH * 2.0)
.unwrap();
model.set_column_style(0, 10, &style).unwrap();
model.delete_column_style(0, 10).unwrap();
// There are no styles in the column
assert!(model.workbook.worksheets[0].cols.len() == 1);
}

View File

@@ -3,6 +3,7 @@ mod test_autofill_columns;
mod test_autofill_rows; mod test_autofill_rows;
mod test_border; mod test_border;
mod test_clear_cells; mod test_clear_cells;
mod test_column_style;
mod test_defined_names; mod test_defined_names;
mod test_diff_queue; mod test_diff_queue;
mod test_evaluation; mod test_evaluation;

View File

@@ -0,0 +1,332 @@
#![allow(clippy::unwrap_used)]
use crate::constants::{DEFAULT_COLUMN_WIDTH, DEFAULT_ROW_HEIGHT, LAST_COLUMN, LAST_ROW};
use crate::expressions::types::Area;
use crate::UserModel;
#[test]
fn column_width() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
let range = Area {
sheet: 0,
row: 1,
column: 7,
width: 1,
height: LAST_ROW,
};
let style = model.get_cell_style(0, 1, 1).unwrap();
assert!(!style.font.i);
assert!(!style.font.b);
assert!(!style.font.u);
assert!(!style.font.strike);
assert_eq!(style.font.color, Some("#000000".to_owned()));
// Set the whole column style and check it works
model.update_range_style(&range, "font.b", "true").unwrap();
let style = model.get_cell_style(0, 109, 7).unwrap();
assert!(style.font.b);
// undo and check it works
model.undo().unwrap();
let style = model.get_cell_style(0, 109, 7).unwrap();
assert!(!style.font.b);
// redo and check it works
model.redo().unwrap();
let style = model.get_cell_style(0, 109, 7).unwrap();
assert!(style.font.b);
// change the column width and check it does not affect the style
model
.set_column_width(0, 7, DEFAULT_COLUMN_WIDTH * 2.0)
.unwrap();
let style = model.get_cell_style(0, 109, 7).unwrap();
assert!(style.font.b);
}
#[test]
fn existing_style() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
let cell_g123 = Area {
sheet: 0,
row: 123,
column: 7,
width: 1,
height: 1,
};
let column_g_range = Area {
sheet: 0,
row: 1,
column: 7,
width: 1,
height: LAST_ROW,
};
// Set G123 background to red
model
.update_range_style(&cell_g123, "fill.bg_color", "#333444")
.unwrap();
// Now set the style of the whole column
model
.update_range_style(&column_g_range, "fill.bg_color", "#555666")
.unwrap();
// Get the style of G123
let style = model.get_cell_style(0, 123, 7).unwrap();
assert_eq!(style.fill.bg_color, Some("#555666".to_owned()));
model.undo().unwrap();
// Check the style of G123 is now what it was before
let style = model.get_cell_style(0, 123, 7).unwrap();
assert_eq!(style.fill.bg_color, Some("#333444".to_owned()));
model.redo().unwrap();
// Check G123 has the column style now
let style = model.get_cell_style(0, 123, 7).unwrap();
assert_eq!(style.fill.bg_color, Some("#555666".to_owned()));
}
#[test]
fn row_column() {
// We set the row style, then a column style
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
let column_g_range = Area {
sheet: 0,
row: 1,
column: 7,
width: 1,
height: LAST_ROW,
};
let row_3_range = Area {
sheet: 0,
row: 3,
column: 1,
width: LAST_COLUMN,
height: 1,
};
// update the row style
model
.update_range_style(&row_3_range, "fill.bg_color", "#333444")
.unwrap();
// update the column style
model
.update_range_style(&column_g_range, "fill.bg_color", "#555666")
.unwrap();
// Check G3 has the column style
let style = model.get_cell_style(0, 3, 7).unwrap();
assert_eq!(style.fill.bg_color, Some("#555666".to_owned()));
// undo twice. Color must be default
model.undo().unwrap();
let style = model.get_cell_style(0, 3, 7).unwrap();
assert_eq!(style.fill.bg_color, Some("#333444".to_owned()));
model.undo().unwrap();
let style = model.get_cell_style(0, 3, 7).unwrap();
assert_eq!(style.fill.bg_color, None);
}
#[test]
fn column_row() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
let default_style = model.get_cell_style(0, 3, 7).unwrap();
let column_g_range = Area {
sheet: 0,
row: 1,
column: 7,
width: 1,
height: LAST_ROW,
};
let row_3_range = Area {
sheet: 0,
row: 3,
column: 1,
width: LAST_COLUMN,
height: 1,
};
// update the column style
model
.update_range_style(&column_g_range, "fill.bg_color", "#555666")
.unwrap();
// update the row style
model
.update_range_style(&row_3_range, "fill.bg_color", "#333444")
.unwrap();
// Check G3 has the row style
let style = model.get_cell_style(0, 3, 7).unwrap();
assert_eq!(style.fill.bg_color, Some("#333444".to_owned()));
model.undo().unwrap();
// Check G3 has the column style
let style = model.get_cell_style(0, 3, 7).unwrap();
assert_eq!(style.fill.bg_color, Some("#555666".to_owned()));
model.undo().unwrap();
// Check G3 has the default_style
let style = model.get_cell_style(0, 3, 7).unwrap();
assert_eq!(style.fill.bg_color, default_style.fill.bg_color);
}
#[test]
fn row_column_column() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
let column_c_range = Area {
sheet: 0,
row: 1,
column: 3,
width: 1,
height: LAST_ROW,
};
let column_e_range = Area {
sheet: 0,
row: 1,
column: 5,
width: 1,
height: LAST_ROW,
};
let row_5_range = Area {
sheet: 0,
row: 5,
column: 1,
width: LAST_COLUMN,
height: 1,
};
// update the row style
model
.update_range_style(&row_5_range, "fill.bg_color", "#333444")
.unwrap();
// update the column style
model
.update_range_style(&column_c_range, "fill.bg_color", "#555666")
.unwrap();
model
.update_range_style(&column_e_range, "fill.bg_color", "#CCC111")
.unwrap();
model.undo().unwrap();
model.undo().unwrap();
model.undo().unwrap();
// Test E5 has the default style
let style = model.get_cell_style(0, 5, 5).unwrap();
assert_eq!(style.fill.bg_color, None);
}
#[test]
fn width_column_undo() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
model
.set_column_width(0, 7, DEFAULT_COLUMN_WIDTH * 2.0)
.unwrap();
let column_g_range = Area {
sheet: 0,
row: 1,
column: 7,
width: 1,
height: LAST_ROW,
};
model
.update_range_style(&column_g_range, "fill.bg_color", "#CCC111")
.unwrap();
model.undo().unwrap();
assert_eq!(
model.get_column_width(0, 7).unwrap(),
DEFAULT_COLUMN_WIDTH * 2.0
);
}
#[test]
fn height_row_undo() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
model
.set_row_height(0, 10, DEFAULT_ROW_HEIGHT * 2.0)
.unwrap();
let row_10_range = Area {
sheet: 0,
row: 10,
column: 1,
width: LAST_COLUMN,
height: 1,
};
model
.update_range_style(&row_10_range, "fill.bg_color", "#CCC111")
.unwrap();
assert_eq!(
model.get_row_height(0, 10).unwrap(),
2.0 * DEFAULT_ROW_HEIGHT
);
model.undo().unwrap();
assert_eq!(
model.get_row_height(0, 10).unwrap(),
2.0 * DEFAULT_ROW_HEIGHT
);
model.undo().unwrap();
assert_eq!(model.get_row_height(0, 10).unwrap(), DEFAULT_ROW_HEIGHT);
}
#[test]
fn cell_row_undo() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
let cell_g12 = Area {
sheet: 0,
row: 12,
column: 7,
width: 1,
height: 1,
};
let row_12_range = Area {
sheet: 0,
row: 12,
column: 1,
width: LAST_COLUMN,
height: 1,
};
// Set G12 background to red
model
.update_range_style(&cell_g12, "fill.bg_color", "#333444")
.unwrap();
model
.update_range_style(&row_12_range, "fill.bg_color", "#CCC111")
.unwrap();
let style = model.get_cell_style(0, 12, 7).unwrap();
assert_eq!(style.fill.bg_color, Some("#CCC111".to_string()));
model.undo().unwrap();
let style = model.get_cell_style(0, 12, 7).unwrap();
assert_eq!(style.fill.bg_color, Some("#333444".to_string()));
}

View File

@@ -312,7 +312,7 @@ impl Default for Styles {
} }
} }
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)] #[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, Default)]
pub struct Style { pub struct Style {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub alignment: Option<Alignment>, pub alignment: Option<Alignment>,

View File

@@ -109,6 +109,78 @@ fn vertical(value: &str) -> Result<VerticalAlignment, String> {
} }
} }
fn update_style(old_value: &Style, style_path: &str, value: &str) -> Result<Style, String> {
let mut style = old_value.clone();
match style_path {
"font.b" => {
style.font.b = boolean(value)?;
}
"font.i" => {
style.font.i = boolean(value)?;
}
"font.u" => {
style.font.u = boolean(value)?;
}
"font.strike" => {
style.font.strike = boolean(value)?;
}
"font.color" => {
style.font.color = color(value)?;
}
"fill.bg_color" => {
style.fill.bg_color = color(value)?;
style.fill.pattern_type = "solid".to_string();
}
"fill.fg_color" => {
style.fill.fg_color = color(value)?;
style.fill.pattern_type = "solid".to_string();
}
"num_fmt" => {
value.clone_into(&mut style.num_fmt);
}
"alignment" => {
if !value.is_empty() {
return Err(format!("Alignment must be empty, but found: '{value}'."));
}
style.alignment = None;
}
"alignment.horizontal" => match style.alignment {
Some(ref mut s) => s.horizontal = horizontal(value)?,
None => {
let alignment = Alignment {
horizontal: horizontal(value)?,
..Default::default()
};
style.alignment = Some(alignment)
}
},
"alignment.vertical" => match style.alignment {
Some(ref mut s) => s.vertical = vertical(value)?,
None => {
let alignment = Alignment {
vertical: vertical(value)?,
..Default::default()
};
style.alignment = Some(alignment)
}
},
"alignment.wrap_text" => match style.alignment {
Some(ref mut s) => s.wrap_text = boolean(value)?,
None => {
let alignment = Alignment {
wrap_text: boolean(value)?,
..Default::default()
};
style.alignment = Some(alignment)
}
},
_ => {
return Err(format!("Invalid style path: '{style_path}'."));
}
}
Ok(style)
}
/// # A wrapper around [`Model`] for a spreadsheet end user. /// # A wrapper around [`Model`] for a spreadsheet end user.
/// UserModel is a wrapper around Model with undo/redo history, _diffs_, automatic evaluation and view management. /// UserModel is a wrapper around Model with undo/redo history, _diffs_, automatic evaluation and view management.
/// ///
@@ -821,7 +893,7 @@ impl UserModel {
let row_index = ((row - row_start) % styles_height) as usize; let row_index = ((row - row_start) % styles_height) as usize;
let column_index = ((column - column_start) % styles_width) as usize; let column_index = ((column - column_start) % styles_width) as usize;
let style = &styles[row_index][column_index]; let style = &styles[row_index][column_index];
let old_value = self.model.get_style_for_cell(sheet, row, column)?; let old_value = self.model.get_cell_style_or_none(sheet, row, column)?;
self.model.set_cell_style(sheet, row, column, style)?; self.model.set_cell_style(sheet, row, column, style)?;
diff_list.push(Diff::SetCellStyle { diff_list.push(Diff::SetCellStyle {
sheet, sheet,
@@ -922,7 +994,7 @@ impl UserModel {
sheet, sheet,
row: row - 1, row: row - 1,
column, column,
old_value: Box::new(old_value_cell_above), old_value: Box::new(Some(old_value_cell_above)),
new_value: Box::new(new_value_cell_above), new_value: Box::new(new_value_cell_above),
}); });
} }
@@ -955,7 +1027,7 @@ impl UserModel {
sheet, sheet,
row, row,
column: column + 1, column: column + 1,
old_value: Box::new(old_value_cell_right), old_value: Box::new(Some(old_value_cell_right)),
new_value: Box::new(new_value_cell_right), new_value: Box::new(new_value_cell_right),
}); });
} }
@@ -988,7 +1060,7 @@ impl UserModel {
sheet, sheet,
row: row + 1, row: row + 1,
column, column,
old_value: Box::new(old_value_cell_below), old_value: Box::new(Some(old_value_cell_below)),
new_value: Box::new(new_value_cell_below), new_value: Box::new(new_value_cell_below),
}); });
} }
@@ -1021,7 +1093,7 @@ impl UserModel {
sheet, sheet,
row, row,
column: column - 1, column: column - 1,
old_value: Box::new(old_value_cell_left), old_value: Box::new(Some(old_value_cell_left)),
new_value: Box::new(new_value_cell_left), new_value: Box::new(new_value_cell_left),
}); });
} }
@@ -1056,7 +1128,7 @@ impl UserModel {
sheet, sheet,
row, row,
column, column,
old_value: Box::new(old_value), old_value: Box::new(Some(old_value)),
new_value: Box::new(new_value), new_value: Box::new(new_value),
}); });
} }
@@ -1081,7 +1153,7 @@ impl UserModel {
sheet, sheet,
row, row,
column, column,
old_value: Box::new(old_value), old_value: Box::new(Some(old_value)),
new_value: Box::new(style), new_value: Box::new(style),
}); });
} }
@@ -1107,7 +1179,7 @@ impl UserModel {
sheet, sheet,
row, row,
column, column,
old_value: Box::new(old_value), old_value: Box::new(Some(old_value)),
new_value: Box::new(style), new_value: Box::new(style),
}); });
} }
@@ -1132,7 +1204,7 @@ impl UserModel {
sheet, sheet,
row, row,
column, column,
old_value: Box::new(old_value), old_value: Box::new(Some(old_value)),
new_value: Box::new(style), new_value: Box::new(style),
}); });
} }
@@ -1157,7 +1229,7 @@ impl UserModel {
sheet, sheet,
row, row,
column, column,
old_value: Box::new(old_value), old_value: Box::new(Some(old_value)),
new_value: Box::new(style), new_value: Box::new(style),
}); });
} }
@@ -1168,6 +1240,32 @@ impl UserModel {
Ok(()) Ok(())
} }
fn update_single_cell_style(
&mut self,
sheet: u32,
row: i32,
column: i32,
style_path: &str,
value: &str,
diff_list: &mut Vec<Diff>,
) -> Result<(), String> {
let old_value = self.model.get_cell_style_or_none(sheet, row, column)?;
let style = match old_value.as_ref() {
Some(s) => s,
None => &Style::default(),
};
let new_style = update_style(style, style_path, value)?;
self.model.set_cell_style(sheet, row, column, &new_style)?;
diff_list.push(Diff::SetCellStyle {
sheet,
row,
column,
old_value: Box::new(old_value),
new_value: Box::new(new_style),
});
Ok(())
}
/// Updates the range with a cell style. /// Updates the range with a cell style.
/// See also: /// See also:
/// * [Model::set_cell_style] /// * [Model::set_cell_style]
@@ -1179,85 +1277,117 @@ impl UserModel {
) -> Result<(), String> { ) -> Result<(), String> {
let sheet = range.sheet; let sheet = range.sheet;
let mut diff_list = Vec::new(); let mut diff_list = Vec::new();
for row in range.row..range.row + range.height { // We need all the rows in the column to update the style
// NB: This is too much, this is all the rows that have values
let data_rows: Vec<i32> = self
.model
.workbook
.worksheet(sheet)?
.sheet_data
.keys()
.copied()
.collect();
let styled_rows = &self.model.workbook.worksheet(sheet)?.rows.clone();
if range.row == 1 && range.height == LAST_ROW {
// Full columns
for column in range.column..range.column + range.width { for column in range.column..range.column + range.width {
let old_value = self.model.get_style_for_cell(sheet, row, column)?; // we set the style of the full column
let mut style = old_value.clone(); let old_style = self.model.get_column_style(sheet, column)?;
match style_path { let style = match old_style.as_ref() {
"font.b" => { Some(s) => s,
style.font.b = boolean(value)?; None => &Style::default(),
} };
"font.i" => { let style = update_style(style, style_path, value)?;
style.font.i = boolean(value)?; self.model.set_column_style(sheet, column, &style)?;
} diff_list.push(Diff::SetColumnStyle {
"font.u" => {
style.font.u = boolean(value)?;
}
"font.strike" => {
style.font.strike = boolean(value)?;
}
"font.color" => {
style.font.color = color(value)?;
}
"fill.bg_color" => {
style.fill.bg_color = color(value)?;
style.fill.pattern_type = "solid".to_string();
}
"fill.fg_color" => {
style.fill.fg_color = color(value)?;
style.fill.pattern_type = "solid".to_string();
}
"num_fmt" => {
value.clone_into(&mut style.num_fmt);
}
"alignment" => {
if !value.is_empty() {
return Err(format!("Alignment must be empty, but found: '{value}'."));
}
style.alignment = None;
}
"alignment.horizontal" => match style.alignment {
Some(ref mut s) => s.horizontal = horizontal(value)?,
None => {
let alignment = Alignment {
horizontal: horizontal(value)?,
..Default::default()
};
style.alignment = Some(alignment)
}
},
"alignment.vertical" => match style.alignment {
Some(ref mut s) => s.vertical = vertical(value)?,
None => {
let alignment = Alignment {
vertical: vertical(value)?,
..Default::default()
};
style.alignment = Some(alignment)
}
},
"alignment.wrap_text" => match style.alignment {
Some(ref mut s) => s.wrap_text = boolean(value)?,
None => {
let alignment = Alignment {
wrap_text: boolean(value)?,
..Default::default()
};
style.alignment = Some(alignment)
}
},
_ => {
return Err(format!("Invalid style path: '{style_path}'."));
}
}
self.model.set_cell_style(sheet, row, column, &style)?;
diff_list.push(Diff::SetCellStyle {
sheet, sheet,
row,
column, column,
old_value: Box::new(old_value), old_value: Box::new(old_style),
new_value: Box::new(style), new_value: Box::new(style),
}); });
// Update style in all cells that have different styles
// FIXME: We need a better way to transverse of cells in a column
for &row in &data_rows {
if let Some(data_row) =
self.model.workbook.worksheet(sheet)?.sheet_data.get(&row)
{
if data_row.get(&column).is_some() {
self.update_single_cell_style(
sheet,
row,
column,
style_path,
value,
&mut diff_list,
)?;
}
}
}
// We need to update the styles in all cells that have a row style
for row_s in styled_rows.iter() {
self.update_single_cell_style(
sheet,
row_s.r,
column,
style_path,
value,
&mut diff_list,
)?;
}
}
} else if range.column == 1 && range.width == LAST_COLUMN {
// Full rows
for row in range.row..range.row + range.height {
// First update the style of the row
let old_style = self.model.get_row_style(sheet, row)?;
let style = match old_style.as_ref() {
Some(s) => s,
None => &Style::default(),
};
let style = update_style(style, style_path, value)?;
self.model.set_row_style(sheet, row, &style)?;
diff_list.push(Diff::SetRowStyle {
sheet,
row,
old_value: Box::new(old_style),
new_value: Box::new(style),
});
// Now update style in all cells that are not empty
let columns: Vec<i32> = self
.model
.workbook
.worksheet(sheet)?
.sheet_data
.get(&row)
.map(|row_data| row_data.keys().copied().collect())
.unwrap_or_default();
for column in columns {
self.update_single_cell_style(
sheet,
row,
column,
style_path,
value,
&mut diff_list,
)?;
}
// since rows take precedence over columns we do not need
// to update the styles in all cells that have a column style
}
} else {
for row in range.row..range.row + range.height {
for column in range.column..range.column + range.width {
self.update_single_cell_style(
sheet,
row,
column,
style_path,
value,
&mut diff_list,
)?;
}
} }
} }
self.push_diff_list(diff_list); self.push_diff_list(diff_list);
@@ -1394,7 +1524,7 @@ impl UserModel {
.worksheet(sheet)? .worksheet(sheet)?
.cell(row, column) .cell(row, column)
.cloned(); .cloned();
let old_style = self.model.get_style_for_cell(sheet, row, column)?; let old_style = self.model.get_cell_style_or_none(sheet, row, column)?;
// compute the new value and set it // compute the new value and set it
let source_row = anchor_row + index; let source_row = anchor_row + index;
@@ -1495,7 +1625,7 @@ impl UserModel {
.worksheet(sheet)? .worksheet(sheet)?
.cell(row, column) .cell(row, column)
.cloned(); .cloned();
let old_style = self.model.get_style_for_cell(sheet, row, column)?; let old_style = self.model.get_cell_style_or_none(sheet, row, column)?;
// compute the new value and set it // compute the new value and set it
let source_column = anchor_column + index; let source_column = anchor_column + index;
@@ -1570,6 +1700,9 @@ impl UserModel {
let mut data = HashMap::new(); let mut data = HashMap::new();
let [row_start, column_start, row_end, column_end] = selected_area.range; let [row_start, column_start, row_end, column_end] = selected_area.range;
let dimension = self.model.workbook.worksheet(sheet)?.dimension();
let row_end = row_end.min(dimension.max_row);
let column_end = column_end.min(dimension.max_column);
for row in row_start..=row_end { for row in row_start..=row_end {
let mut data_row = HashMap::new(); let mut data_row = HashMap::new();
let mut text_row = Vec::new(); let mut text_row = Vec::new();
@@ -1667,9 +1800,9 @@ impl UserModel {
.cell(target_row, target_column) .cell(target_row, target_column)
.cloned(); .cloned();
let old_style = self let old_style =
.model self.model
.get_style_for_cell(sheet, target_row, target_column)?; .get_cell_style_or_none(sheet, target_row, target_column)?;
self.model self.model
.set_user_input(sheet, target_row, target_column, new_value.clone())?; .set_user_input(sheet, target_row, target_column, new_value.clone())?;
@@ -1922,9 +2055,15 @@ impl UserModel {
column, column,
old_value, old_value,
new_value: _, new_value: _,
} => self } => {
.model if let Some(old_style) = old_value.as_ref() {
.set_cell_style(*sheet, *row, *column, old_value)?, self.model
.set_cell_style(*sheet, *row, *column, old_style)?;
} else {
// If the cell did not have a style there was nothing on it
self.model.cell_clear_all(*sheet, *row, *column)?;
}
}
Diff::InsertRow { sheet, row } => { Diff::InsertRow { sheet, row } => {
self.model.delete_rows(*sheet, *row, 1)?; self.model.delete_rows(*sheet, *row, 1)?;
needs_evaluation = true; needs_evaluation = true;
@@ -2073,6 +2212,29 @@ impl UserModel {
self.set_selected_sheet(sheet_index)?; self.set_selected_sheet(sheet_index)?;
} }
Diff::SetColumnStyle {
sheet,
column,
old_value,
new_value: _,
} => match old_value.as_ref() {
Some(s) => self.model.set_column_style(*sheet, *column, s)?,
None => {
self.model.delete_column_style(*sheet, *column)?;
}
},
Diff::SetRowStyle {
sheet,
row,
old_value,
new_value: _,
} => {
if let Some(s) = old_value.as_ref() {
self.model.set_row_style(*sheet, *row, s)?;
} else {
self.model.delete_row_style(*sheet, *row)?;
}
}
} }
} }
if needs_evaluation { if needs_evaluation {
@@ -2244,6 +2406,22 @@ impl UserModel {
.worksheet_mut(*sheet)? .worksheet_mut(*sheet)?
.set_cell_style(*row, *column, 0)?; .set_cell_style(*row, *column, 0)?;
} }
Diff::SetColumnStyle {
sheet,
column,
old_value: _,
new_value,
} => {
self.model.set_column_style(*sheet, *column, new_value)?;
}
Diff::SetRowStyle {
sheet,
row,
old_value: _,
new_value,
} => {
self.model.set_row_style(*sheet, *row, new_value)?;
}
} }
} }

View File

@@ -49,7 +49,7 @@ pub(crate) enum Diff {
sheet: u32, sheet: u32,
row: i32, row: i32,
column: i32, column: i32,
old_value: Box<Style>, old_value: Box<Option<Style>>,
new_value: Box<Style>, new_value: Box<Style>,
}, },
// Column and Row diffs // Column and Row diffs
@@ -65,6 +65,18 @@ pub(crate) enum Diff {
new_value: f64, new_value: f64,
old_value: f64, old_value: f64,
}, },
SetColumnStyle {
sheet: u32,
column: i32,
old_value: Box<Option<Style>>,
new_value: Box<Style>,
},
SetRowStyle {
sheet: u32,
row: i32,
old_value: Box<Option<Style>>,
new_value: Box<Style>,
},
InsertRow { InsertRow {
sheet: u32, sheet: u32,
row: i32, row: i32,

View File

@@ -108,15 +108,17 @@ impl Worksheet {
self.cols = vec![Col { self.cols = vec![Col {
min: 1, min: 1,
max: constants::LAST_COLUMN, max: constants::LAST_COLUMN,
width: constants::DEFAULT_COLUMN_WIDTH / constants::COLUMN_WIDTH_FACTOR, width: constants::DEFAULT_COLUMN_WIDTH,
custom_width: true, custom_width: false,
style: Some(style_index), style: Some(style_index),
}]; }];
Ok(()) Ok(())
} }
pub fn set_column_style(&mut self, column: i32, style_index: i32) -> Result<(), String> { pub fn set_column_style(&mut self, column: i32, style_index: i32) -> Result<(), String> {
let width = constants::DEFAULT_COLUMN_WIDTH / constants::COLUMN_WIDTH_FACTOR; let width = self
.get_column_width(column)
.unwrap_or(constants::DEFAULT_COLUMN_WIDTH);
self.set_column_width_and_style(column, width, Some(style_index)) self.set_column_width_and_style(column, width, Some(style_index))
} }
@@ -139,6 +141,82 @@ impl Worksheet {
Ok(()) Ok(())
} }
pub fn delete_row_style(&mut self, row: i32) -> Result<(), String> {
let mut index = None;
for (i, r) in self.rows.iter().enumerate() {
if r.r == row {
index = Some(i);
break;
}
}
if let Some(i) = index {
self.rows.remove(i);
}
Ok(())
}
pub fn delete_column_style(&mut self, column: i32) -> Result<(), String> {
if !is_valid_column_number(column) {
return Err(format!("Column number '{column}' is not valid."));
}
let cols = &mut self.cols;
let mut index = 0;
let mut split = false;
for c in cols.iter_mut() {
let min = c.min;
let max = c.max;
if min <= column && column <= max {
//
split = true;
break;
}
if column < min {
// We passed, there is nothing to delete
break;
}
index += 1;
}
if split {
let min = cols[index].min;
let max = cols[index].max;
let custom_width = cols[index].custom_width;
let width = cols[index].width;
let pre = Col {
min,
max: column - 1,
width,
custom_width,
style: cols[index].style,
};
let col = Col {
min: column,
max: column,
width,
custom_width,
style: None,
};
let post = Col {
min: column + 1,
max,
width,
custom_width,
style: cols[index].style,
};
cols.remove(index);
if column != max {
cols.insert(index, post);
}
if custom_width {
cols.insert(index, col);
}
if column != min {
cols.insert(index, pre);
}
}
Ok(())
}
pub fn set_cell_style( pub fn set_cell_style(
&mut self, &mut self,
row: i32, row: i32,
@@ -285,11 +363,12 @@ impl Worksheet {
/// Changes the width of a column. /// Changes the width of a column.
/// * If the column does not a have a width we simply add it /// * If the column does not a have a width we simply add it
/// * If it has, it might be part of a range and we ned to split the range. /// * If it has, it might be part of a range and we need to split the range.
/// ///
/// Fails if column index is outside allowed range or width is negative. /// Fails if column index is outside allowed range or width is negative.
pub fn set_column_width(&mut self, column: i32, width: f64) -> Result<(), String> { pub fn set_column_width(&mut self, column: i32, width: f64) -> Result<(), String> {
self.set_column_width_and_style(column, width, None) let style = self.get_column_style(column)?;
self.set_column_width_and_style(column, width, style)
} }
pub(crate) fn set_column_width_and_style( pub(crate) fn set_column_width_and_style(
@@ -309,7 +388,7 @@ impl Worksheet {
min: column, min: column,
max: column, max: column,
width: width / constants::COLUMN_WIDTH_FACTOR, width: width / constants::COLUMN_WIDTH_FACTOR,
custom_width: true, custom_width: width != constants::DEFAULT_COLUMN_WIDTH,
style, style,
}; };
let mut index = 0; let mut index = 0;
@@ -319,6 +398,7 @@ impl Worksheet {
let max = c.max; let max = c.max;
if min <= column && column <= max { if min <= column && column <= max {
if min == column && max == column { if min == column && max == column {
c.style = style;
c.width = width / constants::COLUMN_WIDTH_FACTOR; c.width = width / constants::COLUMN_WIDTH_FACTOR;
return Ok(()); return Ok(());
} }
@@ -383,6 +463,23 @@ impl Worksheet {
Ok(constants::DEFAULT_COLUMN_WIDTH) Ok(constants::DEFAULT_COLUMN_WIDTH)
} }
/// Returns the column style index if present
pub fn get_column_style(&self, column: i32) -> Result<Option<i32>, String> {
if !is_valid_column_number(column) {
return Err(format!("Column number '{column}' is not valid."));
}
let cols = &self.cols;
for col in cols {
let min = col.min;
let max = col.max;
if column >= min && column <= max {
return Ok(col.style);
}
}
Ok(None)
}
// Returns non empty cells in a column // Returns non empty cells in a column
pub fn column_cell_references(&self, column: i32) -> Result<Vec<CellReferenceIndex>, String> { pub fn column_cell_references(&self, column: i32) -> Result<Vec<CellReferenceIndex>, String> {
let mut column_cell_references: Vec<CellReferenceIndex> = Vec::new(); let mut column_cell_references: Vec<CellReferenceIndex> = Vec::new();

View File

@@ -3,7 +3,6 @@ import type { StorybookConfig } from "@storybook/react-vite";
const config: StorybookConfig = { const config: StorybookConfig = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"], stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
addons: [ addons: [
"@storybook/addon-onboarding",
"@storybook/addon-essentials", "@storybook/addon-essentials",
"@chromatic-com/storybook", "@chromatic-com/storybook",
"@storybook/addon-interactions", "@storybook/addon-interactions",

View File

@@ -23,7 +23,6 @@
"@chromatic-com/storybook": "^3.2.4", "@chromatic-com/storybook": "^3.2.4",
"@storybook/addon-essentials": "^8.5.3", "@storybook/addon-essentials": "^8.5.3",
"@storybook/addon-interactions": "^8.5.3", "@storybook/addon-interactions": "^8.5.3",
"@storybook/addon-onboarding": "^8.5.3",
"@storybook/blocks": "^8.5.3", "@storybook/blocks": "^8.5.3",
"@storybook/react": "^8.5.3", "@storybook/react": "^8.5.3",
"@storybook/react-vite": "^8.5.3", "@storybook/react-vite": "^8.5.3",
@@ -45,6 +44,7 @@
} }
}, },
"../../bindings/wasm/pkg": { "../../bindings/wasm/pkg": {
"name": "@ironcalc/wasm",
"version": "0.3.2", "version": "0.3.2",
"license": "MIT/Apache-2.0" "license": "MIT/Apache-2.0"
}, },
@@ -1819,19 +1819,6 @@
"storybook": "^8.5.3" "storybook": "^8.5.3"
} }
}, },
"node_modules/@storybook/addon-onboarding": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/@storybook/addon-onboarding/-/addon-onboarding-8.5.3.tgz",
"integrity": "sha512-NZhYj3UZK65reO7mXcK7FPPu7QkLCRyIa6TpfQ3mRAocfjqg401mcBsRO37JNywYfHCZrU4w1l7pwpqjvcYceg==",
"dev": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/storybook"
},
"peerDependencies": {
"storybook": "^8.5.3"
}
},
"node_modules/@storybook/addon-outline": { "node_modules/@storybook/addon-outline": {
"version": "8.5.3", "version": "8.5.3",
"resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-8.5.3.tgz", "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-8.5.3.tgz",

View File

@@ -11,7 +11,7 @@
"check": "biome check ./src", "check": "biome check ./src",
"check-write": "biome check --write ./src", "check-write": "biome check --write ./src",
"test": "vitest run", "test": "vitest run",
"storybook": "storybook dev -p 6006", "storybook": "storybook dev -p 6006 --no-open",
"build-storybook": "storybook build" "build-storybook": "storybook build"
}, },
"dependencies": { "dependencies": {
@@ -30,7 +30,6 @@
"@chromatic-com/storybook": "^3.2.4", "@chromatic-com/storybook": "^3.2.4",
"@storybook/addon-essentials": "^8.5.3", "@storybook/addon-essentials": "^8.5.3",
"@storybook/addon-interactions": "^8.5.3", "@storybook/addon-interactions": "^8.5.3",
"@storybook/addon-onboarding": "^8.5.3",
"@storybook/blocks": "^8.5.3", "@storybook/blocks": "^8.5.3",
"@storybook/react": "^8.5.3", "@storybook/react": "^8.5.3",
"@storybook/react-vite": "^8.5.3", "@storybook/react-vite": "^8.5.3",

View File

@@ -577,8 +577,8 @@ export default class WorksheetCanvas {
let resizeHandleUp = (event: MouseEvent): void => { let resizeHandleUp = (event: MouseEvent): void => {
div.style.opacity = "0"; div.style.opacity = "0";
this.columnGuide.style.display = "none"; this.columnGuide.style.display = "none";
document.removeEventListener("mousemove", resizeHandleMove); document.removeEventListener("pointermove", resizeHandleMove);
document.removeEventListener("mouseup", resizeHandleUp); document.removeEventListener("pointerup", resizeHandleUp);
const newColumnWidth = columnWidth + event.pageX - initPageX; const newColumnWidth = columnWidth + event.pageX - initPageX;
this.onColumnWidthChanges( this.onColumnWidthChanges(
this.model.getSelectedSheet(), this.model.getSelectedSheet(),
@@ -587,13 +587,14 @@ export default class WorksheetCanvas {
); );
}; };
resizeHandleUp = resizeHandleUp.bind(this); resizeHandleUp = resizeHandleUp.bind(this);
div.addEventListener("mousedown", (event) => { div.addEventListener("pointerdown", (event) => {
event.stopPropagation();
div.style.opacity = "1"; div.style.opacity = "1";
this.columnGuide.style.display = "block"; this.columnGuide.style.display = "block";
this.columnGuide.style.left = `${headerColumnWidth + x}px`; this.columnGuide.style.left = `${headerColumnWidth + x}px`;
initPageX = event.pageX; initPageX = event.pageX;
document.addEventListener("mousemove", resizeHandleMove); document.addEventListener("pointermove", resizeHandleMove);
document.addEventListener("mouseup", resizeHandleUp); document.addEventListener("pointerup", resizeHandleUp);
}); });
} }
@@ -615,20 +616,21 @@ export default class WorksheetCanvas {
let resizeHandleUp = (event: MouseEvent): void => { let resizeHandleUp = (event: MouseEvent): void => {
div.style.opacity = "0"; div.style.opacity = "0";
this.rowGuide.style.display = "none"; this.rowGuide.style.display = "none";
document.removeEventListener("mousemove", resizeHandleMove); document.removeEventListener("pointermove", resizeHandleMove);
document.removeEventListener("mouseup", resizeHandleUp); document.removeEventListener("pointerup", resizeHandleUp);
const newRowHeight = rowHeight + event.pageY - initPageY - 1; const newRowHeight = rowHeight + event.pageY - initPageY - 1;
this.onRowHeightChanges(sheet, row, newRowHeight); this.onRowHeightChanges(sheet, row, newRowHeight);
}; };
resizeHandleUp = resizeHandleUp.bind(this); resizeHandleUp = resizeHandleUp.bind(this);
/* istanbul ignore next */ /* istanbul ignore next */
div.addEventListener("mousedown", (event) => { div.addEventListener("pointerdown", (event) => {
event.stopPropagation();
div.style.opacity = "1"; div.style.opacity = "1";
this.rowGuide.style.display = "block"; this.rowGuide.style.display = "block";
this.rowGuide.style.top = `${y}px`; this.rowGuide.style.top = `${y}px`;
initPageY = event.pageY; initPageY = event.pageY;
document.addEventListener("mousemove", resizeHandleMove); document.addEventListener("pointermove", resizeHandleMove);
document.addEventListener("mouseup", resizeHandleUp); document.addEventListener("pointerup", resizeHandleUp);
}); });
} }

View File

@@ -19,8 +19,6 @@ type FormulaBarProps = {
onTextUpdated: () => void; onTextUpdated: () => void;
}; };
const headerColumnWidth = 35;
function FormulaBar(properties: FormulaBarProps) { function FormulaBar(properties: FormulaBarProps) {
const { const {
cellAddress, cellAddress,

View File

@@ -15,6 +15,9 @@ interface PointerSettings {
worksheetCanvas: RefObject<WorksheetCanvas | null>; worksheetCanvas: RefObject<WorksheetCanvas | null>;
worksheetElement: RefObject<HTMLDivElement | null>; worksheetElement: RefObject<HTMLDivElement | null>;
onCellSelected: (cell: Cell, event: React.MouseEvent) => void; onCellSelected: (cell: Cell, event: React.MouseEvent) => void;
onRowSelected: (row: number) => void;
onColumnSelected: (column: number) => void;
onAllSheetSelected: () => void;
onAreaSelecting: (cell: Cell) => void; onAreaSelecting: (cell: Cell) => void;
onAreaSelected: () => void; onAreaSelected: () => void;
onExtendToCell: (cell: Cell) => void; onExtendToCell: (cell: Cell) => void;
@@ -116,6 +119,7 @@ const usePointer = (options: PointerSettings): PointerEvents => {
const onPointerDown = useCallback( const onPointerDown = useCallback(
(event: PointerEvent) => { (event: PointerEvent) => {
console.log("onPointerDown");
let x = event.clientX; let x = event.clientX;
let y = event.clientY; let y = event.clientY;
const { const {
@@ -125,6 +129,9 @@ const usePointer = (options: PointerSettings): PointerEvents => {
worksheetElement, worksheetElement,
worksheetCanvas, worksheetCanvas,
workbookState, workbookState,
onRowSelected,
onColumnSelected,
onAllSheetSelected,
} = options; } = options;
const worksheet = worksheetCanvas.current; const worksheet = worksheetCanvas.current;
const canvas = canvasElement.current; const canvas = canvasElement.current;
@@ -143,7 +150,10 @@ const usePointer = (options: PointerSettings): PointerEvents => {
y < headerRowHeight || y < headerRowHeight ||
y > canvasRect.height y > canvasRect.height
) { ) {
if ( if (x < headerColumnWidth && y < headerRowHeight) {
// Click on the top left corner
onAllSheetSelected();
} else if (
x > 0 && x > 0 &&
x < headerColumnWidth && x < headerColumnWidth &&
y > headerRowHeight && y > headerRowHeight &&
@@ -152,8 +162,18 @@ const usePointer = (options: PointerSettings): PointerEvents => {
// Click on a row number // Click on a row number
const cell = worksheet.getCellByCoordinates(headerColumnWidth, y); const cell = worksheet.getCellByCoordinates(headerColumnWidth, y);
if (cell) { if (cell) {
// TODO onRowSelected(cell.row);
// Row selected }
} else if (
x > headerColumnWidth &&
x < canvasRect.width &&
y > 0 &&
y < headerRowHeight
) {
// Click on a column letter
const cell = worksheet.getCellByCoordinates(x, headerRowHeight);
if (cell) {
onColumnSelected(cell.column);
} }
} }
return; return;

View File

@@ -1,6 +1,7 @@
import type { Area, Cell } from "./types"; import type { Area, Cell } from "./types";
import { type SelectedView, columnNameFromNumber } from "@ironcalc/wasm"; import { type SelectedView, columnNameFromNumber } from "@ironcalc/wasm";
import { LAST_COLUMN, LAST_ROW } from "./WorksheetCanvas/constants";
/** /**
* Returns true if the keypress should start editing * Returns true if the keypress should start editing
@@ -34,11 +35,23 @@ export const getCellAddress = (selectedArea: Area, selectedCell: Cell) => {
selectedArea.rowStart === selectedArea.rowEnd && selectedArea.rowStart === selectedArea.rowEnd &&
selectedArea.columnEnd === selectedArea.columnStart; selectedArea.columnEnd === selectedArea.columnStart;
return isSingleCell if (isSingleCell) {
? `${columnNameFromNumber(selectedCell.column)}${selectedCell.row}` return `${columnNameFromNumber(selectedCell.column)}${selectedCell.row}`;
: `${columnNameFromNumber(selectedArea.columnStart)}${ }
selectedArea.rowStart if (selectedArea.rowStart === 1 && selectedArea.rowEnd === LAST_ROW) {
}:${columnNameFromNumber(selectedArea.columnEnd)}${selectedArea.rowEnd}`; return `${columnNameFromNumber(selectedArea.columnStart)}:${columnNameFromNumber(
selectedArea.columnEnd,
)}`;
}
if (
selectedArea.columnStart === 1 &&
selectedArea.columnEnd === LAST_COLUMN
) {
return `${selectedArea.rowStart}:${selectedArea.rowEnd}`;
}
return `${columnNameFromNumber(selectedArea.columnStart)}${
selectedArea.rowStart
}:${columnNameFromNumber(selectedArea.columnEnd)}${selectedArea.rowEnd}`;
}; };
export function rangeToStr( export function rangeToStr(

View File

@@ -4,6 +4,8 @@ import { useEffect, useLayoutEffect, useRef, useState } from "react";
import CellContextMenu from "./CellContextMenu"; import CellContextMenu from "./CellContextMenu";
import { import {
COLUMN_WIDTH_SCALE, COLUMN_WIDTH_SCALE,
LAST_COLUMN,
LAST_ROW,
ROW_HEIGH_SCALE, ROW_HEIGH_SCALE,
outlineBackgroundColor, outlineBackgroundColor,
outlineColor, outlineColor,
@@ -155,6 +157,20 @@ function Worksheet(props: {
model, model,
workbookState, workbookState,
refresh, refresh,
onColumnSelected: (column: number) => {
model.setSelectedCell(1, column);
model.setSelectedRange(1, column, LAST_ROW, column);
refresh();
},
onRowSelected: (row: number) => {
model.setSelectedCell(row, 1);
model.setSelectedRange(row, 1, row, LAST_COLUMN);
refresh();
},
onAllSheetSelected: () => {
model.setSelectedCell(1, 1);
model.setSelectedRange(1, 1, LAST_ROW, LAST_COLUMN);
},
onCellSelected: (cell: Cell, event: React.MouseEvent) => { onCellSelected: (cell: Cell, event: React.MouseEvent) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();

View File

@@ -45,7 +45,6 @@
"@chromatic-com/storybook": "^3.2.4", "@chromatic-com/storybook": "^3.2.4",
"@storybook/addon-essentials": "^8.5.3", "@storybook/addon-essentials": "^8.5.3",
"@storybook/addon-interactions": "^8.5.3", "@storybook/addon-interactions": "^8.5.3",
"@storybook/addon-onboarding": "^8.5.3",
"@storybook/blocks": "^8.5.3", "@storybook/blocks": "^8.5.3",
"@storybook/react": "^8.5.3", "@storybook/react": "^8.5.3",
"@storybook/react-vite": "^8.5.3", "@storybook/react-vite": "^8.5.3",