use crate::core::ElementMaybeSignal; use crate::{use_mutation_observer_with_options, watch, watch_with_options, WatchOptions}; use default_struct_builder::DefaultBuilder; use leptos::*; use std::marker::PhantomData; use std::time::Duration; use wasm_bindgen::{JsCast, JsValue}; /// Manipulate CSS variables. /// /// ## Demo /// /// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_css_var) /// /// ## Usage /// /// ``` /// # use leptos::*; /// # use leptos_use::use_css_var; /// # /// # #[component] /// # fn Demo(cx: Scope) -> impl IntoView { /// let (color, set_color) = use_css_var(cx, "--color"); /// /// set_color("red".to_string()); /// # /// # view! { cx, } /// # } /// ``` /// /// The variable name itself can be a `Signal`. /// /// ``` /// # use leptos::*; /// # use leptos_use::use_css_var; /// # /// # #[component] /// # fn Demo(cx: Scope) -> impl IntoView { /// let (key, set_key) = create_signal(cx, "--color".to_string()); /// let (color, set_color) = use_css_var(cx, key); /// # /// # view! { cx, } /// # } /// ``` /// /// You can specify the element that the variable is applied to as well as an initial value in case /// the variable is not set yet. The option to listen for changes to the variable is also available. /// /// ``` /// # use leptos::*; /// # use leptos_use::{use_css_var_with_options, UseCssVarOptions}; /// # /// # #[component] /// # fn Demo(cx: Scope) -> impl IntoView { /// let el = create_node_ref(cx); /// /// let (color, set_color) = use_css_var_with_options( /// cx, /// "--color", /// UseCssVarOptions::default() /// .target(el) /// .initial_value("#eee") /// .observe(true), /// ); /// /// view! { cx, ///
"..."
/// } /// # } /// ``` pub fn use_css_var( cx: Scope, prop: impl Into>, ) -> (ReadSignal, WriteSignal) { use_css_var_with_options(cx, prop, UseCssVarOptions::default()) } /// Version of [`use_css_var`] that takes a `UseCssVarOptions`. See [`use_css_var`] for how to use. pub fn use_css_var_with_options( cx: Scope, prop: P, options: UseCssVarOptions, ) -> (ReadSignal, WriteSignal) where P: Into>, El: Clone, (Scope, El): Into>, T: Into + Clone + 'static, { let UseCssVarOptions { target, initial_value, observe, .. } = options; let (variable, set_variable) = create_signal(cx, initial_value.clone()); let el_signal = (cx, target).into(); let prop = prop.into(); let update_css_var = { let prop = prop.clone(); let el_signal = el_signal.clone(); move || { let key = prop.get_untracked(); if let Some(el) = el_signal.get_untracked() { if let Ok(Some(style)) = window().get_computed_style(&el.into()) { if let Ok(value) = style.get_property_value(&key) { set_variable.update(|var| *var = value.trim().to_string()); return; } } let initial_value = initial_value.clone(); set_variable.update(|var| *var = initial_value); } } }; if observe { let mut init = web_sys::MutationObserverInit::new(); let update_css_var = update_css_var.clone(); let el_signal = el_signal.clone(); init.attribute_filter(&js_sys::Array::from_iter( vec![JsValue::from_str("style")].into_iter(), )); use_mutation_observer_with_options::, T, _>( cx, el_signal, move |_, _| update_css_var(), init, ); } // To get around style attributes on node_refs that are not applied after the first render set_timeout(update_css_var.clone(), Duration::ZERO); { let el_signal = el_signal.clone(); let prop = prop.clone(); let _ = watch_with_options( cx, move || (el_signal.get(), prop.get()), move |_, _, _| update_css_var(), WatchOptions::default().immediate(true), ); } let _ = watch(cx, variable, move |val, _, _| { if let Some(el) = el_signal.get() { let el = el.into().unchecked_into::(); let style = el.style(); let _ = style.set_property(&prop.get_untracked(), val); } }); (variable, set_variable) } /// Options for [`use_css_var_with_options`]. #[derive(DefaultBuilder)] pub struct UseCssVarOptions where El: Clone, (Scope, El): Into>, T: Into + Clone + 'static, { /// The target element to read the variable from and set the variable on. /// Defaults to the `document.documentElement`. target: El, /// The initial value of the variable before it is read. Also the default value /// if the variable isn't defined on the target. Defaults to "". #[builder(into)] initial_value: String, /// If `true` use a `MutationObserver` to monitor variable changes. Defaults to `false`. observe: bool, #[builder(skip)] _marker: PhantomData, } impl Default for UseCssVarOptions { fn default() -> Self { Self { target: document().document_element().expect("No document element"), initial_value: "".into(), observe: false, _marker: PhantomData, } } }