Skip to main content

fret_ui_headless/table/
tanstack_state.rs

1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use serde::{Deserialize, Serialize};
5use serde_json::{Map, Value};
6
7use super::{
8    ColumnFilter, ColumnId, ColumnPinningState, ColumnSizingInfoState, ColumnSizingState,
9    ColumnVisibilityState, ExpandingState, GroupedRowModel, GroupingState, PaginationState, RowKey,
10    RowModel, RowPinningState, SortSpec, TableState,
11};
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct TanStackSortingSpec {
15    pub id: String,
16    pub desc: bool,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct TanStackColumnFilter {
21    pub id: String,
22    pub value: Value,
23}
24
25#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
26pub struct TanStackPaginationState {
27    #[serde(rename = "pageIndex")]
28    pub page_index: usize,
29    #[serde(rename = "pageSize")]
30    pub page_size: usize,
31}
32
33pub type TanStackColumnSizingState = BTreeMap<String, f32>;
34
35#[derive(Debug, Clone, Default, Serialize, Deserialize)]
36pub struct TanStackColumnSizingInfoState {
37    #[serde(default, rename = "columnSizingStart")]
38    pub column_sizing_start: Vec<(String, f32)>,
39    #[serde(default, rename = "deltaOffset")]
40    pub delta_offset: Option<f32>,
41    #[serde(default, rename = "deltaPercentage")]
42    pub delta_percentage: Option<f32>,
43    #[serde(default, rename = "isResizingColumn")]
44    pub is_resizing_column: Value,
45    #[serde(default, rename = "startOffset")]
46    pub start_offset: Option<f32>,
47    #[serde(default, rename = "startSize")]
48    pub start_size: Option<f32>,
49}
50
51#[derive(Debug, Clone, Default, Serialize, Deserialize)]
52pub struct TanStackColumnPinningState {
53    #[serde(default)]
54    pub left: Vec<String>,
55    #[serde(default)]
56    pub right: Vec<String>,
57}
58
59#[derive(Debug, Clone, Copy, Default)]
60struct TanStackStatePresence {
61    sorting: bool,
62    column_filters: bool,
63    global_filter: bool,
64    pagination: bool,
65    grouping: bool,
66    expanded: bool,
67    row_pinning: bool,
68    row_selection: bool,
69    column_pinning: bool,
70    column_order: bool,
71    column_visibility: bool,
72    column_sizing: bool,
73    column_sizing_info: bool,
74}
75
76impl TanStackStatePresence {
77    fn from_json(value: &Value) -> Self {
78        let mut out = Self::default();
79        let Some(map) = value.as_object() else {
80            return out;
81        };
82
83        out.sorting = map.contains_key("sorting");
84        out.column_filters = map.contains_key("columnFilters");
85        out.global_filter = map.contains_key("globalFilter");
86        out.pagination = map.contains_key("pagination");
87        out.grouping = map.contains_key("grouping");
88        out.expanded = map.contains_key("expanded");
89        out.row_pinning = map.contains_key("rowPinning");
90        out.row_selection = map.contains_key("rowSelection");
91        out.column_pinning = map.contains_key("columnPinning");
92        out.column_order = map.contains_key("columnOrder");
93        out.column_visibility = map.contains_key("columnVisibility");
94        out.column_sizing = map.contains_key("columnSizing");
95        out.column_sizing_info = map.contains_key("columnSizingInfo");
96        out
97    }
98}
99
100#[derive(Debug, Clone, Default, Serialize, Deserialize)]
101pub struct TanStackTableState {
102    #[serde(default, skip_serializing_if = "Vec::is_empty")]
103    pub sorting: Vec<TanStackSortingSpec>,
104    #[serde(
105        default,
106        rename = "columnFilters",
107        skip_serializing_if = "Vec::is_empty"
108    )]
109    pub column_filters: Vec<TanStackColumnFilter>,
110    #[serde(
111        default,
112        rename = "globalFilter",
113        skip_serializing_if = "Option::is_none"
114    )]
115    pub global_filter: Option<Value>,
116    #[serde(default, skip_serializing_if = "Option::is_none")]
117    pub pagination: Option<TanStackPaginationState>,
118    #[serde(default, skip_serializing_if = "Vec::is_empty")]
119    pub grouping: Vec<String>,
120    #[serde(default, skip_serializing_if = "Option::is_none")]
121    pub expanded: Option<TanStackExpandedState>,
122    #[serde(
123        default,
124        rename = "rowPinning",
125        skip_serializing_if = "Option::is_none"
126    )]
127    pub row_pinning: Option<TanStackRowPinningState>,
128    #[serde(
129        default,
130        rename = "rowSelection",
131        skip_serializing_if = "Option::is_none"
132    )]
133    pub row_selection: Option<BTreeMap<String, bool>>,
134    #[serde(
135        default,
136        rename = "columnPinning",
137        skip_serializing_if = "Option::is_none"
138    )]
139    pub column_pinning: Option<TanStackColumnPinningState>,
140    #[serde(default, rename = "columnOrder", skip_serializing_if = "Vec::is_empty")]
141    pub column_order: Vec<String>,
142    #[serde(
143        default,
144        rename = "columnVisibility",
145        skip_serializing_if = "Option::is_none"
146    )]
147    pub column_visibility: Option<BTreeMap<String, bool>>,
148    #[serde(
149        default,
150        rename = "columnSizing",
151        skip_serializing_if = "BTreeMap::is_empty"
152    )]
153    pub column_sizing: TanStackColumnSizingState,
154    #[serde(
155        default,
156        rename = "columnSizingInfo",
157        skip_serializing_if = "Option::is_none"
158    )]
159    pub column_sizing_info: Option<TanStackColumnSizingInfoState>,
160    #[serde(skip)]
161    presence: TanStackStatePresence,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
165#[serde(untagged)]
166pub enum TanStackExpandedState {
167    All(bool),
168    Map(BTreeMap<String, bool>),
169}
170
171#[derive(Debug, Clone, Default, Serialize, Deserialize)]
172pub struct TanStackRowPinningState {
173    #[serde(default)]
174    pub top: Vec<String>,
175    #[serde(default)]
176    pub bottom: Vec<String>,
177}
178
179#[derive(Debug, Clone, PartialEq, Eq)]
180pub enum TanStackStateError {
181    InvalidRowSelectionKey {
182        row_id: String,
183    },
184    InvalidExpandedKey {
185        row_id: String,
186    },
187    InvalidRowPinningKey {
188        row_id: String,
189    },
190    UnresolvedRowId {
191        field: &'static str,
192        row_id: String,
193    },
194    UnresolvedRowKey {
195        field: &'static str,
196        row_key: RowKey,
197    },
198    InvalidIsResizingColumnValue {
199        value: Value,
200    },
201}
202
203impl std::fmt::Display for TanStackStateError {
204    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205        match self {
206            Self::InvalidRowSelectionKey { row_id } => {
207                write!(
208                    f,
209                    "tanstack rowSelection key must parse as u64 (row_id={row_id})"
210                )
211            }
212            Self::InvalidExpandedKey { row_id } => {
213                write!(
214                    f,
215                    "tanstack expanded key must parse as u64 (row_id={row_id})"
216                )
217            }
218            Self::InvalidRowPinningKey { row_id } => {
219                write!(
220                    f,
221                    "tanstack rowPinning key must parse as u64 (row_id={row_id})"
222                )
223            }
224            Self::UnresolvedRowId { field, row_id } => {
225                write!(
226                    f,
227                    "tanstack row id must resolve via row model (field={field}, row_id={row_id})"
228                )
229            }
230            Self::UnresolvedRowKey { field, row_key } => {
231                write!(
232                    f,
233                    "tanstack row key must resolve via row model (field={field}, row_key={})",
234                    row_key.0
235                )
236            }
237            Self::InvalidIsResizingColumnValue { value } => {
238                write!(
239                    f,
240                    "tanstack columnSizingInfo.isResizingColumn must be false|null|string (value={value:?})"
241                )
242            }
243        }
244    }
245}
246
247impl std::error::Error for TanStackStateError {}
248
249impl TanStackTableState {
250    pub fn from_json(value: &Value) -> serde_json::Result<Self> {
251        let mut out: Self = serde_json::from_value(value.clone())?;
252        out.presence = TanStackStatePresence::from_json(value);
253        Ok(out)
254    }
255
256    pub fn from_table_state(state: &TableState) -> Self {
257        let sorting: Vec<TanStackSortingSpec> = state
258            .sorting
259            .iter()
260            .map(|s| TanStackSortingSpec {
261                id: s.column.as_ref().to_string(),
262                desc: s.desc,
263            })
264            .collect();
265
266        let column_filters: Vec<TanStackColumnFilter> = state
267            .column_filters
268            .iter()
269            .map(|f| TanStackColumnFilter {
270                id: f.column.as_ref().to_string(),
271                value: f.value.clone(),
272            })
273            .collect();
274
275        let global_filter = state
276            .global_filter
277            .as_ref()
278            .and_then(|v| if v.is_null() { None } else { Some(v.clone()) });
279
280        let pagination = if state.pagination.page_index == 0 && state.pagination.page_size == 10 {
281            None
282        } else {
283            Some(TanStackPaginationState {
284                page_index: state.pagination.page_index,
285                page_size: state.pagination.page_size,
286            })
287        };
288
289        let grouping: Vec<String> = state
290            .grouping
291            .iter()
292            .map(|s| s.as_ref().to_string())
293            .collect();
294
295        let expanded = match &state.expanding {
296            ExpandingState::All => Some(TanStackExpandedState::All(true)),
297            ExpandingState::Keys(keys) if keys.is_empty() => None,
298            ExpandingState::Keys(keys) => Some(TanStackExpandedState::Map(
299                keys.iter().map(|k| (k.0.to_string(), true)).collect(),
300            )),
301        };
302
303        let row_pinning = if state.row_pinning.top.is_empty() && state.row_pinning.bottom.is_empty()
304        {
305            None
306        } else {
307            Some(TanStackRowPinningState {
308                top: state
309                    .row_pinning
310                    .top
311                    .iter()
312                    .map(|k| k.0.to_string())
313                    .collect(),
314                bottom: state
315                    .row_pinning
316                    .bottom
317                    .iter()
318                    .map(|k| k.0.to_string())
319                    .collect(),
320            })
321        };
322
323        let row_selection = if state.row_selection.is_empty() {
324            None
325        } else {
326            Some(
327                state
328                    .row_selection
329                    .iter()
330                    .map(|k| (k.0.to_string(), true))
331                    .collect(),
332            )
333        };
334
335        let column_pinning =
336            if state.column_pinning.left.is_empty() && state.column_pinning.right.is_empty() {
337                None
338            } else {
339                Some(TanStackColumnPinningState {
340                    left: state
341                        .column_pinning
342                        .left
343                        .iter()
344                        .map(|s| s.as_ref().to_string())
345                        .collect(),
346                    right: state
347                        .column_pinning
348                        .right
349                        .iter()
350                        .map(|s| s.as_ref().to_string())
351                        .collect(),
352                })
353            };
354
355        let column_order: Vec<String> = state
356            .column_order
357            .iter()
358            .map(|s| s.as_ref().to_string())
359            .collect();
360
361        let column_visibility = if state.column_visibility.is_empty() {
362            None
363        } else {
364            Some(
365                state
366                    .column_visibility
367                    .iter()
368                    .map(|(k, v)| (k.as_ref().to_string(), *v))
369                    .collect(),
370            )
371        };
372
373        let column_sizing: TanStackColumnSizingState = state
374            .column_sizing
375            .iter()
376            .map(|(k, v)| (k.as_ref().to_string(), *v))
377            .collect();
378
379        let column_sizing_info = {
380            let info = &state.column_sizing_info;
381            let is_default = info.column_sizing_start.is_empty()
382                && info.delta_offset.is_none()
383                && info.delta_percentage.is_none()
384                && info.is_resizing_column.is_none()
385                && info.start_offset.is_none()
386                && info.start_size.is_none();
387            if is_default {
388                None
389            } else {
390                Some(TanStackColumnSizingInfoState {
391                    column_sizing_start: info
392                        .column_sizing_start
393                        .iter()
394                        .map(|(id, size)| (id.as_ref().to_string(), *size))
395                        .collect(),
396                    delta_offset: info.delta_offset,
397                    delta_percentage: info.delta_percentage,
398                    is_resizing_column: match info.is_resizing_column.as_ref() {
399                        None => Value::Bool(false),
400                        Some(id) => Value::String(id.as_ref().to_string()),
401                    },
402                    start_offset: info.start_offset,
403                    start_size: info.start_size,
404                })
405            }
406        };
407
408        let presence = TanStackStatePresence {
409            sorting: !sorting.is_empty(),
410            column_filters: !column_filters.is_empty(),
411            global_filter: global_filter.is_some(),
412            pagination: pagination.is_some(),
413            grouping: !grouping.is_empty(),
414            expanded: expanded.is_some(),
415            row_pinning: row_pinning.is_some(),
416            row_selection: row_selection.is_some(),
417            column_pinning: column_pinning.is_some(),
418            column_order: !column_order.is_empty(),
419            column_visibility: column_visibility.is_some(),
420            column_sizing: !column_sizing.is_empty(),
421            column_sizing_info: column_sizing_info.is_some(),
422        };
423
424        Self {
425            sorting,
426            column_filters,
427            global_filter,
428            pagination,
429            grouping,
430            expanded,
431            row_pinning,
432            row_selection,
433            column_pinning,
434            column_order,
435            column_visibility,
436            column_sizing,
437            column_sizing_info,
438            presence,
439        }
440    }
441
442    pub fn to_json(&self) -> serde_json::Result<Value> {
443        let mut out = Map::new();
444
445        if self.presence.sorting || !self.sorting.is_empty() {
446            out.insert("sorting".to_string(), serde_json::to_value(&self.sorting)?);
447        }
448
449        if self.presence.column_filters || !self.column_filters.is_empty() {
450            out.insert(
451                "columnFilters".to_string(),
452                serde_json::to_value(&self.column_filters)?,
453            );
454        }
455
456        if self.presence.global_filter || self.global_filter.is_some() {
457            out.insert(
458                "globalFilter".to_string(),
459                self.global_filter.clone().unwrap_or(Value::Null),
460            );
461        }
462
463        if self.presence.pagination || self.pagination.is_some() {
464            out.insert(
465                "pagination".to_string(),
466                match self.pagination.as_ref() {
467                    Some(v) => serde_json::to_value(v)?,
468                    None => Value::Null,
469                },
470            );
471        }
472
473        if self.presence.grouping || !self.grouping.is_empty() {
474            out.insert(
475                "grouping".to_string(),
476                serde_json::to_value(&self.grouping)?,
477            );
478        }
479
480        if self.presence.expanded || self.expanded.is_some() {
481            out.insert(
482                "expanded".to_string(),
483                match self.expanded.as_ref() {
484                    Some(v) => serde_json::to_value(v)?,
485                    None => Value::Null,
486                },
487            );
488        }
489
490        if self.presence.row_pinning || self.row_pinning.is_some() {
491            out.insert(
492                "rowPinning".to_string(),
493                match self.row_pinning.as_ref() {
494                    Some(v) => serde_json::to_value(v)?,
495                    None => Value::Null,
496                },
497            );
498        }
499
500        if self.presence.row_selection || self.row_selection.is_some() {
501            out.insert(
502                "rowSelection".to_string(),
503                match self.row_selection.as_ref() {
504                    Some(v) => serde_json::to_value(v)?,
505                    None => Value::Null,
506                },
507            );
508        }
509
510        if self.presence.column_pinning || self.column_pinning.is_some() {
511            out.insert(
512                "columnPinning".to_string(),
513                match self.column_pinning.as_ref() {
514                    Some(v) => serde_json::to_value(v)?,
515                    None => Value::Null,
516                },
517            );
518        }
519
520        if self.presence.column_order || !self.column_order.is_empty() {
521            out.insert(
522                "columnOrder".to_string(),
523                serde_json::to_value(&self.column_order)?,
524            );
525        }
526
527        if self.presence.column_visibility || self.column_visibility.is_some() {
528            out.insert(
529                "columnVisibility".to_string(),
530                match self.column_visibility.as_ref() {
531                    Some(v) => serde_json::to_value(v)?,
532                    None => Value::Null,
533                },
534            );
535        }
536
537        if self.presence.column_sizing || !self.column_sizing.is_empty() {
538            out.insert(
539                "columnSizing".to_string(),
540                serde_json::to_value(&self.column_sizing)?,
541            );
542        }
543
544        if self.presence.column_sizing_info || self.column_sizing_info.is_some() {
545            out.insert(
546                "columnSizingInfo".to_string(),
547                match self.column_sizing_info.as_ref() {
548                    Some(v) => serde_json::to_value(v)?,
549                    None => Value::Null,
550                },
551            );
552        }
553
554        Ok(Value::Object(out))
555    }
556
557    fn with_presence_hint(mut self, source: &Self) -> Self {
558        self.presence = source.presence;
559
560        if source.presence.expanded && self.expanded.is_none() {
561            self.expanded = source.expanded.clone();
562        }
563        if source.presence.row_pinning && self.row_pinning.is_none() {
564            self.row_pinning = source.row_pinning.clone();
565        }
566        if source.presence.row_selection && self.row_selection.is_none() {
567            self.row_selection = source.row_selection.clone();
568        }
569        if source.presence.column_pinning && self.column_pinning.is_none() {
570            self.column_pinning = source.column_pinning.clone();
571        }
572        if source.presence.column_visibility && self.column_visibility.is_none() {
573            self.column_visibility = source.column_visibility.clone();
574        }
575        if source.presence.column_sizing_info && self.column_sizing_info.is_none() {
576            self.column_sizing_info = source.column_sizing_info.clone();
577        }
578        if source.presence.pagination && self.pagination.is_none() {
579            self.pagination = source.pagination;
580        }
581        if source.presence.global_filter && self.global_filter.is_none() {
582            self.global_filter = source.global_filter.clone();
583        }
584
585        self
586    }
587
588    pub fn from_table_state_with_shape(state: &TableState, source: &Self) -> Self {
589        Self::from_table_state(state).with_presence_hint(source)
590    }
591
592    fn resolve_row_id_for_key<'a, TData>(
593        core_row_model: &RowModel<'a, TData>,
594        grouped_row_model: Option<&GroupedRowModel>,
595        field: &'static str,
596        row_key: RowKey,
597    ) -> Result<String, TanStackStateError> {
598        if let Some(grouped) = grouped_row_model
599            && let Some(index) = grouped.row_by_key(row_key)
600            && let Some(row) = grouped.row(index)
601        {
602            return Ok(row.id.as_str().to_string());
603        }
604
605        let index = core_row_model
606            .row_by_key(row_key)
607            .ok_or(TanStackStateError::UnresolvedRowKey { field, row_key })?;
608        let row = core_row_model
609            .row(index)
610            .ok_or(TanStackStateError::UnresolvedRowKey { field, row_key })?;
611        Ok(row.id.as_str().to_string())
612    }
613
614    pub fn from_table_state_with_row_models<'a, TData>(
615        state: &TableState,
616        core_row_model: &RowModel<'a, TData>,
617        grouped_row_model: Option<&GroupedRowModel>,
618    ) -> Result<Self, TanStackStateError> {
619        let mut out = Self::from_table_state(state);
620
621        out.expanded = match &state.expanding {
622            ExpandingState::All => Some(TanStackExpandedState::All(true)),
623            ExpandingState::Keys(keys) if keys.is_empty() => None,
624            ExpandingState::Keys(keys) => Some(TanStackExpandedState::Map(
625                keys.iter()
626                    .map(|row_key| {
627                        let row_id = Self::resolve_row_id_for_key(
628                            core_row_model,
629                            grouped_row_model,
630                            "expanded",
631                            *row_key,
632                        )?;
633                        Ok((row_id, true))
634                    })
635                    .collect::<Result<BTreeMap<_, _>, TanStackStateError>>()?,
636            )),
637        };
638
639        out.row_pinning = if state.row_pinning.top.is_empty() && state.row_pinning.bottom.is_empty()
640        {
641            None
642        } else {
643            Some(TanStackRowPinningState {
644                top: state
645                    .row_pinning
646                    .top
647                    .iter()
648                    .map(|row_key| {
649                        Self::resolve_row_id_for_key(
650                            core_row_model,
651                            grouped_row_model,
652                            "rowPinning.top",
653                            *row_key,
654                        )
655                    })
656                    .collect::<Result<Vec<_>, _>>()?,
657                bottom: state
658                    .row_pinning
659                    .bottom
660                    .iter()
661                    .map(|row_key| {
662                        Self::resolve_row_id_for_key(
663                            core_row_model,
664                            grouped_row_model,
665                            "rowPinning.bottom",
666                            *row_key,
667                        )
668                    })
669                    .collect::<Result<Vec<_>, _>>()?,
670            })
671        };
672
673        out.row_selection = if state.row_selection.is_empty() {
674            None
675        } else {
676            Some(
677                state
678                    .row_selection
679                    .iter()
680                    .map(|row_key| {
681                        let row_id = Self::resolve_row_id_for_key(
682                            core_row_model,
683                            grouped_row_model,
684                            "rowSelection",
685                            *row_key,
686                        )?;
687                        Ok((row_id, true))
688                    })
689                    .collect::<Result<BTreeMap<_, _>, TanStackStateError>>()?,
690            )
691        };
692
693        out.presence.expanded = out.expanded.is_some();
694        out.presence.row_pinning = out.row_pinning.is_some();
695        out.presence.row_selection = out.row_selection.is_some();
696
697        Ok(out)
698    }
699
700    pub fn from_table_state_with_row_model<'a, TData>(
701        state: &TableState,
702        core_row_model: &RowModel<'a, TData>,
703    ) -> Result<Self, TanStackStateError> {
704        Self::from_table_state_with_row_models(state, core_row_model, None)
705    }
706
707    pub fn from_table_state_with_row_models_and_shape<'a, TData>(
708        state: &TableState,
709        core_row_model: &RowModel<'a, TData>,
710        grouped_row_model: Option<&GroupedRowModel>,
711        source: &Self,
712    ) -> Result<Self, TanStackStateError> {
713        Ok(
714            Self::from_table_state_with_row_models(state, core_row_model, grouped_row_model)?
715                .with_presence_hint(source),
716        )
717    }
718
719    pub fn from_table_state_with_row_model_and_shape<'a, TData>(
720        state: &TableState,
721        core_row_model: &RowModel<'a, TData>,
722        source: &Self,
723    ) -> Result<Self, TanStackStateError> {
724        Self::from_table_state_with_row_models_and_shape(state, core_row_model, None, source)
725    }
726
727    pub fn to_table_state(&self) -> Result<TableState, TanStackStateError> {
728        let mut out = TableState::default();
729
730        out.sorting = self
731            .sorting
732            .iter()
733            .map(|s| SortSpec {
734                column: Arc::<str>::from(s.id.as_str()),
735                desc: s.desc,
736            })
737            .collect();
738
739        out.column_filters = self
740            .column_filters
741            .iter()
742            .map(|f| {
743                Ok(ColumnFilter {
744                    column: Arc::<str>::from(f.id.as_str()),
745                    value: f.value.clone(),
746                })
747            })
748            .collect::<Result<_, _>>()?;
749
750        out.global_filter = match self.global_filter.as_ref() {
751            None | Some(Value::Null) => None,
752            Some(v) => Some(v.clone()),
753        };
754
755        if let Some(p) = self.pagination {
756            out.pagination = PaginationState {
757                page_index: p.page_index,
758                page_size: p.page_size,
759            };
760        }
761
762        if !self.grouping.is_empty() {
763            out.grouping = self
764                .grouping
765                .iter()
766                .map(|s| ColumnId::from(s.as_str()))
767                .collect::<GroupingState>();
768        }
769
770        if let Some(expanded) = self.expanded.as_ref() {
771            out.expanding = match expanded {
772                TanStackExpandedState::All(true) => ExpandingState::All,
773                TanStackExpandedState::All(false) => ExpandingState::default(),
774                TanStackExpandedState::Map(map) => ExpandingState::from_iter(
775                    map.iter()
776                        .filter_map(|(k, v)| {
777                            if !*v {
778                                return None;
779                            }
780                            let row_id = k.parse::<u64>().ok()?;
781                            Some(RowKey(row_id))
782                        })
783                        .collect::<Vec<_>>(),
784                ),
785            };
786
787            // Validate row ids if expanded is a map.
788            if let TanStackExpandedState::Map(map) = expanded {
789                for (k, v) in map {
790                    if !*v {
791                        continue;
792                    }
793                    if k.parse::<u64>().is_err() {
794                        return Err(TanStackStateError::InvalidExpandedKey { row_id: k.clone() });
795                    }
796                }
797            }
798        }
799
800        if let Some(pinning) = self.row_pinning.as_ref() {
801            let mut next = RowPinningState::default();
802            for k in &pinning.top {
803                let row_id = k
804                    .parse::<u64>()
805                    .map_err(|_| TanStackStateError::InvalidRowPinningKey { row_id: k.clone() })?;
806                next.top.push(RowKey(row_id));
807            }
808            for k in &pinning.bottom {
809                let row_id = k
810                    .parse::<u64>()
811                    .map_err(|_| TanStackStateError::InvalidRowPinningKey { row_id: k.clone() })?;
812                next.bottom.push(RowKey(row_id));
813            }
814            out.row_pinning = next;
815        }
816
817        if let Some(sel) = self.row_selection.as_ref() {
818            for (k, v) in sel {
819                if !*v {
820                    continue;
821                }
822                let row_id =
823                    k.parse::<u64>()
824                        .map_err(|_| TanStackStateError::InvalidRowSelectionKey {
825                            row_id: k.clone(),
826                        })?;
827                out.row_selection.insert(super::RowKey(row_id));
828            }
829        }
830
831        if let Some(pinning) = self.column_pinning.as_ref() {
832            out.column_pinning = ColumnPinningState {
833                left: pinning
834                    .left
835                    .iter()
836                    .map(|s| ColumnId::from(s.as_str()))
837                    .collect(),
838                right: pinning
839                    .right
840                    .iter()
841                    .map(|s| ColumnId::from(s.as_str()))
842                    .collect(),
843            };
844        }
845
846        if let Some(vis) = self.column_visibility.as_ref() {
847            let mut next: ColumnVisibilityState = ColumnVisibilityState::default();
848            for (k, v) in vis {
849                if *v {
850                    continue;
851                }
852                next.insert(ColumnId::from(k.as_str()), false);
853            }
854            out.column_visibility = next;
855        }
856
857        if !self.column_sizing.is_empty() {
858            let mut next: ColumnSizingState = ColumnSizingState::default();
859            for (k, v) in &self.column_sizing {
860                next.insert(ColumnId::from(k.as_str()), *v);
861            }
862            out.column_sizing = next;
863        }
864
865        if let Some(info) = self.column_sizing_info.as_ref() {
866            let is_resizing_column: Option<ColumnId> = match &info.is_resizing_column {
867                Value::Bool(false) | Value::Null => None,
868                Value::String(s) => Some(ColumnId::from(s.as_str())),
869                v => {
870                    return Err(TanStackStateError::InvalidIsResizingColumnValue {
871                        value: v.clone(),
872                    });
873                }
874            };
875
876            out.column_sizing_info = ColumnSizingInfoState {
877                column_sizing_start: info
878                    .column_sizing_start
879                    .iter()
880                    .map(|(id, size)| (ColumnId::from(id.as_str()), *size))
881                    .collect(),
882                delta_offset: info.delta_offset,
883                delta_percentage: info.delta_percentage,
884                is_resizing_column,
885                start_offset: info.start_offset,
886                start_size: info.start_size,
887            };
888        }
889
890        out.column_order = self
891            .column_order
892            .iter()
893            .map(|s| ColumnId::from(s.as_str()))
894            .collect();
895
896        Ok(out)
897    }
898
899    pub fn to_table_state_with_row_models<'a, TData>(
900        &self,
901        core_row_model: &RowModel<'a, TData>,
902        grouped_row_model: Option<&GroupedRowModel>,
903    ) -> Result<TableState, TanStackStateError> {
904        fn resolve_row_key<'a, TData>(
905            core: &RowModel<'a, TData>,
906            grouped: Option<&GroupedRowModel>,
907            field: &'static str,
908            row_id: &str,
909        ) -> Result<RowKey, TanStackStateError> {
910            if let Some(grouped) = grouped
911                && let Some(index) = grouped.row_by_id(row_id)
912            {
913                let row =
914                    grouped
915                        .row(index)
916                        .ok_or_else(|| TanStackStateError::UnresolvedRowId {
917                            field,
918                            row_id: row_id.to_string(),
919                        })?;
920                return Ok(row.key);
921            }
922            if let Some(index) = core.row_by_id(row_id) {
923                let row = core
924                    .row(index)
925                    .ok_or_else(|| TanStackStateError::UnresolvedRowId {
926                        field,
927                        row_id: row_id.to_string(),
928                    })?;
929                return Ok(row.key);
930            }
931            if let Ok(parsed) = row_id.parse::<u64>() {
932                return Ok(RowKey(parsed));
933            }
934            Err(TanStackStateError::UnresolvedRowId {
935                field,
936                row_id: row_id.to_string(),
937            })
938        }
939
940        let mut base = self.clone();
941        base.expanded = None;
942        base.row_pinning = None;
943        base.row_selection = None;
944
945        let mut out = base.to_table_state()?;
946
947        if let Some(sel) = self.row_selection.as_ref() {
948            out.row_selection = sel
949                .iter()
950                .filter(|(_k, v)| **v)
951                .map(|(k, _)| resolve_row_key(core_row_model, grouped_row_model, "rowSelection", k))
952                .collect::<Result<_, _>>()?;
953        }
954
955        if let Some(expanded) = self.expanded.as_ref() {
956            out.expanding = match expanded {
957                TanStackExpandedState::All(true) => ExpandingState::All,
958                TanStackExpandedState::All(false) => ExpandingState::default(),
959                TanStackExpandedState::Map(map) => ExpandingState::from_iter(
960                    map.iter()
961                        .filter(|(_k, v)| **v)
962                        .map(|(k, _)| {
963                            resolve_row_key(core_row_model, grouped_row_model, "expanded", k)
964                        })
965                        .collect::<Result<Vec<_>, _>>()?,
966                ),
967            };
968        }
969
970        if let Some(pinning) = self.row_pinning.as_ref() {
971            let mut next = RowPinningState::default();
972            for id in &pinning.top {
973                next.top.push(resolve_row_key(
974                    core_row_model,
975                    grouped_row_model,
976                    "rowPinning.top",
977                    id,
978                )?);
979            }
980            for id in &pinning.bottom {
981                next.bottom.push(resolve_row_key(
982                    core_row_model,
983                    grouped_row_model,
984                    "rowPinning.bottom",
985                    id,
986                )?);
987            }
988            out.row_pinning = next;
989        }
990
991        Ok(out)
992    }
993
994    pub fn to_table_state_with_row_model<'a, TData>(
995        &self,
996        core_row_model: &RowModel<'a, TData>,
997    ) -> Result<TableState, TanStackStateError> {
998        self.to_table_state_with_row_models(core_row_model, None)
999    }
1000}