From 5329561687f63bfee71c0d1e08cafec681376ae5 Mon Sep 17 00:00:00 2001 From: jquesada2016 <54370171+jquesada2016@users.noreply.github.com> Date: Mon, 27 Mar 2023 17:03:59 -0600 Subject: [PATCH] feat: add `is_mounted` and `dyn_classes` (#714) --- leptos_dom/src/html.rs | 119 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/leptos_dom/src/html.rs b/leptos_dom/src/html.rs index 4d4f8c907..13c18e173 100644 --- a/leptos_dom/src/html.rs +++ b/leptos_dom/src/html.rs @@ -573,6 +573,23 @@ impl HtmlElement { self } + /// Checks to see if this element is mounted to the DOM as a child + /// of `body`. + /// + /// This method will always return [`None`] on non-wasm CSR targets. + pub fn is_mounted(&self) -> bool { + #[cfg(all(target_arch = "wasm32", feature = "web"))] + { + crate::document() + .body() + .unwrap() + .contains(Some(self.element.as_ref())) + } + + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + false + } + /// Adds an attribute to this element. #[track_caller] pub fn attr( @@ -679,6 +696,108 @@ impl HtmlElement { this } + /// Sets the class on the element as the class signal changes. + #[track_caller] + pub fn dyn_classes( + self, + classes_signal: impl Fn() -> I + 'static, + ) -> Self + where + I: IntoIterator, + C: Into>, + { + #[cfg(all(target_arch = "wasm32", feature = "web"))] + { + use smallvec::SmallVec; + + let class_list = self.element.as_ref().class_list(); + + leptos_reactive::create_effect( + self.cx, + move |prev_classes: Option< + SmallVec<[Cow<'static, str>; 4]>, + >| { + let classes = classes_signal() + .into_iter() + .map(Into::into) + .collect::; 4]>>( + ); + + let mut new_classes = classes + .iter() + .flat_map(|classes| classes.split_whitespace()); + + if let Some(prev_classes) = prev_classes { + let mut old_classes = prev_classes + .iter() + .flat_map(|classes| classes.split_whitespace()); + + // Remove old classes + for prev_class in old_classes.clone() { + if !new_classes.any(|c| c == prev_class) { + class_list.remove_1(prev_class).unwrap_or_else( + |err| { + panic!( + "failed to add class \ + `{prev_class}`, error: {err:#?}" + ) + }, + ); + } + } + + // Add new classes + for class in new_classes { + if !old_classes.any(|c| c == class) { + class_list.add_1(class).unwrap_or_else(|err| { + panic!( + "failed to remove class `{class}`, \ + error: {err:#?}" + ) + }); + } + } + } else { + let new_classes = new_classes + .map(ToOwned::to_owned) + .collect::>(); + + for class in &new_classes { + class_list.add_1(class).unwrap_or_else(|err| { + panic!( + "failed to add class `{class}`, error: \ + {err:#?}" + ) + }); + } + } + + classes + }, + ); + + self + } + + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + { + let mut this = self; + + let this = classes_signal() + .into_iter() + .map(Into::into) + .flat_map(|classes| { + classes + .split_whitespace() + .map(ToString::to_string) + .collect::>() + }) + .fold(this, |this, class| this.class(class, true)); + + this + } + } + /// Sets a property on an element. #[track_caller] pub fn prop(