use std::cell::Cell;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MotionPreference {
Full,
Reduced,
}
thread_local! {
static REDUCED: Cell<bool> = const { Cell::new(false) };
static INSTALLED: Cell<bool> = const { Cell::new(false) };
}
pub fn install() {
INSTALLED.with(|installed| {
if installed.get() {
return;
}
installed.set(true);
});
let Some(window) = web_sys::window() else {
return;
};
let Ok(Some(mql)) = window.match_media("(prefers-reduced-motion: reduce)") else {
return;
};
REDUCED.with(|c| c.set(mql.matches()));
let listener = Closure::<dyn Fn(web_sys::Event)>::new(|ev: web_sys::Event| {
let Some(target) = ev.target() else { return };
let Ok(mql) = target.dyn_into::<web_sys::MediaQueryList>() else {
return;
};
REDUCED.with(|c| c.set(mql.matches()));
});
let _ = mql.add_event_listener_with_callback("change", listener.as_ref().unchecked_ref());
listener.forget();
}
pub fn current() -> MotionPreference {
if REDUCED.with(|c| c.get()) {
MotionPreference::Reduced
} else {
MotionPreference::Full
}
}
pub fn is_reduced() -> bool {
REDUCED.with(|c| c.get())
}
pub fn element_override(el: &web_sys::Element) -> Option<bool> {
let mut cur: Option<web_sys::Element> = Some(el.clone());
while let Some(e) = cur {
if let Some(v) = e.get_attribute("data-pp-motion") {
return match v.as_str() {
"always" => Some(true),
"reduce" => Some(false),
_ => None,
};
}
cur = e.parent_element();
}
None
}
pub fn effective_for(el: &web_sys::Element) -> MotionPreference {
match element_override(el) {
Some(true) => MotionPreference::Full,
Some(false) => MotionPreference::Reduced,
None => current(),
}
}
#[cfg(test)]
mod tests {
use super::MotionPreference;
#[test]
fn motion_preference_eq() {
assert_eq!(MotionPreference::Full, MotionPreference::Full);
assert_ne!(MotionPreference::Full, MotionPreference::Reduced);
}
}