Skip to main content

ui_grid_core/
pinning.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::models::{GridColumnDef, GridLabels, GridOptions};
6
7pub type PinnedColumnState = BTreeMap<String, String>;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "lowercase")]
11pub enum PinDirection {
12    Left,
13    Right,
14    None,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(rename_all = "camelCase")]
19pub struct PinnedOffset {
20    pub side: String,
21    pub offset: String,
22}
23
24pub fn is_pinning_enabled(options: &GridOptions) -> bool {
25    options.enable_pinning
26}
27
28pub fn is_column_pinnable(options: &GridOptions, column: &GridColumnDef) -> bool {
29    is_pinning_enabled(options) && column.enable_pinning
30}
31
32pub fn get_column_pin_direction(
33    pinned_columns: &PinnedColumnState,
34    column: &GridColumnDef,
35) -> PinDirection {
36    match pinned_columns.get(&column.name).map(String::as_str) {
37        Some("left") => PinDirection::Left,
38        Some("right") => PinDirection::Right,
39        _ => PinDirection::None,
40    }
41}
42
43pub fn pin_column_state(
44    current: &PinnedColumnState,
45    column_name: &str,
46    direction: PinDirection,
47) -> PinnedColumnState {
48    let mut next = current.clone();
49    match direction {
50        PinDirection::None => {
51            next.remove(column_name);
52        }
53        PinDirection::Left => {
54            next.insert(column_name.to_string(), "left".to_string());
55        }
56        PinDirection::Right => {
57            next.insert(column_name.to_string(), "right".to_string());
58        }
59    }
60    next
61}
62
63pub fn build_initial_pinned_state(columns: &[GridColumnDef]) -> PinnedColumnState {
64    let mut state = PinnedColumnState::new();
65    for column in columns {
66        if column.pinned_left {
67            state.insert(column.name.clone(), "left".to_string());
68        } else if column.pinned_right {
69            state.insert(column.name.clone(), "right".to_string());
70        }
71    }
72    state
73}
74
75pub fn compute_pinned_offset(
76    visible_columns: &[GridColumnDef],
77    pinned_columns: &PinnedColumnState,
78    column: &GridColumnDef,
79) -> Option<PinnedOffset> {
80    let direction = pinned_columns.get(&column.name)?;
81
82    fn resolve_column_width_for_offset(column: &GridColumnDef) -> String {
83        let Some(width) = &column.width else {
84            return "11rem".to_string();
85        };
86
87        if width.contains("fr") || width.contains("minmax") {
88            return "11rem".to_string();
89        }
90
91        width.clone()
92    }
93
94    if direction == "left" {
95        let mut offset_parts = Vec::new();
96        for current in visible_columns {
97            if current.name == column.name {
98                break;
99            }
100            if pinned_columns
101                .get(&current.name)
102                .is_some_and(|side| side == "left")
103            {
104                offset_parts.push(resolve_column_width_for_offset(current));
105            }
106        }
107
108        return Some(PinnedOffset {
109            side: "left".to_string(),
110            offset: if offset_parts.is_empty() {
111                "0px".to_string()
112            } else {
113                format!("calc({})", offset_parts.join(" + "))
114            },
115        });
116    }
117
118    if direction == "right" {
119        let mut offset_parts = Vec::new();
120        for current in visible_columns.iter().rev() {
121            if current.name == column.name {
122                break;
123            }
124            if pinned_columns
125                .get(&current.name)
126                .is_some_and(|side| side == "right")
127            {
128                offset_parts.push(resolve_column_width_for_offset(current));
129            }
130        }
131
132        return Some(PinnedOffset {
133            side: "right".to_string(),
134            offset: if offset_parts.is_empty() {
135                "0px".to_string()
136            } else {
137                format!("calc({})", offset_parts.join(" + "))
138            },
139        });
140    }
141
142    None
143}
144
145pub fn pinning_button_label(
146    pinned_columns: &PinnedColumnState,
147    column: &GridColumnDef,
148    labels: &GridLabels,
149) -> String {
150    match pinned_columns.get(&column.name).map(String::as_str) {
151        Some("left") | Some("right") => labels.unpin.clone(),
152        _ => labels.pin_left.clone(),
153    }
154}