Skip to main content

leptos_use/
use_cycle_list.rs

1use crate::core::MaybeRwSignal;
2use default_struct_builder::DefaultBuilder;
3use leptos::prelude::*;
4
5/// Cycle through a list of items.
6///
7/// ## Demo
8///
9/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_cycle_list)
10///
11/// ## Usage
12///
13/// ```
14/// # use leptos::prelude::*;
15/// # use leptos::logging::log;
16/// use leptos_use::{use_cycle_list, UseCycleListReturn};
17/// #
18/// # #[component]
19/// # fn Demo() -> impl IntoView {
20/// let UseCycleListReturn { state, next, prev, .. } = use_cycle_list(
21///     vec!["Dog", "Cat", "Lizard", "Shark", "Whale", "Dolphin", "Octopus", "Seal"]
22/// );
23///
24/// log!("{}", state.get()); // "Dog"
25///
26/// prev();
27///
28/// log!("{}", state.get()); // "Seal"
29/// #
30/// # view! { }
31/// # }
32/// ```
33pub fn use_cycle_list<T, L>(
34    list: L,
35) -> UseCycleListReturn<
36    T,
37    impl Fn(usize) -> T + Clone + Send + Sync,
38    impl Fn() + Clone + Send + Sync,
39    impl Fn() + Clone + Send + Sync,
40    impl Fn(i64) -> T + Clone + Send + Sync,
41>
42where
43    T: Clone + PartialEq + Send + Sync + 'static,
44    L: Into<Signal<Vec<T>>>,
45{
46    use_cycle_list_with_options(list, UseCycleListOptions::default())
47}
48
49pub fn use_cycle_list_with_options<T, L>(
50    list: L,
51    options: UseCycleListOptions<T>,
52) -> UseCycleListReturn<
53    T,
54    impl Fn(usize) -> T + Clone,
55    impl Fn() + Clone,
56    impl Fn() + Clone,
57    impl Fn(i64) -> T + Clone,
58>
59where
60    T: Clone + PartialEq + Send + Sync + 'static,
61    L: Into<Signal<Vec<T>>>,
62{
63    let UseCycleListOptions {
64        initial_value,
65        fallback_index,
66        get_position,
67    } = options;
68
69    let list = list.into();
70
71    let get_initial_value = {
72        let list = list.get_untracked();
73        let first = list.first().cloned();
74
75        move || {
76            if let Some(initial_value) = initial_value {
77                initial_value
78            } else {
79                MaybeRwSignal::from(first.expect("The provided list shouldn't be empty"))
80            }
81        }
82    };
83
84    let (state, set_state) = get_initial_value().into_signal();
85
86    let index = Signal::derive(move || {
87        let index = get_position(&state.get(), &list.read());
88
89        if let Some(index) = index {
90            index
91        } else {
92            fallback_index
93        }
94    });
95
96    let set = move |i: usize| {
97        let length = list.read().len();
98
99        let index = i % length;
100        let value = list.read()[index].clone();
101
102        set_state.update({
103            let value = value.clone();
104
105            move |v| *v = value
106        });
107
108        value
109    };
110
111    let shift = move |delta: i64| {
112        let length = list.read().len() as i64;
113
114        let i = index.get_untracked() as i64 + delta;
115        let index = (i % length) + length;
116
117        set(index as usize)
118    };
119
120    let next = move || {
121        shift(1);
122    };
123
124    let prev = move || {
125        shift(-1);
126    };
127
128    let _ = Effect::watch(move || list.get(), move |_, _, _| set(index.get()), false);
129
130    UseCycleListReturn {
131        state,
132        set_state,
133        index,
134        set_index: set,
135        next,
136        prev,
137        shift,
138    }
139}
140
141/// Options for [`use_cycle_list_with_options`].
142#[derive(DefaultBuilder)]
143pub struct UseCycleListOptions<T>
144where
145    T: Clone + PartialEq + Send + Sync + 'static,
146{
147    /// The initial value of the state. Can be a Signal. If none is provided the first entry
148    /// of the list will be used.
149    #[builder(keep_type)]
150    initial_value: Option<MaybeRwSignal<T>>,
151
152    /// The default index when the current value is not found in the list.
153    /// For example when `get_index_of` returns `None`.
154    fallback_index: usize,
155
156    /// Custom function to get the index of the current value. Defaults to `Iterator::position()`
157    #[builder(keep_type)]
158    get_position: fn(&T, &Vec<T>) -> Option<usize>,
159}
160
161impl<T> Default for UseCycleListOptions<T>
162where
163    T: Clone + PartialEq + Send + Sync + 'static,
164{
165    fn default() -> Self {
166        Self {
167            initial_value: None,
168            fallback_index: 0,
169            get_position: |value: &T, list: &Vec<T>| list.iter().position(|v| v == value),
170        }
171    }
172}
173
174/// Return type of [`use_cycle_list`].
175pub struct UseCycleListReturn<T, SetFn, NextFn, PrevFn, ShiftFn>
176where
177    T: Clone + PartialEq + Send + Sync + 'static,
178    SetFn: Fn(usize) -> T + Clone,
179    NextFn: Fn() + Clone,
180    PrevFn: Fn() + Clone,
181    ShiftFn: Fn(i64) -> T + Clone,
182{
183    /// Current value
184    pub state: Signal<T>,
185    /// Set current value
186    pub set_state: WriteSignal<T>,
187    /// Current index of current value in list
188    pub index: Signal<usize>,
189    /// Set current index of current value in list
190    pub set_index: SetFn,
191    /// Go to next value (cyclic)
192    pub next: NextFn,
193    /// Go to previous value (cyclic)
194    pub prev: PrevFn,
195    /// Move by the specified amount from the current value (cyclic)
196    pub shift: ShiftFn,
197}