1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
use cfg_if::cfg_if;
use leptos::*;
#[cfg(feature = "ssr")]
use std::{cell::RefCell, rc::Rc};
/// Contains the current metadata for the document's `<body>`.
#[derive(Clone, Default)]
pub struct BodyContext {
#[cfg(feature = "ssr")]
class: Rc<RefCell<Option<TextProp>>>,
#[cfg(feature = "ssr")]
attributes: Rc<RefCell<Option<MaybeSignal<AdditionalAttributes>>>>,
}
impl BodyContext {
/// Converts the `<body>` metadata into an HTML string.
#[cfg(any(feature = "ssr", doc))]
pub fn as_string(&self) -> Option<String> {
let class = self.class.borrow().as_ref().map(|val| {
format!(
"class=\"{}\"",
leptos::leptos_dom::ssr::escape_attr(&val.get())
)
});
let attributes = self.attributes.borrow().as_ref().map(|val| {
val.with(|val| {
val.into_iter()
.map(|(n, v)| {
format!(
"{}=\"{}\"",
n,
leptos::leptos_dom::ssr::escape_attr(&v.get())
)
})
.collect::<Vec<_>>()
.join(" ")
})
});
let mut val = [class, attributes]
.into_iter()
.flatten()
.collect::<Vec<_>>()
.join(" ");
if val.is_empty() {
None
} else {
val.insert(0, ' ');
Some(val)
}
}
}
impl std::fmt::Debug for BodyContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("TitleContext").finish()
}
}
/// A component to set metadata on the document’s `<body>` element from
/// within the application.
///
/// ```
/// use leptos::*;
/// use leptos_meta::*;
///
/// #[component]
/// fn MyApp(cx: Scope) -> impl IntoView {
/// provide_meta_context(cx);
/// let (prefers_dark, set_prefers_dark) = create_signal(cx, false);
/// let body_class = move || {
/// if prefers_dark.get() {
/// "dark".to_string()
/// } else {
/// "light".to_string()
/// }
/// };
///
/// view! { cx,
/// <main>
/// <Body class=body_class/>
/// </main>
/// }
/// }
/// ```
#[component(transparent)]
pub fn Body(
cx: Scope,
/// The `class` attribute on the `<body>`.
#[prop(optional, into)]
class: Option<TextProp>,
/// Arbitrary attributes to add to the `<html>`
#[prop(optional, into)]
attributes: Option<MaybeSignal<AdditionalAttributes>>,
) -> impl IntoView {
cfg_if! {
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
let el = document().body().expect("there to be a <body> element");
if let Some(class) = class {
create_render_effect(cx, {
let el = el.clone();
move |_| {
let value = class.get();
_ = el.set_attribute("class", &value);
}
});
}
if let Some(attributes) = attributes {
let attributes = attributes.get();
for (attr_name, attr_value) in attributes.into_iter() {
let el = el.clone();
let attr_name = attr_name.to_owned();
let attr_value = attr_value.to_owned();
create_render_effect(cx, move |_|{
let value = attr_value.get();
_ = el.set_attribute(&attr_name, &value);
});
}
}
} else if #[cfg(feature = "ssr")] {
let meta = crate::use_head(cx);
*meta.body.class.borrow_mut() = class;
*meta.body.attributes.borrow_mut() = attributes;
} else {
_ = cx;
_ = class;
_ = attributes;
#[cfg(debug_assertions)]
crate::feature_warning();
}
}
}