1use crate::core::now;
2use crate::filter_builder_methods;
3use crate::utils::{DebounceOptions, FilterOptions, ThrottleOptions};
4use default_struct_builder::DefaultBuilder;
5use leptos::prelude::*;
6use leptos::reactive::wrappers::read::Signal;
7
8pub fn use_idle(timeout: u64) -> UseIdleReturn<impl Fn() + Clone + Send + Sync> {
70 use_idle_with_options(timeout, UseIdleOptions::default())
71}
72
73pub fn use_idle_with_options(
75 timeout: u64,
76 options: UseIdleOptions,
77) -> UseIdleReturn<impl Fn() + Clone + Send + Sync> {
78 let UseIdleOptions {
79 events,
80 listen_for_visibility_change,
81 initial_state,
82 filter,
83 } = options;
84
85 let (idle, set_idle) = signal(initial_state);
86 let (last_active, set_last_active) = signal(now());
87
88 let reset;
89
90 #[cfg(feature = "ssr")]
91 {
92 reset = || ();
93 let _ = timeout;
94 let _ = events;
95 let _ = listen_for_visibility_change;
96 let _ = filter;
97 let _ = set_last_active;
98 let _ = set_idle;
99 }
100
101 #[cfg(not(feature = "ssr"))]
102 {
103 use crate::utils::create_filter_wrapper;
104 use crate::{
105 sendwrap_fn, use_document, use_event_listener, use_event_listener_with_options,
106 UseEventListenerOptions,
107 };
108 use leptos::ev::{visibilitychange, Custom};
109 use leptos::leptos_dom::helpers::TimeoutHandle;
110 use std::cell::Cell;
111 use std::rc::Rc;
112 use std::time::Duration;
113
114 let timer = Rc::new(Cell::new(None::<TimeoutHandle>));
115
116 reset = {
117 let timer = Rc::clone(&timer);
118
119 sendwrap_fn!(move || {
120 set_idle.set(false);
121 if let Some(timer) = timer.replace(
122 set_timeout_with_handle(
123 move || set_idle.set(true),
124 Duration::from_millis(timeout),
125 )
126 .ok(),
127 ) {
128 timer.clear();
129 }
130 })
131 };
132
133 let on_event = {
134 let reset = reset.clone();
135
136 let filtered_callback = create_filter_wrapper(filter.filter_fn(), move || {
137 set_last_active.set(js_sys::Date::now());
138 reset();
139 });
140
141 move |_: web_sys::Event| {
142 filtered_callback();
143 }
144 };
145
146 let listener_options = UseEventListenerOptions::default().passive(true);
147 for event in events {
148 let _ = use_event_listener_with_options(
149 use_document(),
150 Custom::new(event),
151 on_event.clone(),
152 listener_options,
153 );
154 }
155
156 if listen_for_visibility_change {
157 let on_event = on_event.clone();
158
159 let _ = use_event_listener(use_document(), visibilitychange, move |evt| {
160 if !document().hidden() {
161 on_event(evt);
162 }
163 });
164 }
165
166 reset.clone()();
167 }
168
169 UseIdleReturn {
170 idle: idle.into(),
171 last_active: last_active.into(),
172 reset,
173 }
174}
175
176#[derive(DefaultBuilder)]
178pub struct UseIdleOptions {
179 events: Vec<String>,
182
183 listen_for_visibility_change: bool,
186
187 initial_state: bool,
190
191 filter: FilterOptions,
194}
195
196impl Default for UseIdleOptions {
197 fn default() -> Self {
198 Self {
199 events: vec![
200 "mousemove".to_string(),
201 "mousedown".to_string(),
202 "resize".to_string(),
203 "keydown".to_string(),
204 "touchstart".to_string(),
205 "wheel".to_string(),
206 ],
207 listen_for_visibility_change: true,
208 initial_state: false,
209 filter: FilterOptions::throttle(50.0),
210 }
211 }
212}
213
214impl UseIdleOptions {
215 filter_builder_methods!(
216 filter
218 );
219}
220
221pub struct UseIdleReturn<F>
223where
224 F: Fn() + Clone + Send + Sync,
225{
226 pub idle: Signal<bool>,
228
229 pub last_active: Signal<f64>,
231
232 pub reset: F,
234}