use crate::core::MaybeRwSignal; use default_struct_builder::DefaultBuilder; use leptos::*; /// Cycle through a list of items. /// /// ## Demo /// /// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_cycle_list) /// /// ## Usage /// /// ``` /// # use leptos::*; /// # use leptos::logging::log; /// use leptos_use::{use_cycle_list, UseCycleListReturn}; /// # /// # #[component] /// # fn Demo() -> impl IntoView { /// let UseCycleListReturn { state, next, prev, .. } = use_cycle_list( /// vec!["Dog", "Cat", "Lizard", "Shark", "Whale", "Dolphin", "Octopus", "Seal"] /// ); /// /// log!("{}", state.get()); // "Dog" /// /// prev(); /// /// log!("{}", state.get()); // "Seal" /// # /// # view! { } /// # } /// ``` pub fn use_cycle_list( list: L, ) -> UseCycleListReturn< T, impl Fn(usize) -> T + Clone, impl Fn() + Clone, impl Fn() + Clone, impl Fn(i64) -> T + Clone, > where T: Clone + PartialEq + 'static, L: Into>>, { use_cycle_list_with_options(list, UseCycleListOptions::default()) } pub fn use_cycle_list_with_options( list: L, options: UseCycleListOptions, ) -> UseCycleListReturn< T, impl Fn(usize) -> T + Clone, impl Fn() + Clone, impl Fn() + Clone, impl Fn(i64) -> T + Clone, > where T: Clone + PartialEq + 'static, L: Into>>, { let UseCycleListOptions { initial_value, fallback_index, get_position, } = options; let list = list.into(); let get_initial_value = { let list = list.get_untracked(); let first = list.first().cloned(); move || { if let Some(initial_value) = initial_value { initial_value } else { MaybeRwSignal::from(first.expect("The provided list shouldn't be empty")) } } }; let (state, set_state) = get_initial_value().into_signal(); let index = { let list = list.clone(); Signal::derive(move || { list.with(|list| { let index = get_position(&state.get(), list); if let Some(index) = index { index } else { fallback_index } }) }) }; let set = { let list = list.clone(); move |i: usize| { list.with(|list| { let length = list.len(); let index = i % length; let value = list[index].clone(); set_state.update({ let value = value.clone(); move |v| *v = value }); value }) } }; let shift = { let list = list.clone(); let set = set.clone(); move |delta: i64| { let index = list.with(|list| { let length = list.len() as i64; let i = index.get_untracked() as i64 + delta; (i % length) + length }); set(index as usize) } }; let next = { let shift = shift.clone(); move || { shift(1); } }; let prev = { let shift = shift.clone(); move || { shift(-1); } }; let _ = { let set = set.clone(); leptos::watch(move || list.get(), move |_, _, _| set(index.get()), false) }; UseCycleListReturn { state, set_state, index, set_index: set, next, prev, shift, } } /// Options for [`use_cycle_list_with_options`]. #[derive(DefaultBuilder)] pub struct UseCycleListOptions where T: Clone + PartialEq + 'static, { /// The initial value of the state. Can be a Signal. If none is provided the first entry /// of the list will be used. #[builder(keep_type)] initial_value: Option>, /// The default index when the current value is not found in the list. /// For example when `get_index_of` returns `None`. fallback_index: usize, /// Custom function to get the index of the current value. Defaults to `Iterator::position()` #[builder(keep_type)] get_position: fn(&T, &Vec) -> Option, } impl Default for UseCycleListOptions where T: Clone + PartialEq + 'static, { fn default() -> Self { Self { initial_value: None, fallback_index: 0, get_position: |value: &T, list: &Vec| list.iter().position(|v| v == value), } } } /// Return type of [`use_cycle_list`]. pub struct UseCycleListReturn where T: Clone + PartialEq + 'static, SetFn: Fn(usize) -> T + Clone, NextFn: Fn() + Clone, PrevFn: Fn() + Clone, ShiftFn: Fn(i64) -> T + Clone, { /// Current value pub state: Signal, /// Set current value pub set_state: WriteSignal, /// Current index of current value in list pub index: Signal, /// Set current index of current value in list pub set_index: SetFn, /// Go to next value (cyclic) pub next: NextFn, /// Go to previous value (cyclic) pub prev: PrevFn, /// Move by the specified amount from the current value (cyclic) pub shift: ShiftFn, }