Skip to main content

leptos_use/
use_media_query.rs

1#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports, dead_code))]
2
3use crate::use_event_listener;
4use cfg_if::cfg_if;
5use leptos::ev::change;
6use leptos::prelude::*;
7use leptos::reactive::wrappers::read::Signal;
8use std::cell::RefCell;
9use std::rc::Rc;
10
11/// Reactive [Media Query](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Testing_media_queries).
12///
13/// ## Demo
14///
15/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_media_query)
16///
17/// ## Usage
18///
19/// ```
20/// # use leptos::prelude::*;
21/// # use leptos_use::use_media_query;
22/// #
23/// # #[component]
24/// # fn Demo() -> impl IntoView {
25/// #
26/// let is_large_screen = use_media_query("(min-width: 1024px)");
27///
28/// let is_dark_preferred = use_media_query("(prefers-color-scheme: dark)");
29/// #
30/// #    view! { }
31/// # }
32/// ```
33///
34/// ## Server-Side Rendering
35///
36/// > Make sure you follow the [instructions in Server-Side Rendering](https://leptos-use.rs/server_side_rendering.html).
37///
38/// On the server this functions returns a Signal that is always `false`.
39///
40/// ## See also
41///
42/// * [`fn@crate::use_preferred_dark`]
43/// * [`fn@crate::use_preferred_contrast`]
44/// * [`fn@crate::use_prefers_reduced_motion`]
45pub fn use_media_query(query: impl Into<Signal<String>>) -> Signal<bool> {
46    let query = query.into();
47
48    let (matches, set_matches) = signal(false);
49
50    cfg_if! { if #[cfg(not(feature = "ssr"))] {
51        let media_query: Rc<RefCell<Option<web_sys::MediaQueryList>>> = Rc::new(RefCell::new(None));
52        let remove_listener: RemoveListener = Rc::new(RefCell::new(None));
53
54        let listener = Rc::new(RefCell::new(Rc::new(|_| {}) as Rc<dyn Fn(web_sys::Event)>));
55
56        let cleanup = {
57            let remove_listener = Rc::clone(&remove_listener);
58
59            move || {
60                if let Some(remove_listener) = remove_listener.take().as_ref() {
61                    remove_listener();
62                }
63            }
64        };
65
66        let update = {
67            let cleanup = cleanup.clone();
68            let listener = Rc::clone(&listener);
69
70            Rc::new(move || {
71                cleanup();
72
73                let mut media_query = media_query.borrow_mut();
74                *media_query = window().match_media(&query.get()).unwrap_or(None);
75
76                if let Some(media_query) = media_query.as_ref() {
77                    set_matches.set(media_query.matches());
78
79                    let listener = Rc::clone(&*listener.borrow());
80
81                    remove_listener.replace(Some(Box::new(use_event_listener(
82                        media_query.clone(),
83                        change,
84                        move |e| listener(e),
85                    ))));
86                } else {
87                    set_matches.set(false);
88                }
89            })
90        };
91
92        {
93            let update = Rc::clone(&update);
94            listener.replace(Rc::new(move |_| update()) as Rc<dyn Fn(web_sys::Event)>);
95        }
96
97        Effect::new(move |_| update());
98
99        on_cleanup({
100            let cleanup = send_wrapper::SendWrapper::new(cleanup);
101            #[allow(clippy::redundant_closure)]
102            move || cleanup()
103        });
104    }}
105
106    matches.into()
107}
108
109type RemoveListener = Rc<RefCell<Option<Box<dyn Fn()>>>>;