Skip to main content

vizia_core/views/
tree_table.rs

1use accesskit::SortDirection as AccessSortDirection;
2use std::{
3    collections::{HashMap, HashSet},
4    hash::Hash,
5    ops::Deref,
6    rc::Rc,
7    sync::Arc,
8};
9
10use crate::{
11    icons::{ICON_CHEVRON_DOWN, ICON_CHEVRON_RIGHT},
12    prelude::*,
13};
14
15use super::{
16    TableSortCycle, TableSortDirection, TableSortState, table::next_sort_direction,
17    table::sort_direction_for_column,
18};
19
20/// Determines which cells are selected when clicking a cell in the tree table.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum TableSelectionMode {
23    /// Clicking a cell selects only that cell.
24    Cell,
25    /// Clicking a cell selects the entire row.
26    Row,
27}
28
29impl_res_simple!(TableSelectionMode);
30
31#[derive(Clone, PartialEq)]
32enum TableFocus<Id, K>
33where
34    Id: Eq + Hash + Clone + Send + Sync + 'static,
35    K: Clone + PartialEq + Send + Sync + 'static,
36{
37    Row(Id),
38    Cell(Id, K),
39}
40
41#[derive(Clone, PartialEq, Eq, Hash)]
42pub struct Cell<Id, K>(pub Id, pub K)
43where
44    Id: Eq + Hash + Clone + Send + Sync + 'static,
45    K: Eq + Hash + Clone + PartialEq + Send + Sync + 'static;
46
47#[derive(Clone, PartialEq)]
48pub struct TreeNodeRow<Id>
49where
50    Id: Clone + Eq + Hash + 'static,
51{
52    pub id: Id,
53    pub parent_id: Option<Id>,
54}
55
56#[derive(Clone, PartialEq)]
57pub struct TreeTableRow<T, Id>
58where
59    T: PartialEq + Clone + 'static,
60    Id: Clone + Eq + Hash + 'static,
61{
62    pub row: T,
63    pub id: Id,
64    pub parent_id: Option<Id>,
65    pub depth: usize,
66    pub has_children: bool,
67    pub expanded: bool,
68}
69
70fn flatten_visible_rows<T, V, Id>(
71    rows: &V,
72    row_id: &dyn Fn(&T) -> Id,
73    parent_id: &dyn Fn(&T) -> Option<Id>,
74    expanded_row_ids: &[Id],
75) -> Vec<TreeTableRow<T, Id>>
76where
77    V: Deref<Target = [T]>,
78    T: PartialEq + Clone + 'static,
79    Id: Clone + Eq + Hash + 'static,
80{
81    let mut rows_by_parent: HashMap<Option<Id>, Vec<T>> = HashMap::new();
82    for row in rows.deref().iter().cloned() {
83        rows_by_parent.entry(parent_id(&row)).or_default().push(row);
84    }
85
86    let expanded_set: HashSet<Id> = expanded_row_ids.iter().cloned().collect();
87    let mut visible_rows = Vec::new();
88
89    fn visit<T, Id>(
90        rows: &[T],
91        depth: usize,
92        rows_by_parent: &HashMap<Option<Id>, Vec<T>>,
93        expanded_set: &HashSet<Id>,
94        row_id: &dyn Fn(&T) -> Id,
95        out: &mut Vec<TreeTableRow<T, Id>>,
96    ) where
97        T: PartialEq + Clone + 'static,
98        Id: Clone + Eq + Hash + 'static,
99    {
100        for row in rows {
101            let id = row_id(row);
102            let child_rows =
103                rows_by_parent.get(&Some(id.clone())).map(Vec::as_slice).unwrap_or(&[]);
104            let has_children = !child_rows.is_empty();
105            let expanded = has_children && expanded_set.contains(&id);
106
107            out.push(TreeTableRow {
108                row: row.clone(),
109                id: id.clone(),
110                parent_id: None,
111                depth,
112                has_children,
113                expanded,
114            });
115
116            if expanded {
117                visit(child_rows, depth + 1, rows_by_parent, expanded_set, row_id, out);
118            }
119        }
120    }
121
122    let roots = rows_by_parent.get(&None).map(Vec::as_slice).unwrap_or(&[]);
123    visit(roots, 0, &rows_by_parent, &expanded_set, row_id, &mut visible_rows);
124
125    for visible_row in &mut visible_rows {
126        visible_row.parent_id = parent_id(&visible_row.row);
127    }
128
129    visible_rows
130}
131
132fn flatten_hierarchy_rows<U, Id>(
133    tree: &U,
134    root_ids: &dyn Fn(&U) -> Vec<Id>,
135    child_ids: &dyn Fn(&U, &Id) -> Vec<Id>,
136    is_visible: &dyn Fn(&U, &Id) -> bool,
137) -> Vec<TreeNodeRow<Id>>
138where
139    Id: Clone + Eq + Hash + 'static,
140{
141    fn visit<U, Id>(
142        tree: &U,
143        node_id: Id,
144        parent_id: Option<Id>,
145        child_ids: &dyn Fn(&U, &Id) -> Vec<Id>,
146        is_visible: &dyn Fn(&U, &Id) -> bool,
147        out: &mut Vec<TreeNodeRow<Id>>,
148    ) where
149        Id: Clone + Eq + Hash + 'static,
150    {
151        if !is_visible(tree, &node_id) {
152            return;
153        }
154
155        out.push(TreeNodeRow { id: node_id.clone(), parent_id });
156
157        for child_id in child_ids(tree, &node_id) {
158            visit(tree, child_id, Some(node_id.clone()), child_ids, is_visible, out);
159        }
160    }
161
162    let mut rows = Vec::new();
163    for root_id in root_ids(tree) {
164        visit(tree, root_id, None, child_ids, is_visible, &mut rows);
165    }
166
167    rows
168}
169
170/// Event emitted by [`TreeTableFirstCell`] when the disclosure button is pressed.
171pub enum TreeTableFirstCellEvent<Id: Send + Sync + 'static> {
172    Toggle(Id, bool),
173}
174
175#[derive(Clone)]
176pub struct TreeTableFirstCell {
177    offset: Signal<f32>,
178}
179
180impl TreeTableFirstCell {
181    /// Creates the tree-column first cell, showing an indent spacer, disclosure toggle, and
182    /// the caller-supplied cell content. Call this inside the first column's `cell_content`
183    /// closure of a [`TreeTableColumn`].
184    pub fn new<T, Id, F>(
185        cx: &mut Context,
186        row: TreeTableRow<T, Id>,
187        cell_content: F,
188    ) -> Handle<'_, Self>
189    where
190        T: PartialEq + Clone + 'static,
191        Id: PartialEq + Eq + Hash + Clone + Send + Sync + 'static,
192        F: Fn(&mut Context, TreeTableRow<T, Id>) + 'static,
193    {
194        let cell_content: Rc<dyn Fn(&mut Context, TreeTableRow<T, Id>)> = Rc::new(cell_content);
195        let offset = Signal::new(16.0);
196        let depth = offset.map(move |value| Pixels(row.depth as f32 * value));
197        let has_children = row.has_children;
198        let expanded = row.expanded;
199        let node_id = row.id.clone();
200
201        Self { offset }
202            .build(cx, move |cx| {
203                HStack::new(cx, move |cx| {
204                    Element::new(cx)
205                        .class("tree-table-indent")
206                        .width(depth)
207                        .height(Stretch(1.0))
208                        .pointer_events(PointerEvents::None);
209
210                    if has_children {
211                        let icon = if expanded { ICON_CHEVRON_DOWN } else { ICON_CHEVRON_RIGHT };
212
213                        Button::new(cx, move |cx| Svg::new(cx, icon).text_wrap(false))
214                            .variant(ButtonVariant::Text)
215                            .class("tree-table-disclosure")
216                            .pointer_events(PointerEvents::Auto)
217                            .navigable(false)
218                            .on_press(move |cx| {
219                                cx.emit(TreeTableFirstCellEvent::Toggle(
220                                    node_id.clone(),
221                                    !expanded,
222                                ));
223                            });
224                    } else {
225                        Element::new(cx)
226                            .class("tree-table-disclosure-placeholder")
227                            .pointer_events(PointerEvents::None);
228                    }
229
230                    let cell_content = cell_content.clone();
231                    VStack::new(cx, move |cx| {
232                        cell_content(cx, row.clone());
233                    })
234                    .class("tree-table-cell-content")
235                    .width(Stretch(1.0))
236                    .min_width(Auto)
237                    .height(Auto)
238                    .pointer_events(PointerEvents::None);
239                })
240                .alignment(Alignment::Left)
241                .width(Stretch(1.0))
242                .min_width(Auto)
243                .height(Auto)
244                .pointer_events(PointerEvents::None);
245            })
246            .width(Stretch(1.0))
247            .height(Auto)
248            .pointer_events(PointerEvents::None)
249    }
250}
251
252impl View for TreeTableFirstCell {
253    fn element(&self) -> Option<&'static str> {
254        Some("tree-table-first-cell")
255    }
256}
257
258pub trait TreeTableFirstCellModifiers {
259    fn offset<U: Into<f32> + Clone + 'static>(self, sort_state: impl Res<U> + 'static) -> Self;
260}
261
262impl TreeTableFirstCellModifiers for Handle<'_, TreeTableFirstCell> {
263    fn offset<U: Into<f32> + Clone + 'static>(self, offset: impl Res<U> + 'static) -> Self {
264        let offset_signal = offset.to_signal(self.cx);
265        self.bind(offset_signal, move |handle| {
266            let offset = offset_signal.get().into();
267            handle.modify(|tree_table_first_cell| {
268                tree_table_first_cell.offset.set(offset);
269            });
270        })
271    }
272}
273
274type TreeTableHeaderContent<H> = dyn Fn(&mut Context, Memo<TableSortDirection>) -> Handle<H>;
275type TreeTableCellContent<T, Id> = dyn Fn(&mut Context, Memo<TreeTableRow<T, Id>>);
276
277/// A column definition for [`TreeTable`]. All cell content closures receive the full
278/// [`TreeTableRow`] so the first column can call [`TreeTableFirstCell::new`] directly.
279pub struct TreeTableColumn<T, Id, H, K = String>
280where
281    T: PartialEq + Clone + 'static,
282    Id: Clone + Eq + Hash + 'static,
283    H: View,
284    K: Eq + Hash + Clone + PartialEq + Send + Sync + 'static,
285{
286    pub key: K,
287    pub width: Signal<Units>,
288    pub min_width: Signal<f32>,
289    pub sortable: Signal<bool>,
290    pub resizable: Signal<bool>,
291    pub hidden: Signal<bool>,
292    pub cell_content: Rc<TreeTableCellContent<T, Id>>,
293    pub header_content: Rc<TreeTableHeaderContent<H>>,
294}
295
296impl<T, Id, H, K> Clone for TreeTableColumn<T, Id, H, K>
297where
298    T: PartialEq + Clone + 'static,
299    Id: Clone + Eq + Hash + 'static,
300    H: View + Clone,
301    K: Eq + Hash + Clone + PartialEq + Send + Sync + 'static,
302{
303    fn clone(&self) -> Self {
304        Self {
305            key: self.key.clone(),
306            width: self.width,
307            min_width: self.min_width,
308            sortable: self.sortable,
309            resizable: self.resizable,
310            hidden: self.hidden,
311            cell_content: self.cell_content.clone(),
312            header_content: self.header_content.clone(),
313        }
314    }
315}
316
317impl<T, Id, H, K> TreeTableColumn<T, Id, H, K>
318where
319    T: PartialEq + Clone + 'static,
320    Id: Clone + Eq + Hash + 'static,
321    H: View + Clone,
322    K: Eq + Hash + Clone + PartialEq + Send + Sync + 'static,
323{
324    pub fn new(
325        key: impl Into<K>,
326        header_content: impl Fn(&mut Context, Memo<TableSortDirection>) -> Handle<H> + 'static,
327        cell_content: impl Fn(&mut Context, Memo<TreeTableRow<T, Id>>) + 'static,
328    ) -> Self {
329        Self {
330            key: key.into(),
331            width: Signal::new(Pixels(150.0)),
332            min_width: Signal::new(60.0),
333            sortable: Signal::new(true),
334            resizable: Signal::new(false),
335            hidden: Signal::new(false),
336            cell_content: Rc::new(cell_content),
337            header_content: Rc::new(header_content),
338        }
339    }
340
341    pub fn width(self, width: Units) -> Self {
342        self.width.set(match width {
343            Pixels(px) => Pixels(px.max(self.min_width.get_untracked())),
344            Percentage(pct) => Percentage(pct.clamp(0.0, 100.0)),
345            _ => width,
346        });
347        self
348    }
349
350    pub fn min_width(self, min_width: f32) -> Self {
351        self.min_width.set(min_width);
352        if let Pixels(width) = self.width.get_untracked() {
353            self.width.set(Pixels(width.max(min_width)));
354        }
355        self
356    }
357
358    pub fn sortable(self, sortable: bool) -> Self {
359        self.sortable.set(sortable);
360        self
361    }
362
363    pub fn resizable(self, resizable: bool) -> Self {
364        self.resizable.set(resizable);
365        self
366    }
367
368    pub fn hidden(self, hidden: bool) -> Self {
369        self.hidden.set(hidden);
370        self
371    }
372}
373
374pub struct TreeTable<T, V, Id, K = String>
375where
376    V: Deref<Target = [T]> + Clone + 'static,
377    T: PartialEq + Clone + 'static,
378    Id: Eq + Hash + Clone + Send + Sync + 'static,
379    K: Eq + Hash + Clone + PartialEq + Send + Sync + 'static,
380{
381    rows: Signal<V>,
382    visible_rows: Memo<Vec<TreeTableRow<T, Id>>>,
383    columns: Memo<Vec<K>>,
384    row_id: Rc<dyn Fn(&T) -> Id>,
385    parent_id: Rc<dyn Fn(&T) -> Option<Id>>,
386    sort_state: Signal<Option<TableSortState<K>>>,
387    sort_cycle: Signal<TableSortCycle>,
388    resizable_columns: Signal<bool>,
389    selectable: Signal<Selectable>,
390    selection_follows_focus: Signal<bool>,
391    selection: Signal<HashSet<Cell<Id, K>>>,
392    selection_mode: Signal<TableSelectionMode>,
393    expanded_row_ids: Signal<Vec<Id>>,
394    focused: Signal<Option<TableFocus<Id, K>>>,
395    treegrid_label: Signal<Option<String>>,
396    on_sort: Option<Arc<dyn Fn(&mut EventContext, K, TableSortDirection) + Send + Sync>>,
397    on_select: Option<Box<dyn Fn(&mut EventContext, HashSet<Cell<Id, K>>) + Send + Sync>>,
398    on_row_toggle: Option<Box<dyn Fn(&mut EventContext, Id, bool)>>,
399}
400
401pub enum TreeTableEvent<K, Id = String> {
402    RequestSort(K, TableSortDirection),
403    SelectColumn(K),
404    SelectCell(Id, K),
405    SelectRow(usize),
406    SelectFocused,
407    ExpandSelected,
408    FocusRight,
409    FocusLeft,
410    FocusUp,
411    FocusDown,
412    PageUp,
413    PageDown,
414    FocusHome,
415    FocusEnd,
416    CtrlHome,
417    CtrlEnd,
418}
419
420impl<T, V, Id, K> TreeTable<T, V, Id, K>
421where
422    V: Deref<Target = [T]> + Clone + 'static,
423    T: PartialEq + Clone + 'static,
424    Id: Eq + Hash + Clone + Send + Sync + 'static,
425    K: Eq + Hash + Clone + PartialEq + Send + Sync + 'static,
426{
427    pub fn new<S, U, C, R, H, F>(
428        cx: &mut Context,
429        tree: S,
430        columns: C,
431        flatten_rows: F,
432        row_id: impl Fn(&T) -> Id + 'static,
433        parent_id: impl Fn(&T) -> Option<Id> + 'static,
434    ) -> Handle<Self>
435    where
436        S: Res<U> + 'static,
437        U: Clone + 'static,
438        C: Res<R> + 'static,
439        R: Deref<Target = [TreeTableColumn<T, Id, H, K>]> + Clone + 'static,
440        H: Clone + View,
441        F: Fn(&U) -> V + 'static,
442    {
443        let tree_signal = tree.to_signal(cx);
444        let flatten_rows: Rc<dyn Fn(&U) -> V> = Rc::new(flatten_rows);
445        let row_signal = Signal::new(tree_signal.with(|tree| flatten_rows(tree)));
446        let column_signal = columns.to_signal(cx);
447        let row_id: Rc<dyn Fn(&T) -> Id> = Rc::new(row_id);
448        let parent_id: Rc<dyn Fn(&T) -> Option<Id>> = Rc::new(parent_id);
449        let sort_state = Signal::new(None);
450        let sort_cycle = Signal::new(TableSortCycle::BiState);
451        let resizable_columns = Signal::new(false);
452        let selectable = Signal::new(Selectable::None);
453        let selection_follows_focus = Signal::new(false);
454        let selection = Signal::new(HashSet::new());
455        let selection_mode = Signal::new(TableSelectionMode::Cell);
456        let expanded_row_ids = Signal::new(Vec::new());
457        let focused = Signal::new(None);
458        let treegrid_label = Signal::new(None::<String>);
459        let visible_rows = Memo::new({
460            let row_id = row_id.clone();
461            let parent_id = parent_id.clone();
462            move |_| {
463                row_signal.with(|rows| {
464                    expanded_row_ids.with(|expanded| {
465                        flatten_visible_rows(rows, &*row_id, &*parent_id, expanded)
466                    })
467                })
468            }
469        });
470
471        // let selected_indices = Memo::new(move |_| {
472        //     visible_rows.with(|rows| {
473        //         selection.with(|selected_ids| {
474        //             rows.iter()
475        //                 .enumerate()
476        //                 .filter_map(|(index, row)| {
477        //                     if selected_ids.contains(&Cell(row.id.clone(), _)) {
478        //                         Some(index)
479        //                     } else {
480        //                         None
481        //                     }
482        //                 })
483        //                 .collect::<Vec<usize>>()
484        //         })
485        //     })
486        // });
487
488        let columns = Memo::new(move |_| {
489            column_signal.with(|columns| {
490                columns
491                    .deref()
492                    .iter()
493                    .filter_map(
494                        |column| if !column.hidden.get() { Some(column.key.clone()) } else { None },
495                    )
496                    .collect::<Vec<_>>()
497            })
498        });
499
500        let handle = Self {
501            rows: row_signal,
502            row_id,
503            parent_id,
504            sort_state,
505            sort_cycle,
506            resizable_columns,
507            selectable,
508            selection_follows_focus,
509            selection,
510            selection_mode,
511            expanded_row_ids,
512            focused,
513            treegrid_label,
514            on_sort: None,
515            on_select: None,
516            on_row_toggle: None,
517            visible_rows,
518            columns,
519        }
520        .build(cx, move |cx| {
521            Keymap::from(vec![
522                (
523                    KeyChord::new(Modifiers::empty(), Code::ArrowRight),
524                    KeymapEntry::new("Focus Right", |cx| {
525                        cx.emit(TreeTableEvent::<K, Id>::FocusRight)
526                    }),
527                ),
528                (
529                    KeyChord::new(Modifiers::empty(), Code::ArrowLeft),
530                    KeymapEntry::new("Focus Left", |cx| {
531                        cx.emit(TreeTableEvent::<K, Id>::FocusLeft)
532                    }),
533                ),
534                (
535                    KeyChord::new(Modifiers::empty(), Code::ArrowUp),
536                    KeymapEntry::new("Focus Up", |cx| cx.emit(TreeTableEvent::<K, Id>::FocusUp)),
537                ),
538                (
539                    KeyChord::new(Modifiers::empty(), Code::ArrowDown),
540                    KeymapEntry::new("Focus Down", |cx| {
541                        cx.emit(TreeTableEvent::<K, Id>::FocusDown)
542                    }),
543                ),
544                (
545                    KeyChord::new(Modifiers::empty(), Code::Home),
546                    KeymapEntry::new("Focus Home", |cx| {
547                        cx.emit(TreeTableEvent::<K, Id>::FocusHome)
548                    }),
549                ),
550                (
551                    KeyChord::new(Modifiers::empty(), Code::End),
552                    KeymapEntry::new("Focus End", |cx| cx.emit(TreeTableEvent::<K, Id>::FocusEnd)),
553                ),
554                (
555                    KeyChord::new(Modifiers::empty(), Code::PageUp),
556                    KeymapEntry::new("Focus Page Up", |cx| {
557                        cx.emit(TreeTableEvent::<K, Id>::PageUp)
558                    }),
559                ),
560                (
561                    KeyChord::new(Modifiers::empty(), Code::PageDown),
562                    KeymapEntry::new("Focus Page Down", |cx| {
563                        cx.emit(TreeTableEvent::<K, Id>::PageDown)
564                    }),
565                ),
566                (
567                    KeyChord::new(Modifiers::CTRL, Code::Home),
568                    KeymapEntry::new("Focus Control Home", |cx| {
569                        cx.emit(TreeTableEvent::<K, Id>::CtrlHome)
570                    }),
571                ),
572                (
573                    KeyChord::new(Modifiers::CTRL, Code::End),
574                    KeymapEntry::new("Focus Control End", |cx| {
575                        cx.emit(TreeTableEvent::<K, Id>::CtrlEnd)
576                    }),
577                ),
578                (
579                    KeyChord::new(Modifiers::empty(), Code::Enter),
580                    KeymapEntry::new("Select Focused", |cx| {
581                        cx.emit(TreeTableEvent::<K, Id>::SelectFocused)
582                    }),
583                ),
584            ])
585            .build(cx);
586
587            Binding::new(cx, columns, move |cx| {
588                let visible_columns = column_signal.with(|columns| {
589                    columns
590                        .deref()
591                        .iter()
592                        .filter(|column| !column.hidden.get())
593                        .cloned()
594                        .collect::<Vec<_>>()
595                });
596
597                let last_header_index = visible_columns.len().saturating_sub(1);
598                let header_columns = Rc::new(visible_columns);
599                let body_columns = header_columns.clone();
600
601                HStack::new(cx, move |cx| {
602                    for (column_index, column) in header_columns.iter().cloned().enumerate() {
603                        let width_signal = column.width;
604                        let sort_state = sort_state;
605                        let resizable_columns = resizable_columns;
606                        let min_width = column.min_width;
607                        let sortable = column.sortable;
608                        let resizable = column.resizable;
609                        let is_last_column = column_index == last_header_index;
610                        let header_content = column.header_content.clone();
611                        let column_key = column.key.clone();
612                        let sort_direction = sort_state.map({
613                            let column_key = column_key.clone();
614                            move |state| sort_direction_for_column(state.as_ref(), &column_key)
615                        });
616
617                        if is_last_column {
618                            let header = header_content(cx, sort_direction);
619                            let on_press_column_key = column_key.clone();
620                            header
621                                .class("table-header-cell")
622                                .role(Role::ColumnHeader)
623                                .sort_direction(sort_direction.map(|direction| match direction {
624                                    TableSortDirection::Ascending => {
625                                        Some(AccessSortDirection::Ascending)
626                                    }
627                                    TableSortDirection::Descending => {
628                                        Some(AccessSortDirection::Descending)
629                                    }
630                                    TableSortDirection::None => None,
631                                }))
632                                .toggle_class("sortable", sortable)
633                                .toggle_class("resizable", false)
634                                .width(Stretch(1.0))
635                                .min_width(Auto)
636                                .on_press(move |cx| {
637                                    cx.emit(TreeTableEvent::<K, Id>::SelectColumn(
638                                        on_press_column_key.clone(),
639                                    ));
640                                });
641                        } else {
642                            Resizable::new(
643                                cx,
644                                width_signal,
645                                ResizeStackDirection::Right,
646                                move |_cx, new_size| {
647                                    if resizable_columns.get() && resizable.get() {
648                                        width_signal.set(Pixels(new_size.max(min_width.get())));
649                                    }
650                                },
651                                move |cx| {
652                                    let header = header_content(cx, sort_direction);
653                                    let on_press_column_key = column_key.clone();
654                                    header
655                                        .class("table-header-cell")
656                                        .role(Role::ColumnHeader)
657                                        .sort_direction(sort_direction.map(|direction| {
658                                            match direction {
659                                                TableSortDirection::Ascending => {
660                                                    Some(AccessSortDirection::Ascending)
661                                                }
662                                                TableSortDirection::Descending => {
663                                                    Some(AccessSortDirection::Descending)
664                                                }
665                                                TableSortDirection::None => None,
666                                            }
667                                        }))
668                                        .toggle_class("sortable", sortable)
669                                        .toggle_class(
670                                            "resizable",
671                                            resizable_columns
672                                                .map(move |enabled| *enabled && resizable.get()),
673                                        )
674                                        .min_width(min_width.map(|value| Pixels(*value)))
675                                        .on_press(move |cx| {
676                                            cx.emit(TreeTableEvent::<K, Id>::SelectColumn(
677                                                on_press_column_key.clone(),
678                                            ));
679                                        });
680                                },
681                            )
682                            .width(width_signal)
683                            .min_width(min_width.map(|value| Pixels(*value)));
684                        }
685                    }
686                })
687                .class("table-header-row")
688                .height(Auto)
689                .width(Stretch(1.0))
690                .min_width(Auto);
691
692                let focused_index = Memo::new(move |_| {
693                    visible_rows.with(|rows| {
694                        let focused_row_id = focused.with(|focused| match focused {
695                            Some(TableFocus::Row(id)) => Some(id.clone()),
696                            Some(TableFocus::Cell(_id, _)) => None,
697                            None => None,
698                        });
699
700                        rows.iter().position(|row| Some(&row.id) == focused_row_id.as_ref())
701                    })
702                });
703
704                List::new_custom_items_with_selection(
705                    cx,
706                    visible_rows,
707                    move |cx, row_index, row, _selected| {
708                        let row: Memo<TreeTableRow<T, Id>> = Memo::new(move |_| row.get());
709                        let mut row_handle = HStack::new(cx, |cx| {
710                            for (column_index, column) in body_columns.iter().enumerate() {
711                                let width_signal = column.width;
712                                let min_width = column.min_width;
713                                let cell_content = column.cell_content.clone();
714                                let is_last_column = column_index + 1 == body_columns.len();
715
716                                let col_key = column.key.clone();
717                                let _row_id = row.map(|value| value.id.clone());
718                                let cell_row_id = row.map(|value| value.id.clone());
719                                let cell_col_key = col_key.clone();
720
721                                let col_key_for_focus = col_key.clone();
722                                let row_id_for_focus = row.map(|value| value.id.clone());
723                                let cell_is_focused = focused.map(move |focused| match focused {
724                                    Some(TableFocus::Cell(id, col)) => {
725                                        *col == col_key_for_focus && *id == row_id_for_focus.get()
726                                    }
727                                    _ => false,
728                                });
729
730                                let col_key_for_select = col_key.clone();
731                                let row_id_for_select = row.map(|value| value.id.clone());
732                                let cell_is_selected = selection.map(move |selection| {
733                                    selection.contains(&Cell(
734                                        row_id_for_select.get(),
735                                        col_key_for_select.clone(),
736                                    ))
737                                });
738
739                                let cell_col_key_1 = cell_col_key.clone();
740                                let cell_col_key_2 = cell_col_key.clone();
741
742                                if is_last_column {
743                                    VStack::new(cx, move |cx| {
744                                        cell_content(cx, row);
745                                    })
746                                    .class("table-cell")
747                                    .role(Role::GridCell)
748                                    .toggle_class("selected", cell_is_selected)
749                                    .selected(cell_is_selected)
750                                    .focusable(true)
751                                    .navigable(false)
752                                    .focused_with_visibility(cell_is_focused, true)
753                                    .width(Stretch(1.0))
754                                    .min_width(Auto)
755                                    .height(Stretch(1.0))
756                                    .min_height(Auto)
757                                    .on_press(move |cx| {
758                                        cx.emit(TreeTableEvent::<K, Id>::SelectCell(
759                                            cell_row_id.get(),
760                                            cell_col_key_1.clone(),
761                                        ));
762                                    });
763                                } else {
764                                    VStack::new(cx, move |cx| {
765                                        cell_content(cx, row);
766                                    })
767                                    .class("table-cell")
768                                    .role(Role::GridCell)
769                                    .toggle_class("selected", cell_is_selected)
770                                    .selected(cell_is_selected)
771                                    .focusable(true)
772                                    .navigable(false)
773                                    .focused_with_visibility(cell_is_focused, true)
774                                    .width(width_signal)
775                                    .min_width(min_width.map(|value| Pixels(*value)))
776                                    .height(Stretch(1.0))
777                                    .min_height(Auto)
778                                    .on_press(move |cx| {
779                                        cx.emit(TreeTableEvent::<K, Id>::SelectCell(
780                                            cell_row_id.get(),
781                                            cell_col_key_2.clone(),
782                                        ));
783                                    });
784                                }
785                            }
786                        })
787                        .class("table-row")
788                        .toggle_class("odd", row_index % 2 == 1)
789                        .toggle_class("even", row_index % 2 == 0)
790                        .toggle_class("expanded", row.map(|value| value.expanded))
791                        .toggle_class("collapsible", row.map(|value| value.has_children))
792                        .alignment(Alignment::Left)
793                        .height(Auto)
794                        .width(Stretch(1.0))
795                        .min_width(Auto)
796                        .role(Role::Row)
797                        .level(row.map(|value| value.depth + 1))
798                        .on_press(move |cx| cx.emit(ListEvent::Select(row_index)));
799
800                        if row.get().has_children {
801                            row_handle = row_handle.expanded(row.map(|value| value.expanded));
802                        }
803
804                        //Divider::new(cx).width(Stretch(1.0));
805
806                        row_handle
807                    },
808                )
809                .width(Stretch(1.0))
810                .min_width(Auto)
811                .height(Stretch(1.0))
812                .min_height(Auto)
813                .class("table-body")
814                .class("tree-table-body")
815                .focused_index(focused_index)
816                .on_focus(move |_cx, index| {
817                    visible_rows.with(|rows| {
818                        if let Some(row) = rows.get(index) {
819                            focused.set(Some(TableFocus::Row(row.id.clone())));
820                            //cx.emit(TreeViewEvent::FocusRow(row.id.clone()));
821                        }
822                    });
823                })
824                //.selection(selected_indices)
825                //.selectable(selectable)
826                //.selection_follows_focus(selection_follows_focus)
827                //.on_select(move |cx, index| cx.emit(TreeTableEvent::<K>::SelectRow(index)))
828                .on_build(|cx| {
829                    cx.emit_to(
830                        cx.current(),
831                        KeymapEvent::RemoveAction(
832                            KeyChord::new(Modifiers::empty(), Code::ArrowDown),
833                            "Focus Next",
834                        ),
835                    );
836
837                    cx.emit_to(
838                        cx.current(),
839                        KeymapEvent::RemoveAction(
840                            KeyChord::new(Modifiers::empty(), Code::ArrowUp),
841                            "Focus Previous",
842                        ),
843                    );
844                });
845            });
846        })
847        .class("table")
848        .class("tree-table")
849        .navigable(true)
850        .name(treegrid_label.map(|label| label.clone().unwrap_or_else(|| "Tree table".to_string())))
851        .multiselectable(selectable.map(|mode| *mode == Selectable::Multi))
852        .role(Role::TreeGrid);
853
854        let flatten_rows_for_bind = flatten_rows.clone();
855        handle.bind(tree_signal, move |handle| {
856            let rows = tree_signal.with(|tree| flatten_rows_for_bind(tree));
857            handle.modify(|table: &mut TreeTable<T, V, Id, K>| table.rows.set(rows));
858        })
859    }
860
861    pub fn from_rows<S, C, R, H>(
862        cx: &mut Context,
863        rows: S,
864        columns: C,
865        row_id: impl Fn(&T) -> Id + 'static,
866        parent_id: impl Fn(&T) -> Option<Id> + 'static,
867    ) -> Handle<Self>
868    where
869        S: Res<V> + 'static,
870        C: Res<R> + 'static,
871        R: Deref<Target = [TreeTableColumn<T, Id, H, K>]> + Clone + 'static,
872        H: Clone + View,
873    {
874        Self::new(cx, rows, columns, |rows: &V| rows.clone(), row_id, parent_id)
875    }
876
877    fn emit_toggle(&self, cx: &mut EventContext, row_id: Id, next_expanded: bool) {
878        if let Some(callback) = &self.on_row_toggle {
879            (callback)(cx, row_id, next_expanded);
880        }
881    }
882
883    // Get the focused visible row id
884    fn focused_visible_row(&self) -> Option<TreeTableRow<T, Id>> {
885        let focused_id = self.focused.get().map(|focused| match focused {
886            TableFocus::Row(id) => id.clone(),
887            TableFocus::Cell(id, _) => id.clone(),
888        })?;
889
890        self.visible_rows.with(|rows| rows.clone()).into_iter().find(|row| row.id == focused_id)
891    }
892
893    fn focus_row_id(&self, row_id: Id) {
894        let next_focus = match self.focused.get() {
895            Some(TableFocus::Cell(_, column_key)) => TableFocus::Cell(row_id, column_key),
896            _ => TableFocus::Row(row_id),
897        };
898
899        self.focused.set(Some(next_focus));
900    }
901}
902
903impl<Id, K> TreeTable<TreeNodeRow<Id>, Vec<TreeNodeRow<Id>>, Id, K>
904where
905    Id: Eq + Hash + Clone + Send + Sync + 'static,
906    K: Eq + Hash + Clone + PartialEq + Send + Sync + 'static,
907{
908    /// Creates a [`TreeTable`] directly from hierarchical data.
909    ///
910    /// Provide closures to enumerate root node IDs and child node IDs for a given parent.
911    /// The returned order is the order provided by those closures; filtering only removes
912    /// invisible nodes and their descendants.
913    /// The table manages expand/collapse state and internally projects IDs into
914    /// [`TreeNodeRow`] values.
915    pub fn from_hierarchy<S, U, C, R, H>(
916        cx: &mut Context,
917        tree: S,
918        columns: C,
919        root_ids: impl Fn(&U) -> Vec<Id> + 'static,
920        child_ids: impl Fn(&U, &Id) -> Vec<Id> + 'static,
921        is_visible: impl Fn(&U, &Id) -> bool + 'static,
922    ) -> Handle<Self>
923    where
924        S: Res<U> + 'static,
925        U: Clone + 'static,
926        C: Res<R> + 'static,
927        R: Deref<Target = [TreeTableColumn<TreeNodeRow<Id>, Id, H, K>]> + Clone + 'static,
928        H: Clone + View,
929    {
930        let root_ids: Rc<dyn Fn(&U) -> Vec<Id>> = Rc::new(root_ids);
931        let child_ids: Rc<dyn Fn(&U, &Id) -> Vec<Id>> = Rc::new(child_ids);
932        let is_visible: Rc<dyn Fn(&U, &Id) -> bool> = Rc::new(is_visible);
933
934        Self::new(
935            cx,
936            tree,
937            columns,
938            move |tree: &U| flatten_hierarchy_rows(tree, &*root_ids, &*child_ids, &*is_visible),
939            |row: &TreeNodeRow<Id>| row.id.clone(),
940            |row: &TreeNodeRow<Id>| row.parent_id.clone(),
941        )
942    }
943}
944
945impl<T, V, Id, K> View for TreeTable<T, V, Id, K>
946where
947    V: Deref<Target = [T]> + Clone + 'static,
948    T: PartialEq + Clone + 'static,
949    Id: Eq + Hash + Clone + Send + Sync + 'static,
950    K: Eq + Hash + Clone + PartialEq + Send + Sync + 'static,
951{
952    fn element(&self) -> Option<&'static str> {
953        Some("tree-table")
954    }
955
956    fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
957        event.map(|tree_event: &TreeTableEvent<K, Id>, _| match tree_event {
958            TreeTableEvent::RequestSort(key, direction) => {
959                if let Some(callback) = &self.on_sort {
960                    (callback)(cx, key.clone(), *direction);
961                }
962            }
963
964            TreeTableEvent::SelectRow(index) => {
965                if self.selectable.get() == Selectable::None {
966                    return;
967                }
968
969                let visible_rows = flatten_visible_rows(
970                    &self.rows.get(),
971                    &*self.row_id,
972                    &*self.parent_id,
973                    &self.expanded_row_ids.get(),
974                );
975
976                if let Some(row) = visible_rows.get(*index) {
977                    let cols = self.columns.get();
978                    self.selection.set(HashSet::from_iter(
979                        cols.iter().map(|col| Cell(row.id.clone(), col.clone())),
980                    ));
981
982                    if let Some(callback) = &self.on_select {
983                        (callback)(cx, self.selection.get());
984                    }
985                }
986            }
987
988            TreeTableEvent::SelectColumn(column_key) => {
989                let visible_rows = flatten_visible_rows(
990                    &self.rows.get(),
991                    &*self.row_id,
992                    &*self.parent_id,
993                    &self.expanded_row_ids.get(),
994                );
995
996                let column_cells = HashSet::from_iter(
997                    visible_rows.iter().map(|row| Cell(row.id.clone(), column_key.clone())),
998                );
999
1000                let current_selection = self.selection.get();
1001
1002                // If the column is already fully selected, deselect it; otherwise select it.
1003                let next_selection =
1004                    if current_selection == column_cells { HashSet::new() } else { column_cells };
1005
1006                self.selection.set(next_selection.clone());
1007
1008                if let Some(callback) = &self.on_select {
1009                    (callback)(cx, next_selection);
1010                }
1011            }
1012
1013            TreeTableEvent::SelectCell(row_id, column_key) => {
1014                let selectable = self.selectable.get();
1015                if selectable == Selectable::None {
1016                    return;
1017                }
1018
1019                let selection_mode = self.selection_mode.get();
1020
1021                let next_selection = match selection_mode {
1022                    TableSelectionMode::Cell => {
1023                        let cell = Cell(row_id.clone(), column_key.clone());
1024                        if selectable == Selectable::Single {
1025                            // Single mode: replace selection, or deselect if same cell clicked again
1026                            let current = self.selection.get();
1027                            if current.contains(&cell) {
1028                                HashSet::new()
1029                            } else {
1030                                HashSet::from([cell])
1031                            }
1032                        } else {
1033                            // Multi mode: toggle the clicked cell in the set
1034                            let current = self.selection.get();
1035                            let mut next = current;
1036                            if next.contains(&cell) {
1037                                next.remove(&cell);
1038                            } else {
1039                                next.insert(cell);
1040                            }
1041                            next
1042                        }
1043                    }
1044                    TableSelectionMode::Row => {
1045                        let cols = self.columns.get();
1046                        let row_cells: HashSet<Cell<Id, K>> = HashSet::from_iter(
1047                            cols.iter().map(|col| Cell(row_id.clone(), col.clone())),
1048                        );
1049
1050                        if selectable == Selectable::Single {
1051                            // Single: replace with this row, or deselect if already selected
1052                            let current = self.selection.get();
1053                            if current == row_cells { HashSet::new() } else { row_cells }
1054                        } else {
1055                            // Multi: toggle this row in/out of the accumulated selection
1056                            let mut next = self.selection.get();
1057                            let row_selected = row_cells.iter().all(|c| next.contains(c));
1058                            if row_selected {
1059                                for c in &row_cells {
1060                                    next.remove(c);
1061                                }
1062                            } else {
1063                                next.extend(row_cells);
1064                            }
1065                            next
1066                        }
1067                    }
1068                };
1069
1070                self.selection.set(next_selection.clone());
1071
1072                if let Some(callback) = &self.on_select {
1073                    (callback)(cx, next_selection);
1074                }
1075            }
1076
1077            TreeTableEvent::SelectFocused => self.focused.with(|focused| match focused {
1078                Some(TableFocus::Row(id)) => {
1079                    if self.selectable.get() == Selectable::None {
1080                        return;
1081                    }
1082                    let cols = self.columns.get();
1083                    self.selection.set(HashSet::from_iter(
1084                        cols.iter().map(|col| Cell(id.clone(), col.clone())),
1085                    ));
1086
1087                    if let Some(callback) = &self.on_select {
1088                        (callback)(cx, self.selection.get());
1089                    }
1090                }
1091                Some(TableFocus::Cell(id, col)) => {
1092                    self.selection.set(HashSet::from([Cell(id.clone(), col.clone())]));
1093
1094                    if let Some(callback) = &self.on_select {
1095                        (callback)(cx, self.selection.get());
1096                    }
1097                }
1098                None => {}
1099            }),
1100
1101            TreeTableEvent::ExpandSelected => {
1102                if let Some(row) = self.focused_visible_row() {
1103                    if row.has_children && !row.expanded {
1104                        self.emit_toggle(cx, row.id, true);
1105                    } else if row.has_children {
1106                        let child_id = self.visible_rows.with(|rows| {
1107                            rows.iter()
1108                                .find(|candidate| candidate.parent_id.as_ref() == Some(&row.id))
1109                                .map(|candidate| candidate.id.clone())
1110                        });
1111
1112                        if let Some(child_id) = child_id {
1113                            self.focus_row_id(child_id);
1114                        }
1115                    }
1116                }
1117            }
1118
1119            TreeTableEvent::FocusRight => {
1120                // If focus is on a row, move it to the first cell. If it's on a cell, move it to the next cell.
1121                let next_focus = match self.focused.get() {
1122                    Some(TableFocus::Row(id)) => {
1123                        if let Some(row) = self.focused_visible_row() {
1124                            if row.has_children && !row.expanded {
1125                                cx.emit(TreeTableEvent::<K, Id>::ExpandSelected);
1126                                return;
1127                            }
1128                        }
1129                        let first_column_key =
1130                            self.columns.with(|columns| columns.first().cloned()).unwrap();
1131                        Some(TableFocus::Cell(id.clone(), first_column_key))
1132                    }
1133                    Some(TableFocus::Cell(id, col)) => {
1134                        let next_col = self.columns.with(|columns| {
1135                            columns
1136                                .iter()
1137                                .position(|column| column == &col)
1138                                .and_then(|index| index.checked_add(1))
1139                                .and_then(|index| columns.get(index))
1140                                .cloned()
1141                        });
1142
1143                        next_col.map(|next_col| TableFocus::Cell(id.clone(), next_col))
1144                    }
1145                    None => None,
1146                };
1147
1148                if next_focus.is_some() {
1149                    self.focused.set(next_focus);
1150                }
1151            }
1152
1153            TreeTableEvent::FocusLeft => {
1154                // If focus is on a cell, move it to the previous cell. If it's on the first cell, move it to the row.
1155                let next_focus = match self.focused.get() {
1156                    // If focus is on an expanded row, collapse it. Otherwise, move focus to the parent row if there is one.
1157                    Some(TableFocus::Row(_)) => {
1158                        if let Some(row) = self.focused_visible_row() {
1159                            if row.has_children && row.expanded {
1160                                self.emit_toggle(cx, row.id, false);
1161                            }
1162                        }
1163                        None
1164                    }
1165                    Some(TableFocus::Cell(id, col)) => {
1166                        let prev_col = self.columns.with(|columns| {
1167                            columns
1168                                .iter()
1169                                .position(|column| column == &col)
1170                                .and_then(|index| index.checked_sub(1))
1171                                .and_then(|index| columns.get(index))
1172                                .cloned()
1173                        });
1174
1175                        prev_col
1176                            .map(|prev_col| TableFocus::Cell(id.clone(), prev_col))
1177                            .or_else(|| Some(TableFocus::Row(id.clone())))
1178                    }
1179                    None => None,
1180                };
1181
1182                if next_focus.is_some() {
1183                    self.focused.set(next_focus);
1184                }
1185            }
1186
1187            TreeTableEvent::FocusHome => {
1188                let next_focus = self.focused.with(|focused| match focused {
1189                    Some(TableFocus::Row(_)) => self
1190                        .visible_rows
1191                        .with(|rows| rows.first().map(|row| TableFocus::Row(row.id.clone()))),
1192                    Some(TableFocus::Cell(_, col)) => self.visible_rows.with(|rows| {
1193                        rows.first().map(|row| TableFocus::Cell(row.id.clone(), col.clone()))
1194                    }),
1195                    None => None,
1196                });
1197
1198                if next_focus.is_some() {
1199                    self.focused.set(next_focus);
1200                }
1201            }
1202
1203            TreeTableEvent::FocusEnd => {
1204                let next_focus = self.focused.with(|focused| match focused {
1205                    Some(TableFocus::Row(_)) => self
1206                        .visible_rows
1207                        .with(|rows| rows.last().map(|row| TableFocus::Row(row.id.clone()))),
1208                    Some(TableFocus::Cell(_, col)) => self.visible_rows.with(|rows| {
1209                        rows.last().map(|row| TableFocus::Cell(row.id.clone(), col.clone()))
1210                    }),
1211                    None => None,
1212                });
1213
1214                if next_focus.is_some() {
1215                    self.focused.set(next_focus);
1216                }
1217            }
1218
1219            TreeTableEvent::PageUp => {
1220                let page_size = self.visible_rows.with(|rows| rows.len().saturating_div(2).max(1));
1221                let next_focus = self.focused.with(|focused| {
1222                    self.visible_rows.with(|rows| {
1223                        let current_index = focused.as_ref().and_then(|focused| match focused {
1224                            TableFocus::Row(id) | TableFocus::Cell(id, _) => {
1225                                rows.iter().position(|row| row.id == id.clone())
1226                            }
1227                        });
1228
1229                        current_index.and_then(|index| {
1230                            let next_index = index.saturating_sub(page_size);
1231                            rows.get(next_index).map(|row| match focused.as_ref() {
1232                                Some(TableFocus::Cell(_, col)) => {
1233                                    TableFocus::Cell(row.id.clone(), col.clone())
1234                                }
1235                                _ => TableFocus::Row(row.id.clone()),
1236                            })
1237                        })
1238                    })
1239                });
1240
1241                if next_focus.is_some() {
1242                    self.focused.set(next_focus);
1243                }
1244            }
1245
1246            TreeTableEvent::PageDown => {
1247                let page_size = self.visible_rows.with(|rows| rows.len().saturating_div(2).max(1));
1248                let next_focus = self.focused.with(|focused| {
1249                    self.visible_rows.with(|rows| {
1250                        let current_index = focused.as_ref().and_then(|focused| match focused {
1251                            TableFocus::Row(id) | TableFocus::Cell(id, _) => {
1252                                rows.iter().position(|row| row.id == id.clone())
1253                            }
1254                        });
1255
1256                        current_index.and_then(|index| {
1257                            let next_index = (index + page_size).min(rows.len().saturating_sub(1));
1258                            rows.get(next_index).map(|row| match focused.as_ref() {
1259                                Some(TableFocus::Cell(_, col)) => {
1260                                    TableFocus::Cell(row.id.clone(), col.clone())
1261                                }
1262                                _ => TableFocus::Row(row.id.clone()),
1263                            })
1264                        })
1265                    })
1266                });
1267
1268                if next_focus.is_some() {
1269                    self.focused.set(next_focus);
1270                }
1271            }
1272
1273            TreeTableEvent::CtrlHome => {
1274                let next_focus = self.focused.with(|focused| {
1275                    self.visible_rows.with(|rows| {
1276                        rows.first().map(|row| match focused {
1277                            Some(TableFocus::Cell(_, col)) => {
1278                                TableFocus::Cell(row.id.clone(), col.clone())
1279                            }
1280                            _ => TableFocus::Row(row.id.clone()),
1281                        })
1282                    })
1283                });
1284
1285                if next_focus.is_some() {
1286                    self.focused.set(next_focus);
1287                }
1288            }
1289
1290            TreeTableEvent::CtrlEnd => {
1291                let next_focus = self.focused.with(|focused| {
1292                    self.visible_rows.with(|rows| {
1293                        rows.last().map(|row| match focused {
1294                            Some(TableFocus::Cell(_, col)) => {
1295                                TableFocus::Cell(row.id.clone(), col.clone())
1296                            }
1297                            _ => TableFocus::Row(row.id.clone()),
1298                        })
1299                    })
1300                });
1301
1302                if next_focus.is_some() {
1303                    self.focused.set(next_focus);
1304                }
1305            }
1306
1307            TreeTableEvent::FocusUp => {
1308                // Move focus to the previous row, keeping the same column if possible.
1309                let next_focus = self.focused.with(|focused| match focused {
1310                    Some(TableFocus::Row(id)) => self.visible_rows.with(|rows| {
1311                        rows.iter()
1312                            .take_while(|row| row.id != *id)
1313                            .last()
1314                            .map(|row| TableFocus::Row(row.id.clone()))
1315                    }),
1316                    Some(TableFocus::Cell(id, col)) => self.visible_rows.with(|rows| {
1317                        rows.iter()
1318                            .take_while(|row| row.id != *id)
1319                            .last()
1320                            .map(|row| TableFocus::Cell(row.id.clone(), col.clone()))
1321                    }),
1322                    None => None,
1323                });
1324
1325                if next_focus.is_some() {
1326                    self.focused.set(next_focus);
1327                }
1328            }
1329
1330            TreeTableEvent::FocusDown => {
1331                // Move focus to the next row, keeping the same column if possible.
1332                let next_focus = self.focused.with(|focused| match focused {
1333                    Some(TableFocus::Row(id)) => self.visible_rows.with(|rows| {
1334                        rows.iter()
1335                            .skip_while(|row| row.id != *id)
1336                            .nth(1)
1337                            .map(|row| TableFocus::Row(row.id.clone()))
1338                    }),
1339                    Some(TableFocus::Cell(id, col)) => self.visible_rows.with(|rows| {
1340                        rows.iter()
1341                            .skip_while(|row| row.id != *id)
1342                            .nth(1)
1343                            .map(|row| TableFocus::Cell(row.id.clone(), col.clone()))
1344                    }),
1345                    None => None,
1346                });
1347                if next_focus.is_some() {
1348                    self.focused.set(next_focus);
1349                }
1350            }
1351        });
1352
1353        event.map(|cell_event: &TreeTableFirstCellEvent<Id>, _| {
1354            let TreeTableFirstCellEvent::Toggle(id, next) = cell_event;
1355            self.emit_toggle(cx, id.clone(), *next);
1356        });
1357
1358        event.map(|tree_event: &TableEvent<K>, _| match tree_event {
1359            TableEvent::ToggleSort(col) => {
1360                let visible_rows = flatten_visible_rows(
1361                    &self.rows.get(),
1362                    &*self.row_id,
1363                    &*self.parent_id,
1364                    &self.expanded_row_ids.get(),
1365                );
1366
1367                let column_cells = HashSet::from_iter(
1368                    visible_rows.iter().map(|row| Cell(row.id.clone(), col.clone())),
1369                );
1370
1371                let current_selection = self.selection.get();
1372
1373                // If the column is already fully selected, deselect it; otherwise select it.
1374                let next_selection =
1375                    if current_selection == column_cells { HashSet::new() } else { column_cells };
1376
1377                self.selection.set(next_selection.clone());
1378
1379                if let Some(callback) = &self.on_select {
1380                    (callback)(cx, next_selection);
1381                }
1382
1383                if let Some(callback) = &self.on_sort {
1384                    let current_direction =
1385                        sort_direction_for_column(self.sort_state.get().as_ref(), col);
1386                    let next_direction =
1387                        next_sort_direction(self.sort_cycle.get(), current_direction);
1388                    (callback)(cx, col.clone(), next_direction);
1389                }
1390            }
1391
1392            _ => {}
1393        });
1394    }
1395}
1396
1397pub trait TreeTableModifiers<Id, K = String>: Sized
1398where
1399    Id: Eq + Hash + Clone + Send + Sync + 'static,
1400    K: Eq + Hash + Clone + PartialEq + Send + Sync + 'static,
1401{
1402    fn sort_state(self, sort_state: impl Res<Option<TableSortState<K>>> + 'static) -> Self;
1403
1404    fn resizable_columns<U: Into<bool> + Clone + 'static>(
1405        self,
1406        flag: impl Res<U> + 'static,
1407    ) -> Self;
1408
1409    fn sort_cycle<U: Into<TableSortCycle> + Clone + 'static>(
1410        self,
1411        cycle: impl Res<U> + 'static,
1412    ) -> Self;
1413
1414    fn selectable<U: Into<Selectable> + Clone + 'static>(
1415        self,
1416        selectable: impl Res<U> + 'static,
1417    ) -> Self;
1418
1419    fn selection_follows_focus<U: Into<bool> + Clone + 'static>(
1420        self,
1421        flag: impl Res<U> + 'static,
1422    ) -> Self;
1423
1424    fn selection_mode<U: Into<TableSelectionMode> + Clone + 'static>(
1425        self,
1426        mode: impl Res<U> + 'static,
1427    ) -> Self;
1428
1429    fn treegrid_label<U: Into<Option<String>> + Clone + 'static>(
1430        self,
1431        label: impl Res<U> + 'static,
1432    ) -> Self;
1433
1434    fn selection<R>(self, selection: impl Res<R> + 'static) -> Self
1435    where
1436        R: Deref<Target = [Cell<Id, K>]> + Clone + 'static;
1437
1438    fn expanded_row_ids<R>(self, expanded_row_ids: impl Res<R> + 'static) -> Self
1439    where
1440        R: Deref<Target = [Id]> + Clone + 'static;
1441
1442    fn on_sort<F>(self, callback: F) -> Self
1443    where
1444        F: 'static + Fn(&mut EventContext, K, TableSortDirection) + Send + Sync;
1445
1446    fn on_select<F>(self, callback: F) -> Self
1447    where
1448        F: 'static + Fn(&mut EventContext, HashSet<Cell<Id, K>>) + Send + Sync;
1449
1450    fn on_row_toggle<F>(self, callback: F) -> Self
1451    where
1452        F: 'static + Fn(&mut EventContext, Id, bool);
1453}
1454
1455impl<T, V, Id, K> TreeTableModifiers<Id, K> for Handle<'_, TreeTable<T, V, Id, K>>
1456where
1457    V: Deref<Target = [T]> + Clone + 'static,
1458    T: PartialEq + Clone + 'static,
1459    Id: Eq + Hash + Clone + Send + Sync + 'static,
1460    K: Eq + Hash + Clone + PartialEq + Send + Sync + 'static,
1461{
1462    fn sort_state(self, sort_state: impl Res<Option<TableSortState<K>>> + 'static) -> Self {
1463        let sort_state = sort_state.to_signal(self.cx);
1464        self.bind(sort_state, move |handle| {
1465            let sort_state = sort_state.get();
1466            handle.modify(|table: &mut TreeTable<T, V, Id, K>| table.sort_state.set(sort_state));
1467        })
1468    }
1469
1470    fn resizable_columns<U: Into<bool> + Clone + 'static>(
1471        self,
1472        flag: impl Res<U> + 'static,
1473    ) -> Self {
1474        let flag = flag.to_signal(self.cx);
1475        self.bind(flag, move |handle| {
1476            let flag = flag.get().into();
1477            handle.modify(|table: &mut TreeTable<T, V, Id, K>| table.resizable_columns.set(flag));
1478        })
1479    }
1480
1481    fn sort_cycle<U: Into<TableSortCycle> + Clone + 'static>(
1482        self,
1483        cycle: impl Res<U> + 'static,
1484    ) -> Self {
1485        let cycle = cycle.to_signal(self.cx);
1486        self.bind(cycle, move |handle| {
1487            let cycle = cycle.get().into();
1488            handle.modify(|table: &mut TreeTable<T, V, Id, K>| table.sort_cycle.set(cycle));
1489        })
1490    }
1491
1492    fn selectable<U: Into<Selectable> + Clone + 'static>(
1493        self,
1494        selectable: impl Res<U> + 'static,
1495    ) -> Self {
1496        let selectable = selectable.to_signal(self.cx);
1497        self.bind(selectable, move |handle| {
1498            let selectable = selectable.get().into();
1499            handle.modify(|table: &mut TreeTable<T, V, Id, K>| table.selectable.set(selectable));
1500        })
1501    }
1502
1503    fn selection_follows_focus<U: Into<bool> + Clone + 'static>(
1504        self,
1505        flag: impl Res<U> + 'static,
1506    ) -> Self {
1507        let flag = flag.to_signal(self.cx);
1508        self.bind(flag, move |handle| {
1509            let flag = flag.get().into();
1510            handle.modify(|table: &mut TreeTable<T, V, Id, K>| {
1511                table.selection_follows_focus.set(flag)
1512            });
1513        })
1514    }
1515
1516    fn selection_mode<U: Into<TableSelectionMode> + Clone + 'static>(
1517        self,
1518        mode: impl Res<U> + 'static,
1519    ) -> Self {
1520        let mode = mode.to_signal(self.cx);
1521        self.bind(mode, move |handle| {
1522            let mode = mode.get().into();
1523            handle.modify(|table: &mut TreeTable<T, V, Id, K>| table.selection_mode.set(mode));
1524        })
1525    }
1526
1527    fn treegrid_label<U: Into<Option<String>> + Clone + 'static>(
1528        self,
1529        label: impl Res<U> + 'static,
1530    ) -> Self {
1531        let label = label.to_signal(self.cx);
1532        self.bind(label, move |handle| {
1533            let label = label.get().into();
1534            handle.modify(|table: &mut TreeTable<T, V, Id, K>| table.treegrid_label.set(label));
1535        })
1536    }
1537
1538    fn selection<R>(self, selection: impl Res<R> + 'static) -> Self
1539    where
1540        R: Deref<Target = [Cell<Id, K>]> + Clone + 'static,
1541    {
1542        let selection = selection.to_signal(self.cx);
1543        self.bind(selection, move |handle| {
1544            let ids = selection.with(|ids| ids.deref().to_vec());
1545            handle.modify(|table: &mut TreeTable<T, V, Id, K>| {
1546                table.selection.set(HashSet::from_iter(ids))
1547            });
1548        })
1549    }
1550
1551    fn expanded_row_ids<R>(self, expanded_row_ids: impl Res<R> + 'static) -> Self
1552    where
1553        R: Deref<Target = [Id]> + Clone + 'static,
1554    {
1555        let expanded_row_ids = expanded_row_ids.to_signal(self.cx);
1556        self.bind(expanded_row_ids, move |handle| {
1557            let ids = expanded_row_ids.with(|ids| ids.deref().to_vec());
1558            handle.modify(|table: &mut TreeTable<T, V, Id, K>| table.expanded_row_ids.set(ids));
1559        })
1560    }
1561
1562    fn on_sort<F>(self, callback: F) -> Self
1563    where
1564        F: 'static + Fn(&mut EventContext, K, TableSortDirection) + Send + Sync,
1565    {
1566        self.modify(|table: &mut TreeTable<T, V, Id, K>| table.on_sort = Some(Arc::new(callback)))
1567    }
1568
1569    fn on_select<F>(self, callback: F) -> Self
1570    where
1571        F: 'static + Fn(&mut EventContext, HashSet<Cell<Id, K>>) + Send + Sync,
1572    {
1573        self.modify(|table: &mut TreeTable<T, V, Id, K>| table.on_select = Some(Box::new(callback)))
1574    }
1575
1576    fn on_row_toggle<F>(self, callback: F) -> Self
1577    where
1578        F: 'static + Fn(&mut EventContext, Id, bool),
1579    {
1580        self.modify(|table: &mut TreeTable<T, V, Id, K>| {
1581            table.on_row_toggle = Some(Box::new(callback))
1582        })
1583    }
1584}