Skip to main content

orbital_core_components/tree/
api.rs

1use leptos::prelude::*;
2use orbital_base_components::{
3    apply_sibling_dom_reorder, install_tree_drag_listeners, TreeDropPosition, TreeExpansionState,
4    TreeFocusState, TreeItemDomRegistry, TreeItemRegistry, TreeSelectionState, TreeState,
5};
6
7use super::types::{TreeAppearance, TreeBehavior, TreeExpansion, TreeSelection};
8
9#[derive(Clone, Default)]
10pub struct TreeApiRef(RwSignal<Option<TreeApiHandle>>);
11
12#[derive(Clone)]
13pub struct TreeApiHandle {
14    state: TreeState,
15}
16
17impl TreeApiRef {
18    pub fn new() -> Self {
19        Self(RwSignal::new(None))
20    }
21
22    pub fn load(&self, handle: TreeApiHandle) {
23        self.0.set(Some(handle));
24    }
25
26    pub fn with<F, R>(&self, f: F) -> Option<R>
27    where
28        F: FnOnce(&TreeApiHandle) -> R,
29    {
30        self.0.with(|handle| handle.as_ref().map(f))
31    }
32}
33
34impl TreeApiHandle {
35    pub fn from_state(state: TreeState) -> Self {
36        Self { state }
37    }
38
39    pub fn get_item(&self, item_id: &str) -> Option<orbital_base_components::TreeRegistryEntry> {
40        self.state.registry.get_entry(item_id)
41    }
42
43    pub fn get_item_dom_element(&self, item_id: &str) -> Option<web_sys::HtmlElement> {
44        self.state.dom_registry.get_dom_element(item_id)
45    }
46
47    pub fn get_item_tree(&self) -> Vec<(String, Vec<String>)> {
48        self.state.registry.item_tree()
49    }
50
51    pub fn get_item_ordered_children_ids(&self, item_id: &str) -> Vec<String> {
52        self.state.registry.ordered_children_ids(item_id)
53    }
54
55    pub fn select_item(
56        &self,
57        item_id: String,
58        keep_existing: bool,
59        should_be_selected: Option<bool>,
60        shift_range: Option<Vec<String>>,
61    ) {
62        self.state.selection.select_item(
63            item_id,
64            keep_existing,
65            should_be_selected,
66            shift_range,
67            Some(&self.state.registry),
68        );
69    }
70
71    pub fn set_item_expansion(&self, item_id: String, expanded: bool) {
72        self.state.expansion.set_expansion(item_id, expanded);
73    }
74
75    pub fn focus_item(&self, item_id: String) {
76        self.state.focus.focus_item(item_id.clone());
77        self.state.dom_registry.focus_dom_element(&item_id);
78    }
79
80    pub fn update_item_label(&self, item_id: String, label: String) {
81        self.state.registry.update_label(&item_id, label);
82    }
83}
84
85pub fn build_tree_state(
86    expansion: TreeExpansion,
87    selection: TreeSelection,
88    behavior: TreeBehavior,
89    appearance: TreeAppearance,
90) -> TreeState {
91    let mut expansion_state = TreeExpansionState::new(expansion.open_items)
92        .with_default_open(expansion.default_open)
93        .with_expansion_trigger(expansion.expansion_trigger);
94    if let Some(handler) = expansion.on_expansion_toggle {
95        expansion_state = expansion_state.with_on_expansion_toggle(handler);
96    }
97
98    let mut selection_state = TreeSelectionState::new(selection.mode, selection.selected_items)
99        .with_default_selected(selection.default_selected)
100        .with_cascade(selection.cascade);
101    if let Some(handler) = selection.on_select {
102        selection_state = selection_state.with_on_select(handler);
103    }
104    if let Some(handler) = selection.on_selection_toggle {
105        selection_state = selection_state.with_on_selection_toggle(handler);
106    }
107
108    TreeState {
109        expansion: expansion_state,
110        selection: selection_state,
111        focus: TreeFocusState::new(),
112        registry: TreeItemRegistry::new(),
113        dom_registry: TreeItemDomRegistry::new(),
114        size: appearance.size,
115        disabled_items: behavior.disabled_items,
116        disabled_items_focusable: behavior.disabled_items_focusable,
117        editable: behavior.editable,
118        editable_items: behavior.editable_items,
119        reorderable: behavior.reorderable,
120        on_item_click: behavior.on_item_click,
121        on_label_change: behavior.on_label_change,
122        on_reorder: behavior.on_reorder,
123        drag_state: orbital_base_components::TreeDragState::new(),
124    }
125}
126
127pub fn install_tree_reorder_listeners(
128    state: TreeState,
129) -> orbital_base_components::TreeDragListenerHandle {
130    let reorderable = state.reorderable;
131    let on_reorder = state.on_reorder.clone();
132    let registry = state.registry.clone();
133    let dom_registry = state.dom_registry.clone();
134    let drag_state = state.drag_state.clone();
135    install_tree_drag_listeners(
136        drag_state,
137        reorderable,
138        Callback::new(
139            move |(source_id, target_id, position): (String, String, TreeDropPosition)| {
140                let order = match position {
141                    TreeDropPosition::Before => 0,
142                    TreeDropPosition::After => 1,
143                };
144                if let Some(handler) = &on_reorder {
145                    handler.run((source_id.clone(), target_id.clone(), order));
146                } else if registry.reorder_siblings(&source_id, &target_id, order) {
147                    apply_sibling_dom_reorder(&dom_registry, &source_id, &target_id, order);
148                }
149            },
150        ),
151    )
152}