Skip to main content

ui_grid_core/
edit.rs

1use serde::{Deserialize, Serialize};
2use serde_json::{Number, Value};
3
4use crate::models::{GridCellPosition, GridColumnDef, GridColumnType, GridOptions, GridRow};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "camelCase")]
8pub enum GridMoveDirection {
9    Left,
10    Right,
11    Up,
12    Down,
13}
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct GridEditSession {
17    pub focused_cell: GridCellPosition,
18    pub editing_cell: GridCellPosition,
19    pub editing_value: String,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct GridFocusCellResult {
24    pub focused_cell: GridCellPosition,
25    pub should_begin_edit: bool,
26}
27
28pub fn is_grid_cell_position(
29    position: Option<&GridCellPosition>,
30    row_id: &str,
31    column_name: &str,
32) -> bool {
33    position
34        .is_some_and(|position| position.row_id == row_id && position.column_name == column_name)
35}
36
37pub fn begin_grid_edit_session(
38    row_id: impl Into<String>,
39    column_name: impl Into<String>,
40    editing_value: impl Into<String>,
41) -> GridEditSession {
42    let position = GridCellPosition {
43        row_id: row_id.into(),
44        column_name: column_name.into(),
45    };
46
47    GridEditSession {
48        focused_cell: position.clone(),
49        editing_cell: position,
50        editing_value: editing_value.into(),
51    }
52}
53
54pub fn should_grid_edit_on_focus(options: &GridOptions, column: &GridColumnDef) -> bool {
55    column.enable_cell_edit_on_focus || options.enable_cell_edit_on_focus
56}
57
58pub fn build_grid_focus_cell_result(
59    current_focused_cell: Option<&GridCellPosition>,
60    current_editing_cell: Option<&GridCellPosition>,
61    row_id: impl Into<String>,
62    column_name: impl Into<String>,
63    should_edit_on_focus: bool,
64    is_cell_editable: bool,
65) -> GridFocusCellResult {
66    let focused_cell = GridCellPosition {
67        row_id: row_id.into(),
68        column_name: column_name.into(),
69    };
70
71    let should_begin_edit = should_edit_on_focus
72        && is_cell_editable
73        && !is_grid_cell_position(
74            current_focused_cell,
75            &focused_cell.row_id,
76            &focused_cell.column_name,
77        )
78        && !is_grid_cell_position(
79            current_editing_cell,
80            &focused_cell.row_id,
81            &focused_cell.column_name,
82        );
83
84    GridFocusCellResult {
85        focused_cell,
86        should_begin_edit,
87    }
88}
89
90pub fn clear_grid_edit_session() -> (Option<GridCellPosition>, String) {
91    (None, String::new())
92}
93
94pub fn find_next_grid_cell<F>(
95    rows: &[GridRow],
96    columns: &[GridColumnDef],
97    row_id: &str,
98    column_name: &str,
99    direction: GridMoveDirection,
100    is_cell_allowed: Option<F>,
101) -> Option<GridCellPosition>
102where
103    F: Fn(&GridRow, &GridColumnDef) -> bool,
104{
105    let row_index = rows.iter().position(|candidate| candidate.id == row_id)?;
106    let column_index = columns
107        .iter()
108        .position(|candidate| candidate.name == column_name)?;
109
110    let mut next_row_index = row_index as isize;
111    let mut next_column_index = column_index as isize;
112
113    loop {
114        match direction {
115            GridMoveDirection::Left => {
116                next_column_index -= 1;
117                if next_column_index < 0 {
118                    next_row_index -= 1;
119                    next_column_index = columns.len() as isize - 1;
120                }
121            }
122            GridMoveDirection::Right => {
123                next_column_index += 1;
124                if next_column_index >= columns.len() as isize {
125                    next_row_index += 1;
126                    next_column_index = 0;
127                }
128            }
129            GridMoveDirection::Up => next_row_index -= 1,
130            GridMoveDirection::Down => next_row_index += 1,
131        }
132
133        if next_row_index < 0
134            || next_row_index >= rows.len() as isize
135            || next_column_index < 0
136            || next_column_index >= columns.len() as isize
137        {
138            return None;
139        }
140
141        let next_row = &rows[next_row_index as usize];
142        let next_column = &columns[next_column_index as usize];
143        let allowed = is_cell_allowed
144            .as_ref()
145            .is_none_or(|predicate| predicate(next_row, next_column));
146        if allowed {
147            return Some(GridCellPosition {
148                row_id: next_row.id.clone(),
149                column_name: next_column.name.clone(),
150            });
151        }
152    }
153}
154
155pub fn stringify_grid_editor_value(value: &Value) -> String {
156    match value {
157        Value::Null => String::new(),
158        Value::String(value) => value.clone(),
159        Value::Bool(value) => value.to_string(),
160        Value::Number(value) => value.to_string(),
161        other => other.to_string(),
162    }
163}
164
165pub fn parse_grid_edited_value(column: &GridColumnDef, value: &str, old_value: &Value) -> Value {
166    match column.r#type {
167        GridColumnType::Number => value
168            .parse::<f64>()
169            .ok()
170            .filter(|parsed| parsed.is_finite())
171            .and_then(Number::from_f64)
172            .map(Value::Number)
173            .unwrap_or_else(|| old_value.clone()),
174        GridColumnType::Boolean => Value::Bool(value == "true"),
175        GridColumnType::Date | GridColumnType::String | GridColumnType::Object => {
176            Value::String(value.to_string())
177        }
178    }
179}