1use std::cmp::Ordering;
2use std::collections::HashMap;
3use std::sync::Arc;
4
5use super::{
6 ColumnDef, ColumnId, RowIndex, RowKey, RowModel, SortCmpFn, SortUndefined, TableOptions,
7 column::{BuiltInSortingFn, SortIsUndefinedFn, SortValueFn, SortingFnSpec, TanStackValue},
8};
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct SortSpec {
12 pub column: ColumnId,
13 pub desc: bool,
14}
15
16pub type SortingState = Vec<SortSpec>;
17
18#[derive(Clone)]
19pub enum SortingFnDef<TData> {
20 BuiltIn(BuiltInSortingFn),
21 Cmp(SortCmpFn<TData>),
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct SortToggleColumn {
30 pub id: ColumnId,
31 pub enable_sorting: bool,
32 pub enable_multi_sort: bool,
33 pub sort_desc_first: Option<bool>,
34 pub has_sort_value_source: bool,
35}
36
37pub fn sort_for_column(sorting: &[SortSpec], id: &str) -> Option<bool> {
38 sorting
39 .iter()
40 .find(|s| s.column.as_ref() == id)
41 .map(|s| s.desc)
42}
43
44pub fn toggle_sort_for_column(sorting: &mut SortingState, column: ColumnId, multi: bool) {
45 let pos = sorting
46 .iter()
47 .position(|s| s.column.as_ref() == column.as_ref());
48
49 let next = match pos.and_then(|i| sorting.get(i).map(|s| s.desc)) {
50 None => Some(false),
51 Some(false) => Some(true),
52 Some(true) => None,
53 };
54
55 if !multi {
56 sorting.clear();
57 if let Some(desc) = next {
58 sorting.push(SortSpec { column, desc });
59 }
60 return;
61 }
62
63 match (pos, next) {
64 (None, Some(desc)) => sorting.push(SortSpec { column, desc }),
65 (Some(i), Some(desc)) => {
66 if let Some(spec) = sorting.get_mut(i) {
67 spec.desc = desc;
68 }
69 }
70 (Some(i), None) => {
71 sorting.remove(i);
72 }
73 (None, None) => {}
74 }
75}
76
77pub fn toggle_sorting_state_tanstack(
92 sorting: &mut SortingState,
93 column: &SortToggleColumn,
94 options: TableOptions,
95 multi: bool,
96 auto_sort_dir_desc: bool,
97) {
98 let existing_index = sorting
99 .iter()
100 .position(|s| s.column.as_ref() == column.id.as_ref());
101 let existing_sorting = existing_index.and_then(|i| sorting.get(i)).cloned();
102
103 let first_sort_direction_desc = column
107 .sort_desc_first
108 .or(options.sort_desc_first)
109 .unwrap_or(auto_sort_dir_desc);
110
111 let is_sorted = existing_sorting
112 .as_ref()
113 .map(|s| if s.desc { "desc" } else { "asc" });
114
115 let enable_sorting_removal = options.enable_sorting_removal;
116
117 let next_sorting_order = match is_sorted {
118 None => Some(if first_sort_direction_desc {
119 "desc"
120 } else {
121 "asc"
122 }),
123 Some(current) => {
124 let first = if first_sort_direction_desc {
125 "desc"
126 } else {
127 "asc"
128 };
129 if current != first && enable_sorting_removal {
130 None
131 } else if current == "desc" {
132 Some("asc")
133 } else {
134 Some("desc")
135 }
136 }
137 };
138
139 let next_desc = next_sorting_order == Some("desc");
140
141 let can_multi_sort = options.enable_multi_sort && column.enable_multi_sort;
142 let multi = multi && can_multi_sort;
143
144 enum SortAction {
145 Add,
146 Remove,
147 Toggle,
148 Replace,
149 }
150
151 let old_len = sorting.len();
152
153 let mut sort_action = if old_len > 0 && multi {
154 if existing_sorting.is_some() {
155 SortAction::Toggle
156 } else {
157 SortAction::Add
158 }
159 } else if old_len > 0 && existing_index.is_some_and(|i| i != old_len.saturating_sub(1)) {
160 SortAction::Replace
161 } else if existing_sorting.is_some() {
162 SortAction::Toggle
163 } else {
164 SortAction::Replace
165 };
166
167 if matches!(sort_action, SortAction::Toggle) && next_sorting_order.is_none() {
168 sort_action = SortAction::Remove;
169 }
170
171 match sort_action {
172 SortAction::Add => {
173 sorting.push(SortSpec {
174 column: column.id.clone(),
175 desc: next_desc,
176 });
177
178 if let Some(max) = options.max_multi_sort_col_count
179 && max > 0
180 && sorting.len() > max
181 {
182 let to_remove = sorting.len().saturating_sub(max);
183 sorting.drain(0..to_remove);
184 }
185 }
186 SortAction::Toggle => {
187 if let Some(i) = existing_index
188 && let Some(spec) = sorting.get_mut(i)
189 {
190 spec.desc = next_desc;
191 }
192 }
193 SortAction::Remove => {
194 if let Some(i) = existing_index {
195 sorting.remove(i);
196 }
197 }
198 SortAction::Replace => {
199 sorting.clear();
200 sorting.push(SortSpec {
201 column: column.id.clone(),
202 desc: next_desc,
203 });
204 }
205 }
206}
207
208pub fn toggle_sorting_tanstack<TData>(
209 sorting: &mut SortingState,
210 column: &ColumnDef<TData>,
211 options: TableOptions,
212 multi: bool,
213 auto_sort_dir_desc: bool,
214) {
215 let col = SortToggleColumn {
216 id: column.id.clone(),
217 enable_sorting: column.enable_sorting,
218 enable_multi_sort: column.enable_multi_sort,
219 sort_desc_first: column.sort_desc_first,
220 has_sort_value_source: column.sort_cmp.is_some() || column.sort_value.is_some(),
221 };
222 toggle_sorting_state_tanstack(sorting, &col, options, multi, auto_sort_dir_desc);
223}
224
225pub fn toggle_sorting_state_handler_tanstack(
231 sorting: &mut SortingState,
232 column: &SortToggleColumn,
233 options: TableOptions,
234 event_multi: bool,
235 auto_sort_dir_desc: bool,
236) {
237 let can_sort = options.enable_sorting && column.enable_sorting && column.has_sort_value_source;
238 if !can_sort {
239 return;
240 }
241
242 let can_multi_sort = options.enable_multi_sort && column.enable_multi_sort;
243 let multi = if can_multi_sort { event_multi } else { false };
244
245 toggle_sorting_state_tanstack(sorting, column, options, multi, auto_sort_dir_desc);
246}
247
248pub fn toggle_sorting_handler_tanstack<TData>(
254 sorting: &mut SortingState,
255 column: &ColumnDef<TData>,
256 options: TableOptions,
257 event_multi: bool,
258 auto_sort_dir_desc: bool,
259) {
260 let col = SortToggleColumn {
261 id: column.id.clone(),
262 enable_sorting: column.enable_sorting,
263 enable_multi_sort: column.enable_multi_sort,
264 sort_desc_first: column.sort_desc_first,
265 has_sort_value_source: column.sort_cmp.is_some() || column.sort_value.is_some(),
266 };
267 toggle_sorting_state_handler_tanstack(sorting, &col, options, event_multi, auto_sort_dir_desc);
268}
269
270fn builtin_sorting_fn_key(key: &str) -> Option<BuiltInSortingFn> {
271 Some(match key {
272 "alphanumeric" => BuiltInSortingFn::Alphanumeric,
273 "alphanumericCaseSensitive" => BuiltInSortingFn::AlphanumericCaseSensitive,
274 "text" => BuiltInSortingFn::Text,
275 "textCaseSensitive" => BuiltInSortingFn::TextCaseSensitive,
276 "datetime" => BuiltInSortingFn::Datetime,
277 "basic" => BuiltInSortingFn::Basic,
278 _ => return None,
279 })
280}
281
282fn strict_equal(a: &TanStackValue, b: &TanStackValue) -> bool {
283 match (a, b) {
284 (TanStackValue::Undefined, TanStackValue::Undefined) => true,
285 (TanStackValue::Null, TanStackValue::Null) => true,
286 (TanStackValue::Bool(a), TanStackValue::Bool(b)) => a == b,
287 (TanStackValue::Number(a), TanStackValue::Number(b)) => {
288 if a.is_nan() || b.is_nan() {
289 false
290 } else {
291 a == b
292 }
293 }
294 (TanStackValue::String(a), TanStackValue::String(b)) => a.as_ref() == b.as_ref(),
295 (TanStackValue::DateTime(a), TanStackValue::DateTime(b)) => {
296 if a.is_nan() || b.is_nan() {
297 false
298 } else {
299 a == b
300 }
301 }
302 _ => false,
303 }
304}
305
306fn to_number_for_compare(value: &TanStackValue) -> f64 {
307 match value {
308 TanStackValue::Undefined => f64::NAN,
309 TanStackValue::Null => 0.0,
310 TanStackValue::Bool(b) => {
311 if *b {
312 1.0
313 } else {
314 0.0
315 }
316 }
317 TanStackValue::Number(v) => *v,
318 TanStackValue::DateTime(v) => *v,
319 TanStackValue::String(s) => {
320 let trimmed = s.as_ref().trim();
321 if trimmed.is_empty() {
322 0.0
323 } else {
324 trimmed.parse::<f64>().unwrap_or(f64::NAN)
325 }
326 }
327 TanStackValue::Array(_) => f64::NAN,
328 }
329}
330
331fn abstract_gt(a: &TanStackValue, b: &TanStackValue) -> bool {
332 match (a, b) {
333 (TanStackValue::String(a), TanStackValue::String(b)) => a.as_ref() > b.as_ref(),
334 _ => {
335 let a = to_number_for_compare(a);
336 let b = to_number_for_compare(b);
337 if a.is_nan() || b.is_nan() {
338 false
339 } else {
340 a > b
341 }
342 }
343 }
344}
345
346fn abstract_lt(a: &TanStackValue, b: &TanStackValue) -> bool {
347 match (a, b) {
348 (TanStackValue::String(a), TanStackValue::String(b)) => a.as_ref() < b.as_ref(),
349 _ => {
350 let a = to_number_for_compare(a);
351 let b = to_number_for_compare(b);
352 if a.is_nan() || b.is_nan() {
353 false
354 } else {
355 a < b
356 }
357 }
358 }
359}
360
361fn compare_basic_tanstack(a: &TanStackValue, b: &TanStackValue) -> Ordering {
362 if strict_equal(a, b) {
363 return Ordering::Equal;
364 }
365 if abstract_gt(a, b) {
366 Ordering::Greater
367 } else {
368 Ordering::Less
369 }
370}
371
372fn to_string_for_sorting(value: &TanStackValue) -> String {
373 match value {
374 TanStackValue::Number(v) => {
375 if v.is_nan() || v.is_infinite() {
376 String::new()
377 } else {
378 v.to_string()
379 }
380 }
381 TanStackValue::String(s) => s.as_ref().to_string(),
382 _ => String::new(),
383 }
384}
385
386fn split_alpha_numeric_tokens(s: &str) -> Vec<&str> {
387 let mut out: Vec<&str> = Vec::new();
388 let mut start: Option<usize> = None;
389 let mut current_is_digit: Option<bool> = None;
390
391 for (i, ch) in s.char_indices() {
392 let is_digit = ch.is_ascii_digit();
393 match (start, current_is_digit) {
394 (None, None) => {
395 start = Some(i);
396 current_is_digit = Some(is_digit);
397 }
398 (Some(_), Some(kind)) if kind == is_digit => {}
399 (Some(start_idx), Some(_)) => {
400 if start_idx != i {
401 out.push(&s[start_idx..i]);
402 }
403 start = Some(i);
404 current_is_digit = Some(is_digit);
405 }
406 _ => {}
407 }
408 }
409
410 if let (Some(start_idx), Some(_)) = (start, current_is_digit)
411 && start_idx < s.len()
412 {
413 out.push(&s[start_idx..]);
414 }
415
416 out.into_iter().filter(|seg| !seg.is_empty()).collect()
417}
418
419fn compare_alphanumeric(a_str: &str, b_str: &str) -> Ordering {
420 let a = split_alpha_numeric_tokens(a_str);
421 let b = split_alpha_numeric_tokens(b_str);
422
423 let mut ai = 0usize;
424 let mut bi = 0usize;
425
426 while ai < a.len() && bi < b.len() {
427 let aa = a[ai];
428 let bb = b[bi];
429 ai += 1;
430 bi += 1;
431
432 let an = aa.parse::<i64>().ok();
433 let bn = bb.parse::<i64>().ok();
434
435 match (an, bn) {
436 (None, None) => {
437 if aa > bb {
438 return Ordering::Greater;
439 }
440 if bb > aa {
441 return Ordering::Less;
442 }
443 }
444 (Some(_), None) => return Ordering::Greater,
445 (None, Some(_)) => return Ordering::Less,
446 (Some(an), Some(bn)) => {
447 if an > bn {
448 return Ordering::Greater;
449 }
450 if bn > an {
451 return Ordering::Less;
452 }
453 }
454 }
455 }
456
457 a.len().cmp(&b.len())
458}
459
460fn auto_sorting_fn_builtin<'a, TData>(
461 row_model: &RowModel<'a, TData>,
462 column: &ColumnDef<TData>,
463) -> BuiltInSortingFn {
464 let Some(get_value) = column.sort_value.as_ref() else {
465 return BuiltInSortingFn::Basic;
466 };
467
468 let mut saw_string = false;
470 for &row_index in row_model.flat_rows().iter().skip(10) {
471 let Some(row) = row_model.row(row_index) else {
472 continue;
473 };
474 let value = (get_value)(row.original);
475
476 if matches!(value, TanStackValue::DateTime(_)) {
477 return BuiltInSortingFn::Datetime;
478 }
479
480 if let TanStackValue::String(s) = value {
481 saw_string = true;
482 if split_alpha_numeric_tokens(s.as_ref()).len() > 1 {
483 return BuiltInSortingFn::Alphanumeric;
484 }
485 }
486 }
487
488 if saw_string {
489 BuiltInSortingFn::Text
490 } else {
491 BuiltInSortingFn::Basic
492 }
493}
494
495fn compare_builtin_sort<TData>(
496 kind: BuiltInSortingFn,
497 get_value: &SortValueFn<TData>,
498 a: &TData,
499 b: &TData,
500) -> Ordering {
501 let a = (get_value)(a);
502 let b = (get_value)(b);
503
504 match kind {
505 BuiltInSortingFn::Basic => compare_basic_tanstack(&a, &b),
506 BuiltInSortingFn::Datetime => {
507 if abstract_gt(&a, &b) {
508 Ordering::Greater
509 } else if abstract_lt(&a, &b) {
510 Ordering::Less
511 } else {
512 Ordering::Equal
513 }
514 }
515 BuiltInSortingFn::Text => {
516 let a = to_string_for_sorting(&a).to_lowercase();
517 let b = to_string_for_sorting(&b).to_lowercase();
518 if a == b {
519 Ordering::Equal
520 } else if a > b {
521 Ordering::Greater
522 } else {
523 Ordering::Less
524 }
525 }
526 BuiltInSortingFn::TextCaseSensitive => {
527 let a = to_string_for_sorting(&a);
528 let b = to_string_for_sorting(&b);
529 if a == b {
530 Ordering::Equal
531 } else if a > b {
532 Ordering::Greater
533 } else {
534 Ordering::Less
535 }
536 }
537 BuiltInSortingFn::Alphanumeric => {
538 let a = to_string_for_sorting(&a).to_lowercase();
539 let b = to_string_for_sorting(&b).to_lowercase();
540 compare_alphanumeric(&a, &b)
541 }
542 BuiltInSortingFn::AlphanumericCaseSensitive => {
543 let a = to_string_for_sorting(&a);
544 let b = to_string_for_sorting(&b);
545 compare_alphanumeric(&a, &b)
546 }
547 }
548}
549
550#[derive(Clone)]
551enum ResolvedSortCmp<TData> {
552 Cmp(SortCmpFn<TData>),
553 BuiltIn {
554 kind: BuiltInSortingFn,
555 get_value: SortValueFn<TData>,
556 },
557}
558
559#[derive(Clone)]
560enum ResolvedIsUndefined<TData> {
561 Fn(SortIsUndefinedFn<TData>),
562 Value(SortValueFn<TData>),
563}
564
565fn resolve_sort_cmp_for_column<'a, TData>(
566 row_model: &RowModel<'a, TData>,
567 column: &ColumnDef<TData>,
568 sorting_fns: &HashMap<Arc<str>, SortingFnDef<TData>>,
569) -> Option<ResolvedSortCmp<TData>> {
570 if let Some(cmp) = column.sort_cmp.as_ref() {
571 return Some(ResolvedSortCmp::Cmp(cmp.clone()));
572 }
573
574 let spec = column.sorting_fn.as_ref()?;
575
576 match spec {
577 SortingFnSpec::Auto => {
578 let get_value = column.sort_value.as_ref()?.clone();
579 let builtin = auto_sorting_fn_builtin(row_model, column);
580 Some(ResolvedSortCmp::BuiltIn {
581 kind: builtin,
582 get_value,
583 })
584 }
585 SortingFnSpec::BuiltIn(builtin) => {
586 let get_value = column.sort_value.as_ref()?.clone();
587 Some(ResolvedSortCmp::BuiltIn {
588 kind: *builtin,
589 get_value,
590 })
591 }
592 SortingFnSpec::Named(key) => {
593 if let Some(def) = sorting_fns.get(key.as_ref()) {
594 return Some(match def {
595 SortingFnDef::Cmp(cmp) => ResolvedSortCmp::Cmp(cmp.clone()),
596 SortingFnDef::BuiltIn(builtin) => ResolvedSortCmp::BuiltIn {
597 kind: *builtin,
598 get_value: column.sort_value.as_ref()?.clone(),
599 },
600 });
601 }
602
603 let builtin = builtin_sorting_fn_key(key.as_ref())?;
604 let get_value = column.sort_value.as_ref()?.clone();
605 Some(ResolvedSortCmp::BuiltIn {
606 kind: builtin,
607 get_value,
608 })
609 }
610 }
611}
612
613pub fn sort_row_model<'a, TData>(
614 row_model: &RowModel<'a, TData>,
615 columns: &[ColumnDef<TData>],
616 sorting: &[SortSpec],
617 sorting_fns: &HashMap<Arc<str>, SortingFnDef<TData>>,
618) -> RowModel<'a, TData> {
619 if sorting.is_empty() || row_model.root_rows().is_empty() {
620 return row_model.clone();
621 }
622
623 #[derive(Clone)]
624 struct ResolvedSortColumn<TData> {
625 cmp: ResolvedSortCmp<TData>,
626 invert_sorting: bool,
627 sort_undefined: Option<SortUndefined>,
628 sort_is_undefined: Option<ResolvedIsUndefined<TData>>,
629 }
630
631 let sort_cfg_by_id: HashMap<&str, ResolvedSortColumn<TData>> = columns
632 .iter()
633 .filter_map(|c| {
634 let cmp = resolve_sort_cmp_for_column(row_model, c, sorting_fns)?;
635
636 let sort_undefined = c.sort_undefined.or_else(|| {
641 if c.sort_value.is_some() {
642 Some(SortUndefined::Dir(1))
643 } else {
644 None
645 }
646 });
647
648 let sort_is_undefined = c
649 .sort_is_undefined
650 .clone()
651 .map(ResolvedIsUndefined::Fn)
652 .or_else(|| {
653 if sort_undefined.is_some() {
654 c.sort_value.clone().map(ResolvedIsUndefined::Value)
655 } else {
656 None
657 }
658 });
659
660 Some((
661 c.id.as_ref(),
662 ResolvedSortColumn {
663 cmp,
664 invert_sorting: c.invert_sorting,
665 sort_undefined,
666 sort_is_undefined,
667 },
668 ))
669 })
670 .collect();
671
672 let mut out = row_model.clone();
673
674 fn tiebreaker(a: RowKey, b: RowKey) -> Ordering {
675 a.cmp(&b)
676 }
677
678 fn cmp_rows<TData>(
679 arena: &[super::Row<'_, TData>],
680 sort_cfg_by_id: &HashMap<&str, ResolvedSortColumn<TData>>,
681 sorting: &[SortSpec],
682 a: RowIndex,
683 b: RowIndex,
684 ) -> Ordering {
685 let Some(a_row) = arena.get(a) else {
686 return Ordering::Equal;
687 };
688 let Some(b_row) = arena.get(b) else {
689 return Ordering::Equal;
690 };
691
692 for spec in sorting {
693 let Some(cfg) = sort_cfg_by_id.get(spec.column.as_ref()) else {
694 continue;
695 };
696
697 let mut ord = Ordering::Equal;
698 if let Some(sort_undefined) = cfg.sort_undefined
699 && !matches!(sort_undefined, SortUndefined::Disabled)
700 && let Some(is_undefined) = cfg.sort_is_undefined.as_ref()
701 {
702 let a_undefined = match is_undefined {
703 ResolvedIsUndefined::Fn(f) => f(a_row.original),
704 ResolvedIsUndefined::Value(get_value) => {
705 matches!((get_value)(a_row.original), TanStackValue::Undefined)
706 }
707 };
708 let b_undefined = match is_undefined {
709 ResolvedIsUndefined::Fn(f) => f(b_row.original),
710 ResolvedIsUndefined::Value(get_value) => {
711 matches!((get_value)(b_row.original), TanStackValue::Undefined)
712 }
713 };
714
715 if a_undefined || b_undefined {
716 ord = match sort_undefined {
717 SortUndefined::First => {
718 if a_undefined == b_undefined {
719 Ordering::Equal
720 } else if a_undefined {
721 Ordering::Less
722 } else {
723 Ordering::Greater
724 }
725 }
726 SortUndefined::Last => {
727 if a_undefined == b_undefined {
728 Ordering::Equal
729 } else if a_undefined {
730 Ordering::Greater
731 } else {
732 Ordering::Less
733 }
734 }
735 SortUndefined::Dir(dir) => {
736 if a_undefined == b_undefined {
737 Ordering::Equal
738 } else if a_undefined {
739 if dir < 0 {
740 Ordering::Less
741 } else {
742 Ordering::Greater
743 }
744 } else if dir < 0 {
745 Ordering::Greater
746 } else {
747 Ordering::Less
748 }
749 }
750 SortUndefined::Disabled => Ordering::Equal,
751 };
752
753 if matches!(sort_undefined, SortUndefined::First | SortUndefined::Last)
755 && ord != Ordering::Equal
756 {
757 return ord;
758 }
759 }
760 }
761
762 if ord == Ordering::Equal {
763 ord = match &cfg.cmp {
764 ResolvedSortCmp::Cmp(cmp) => cmp(a_row.original, b_row.original),
765 ResolvedSortCmp::BuiltIn { kind, get_value } => {
766 compare_builtin_sort(*kind, get_value, a_row.original, b_row.original)
767 }
768 };
769 }
770
771 if ord != Ordering::Equal {
772 if spec.desc {
773 ord = ord.reverse();
774 }
775 if cfg.invert_sorting {
776 ord = ord.reverse();
777 }
778 return ord;
779 }
780 }
781
782 tiebreaker(a_row.key, b_row.key)
783 }
784
785 fn sort_children<TData>(
786 row_model: &mut RowModel<'_, TData>,
787 sort_cfg_by_id: &HashMap<&str, ResolvedSortColumn<TData>>,
788 sorting: &[SortSpec],
789 row: RowIndex,
790 ) {
791 let Some(children) = row_model.row(row).map(|r| r.sub_rows.clone()) else {
792 return;
793 };
794 let arena = row_model.arena.as_slice();
795 let mut sorted = children;
796 sorted.sort_by(|&a, &b| cmp_rows(arena, sort_cfg_by_id, sorting, a, b));
797
798 if let Some(r) = row_model.arena.get_mut(row) {
799 r.sub_rows = sorted.clone();
800 }
801
802 for child in sorted {
803 sort_children(row_model, sort_cfg_by_id, sorting, child);
804 }
805 }
806
807 let arena = out.arena.as_slice();
808 out.root_rows
809 .sort_by(|&a, &b| cmp_rows(arena, &sort_cfg_by_id, sorting, a, b));
810 let roots = out.root_rows.clone();
811 for root in roots {
812 sort_children(&mut out, &sort_cfg_by_id, sorting, root);
813 }
814
815 out.flat_rows.clear();
816 fn push_flat<TData>(row_model: &mut RowModel<'_, TData>, row: RowIndex) {
817 row_model.flat_rows.push(row);
818 let Some(r) = row_model.row(row) else {
819 return;
820 };
821 let children = r.sub_rows.clone();
822 for child in children {
823 push_flat(row_model, child);
824 }
825 }
826 let roots = out.root_rows.clone();
827 for root in roots {
828 push_flat(&mut out, root);
829 }
830
831 out
832}
833
834#[cfg(test)]
835mod tests {
836 use super::*;
837 use crate::table::{Table, TableOptions, create_column_helper};
838
839 #[derive(Debug, Clone)]
840 struct Item {
841 value: i32,
842 }
843
844 #[test]
845 fn sort_row_model_sorts_root_rows_by_spec() {
846 let data = vec![Item { value: 2 }, Item { value: 1 }, Item { value: 3 }];
847 let table = Table::builder(&data).build();
848 let core = table.core_row_model();
849
850 let helper = create_column_helper::<Item>();
851 let columns = vec![helper.accessor("value", |it| it.value)];
852 let sorting = vec![SortSpec {
853 column: "value".into(),
854 desc: false,
855 }];
856
857 let sorting_fns = HashMap::new();
858 let sorted = sort_row_model(core, &columns, &sorting, &sorting_fns);
859 let ids = sorted
860 .root_rows()
861 .iter()
862 .filter_map(|&i| sorted.row(i).map(|r| r.key.0))
863 .collect::<Vec<_>>();
864
865 assert_eq!(ids, vec![1, 0, 2]);
866 }
867
868 #[test]
869 fn sort_row_model_respects_descending() {
870 let data = vec![Item { value: 2 }, Item { value: 1 }, Item { value: 3 }];
871 let table = Table::builder(&data).build();
872 let core = table.core_row_model();
873
874 let helper = create_column_helper::<Item>();
875 let columns = vec![helper.accessor("value", |it| it.value)];
876 let sorting = vec![SortSpec {
877 column: "value".into(),
878 desc: true,
879 }];
880
881 let sorting_fns = HashMap::new();
882 let sorted = sort_row_model(core, &columns, &sorting, &sorting_fns);
883 let ids = sorted
884 .root_rows()
885 .iter()
886 .filter_map(|&i| sorted.row(i).map(|r| r.key.0))
887 .collect::<Vec<_>>();
888
889 assert_eq!(ids, vec![2, 0, 1]);
890 }
891
892 #[test]
893 fn sort_row_model_uses_row_key_tiebreaker_for_determinism() {
894 let data = vec![Item { value: 1 }, Item { value: 1 }, Item { value: 1 }];
895 let table = Table::builder(&data).build();
896 let core = table.core_row_model();
897
898 let helper = create_column_helper::<Item>();
899 let columns = vec![helper.accessor("value", |it| it.value)];
900 let sorting = vec![SortSpec {
901 column: "value".into(),
902 desc: false,
903 }];
904
905 let sorting_fns = HashMap::new();
906 let sorted = sort_row_model(core, &columns, &sorting, &sorting_fns);
907 let ids = sorted
908 .root_rows()
909 .iter()
910 .filter_map(|&i| sorted.row(i).map(|r| r.key.0))
911 .collect::<Vec<_>>();
912
913 assert_eq!(ids, vec![0, 1, 2]);
914 }
915
916 #[test]
917 fn sort_row_model_inverts_sorting_when_column_requests_it() {
918 let data = vec![Item { value: 2 }, Item { value: 1 }, Item { value: 3 }];
919 let table = Table::builder(&data).build();
920 let core = table.core_row_model();
921
922 let helper = create_column_helper::<Item>();
923 let columns = vec![helper.accessor("value", |it| it.value).invert_sorting(true)];
924 let sorting = vec![SortSpec {
925 column: "value".into(),
926 desc: false,
927 }];
928
929 let sorting_fns = HashMap::new();
930 let sorted = sort_row_model(core, &columns, &sorting, &sorting_fns);
931 let ids = sorted
932 .root_rows()
933 .iter()
934 .filter_map(|&i| sorted.row(i).map(|r| r.key.0))
935 .collect::<Vec<_>>();
936
937 assert_eq!(ids, vec![2, 0, 1]);
938 }
939
940 #[test]
941 fn sort_row_model_invert_sorting_composes_with_descending() {
942 let data = vec![Item { value: 2 }, Item { value: 1 }, Item { value: 3 }];
943 let table = Table::builder(&data).build();
944 let core = table.core_row_model();
945
946 let helper = create_column_helper::<Item>();
947 let columns = vec![helper.accessor("value", |it| it.value).invert_sorting(true)];
948 let sorting = vec![SortSpec {
949 column: "value".into(),
950 desc: true,
951 }];
952
953 let sorting_fns = HashMap::new();
954 let sorted = sort_row_model(core, &columns, &sorting, &sorting_fns);
955 let ids = sorted
956 .root_rows()
957 .iter()
958 .filter_map(|&i| sorted.row(i).map(|r| r.key.0))
959 .collect::<Vec<_>>();
960
961 assert_eq!(ids, vec![1, 0, 2]);
962 }
963
964 #[test]
965 fn toggle_sort_for_column_cycles_and_resets_when_single() {
966 let mut sorting: SortingState = Vec::new();
967
968 toggle_sort_for_column(&mut sorting, "a".into(), false);
969 assert_eq!(sorting.len(), 1);
970 assert_eq!(sorting[0].column.as_ref(), "a");
971 assert!(!sorting[0].desc);
972
973 toggle_sort_for_column(&mut sorting, "a".into(), false);
974 assert_eq!(sorting.len(), 1);
975 assert!(sorting[0].desc);
976
977 toggle_sort_for_column(&mut sorting, "a".into(), false);
978 assert!(sorting.is_empty());
979
980 toggle_sort_for_column(&mut sorting, "b".into(), false);
981 assert_eq!(sorting.len(), 1);
982 assert_eq!(sorting[0].column.as_ref(), "b");
983 }
984
985 #[test]
986 fn toggle_sorting_tanstack_respects_sort_desc_first() {
987 let mut sorting: SortingState = Vec::new();
988
989 let helper = create_column_helper::<Item>();
990 let column = helper
991 .accessor("value", |it| it.value)
992 .sort_desc_first(true);
993
994 toggle_sorting_tanstack(&mut sorting, &column, TableOptions::default(), false, false);
995 assert_eq!(sorting.len(), 1);
996 assert_eq!(sorting[0].column.as_ref(), "value");
997 assert!(sorting[0].desc);
998
999 toggle_sorting_tanstack(&mut sorting, &column, TableOptions::default(), false, false);
1000 assert_eq!(sorting.len(), 1);
1001 assert!(!sorting[0].desc);
1002
1003 toggle_sorting_tanstack(&mut sorting, &column, TableOptions::default(), false, false);
1004 assert!(sorting.is_empty());
1005 }
1006}