Skip to main content

orbital_base_components/navigation/carousel/
state.rs

1use leptos::prelude::*;
2
3/// Carousel navigation state shared between viewport, slides, and nav controls.
4#[derive(Clone, Copy)]
5pub struct CarouselState {
6    pub(crate) active_index: RwSignal<i32>,
7    wrap: Signal<bool>,
8    slide_count: RwSignal<i32>,
9    next_index: RwSignal<i32>,
10    on_active_index_change: Option<Callback<i32>>,
11}
12
13impl CarouselState {
14    pub fn new(
15        default_active_index: i32,
16        wrap: Signal<bool>,
17        on_active_index_change: Option<Callback<i32>>,
18    ) -> Self {
19        Self {
20            active_index: RwSignal::new(default_active_index),
21            wrap,
22            slide_count: RwSignal::new(0),
23            next_index: RwSignal::new(0),
24            on_active_index_change,
25        }
26    }
27
28    pub fn active_index(&self) -> i32 {
29        self.active_index.get()
30    }
31
32    pub fn active_index_signal(&self) -> ReadSignal<i32> {
33        self.active_index.read_only()
34    }
35
36    pub fn wrap(&self) -> bool {
37        self.wrap.get()
38    }
39
40    pub fn slide_count(&self) -> i32 {
41        self.slide_count.get()
42    }
43
44    pub fn slide_count_signal(&self) -> ReadSignal<i32> {
45        self.slide_count.read_only()
46    }
47
48    pub fn next_index(&self) -> i32 {
49        self.next_index.get()
50    }
51
52    pub fn set_active_index(&self, index: i32) {
53        self.active_index.set(index);
54    }
55
56    pub fn register_slide(&self) -> i32 {
57        let index = self.next_index.get();
58        self.next_index.update(|value| *value += 1);
59        self.slide_count.update(|count| *count += 1);
60        index
61    }
62
63    pub fn go_to(&self, index: i32) {
64        let count = self.slide_count.get();
65        if count == 0 {
66            return;
67        }
68        let target = if self.wrap.get() {
69            let count = count as i64;
70            let normalized = index as i64 % count;
71            (if normalized < 0 {
72                normalized + count
73            } else {
74                normalized
75            }) as i32
76        } else {
77            index.clamp(0, count - 1)
78        };
79        if self.active_index.get_untracked() != target {
80            self.active_index.set(target);
81            if let Some(callback) = self.on_active_index_change {
82                callback.run(target);
83            }
84        }
85    }
86
87    pub fn next(&self) {
88        let count = self.slide_count.get();
89        if count == 0 {
90            return;
91        }
92        let current = self.active_index.get();
93        let next = if current >= count - 1 {
94            if self.wrap.get() {
95                0
96            } else {
97                current
98            }
99        } else {
100            current + 1
101        };
102        self.go_to(next);
103    }
104
105    pub fn prev(&self) {
106        let count = self.slide_count.get();
107        if count == 0 {
108            return;
109        }
110        let current = self.active_index.get();
111        let previous = if current <= 0 {
112            if self.wrap.get() {
113                count - 1
114            } else {
115                current
116            }
117        } else {
118            current - 1
119        };
120        self.go_to(previous);
121    }
122}
123
124#[derive(Clone, Copy)]
125pub struct CarouselStateInjection(pub CarouselState);
126
127impl CarouselStateInjection {
128    pub fn expect_context() -> CarouselState {
129        expect_context::<Self>().0
130    }
131
132    pub fn use_context() -> Option<CarouselState> {
133        use_context::<Self>().map(|injection| injection.0)
134    }
135}