#![deny(missing_docs)] #![forbid(unsafe_code)] #![cfg_attr(feature = "nightly", feature(fn_traits))] #![cfg_attr(feature = "nightly", feature(unboxed_closures))] // to prevent warnings from popping up when a nightly feature is stabilized #![allow(stable_features)] //! The DOM implementation for `leptos`. #[doc(hidden)] #[cfg_attr(any(debug_assertions, feature = "ssr"), macro_use)] pub extern crate tracing; mod components; mod directive; mod events; pub mod helpers; pub mod html; mod hydration; /// Utilities for simple isomorphic logging to the console or terminal. pub mod logging; mod macro_helpers; pub mod math; mod node_ref; /// Utilities for exporting nonces to be used for a Content Security Policy. pub mod nonce; pub mod ssr; pub mod ssr_in_order; pub mod svg; mod transparent; use cfg_if::cfg_if; pub use components::*; pub use directive::*; #[cfg(all(target_arch = "wasm32", feature = "web"))] pub use events::add_event_helper; #[cfg(all(target_arch = "wasm32", feature = "web"))] use events::{add_event_listener, add_event_listener_undelegated}; pub use events::{ typed as ev, typed::{EventHandler, EventHandlerFn}, }; pub use html::HtmlElement; use html::{AnyElement, ElementDescriptor}; pub use hydration::{HydrationCtx, HydrationKey}; #[cfg(not(feature = "nightly"))] use leptos_reactive::{ MaybeProp, MaybeSignal, Memo, ReadSignal, RwSignal, Signal, SignalGet, }; use leptos_reactive::{Oco, TextProp}; pub use macro_helpers::*; pub use node_ref::*; #[cfg(all(target_arch = "wasm32", feature = "web"))] use once_cell::unsync::Lazy as LazyCell; #[cfg(not(all(target_arch = "wasm32", feature = "web")))] use smallvec::SmallVec; #[cfg(all(target_arch = "wasm32", feature = "web"))] use std::cell::RefCell; use std::{borrow::Cow, fmt, rc::Rc}; pub use transparent::*; #[cfg(all(target_arch = "wasm32", feature = "web"))] use wasm_bindgen::JsCast; use wasm_bindgen::UnwrapThrowExt; #[cfg(all(target_arch = "wasm32", feature = "web"))] thread_local! { static COMMENT: LazyCell = LazyCell::new(|| document().create_comment("").unchecked_into()); static RANGE: LazyCell = LazyCell::new(|| web_sys::Range::new().unwrap()); } /// Converts the value into a [`View`]. pub trait IntoView { /// Converts the value into [`View`]. fn into_view(self) -> View; } #[cfg(all(target_arch = "wasm32", feature = "web"))] #[doc(hidden)] pub trait Mountable { /// Gets the [`web_sys::Node`] that can be directly inserted as /// a child of another node. Typically, this is a [`web_sys::DocumentFragment`] /// for components, and [`web_sys::HtmlElement`] for elements. /// /// ### Important Note /// Calling this method assumes that you are intending to move this /// view, and will unmount it's nodes from the DOM if this view is a /// component. In other words, don't call this method unless you intend /// to mount this view to another view or element. fn get_mountable_node(&self) -> web_sys::Node; /// Get's the first node of the [`View`]. /// Typically, for [`HtmlElement`], this will be the /// `element` node. For components, this would be the /// first child node, or the `closing` marker comment node if /// no children are available. fn get_opening_node(&self) -> web_sys::Node; /// Get's the closing marker node. fn get_closing_node(&self) -> web_sys::Node; } impl IntoView for () { #[cfg_attr( any(debug_assertions, feature = "ssr"), instrument(level = "trace", name = "<() />", skip_all) )] fn into_view(self) -> View { Unit.into_view() } } impl IntoView for Option where T: IntoView, { #[cfg_attr( any(debug_assertions, feature = "ssr"), instrument(level = "trace", name = "Option", skip_all) )] fn into_view(self) -> View { if let Some(t) = self { t.into_view() } else { Unit.into_view() } } } impl IntoView for F where F: Fn() -> N + 'static, N: IntoView, { #[cfg_attr( any(debug_assertions, feature = "ssr"), instrument(level = "trace", name = "Fn() -> impl IntoView", skip_all) )] #[track_caller] fn into_view(self) -> View { DynChild::new(self).into_view() } } impl IntoView for Rc N> where N: IntoView + 'static, { #[inline] fn into_view(self) -> View { // reuse impl for `Fn() -> impl IntoView` IntoView::into_view(move || self()) } } #[cfg(not(feature = "nightly"))] impl IntoView for ReadSignal where T: IntoView + Clone, { #[cfg_attr( any(debug_assertions, feature = "ssr"), instrument(level = "trace", name = "ReadSignal", skip_all) )] fn into_view(self) -> View { DynChild::new(move || self.get()).into_view() } } #[cfg(not(feature = "nightly"))] impl IntoView for RwSignal where T: IntoView + Clone, { #[cfg_attr( any(debug_assertions, feature = "ssr"), instrument(level = "trace", name = "RwSignal", skip_all) )] fn into_view(self) -> View { DynChild::new(move || self.get()).into_view() } } #[cfg(not(feature = "nightly"))] impl IntoView for Memo where T: IntoView + Clone, { #[cfg_attr( any(debug_assertions, feature = "ssr"), instrument(level = "trace", name = "Memo", skip_all) )] fn into_view(self) -> View { DynChild::new(move || self.get()).into_view() } } #[cfg(not(feature = "nightly"))] impl IntoView for Signal where T: IntoView + Clone, { #[cfg_attr( any(debug_assertions, feature = "ssr"), instrument(level = "trace", name = "Signal", skip_all) )] fn into_view(self) -> View { DynChild::new(move || self.get()).into_view() } } #[cfg(not(feature = "nightly"))] impl IntoView for MaybeSignal where T: IntoView + Clone, { #[cfg_attr( any(debug_assertions, feature = "ssr"), instrument(level = "trace", name = "MaybeSignal", skip_all) )] fn into_view(self) -> View { DynChild::new(move || self.get()).into_view() } } #[cfg(not(feature = "nightly"))] impl IntoView for MaybeProp where T: IntoView + Clone, { #[cfg_attr( any(debug_assertions, feature = "ssr"), instrument(level = "trace", name = "MaybeSignal", skip_all) )] fn into_view(self) -> View { DynChild::new(move || self.get()).into_view() } } impl IntoView for TextProp { fn into_view(self) -> View { self.get().into_view() } } /// Collects an iterator or collection into a [`View`]. pub trait CollectView { /// Collects an iterator or collection into a [`View`]. fn collect_view(self) -> View; } impl, T: IntoView> CollectView for I { #[cfg_attr( any(debug_assertions, feature = "ssr"), instrument(level = "trace", name = "#text", skip_all) )] fn collect_view(self) -> View { self.into_iter() .map(|v| v.into_view()) .collect::() .into_view() } } cfg_if! { if #[cfg(all(target_arch = "wasm32", feature = "web"))] { /// HTML element. #[derive(Clone, PartialEq, Eq)] pub struct Element { #[doc(hidden)] #[cfg(debug_assertions)] pub name: Oco<'static, str>, #[doc(hidden)] pub element: web_sys::HtmlElement, #[cfg(debug_assertions)] /// Optional marker for the view macro source of the element. pub view_marker: Option } impl fmt::Debug for Element { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let html = self.element.outer_html(); f.write_str(&html) } } } else { use crate::html::ElementChildren; /// HTML element. #[derive(Clone, PartialEq, Eq)] pub struct Element { name: Oco<'static, str>, is_void: bool, attrs: SmallVec<[(Oco<'static, str>, Oco<'static, str>); 4]>, children: ElementChildren, id: Option, #[cfg(debug_assertions)] /// Optional marker for the view macro source, in debug mode. pub view_marker: Option } impl fmt::Debug for Element { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use fmt::Write; let attrs = self.attrs.iter().fold(String::new(), |mut output, (n, v)| { // can safely ignore output // see https://rust-lang.github.io/rust-clippy/master/index.html#/format_collect let _ = write!(output, " {n}=\"{v}\""); output }); if self.is_void { write!(f, "<{}{attrs} />", self.name) } else { writeln!(f, "<{}{attrs}>", self.name)?; let mut pad_adapter = pad_adapter::PadAdapter::new(f); if let ElementChildren::Children(children) = &self.children { for child in children { writeln!(pad_adapter, "{child:#?}")?; } } write!(f, "", self.name) } } } } } impl Element { /// Converts this leptos [`Element`] into [`HtmlElement`]. pub fn into_html_element(self) -> HtmlElement { #[cfg(all(target_arch = "wasm32", feature = "web"))] { let Self { element, #[cfg(debug_assertions)] view_marker, .. } = self; let name = element.node_name().to_ascii_lowercase(); let element = AnyElement { name: name.into(), element, is_void: false, }; HtmlElement { element, #[cfg(debug_assertions)] span: ::tracing::Span::current(), #[cfg(debug_assertions)] view_marker, } } #[cfg(not(all(target_arch = "wasm32", feature = "web")))] { let Self { name, is_void, attrs, children, id, #[cfg(debug_assertions)] view_marker, } = self; let element = AnyElement { name, is_void, id }; HtmlElement { element, attrs, children, #[cfg(debug_assertions)] view_marker, } } } } impl IntoView for Element { #[cfg_attr(debug_assertions, instrument(level = "trace", name = "", skip_all, fields(tag = %self.name)))] fn into_view(self) -> View { View::Element(self) } } impl Element { #[track_caller] fn new(el: El) -> Self { cfg_if! { if #[cfg(all(target_arch = "wasm32", feature = "web"))] { Self { #[cfg(debug_assertions)] name: el.name(), element: el.as_ref().clone(), #[cfg(debug_assertions)] view_marker: None } } else { Self { name: el.name(), is_void: el.is_void(), attrs: Default::default(), children: Default::default(), id: *el.hydration_id(), #[cfg(debug_assertions)] view_marker: None } } } } } #[derive(Debug, Clone, PartialEq, Eq)] struct Comment { #[cfg(all(target_arch = "wasm32", feature = "web"))] node: web_sys::Node, content: Oco<'static, str>, } impl Comment { #[inline] fn new( content: impl Into>, id: &Option, closing: bool, ) -> Self { Self::new_inner(content.into(), id, closing) } fn new_inner( content: Oco<'static, str>, id: &Option, closing: bool, ) -> Self { cfg_if! { if #[cfg(not(all(target_arch = "wasm32", feature = "web")))] { let _ = id; let _ = closing; Self { content } } else { #[cfg(not(feature = "hydrate"))] { _ = id; _ = closing; } let node = COMMENT.with(|comment| comment.clone_node().unwrap()); #[cfg(debug_assertions)] node.set_text_content(Some(&format!(" {content} "))); #[cfg(feature = "hydrate")] if HydrationCtx::is_hydrating() && id.is_some() { let id = id.as_ref().unwrap(); let id = HydrationCtx::to_string(id, closing); if let Some(marker) = hydration::get_marker(&id) { marker.before_with_node_1(&node).unwrap(); marker.remove(); } else { crate::warn!( "component with id {id} not found, ignoring it for \ hydration" ); } } Self { node, content, } } } } } /// HTML text #[derive(Clone, PartialEq, Eq)] pub struct Text { /// In order to support partial updates on text nodes, that is, /// to update the node without recreating it, we need to be able /// to possibly reuse a previous node. #[cfg(all(target_arch = "wasm32", feature = "web"))] pub(crate) node: web_sys::Node, /// The current contents of the text node. pub content: Oco<'static, str>, } impl fmt::Debug for Text { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.content, f) } } impl IntoView for Text { #[cfg_attr(debug_assertions, instrument(level = "trace", name = "#text", skip_all, fields(content = %self.content)))] fn into_view(self) -> View { View::Text(self) } } impl Text { /// Creates a new [`Text`]. pub fn new(content: Oco<'static, str>) -> Self { Self { #[cfg(all(target_arch = "wasm32", feature = "web"))] node: crate::document() .create_text_node(&content) .unchecked_into::(), content, } } } /// A leptos view which can be mounted to the DOM. #[derive(Clone, PartialEq, Eq)] #[must_use = "You are creating a View but not using it. An unused view can \ cause your view to be rendered as () unexpectedly, and it can \ also cause issues with client-side hydration."] pub enum View { /// HTML element node. Element(Element), /// HTML text node. Text(Text), /// Custom leptos component. Component(ComponentRepr), /// leptos core-component. CoreComponent(CoreComponent), /// Wraps arbitrary data that's not part of the view but is /// passed via the view tree. Transparent(Transparent), /// Marks the contents of Suspense component, which can be replaced in streaming SSR. Suspense(HydrationKey, CoreComponent), } impl fmt::Debug for View { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Element(el) => el.fmt(f), Self::Text(t) => t.fmt(f), Self::Component(c) => c.fmt(f), Self::CoreComponent(c) => c.fmt(f), Self::Transparent(arg0) => { f.debug_tuple("Transparent").field(arg0).finish() } Self::Suspense(id, c) => { f.debug_tuple("Suspense").field(id).field(c).finish() } } } } /// The default [`View`] is the [`Unit`] core-component. impl Default for View { fn default() -> Self { Self::CoreComponent(Default::default()) } } impl IntoView for View { #[cfg_attr(debug_assertions, instrument(level = "trace", name = "Node", skip_all, fields(kind = self.kind_name())))] fn into_view(self) -> View { self } } impl IntoView for &View { fn into_view(self) -> View { self.clone() } } impl IntoView for [View; N] { #[cfg_attr( any(debug_assertions, feature = "ssr"), instrument(level = "trace", name = "[Node; N]", skip_all) )] fn into_view(self) -> View { Fragment::new(self.into_iter().collect()).into_view() } } impl IntoView for &Fragment { fn into_view(self) -> View { self.to_owned().into_view() } } impl FromIterator for View { fn from_iter>(iter: T) -> Self { iter.into_iter().collect::().into() } } #[cfg(all(target_arch = "wasm32", feature = "web"))] impl Mountable for View { fn get_mountable_node(&self) -> web_sys::Node { match self { Self::Element(element) => { element.element.unchecked_ref::().clone() } Self::Text(t) => t.node.clone(), Self::CoreComponent(c) | Self::Suspense(_, c) => match c { CoreComponent::Unit(u) => u.get_mountable_node(), CoreComponent::DynChild(dc) => dc.get_mountable_node(), CoreComponent::Each(e) => e.get_mountable_node(), }, Self::Component(c) => c.get_mountable_node(), Self::Transparent(_) => { panic!("tried to mount a Transparent node.") } } } fn get_opening_node(&self) -> web_sys::Node { match self { Self::Text(t) => t.node.clone(), Self::Element(el) => el.element.clone().unchecked_into(), Self::CoreComponent(c) | Self::Suspense(_, c) => match c { CoreComponent::DynChild(dc) => dc.get_opening_node(), CoreComponent::Each(e) => e.get_opening_node(), CoreComponent::Unit(u) => u.get_opening_node(), }, Self::Component(c) => c.get_opening_node(), Self::Transparent(_) => { panic!("tried to get opening node for a Transparent node.") } } } fn get_closing_node(&self) -> web_sys::Node { match self { Self::Text(t) => t.node.clone(), Self::Element(el) => el.element.clone().unchecked_into(), Self::CoreComponent(c) | Self::Suspense(_, c) => match c { CoreComponent::DynChild(dc) => dc.get_closing_node(), CoreComponent::Each(e) => e.get_closing_node(), CoreComponent::Unit(u) => u.get_closing_node(), }, Self::Component(c) => c.get_closing_node(), Self::Transparent(_) => { panic!("tried to get closing node for a Transparent node.") } } } } impl View { #[cfg(debug_assertions)] fn kind_name(&self) -> &'static str { match self { Self::Component(..) => "Component", Self::Element(..) => "Element", Self::Text(..) => "Text", Self::CoreComponent(c) => match c { CoreComponent::DynChild(..) => "DynChild", CoreComponent::Each(..) => "Each", CoreComponent::Unit(..) => "Unit", }, Self::Transparent(..) => "Transparent", Self::Suspense(..) => "Suspense", } } #[cfg(all(target_arch = "wasm32", feature = "web"))] fn get_text(&self) -> Option<&Text> { if let Self::Text(t) = self { Some(t) } else { None } } /// Returns [`Some`] [`Text`] if the view is of this type. [`None`] /// otherwise. pub fn as_text(&self) -> Option<&Text> { if let Self::Text(t) = self { Some(t) } else { None } } /// Returns [`Some`] [`Element`] if the view is of this type. [`None`] /// otherwise. pub fn as_element(&self) -> Option<&Element> { if let Self::Element(el) = self { Some(el) } else { None } } /// Returns [`Some`] [`Transparent`] if the view is of this type. [`None`] /// otherwise. pub fn as_transparent(&self) -> Option<&Transparent> { match &self { Self::Transparent(t) => Some(t), _ => None, } } /// Returns [`Ok(HtmlElement)`] if this [`View`] is /// of type [`Element`]. [`Err(View)`] otherwise. pub fn into_html_element(self) -> Result, Self> { if let Self::Element(el) = self { Ok(el.into_html_element()) } else { Err(self) } } /// Adds an event listener, analogous to [`HtmlElement::on`]. /// /// This method will attach an event listener to **all** children #[inline(always)] pub fn on( self, event: E, #[allow(unused_mut)] mut event_handler: impl FnMut(E::EventType) + 'static, ) -> Self { cfg_if::cfg_if! { if #[cfg(debug_assertions)] { trace!("calling on() {}", event.name()); let span = ::tracing::Span::current(); let event_handler = move |e| { let _guard = span.enter(); event_handler(e); }; } } self.on_impl(event, Box::new(event_handler)) } fn on_impl( self, event: E, event_handler: Box, ) -> Self { cfg_if! { if #[cfg(all(target_arch = "wasm32", feature = "web"))] { match &self { Self::Element(el) => { if E::BUBBLES { add_event_listener(&el.element, event.event_delegation_key(), event.name(), event_handler, &None); } else { add_event_listener_undelegated( &el.element, &event.name(), event_handler, &None, ); } } Self::Component(c) => { let event_handler = Rc::new(RefCell::new(event_handler)); c.children.iter().cloned().for_each(|c| { let event_handler = Rc::clone(&event_handler); _ = c.on(event.clone(), Box::new(move |e| event_handler.borrow_mut()(e))); }); } Self::CoreComponent(c) => match c { CoreComponent::DynChild(d) => { if let Some(subview) = *d.child.take() { let subview = subview.on(event, event_handler); d.child.replace(Box::new(Some(subview))); } } CoreComponent::Each(each) => { let event_handler = Rc::new(RefCell::new(event_handler)); let new_children = each.children.take().into_iter().map(|item| { if let Some(mut item) = item { let event_handler = Rc::clone(&event_handler); item.child = item.child.on(event.clone(), Box::new(move |e| event_handler.borrow_mut()(e))); Some(item) } else { None } }).collect::>(); each.children.replace(new_children); } CoreComponent::Unit(_) => {} }, _ => {} } } else { _ = event; _ = event_handler; } } self } /// Adds a directive analogous to [`HtmlElement::directive`]. /// /// This method will attach directive to **all** child /// [`HtmlElement`] children. #[inline(always)] pub fn directive( self, handler: impl Directive + 'static, param: P, ) -> Self where T: ?Sized + 'static, P: Clone + 'static, { cfg_if::cfg_if! { if #[cfg(debug_assertions)] { trace!("calling directive()"); let span = ::tracing::Span::current(); let handler = move |e, p| { let _guard = span.enter(); handler.run(e, p); }; } } self.directive_impl(Box::new(handler), param) } fn directive_impl( self, handler: Box>, param: P, ) -> Self where T: ?Sized + 'static, P: Clone + 'static, { cfg_if! { if #[cfg(all(target_arch = "wasm32", feature = "web"))] { match &self { Self::Element(el) => { let _ = el.clone().into_html_element().directive(handler, param); } Self::Component(c) => { let handler = Rc::from(handler); for child in c.children.iter().cloned() { let _ = child.directive(Rc::clone(&handler), param.clone()); } } _ => {} } } else { let _ = handler; let _ = param; }} self } } #[cfg_attr(debug_assertions, instrument)] #[track_caller] #[cfg(all(target_arch = "wasm32", feature = "web"))] #[doc(hidden)] pub fn mount_child( kind: MountKind, child: &GWSN, ) { let child = child.get_mountable_node(); match kind { MountKind::Append(el) => { el.append_child(&child) .expect("append operation to not err"); } MountKind::Before(closing) => { closing .unchecked_ref::() .before_with_node_1(&child) .expect("before to not err"); } } } #[cfg(all(target_arch = "wasm32", feature = "web"))] #[track_caller] fn unmount_child(start: &web_sys::Node, end: &web_sys::Node) { let mut sibling = start.clone(); while sibling != *end { if let Some(next_sibling) = sibling.next_sibling() { sibling.unchecked_ref::().remove(); sibling = next_sibling; } else { break; } } } /// Similar to [`unmount_child`], but instead of removing entirely /// from the DOM, it inserts all child nodes into the [`DocumentFragment`]. /// /// [DocumentFragment]: web_sys::DocumentFragment #[cfg(all(target_arch = "wasm32", feature = "web"))] #[track_caller] fn prepare_to_move( frag: &web_sys::DocumentFragment, opening: &web_sys::Node, closing: &web_sys::Node, ) { let mut sibling = opening.clone(); while sibling != *closing { if let Some(next_sibling) = sibling.next_sibling() { frag.append_child(&sibling).unwrap(); sibling = next_sibling; } else { frag.append_child(&sibling).unwrap(); break; } } frag.append_child(closing).unwrap(); } #[cfg(all(target_arch = "wasm32", feature = "web"))] #[derive(Debug)] #[doc(hidden)] pub enum MountKind<'a> { Before( // The closing node &'a web_sys::Node, ), Append(&'a web_sys::Node), } /// Runs the provided closure and mounts the result to the ``. pub fn mount_to_body(f: F) where F: FnOnce() -> N + 'static, N: IntoView, { #[cfg(all(feature = "web", feature = "ssr"))] crate::logging::console_warn( "You have both `csr` and `ssr` or `hydrate` and `ssr` enabled as \ features, which may cause issues like ` failing to work \ silently.", ); cfg_if! { if #[cfg(all(target_arch = "wasm32", feature = "web"))] { mount_to(crate::document().body().expect("body element to exist"), f) } else { _ = f; crate::warn!("`mount_to_body` should not be called outside the browser."); } } } /// Runs the provided closure and mounts the result to the provided element. pub fn mount_to(parent: web_sys::HtmlElement, f: F) where F: FnOnce() -> N + 'static, N: IntoView, { mount_to_with_stop_hydrating(parent, true, f) } /// Runs the provided closure and mounts the result to the provided element. pub fn mount_to_with_stop_hydrating( parent: web_sys::HtmlElement, stop_hydrating: bool, f: F, ) where F: FnOnce() -> N + 'static, N: IntoView, { cfg_if! { if #[cfg(all(target_arch = "wasm32", feature = "web"))] { let node = f().into_view(); if stop_hydrating { HydrationCtx::stop_hydrating(); } if cfg!(feature = "csr") { parent.append_child(&node.get_mountable_node()).unwrap(); } std::mem::forget(node); } else { _ = parent; _ = f; _ = stop_hydrating; crate::warn!("`mount_to` should not be called outside the browser."); } } } thread_local! { pub(crate) static WINDOW: web_sys::Window = web_sys::window().unwrap_throw(); pub(crate) static DOCUMENT: web_sys::Document = web_sys::window().unwrap_throw().document().unwrap_throw(); } /// Returns the [`Window`](https://developer.mozilla.org/en-US/docs/Web/API/Window). /// /// This is cached as a thread-local variable, so calling `window()` multiple times /// requires only one call out to JavaScript. pub fn window() -> web_sys::Window { WINDOW.with(Clone::clone) } /// Returns the [`Document`](https://developer.mozilla.org/en-US/docs/Web/API/Document). /// /// This is cached as a thread-local variable, so calling `document()` multiple times /// requires only one call out to JavaScript. pub fn document() -> web_sys::Document { DOCUMENT.with(Clone::clone) } /// Returns true if running on the server (SSR). /// /// In the past, this was implemented by checking whether `not(target_arch = "wasm32")`. /// Now that some cloud platforms are moving to run Wasm on the edge, we really can't /// guarantee that compiling to Wasm means browser APIs are available, or that not compiling /// to Wasm means we're running on the server. /// /// ``` /// # use leptos_dom::is_server; /// let todos = if is_server() { /// // if on the server, load from DB /// } else { /// // if on the browser, do something else /// }; /// ``` pub const fn is_server() -> bool { !is_browser() } /// Returns true if running on the browser (CSR). /// /// ``` /// # use leptos_dom::is_browser; /// let todos = if is_browser() { /// // if on the browser, call `wasm_bindgen` methods /// } else { /// // if on the server, do something else /// }; /// ``` pub const fn is_browser() -> bool { cfg!(all(target_arch = "wasm32", feature = "web")) } /// Returns true if `debug_assertions` are enabled. /// ``` /// # use leptos_dom::is_dev; /// if is_dev() { /// // log something or whatever /// } /// ``` pub const fn is_dev() -> bool { cfg!(debug_assertions) } /// Returns true if `debug_assertions` are disabled. pub const fn is_release() -> bool { !is_dev() } macro_rules! impl_into_view_for_tuples { ($($ty:ident),* $(,)?) => { impl<$($ty),*> IntoView for ($($ty,)*) where $($ty: IntoView),* { #[inline] fn into_view(self) -> View { paste::paste! { let ($([<$ty:lower>],)*) = self; [ $([<$ty:lower>].into_view()),* ].into_view() } } } }; } impl_into_view_for_tuples!(A); impl_into_view_for_tuples!(A, B); impl_into_view_for_tuples!(A, B, C); impl_into_view_for_tuples!(A, B, C, D); impl_into_view_for_tuples!(A, B, C, D, E); impl_into_view_for_tuples!(A, B, C, D, E, F); impl_into_view_for_tuples!(A, B, C, D, E, F, G); impl_into_view_for_tuples!(A, B, C, D, E, F, G, H); impl_into_view_for_tuples!(A, B, C, D, E, F, G, H, I); impl_into_view_for_tuples!(A, B, C, D, E, F, G, H, I, J); impl_into_view_for_tuples!(A, B, C, D, E, F, G, H, I, J, K); impl_into_view_for_tuples!(A, B, C, D, E, F, G, H, I, J, K, L); impl_into_view_for_tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M); impl_into_view_for_tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N); impl_into_view_for_tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O); impl_into_view_for_tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P); impl_into_view_for_tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q); impl_into_view_for_tuples!( A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R ); impl_into_view_for_tuples!( A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S ); impl_into_view_for_tuples!( A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T ); impl_into_view_for_tuples!( A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U ); impl_into_view_for_tuples!( A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V ); impl_into_view_for_tuples!( A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W ); impl_into_view_for_tuples!( A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X ); impl_into_view_for_tuples!( A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y ); impl_into_view_for_tuples!( A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z ); macro_rules! api_planning { ($($tt:tt)*) => {}; } api_planning! { let c = Component::::new("MyComponent") .props(Props::default()) // Can only be called once .child(Child1) // Anything that impl Into .child(Child2); } impl IntoView for String { #[cfg_attr( any(debug_assertions, feature = "ssr"), instrument(level = "trace", name = "#text", skip_all) )] #[inline(always)] fn into_view(self) -> View { View::Text(Text::new(self.into())) } } impl IntoView for &'static str { #[cfg_attr( any(debug_assertions, feature = "ssr"), instrument(level = "trace", name = "#text", skip_all) )] #[inline(always)] fn into_view(self) -> View { View::Text(Text::new(self.into())) } } impl IntoView for Cow<'static, str> { #[cfg_attr( any(debug_assertions, feature = "ssr"), instrument(level = "info", name = "#text", skip_all) )] #[inline(always)] fn into_view(self) -> View { View::Text(Text::new(self.into())) } } impl IntoView for Rc { #[cfg_attr( any(debug_assertions, feature = "ssr"), instrument(level = "trace", name = "#text", skip_all) )] #[inline(always)] fn into_view(self) -> View { View::Text(Text::new(self.into())) } } impl IntoView for Oco<'static, str> { #[cfg_attr( any(debug_assertions, feature = "ssr"), instrument(level = "trace", name = "#text", skip_all) )] #[inline(always)] fn into_view(self) -> View { View::Text(Text::new(self)) } } impl IntoView for Vec where V: IntoView, { #[cfg_attr( any(debug_assertions, feature = "ssr"), instrument(level = "trace", name = "#text", skip_all) )] fn into_view(self) -> View { self.into_iter() .map(|v| v.into_view()) .collect::() .into_view() } } impl IntoView for core::fmt::Arguments<'_> { #[cfg_attr( any(debug_assertions, feature = "ssr"), instrument(level = "trace", name = "#text", skip_all) )] fn into_view(self) -> View { match self.as_str() { Some(s) => s.into_view(), None => self.to_string().into_view(), } } } macro_rules! viewable_primitive { ($($child_type:ty),* $(,)?) => { $( impl IntoView for $child_type { #[inline(always)] fn into_view(self) -> View { View::Text(Text::new(self.to_string().into())) } } )* }; } viewable_primitive![ &String, usize, u8, u16, u32, u64, u128, isize, i8, i16, i32, i64, i128, f32, f64, char, bool, std::net::IpAddr, std::net::SocketAddr, std::net::SocketAddrV4, std::net::SocketAddrV6, std::net::Ipv4Addr, std::net::Ipv6Addr, std::char::ToUppercase, std::char::ToLowercase, std::num::NonZeroI8, std::num::NonZeroU8, std::num::NonZeroI16, std::num::NonZeroU16, std::num::NonZeroI32, std::num::NonZeroU32, std::num::NonZeroI64, std::num::NonZeroU64, std::num::NonZeroI128, std::num::NonZeroU128, std::num::NonZeroIsize, std::num::NonZeroUsize, std::panic::Location<'_>, ]; cfg_if! { if #[cfg(feature = "nightly")] { viewable_primitive! { std::backtrace::Backtrace } } }