leptos_pagination/hooks/
pagination.rs1use std::fmt::Debug;
2
3use default_struct_builder::DefaultBuilder;
4use leptos::prelude::*;
5use leptos_windowing::{
6 InternalLoader, ItemWindow,
7 hook::{UseLoadOnDemandResult, use_load_on_demand},
8};
9use reactive_stores::Store;
10
11use crate::{PaginationState, PaginationStateStoreFields};
12
13#[must_use]
70pub fn use_pagination<T, L, Q, M>(
71 state: Store<PaginationState>,
72 loader: L,
73 query: impl Into<Signal<Q>>,
74 item_count_per_page: impl Into<Signal<usize>>,
75 options: UsePaginationOptions,
76) -> ItemWindow<T>
77where
78 T: Send + Sync + 'static,
79 L: InternalLoader<M, Item = T, Query = Q> + 'static,
80 L::Error: Send + Sync,
81 Q: Send + Sync + 'static,
82{
83 let UsePaginationOptions {
84 overscan_page_count,
85 } = options;
86
87 let item_count_per_page = item_count_per_page.into();
88
89 let item_count = RwSignal::new(None::<usize>);
90
91 Effect::new(move || {
92 if let Some(item_count) = item_count.get() {
93 state
94 .page_count()
95 .set(Some(item_count.div_ceil(item_count_per_page.get())));
96 }
97 });
98
99 let start_index_to_load = Signal::derive(move || {
100 let current_page = state.current_page().get();
101 current_page.saturating_sub(overscan_page_count) * item_count_per_page.get()
102 });
103
104 let end_index_to_load = Signal::derive(move || {
105 let current_page = state.current_page().get();
106 (current_page + overscan_page_count) * item_count_per_page.get()
107 });
108
109 let range_to_load = Memo::new(move |_| {
110 let start_index = start_index_to_load.get();
111 let end_index = end_index_to_load.get();
112
113 start_index..end_index
114 });
115
116 let range_to_display = Memo::new(move |_| {
117 let item_count_per_page = item_count_per_page.get();
118 let start_index = state.current_page().get() * item_count_per_page;
119 let end_index = start_index + item_count_per_page;
120
121 start_index..end_index
122 });
123
124 let UseLoadOnDemandResult {
125 item_count_result,
126 item_window,
127 } = use_load_on_demand(range_to_load, range_to_display, loader, query);
128
129 Effect::new(move || {
130 match &*item_count_result.read() {
131 Ok(None) => {
132 *state.page_count_error().write() =
133 Some("Data source didn't provide an item/page count".to_string())
134 }
135 Ok(Some(count)) => {
136 item_count.set(Some(*count));
138 *state.page_count_error().write() = None;
139 }
140 Err(err) => {
141 *state.page_count_error().write() =
142 Some(format!("Error fetching item/page count: {err:?}"))
143 }
144 }
145 });
146
147 item_window
148}
149
150#[derive(Debug, Clone, DefaultBuilder)]
151pub struct UsePaginationOptions {
152 overscan_page_count: usize,
157}
158
159impl Default for UsePaginationOptions {
160 fn default() -> Self {
161 Self {
162 overscan_page_count: 1,
163 }
164 }
165}