mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
initial work on meta
This commit is contained in:
parent
30c1cd921b
commit
39607adc94
15 changed files with 599 additions and 287 deletions
|
@ -7,6 +7,7 @@ members = [
|
|||
"const_str_slice_concat",
|
||||
"either_of",
|
||||
"next_tuple",
|
||||
"oco",
|
||||
"or_poisoned",
|
||||
"routing_utils",
|
||||
|
||||
|
@ -58,6 +59,7 @@ leptos_router = { path = "./router", version = "0.6.5" }
|
|||
leptos_server = { path = "./leptos_server", version = "0.6.5" }
|
||||
leptos_meta = { path = "./meta", version = "0.6.5" }
|
||||
next_tuple = { path = "./next_tuple" }
|
||||
oco = { path = "./oco" }
|
||||
or_poisoned = { path = "./or_poisoned" }
|
||||
reactive_graph = { path = "./reactive_graph" }
|
||||
routing = { path = "./routing" }
|
||||
|
|
|
@ -14,7 +14,7 @@ actix-web = "4"
|
|||
futures = "0.3"
|
||||
leptos = { workspace = true, features = ["ssr"] }
|
||||
leptos_macro = { workspace = true, features = ["actix"] }
|
||||
leptos_meta = { workspace = true, features = ["ssr"] }
|
||||
leptos_meta = { workspace = true }
|
||||
leptos_router = { workspace = true, features = ["ssr"] }
|
||||
leptos_integration_utils = { workspace = true }
|
||||
server_fn = { workspace = true, features = ["actix"] }
|
||||
|
|
|
@ -17,7 +17,7 @@ http-body-util = "0.1"
|
|||
leptos = { workspace = true, features = ["ssr"] }
|
||||
server_fn = { workspace = true, features = ["axum-no-default"] }
|
||||
leptos_macro = { workspace = true, features = ["axum"] }
|
||||
leptos_meta = { workspace = true, features = ["ssr"] }
|
||||
leptos_meta = { workspace = true }
|
||||
leptos_router = { workspace = true, features = ["ssr"] }
|
||||
leptos_integration_utils = { workspace = true }
|
||||
parking_lot = "0.12"
|
||||
|
|
|
@ -12,7 +12,7 @@ rust-version.workspace = true
|
|||
futures = "0.3"
|
||||
leptos = { workspace = true, features = ["ssr"] }
|
||||
leptos_hot_reload = { workspace = true }
|
||||
leptos_meta = { workspace = true, features = ["ssr"] }
|
||||
leptos_meta = { workspace = true }
|
||||
leptos_config = { workspace = true }
|
||||
tracing = "0.1.37"
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ license = "MIT"
|
|||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Leptos is a full-stack, isomorphic Rust web framework leveraging fine-grained reactivity to build declarative user interfaces."
|
||||
readme = "../README.md"
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
any_spawner = { workspace = true, features = ["wasm-bindgen"] }
|
||||
|
@ -17,7 +16,8 @@ leptos_macro = { workspace = true }
|
|||
leptos_reactive = { workspace = true }
|
||||
leptos_server = { workspace = true }
|
||||
leptos_config = { workspace = true }
|
||||
leptos-spin-macro = { version = "0.1", optional = true }
|
||||
leptos-spin-macro = { git = "https://github.com/fermyon/leptos-spin", optional = true }
|
||||
oco = { workspace = true }
|
||||
paste = "1"
|
||||
reactive_graph = { workspace = true, features = ["serde"] }
|
||||
tachys = { workspace = true, features = ["reactive_graph"] }
|
||||
|
@ -37,31 +37,29 @@ web-sys = { version = "0.3.63", features = [
|
|||
"ShadowRootInit",
|
||||
"ShadowRootMode",
|
||||
] }
|
||||
wasm-bindgen = { version = "0.2", optional = true }
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
|
||||
[features]
|
||||
default = ["serde"]
|
||||
csr = [
|
||||
"leptos_macro/csr",
|
||||
"leptos_reactive/csr",
|
||||
"dep:wasm-bindgen",
|
||||
]
|
||||
csr = ["leptos_macro/csr", "leptos_reactive/csr", "leptos_server/csr"]
|
||||
hydrate = [
|
||||
"leptos_macro/hydrate",
|
||||
"leptos_reactive/hydrate",
|
||||
"dep:wasm-bindgen",
|
||||
"leptos_server/hydrate",
|
||||
]
|
||||
default-tls = ["leptos_server/default-tls", "server_fn/default-tls"]
|
||||
rustls = ["leptos_server/rustls", "server_fn/rustls"]
|
||||
ssr = [
|
||||
"leptos_macro/ssr",
|
||||
"leptos_reactive/ssr",
|
||||
"leptos_server/ssr",
|
||||
"server_fn/ssr",
|
||||
]
|
||||
nightly = [
|
||||
"leptos_dom/nightly",
|
||||
"leptos_macro/nightly",
|
||||
"leptos_reactive/nightly",
|
||||
"leptos_server/nightly",
|
||||
"tachys/nightly",
|
||||
]
|
||||
serde = ["leptos_reactive/serde"]
|
||||
|
@ -96,8 +94,6 @@ denylist = [
|
|||
"wasm-bindgen",
|
||||
"rkyv", # was causing clippy issues on nightly
|
||||
"trace-component-props",
|
||||
"spin",
|
||||
"experimental-islands",
|
||||
]
|
||||
skip_feature_sets = [
|
||||
[
|
||||
|
|
|
@ -157,6 +157,7 @@ pub mod component;
|
|||
mod for_loop;
|
||||
mod hydration_scripts;
|
||||
mod show;
|
||||
pub mod text_prop;
|
||||
pub use for_loop::*;
|
||||
pub use hydration_scripts::*;
|
||||
pub use leptos_macro::*;
|
||||
|
@ -174,10 +175,12 @@ mod into_view;
|
|||
pub use into_view::IntoView;
|
||||
pub use leptos_dom;
|
||||
pub use tachys;
|
||||
|
||||
pub mod logging;
|
||||
mod mount;
|
||||
pub use any_spawner::Executor;
|
||||
pub use mount::*;
|
||||
pub use oco;
|
||||
|
||||
/*mod additional_attributes;
|
||||
pub use additional_attributes::*;
|
||||
mod await_;
|
||||
|
@ -206,10 +209,6 @@ pub use leptos_dom::{
|
|||
CollectView, Errors, EventHandlerFn, Fragment, HtmlElement, IntoAttribute,
|
||||
IntoClass, IntoProperty, IntoStyle, IntoView, NodeRef, Property, View,
|
||||
};
|
||||
/// Utilities for simple isomorphic logging to the console or terminal.
|
||||
pub mod logging {
|
||||
pub use leptos_dom::{debug_warn, error, log, warn};
|
||||
}
|
||||
|
||||
/// Types to make it easier to handle errors in your application.
|
||||
pub mod error {
|
||||
|
|
102
leptos/src/logging.rs
Normal file
102
leptos/src/logging.rs
Normal file
|
@ -0,0 +1,102 @@
|
|||
//! Utilities for simple isomorphic logging to the console or terminal.
|
||||
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
/// Uses `println!()`-style formatting to log something to the console (in the browser)
|
||||
/// or via `println!()` (if not in the browser).
|
||||
#[macro_export]
|
||||
macro_rules! log {
|
||||
($($t:tt)*) => ($crate::logging::console_log(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
/// Uses `println!()`-style formatting to log warnings to the console (in the browser)
|
||||
/// or via `eprintln!()` (if not in the browser).
|
||||
#[macro_export]
|
||||
macro_rules! warn {
|
||||
($($t:tt)*) => ($crate::logging::console_warn(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
/// Uses `println!()`-style formatting to log errors to the console (in the browser)
|
||||
/// or via `eprintln!()` (if not in the browser).
|
||||
#[macro_export]
|
||||
macro_rules! error {
|
||||
($($t:tt)*) => ($crate::logging::console_error(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
/// Uses `println!()`-style formatting to log warnings to the console (in the browser)
|
||||
/// or via `eprintln!()` (if not in the browser), but only if it's a debug build.
|
||||
#[macro_export]
|
||||
macro_rules! debug_warn {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
$crate::warn!($($x)*)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
($($x)*)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fn log_to_stdout() -> bool {
|
||||
cfg!(not(all(
|
||||
target_arch = "wasm32",
|
||||
not(any(target_os = "emscripten", target_os = "wasi"))
|
||||
)))
|
||||
}
|
||||
|
||||
/// Log a string to the console (in the browser)
|
||||
/// or via `println!()` (if not in the browser).
|
||||
pub fn console_log(s: &str) {
|
||||
#[allow(clippy::print_stdout)]
|
||||
if log_to_stdout() {
|
||||
println!("{s}");
|
||||
} else {
|
||||
web_sys::console::log_1(&JsValue::from_str(s));
|
||||
}
|
||||
}
|
||||
|
||||
/// Log a warning to the console (in the browser)
|
||||
/// or via `println!()` (if not in the browser).
|
||||
pub fn console_warn(s: &str) {
|
||||
if log_to_stdout() {
|
||||
eprintln!("{s}");
|
||||
} else {
|
||||
web_sys::console::warn_1(&JsValue::from_str(s));
|
||||
}
|
||||
}
|
||||
|
||||
/// Log an error to the console (in the browser)
|
||||
/// or via `println!()` (if not in the browser).
|
||||
#[inline(always)]
|
||||
pub fn console_error(s: &str) {
|
||||
if log_to_stdout() {
|
||||
eprintln!("{s}");
|
||||
} else {
|
||||
web_sys::console::error_1(&JsValue::from_str(s));
|
||||
}
|
||||
}
|
||||
|
||||
/// Log an error to the console (in the browser)
|
||||
/// or via `println!()` (if not in the browser), but only in a debug build.
|
||||
#[inline(always)]
|
||||
pub fn console_debug_warn(s: &str) {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
if log_to_stdout() {
|
||||
eprintln!("{s}");
|
||||
} else {
|
||||
web_sys::console::warn_1(&JsValue::from_str(s));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
let _ = s;
|
||||
}
|
||||
}
|
||||
|
||||
|
75
leptos/src/text_prop.rs
Normal file
75
leptos/src/text_prop.rs
Normal file
|
@ -0,0 +1,75 @@
|
|||
use oco::Oco;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Describes a value that is either a static or a reactive string, i.e.,
|
||||
/// a [`String`], a [`&str`], or a reactive `Fn() -> String`.
|
||||
#[derive(Clone)]
|
||||
pub struct TextProp(Arc<dyn Fn() -> Oco<'static, str> + Send + Sync>);
|
||||
|
||||
impl TextProp {
|
||||
/// Accesses the current value of the property.
|
||||
#[inline(always)]
|
||||
pub fn get(&self) -> Oco<'static, str> {
|
||||
(self.0)()
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for TextProp {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_tuple("TextProp").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for TextProp {
|
||||
fn from(s: String) -> Self {
|
||||
let s: Oco<'_, str> = Oco::Counted(Arc::from(s));
|
||||
TextProp(Arc::new(move || s.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for TextProp {
|
||||
fn from(s: &'static str) -> Self {
|
||||
let s: Oco<'_, str> = s.into();
|
||||
TextProp(Arc::new(move || s.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Arc<str>> for TextProp {
|
||||
fn from(s: Arc<str>) -> Self {
|
||||
let s: Oco<'_, str> = s.into();
|
||||
TextProp(Arc::new(move || s.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Oco<'static, str>> for TextProp {
|
||||
fn from(s: Oco<'static, str>) -> Self {
|
||||
TextProp(Arc::new(move || s.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
/*impl<T> From<T> for MaybeProp<TextProp>
|
||||
where
|
||||
T: Into<Oco<'static, str>>,
|
||||
{
|
||||
fn from(s: T) -> Self {
|
||||
Self(Some(MaybeSignal::from(Some(s.into().into()))))
|
||||
}
|
||||
}*/
|
||||
|
||||
impl<F, S> From<F> for TextProp
|
||||
where
|
||||
F: Fn() -> S + 'static + Send + Sync,
|
||||
S: Into<Oco<'static, str>>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn from(s: F) -> Self {
|
||||
TextProp(Arc::new(move || s().into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TextProp {
|
||||
fn default() -> Self {
|
||||
Self(Arc::new(|| Oco::Borrowed("")))
|
||||
}
|
||||
}
|
|
@ -9,8 +9,8 @@ description = "Tools to set HTML metadata in the Leptos web framework."
|
|||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "1"
|
||||
leptos = { workspace = true }
|
||||
or_poisoned = { workspace = true }
|
||||
tracing = "0.1"
|
||||
wasm-bindgen = "0.2"
|
||||
indexmap = "2"
|
||||
|
@ -21,14 +21,6 @@ features = ["HtmlLinkElement", "HtmlMetaElement", "HtmlTitleElement"]
|
|||
|
||||
[features]
|
||||
default = []
|
||||
csr = ["leptos/csr"]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = ["leptos/ssr"]
|
||||
nightly = ["leptos/nightly"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["nightly"]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
|
266
meta/src/body.rs
266
meta/src/body.rs
|
@ -1,72 +1,76 @@
|
|||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
#[cfg(feature = "ssr")]
|
||||
use std::collections::HashMap;
|
||||
#[cfg(feature = "ssr")]
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use crate::{use_head, ServerMetaContext};
|
||||
use indexmap::IndexMap;
|
||||
use leptos::{
|
||||
component,
|
||||
oco::Oco,
|
||||
reactive_graph::{effect::RenderEffect, owner::use_context},
|
||||
tachys::{
|
||||
dom::document,
|
||||
error::Result,
|
||||
html::attribute::{
|
||||
any_attribute::{AnyAttribute, AnyAttributeState},
|
||||
Attribute,
|
||||
},
|
||||
hydration::Cursor,
|
||||
reactive_graph::RenderEffectState,
|
||||
renderer::{dom::Dom, Renderer},
|
||||
view::{Mountable, Position, PositionState, Render, RenderHtml},
|
||||
},
|
||||
text_prop::TextProp,
|
||||
IntoView,
|
||||
};
|
||||
use or_poisoned::OrPoisoned;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
mem,
|
||||
rc::Rc,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
use web_sys::{HtmlBodyElement, HtmlElement};
|
||||
|
||||
/// 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")]
|
||||
id: Rc<RefCell<Option<TextProp>>>,
|
||||
#[cfg(feature = "ssr")]
|
||||
attributes: Rc<RefCell<HashMap<&'static str, Attribute>>>,
|
||||
class: Arc<RwLock<Option<TextProp>>>,
|
||||
attributes: Arc<RwLock<Vec<AnyAttribute<Dom>>>>,
|
||||
}
|
||||
|
||||
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())
|
||||
)
|
||||
});
|
||||
///
|
||||
/// This consumes the list of `attributes`, and should only be called once per request.
|
||||
pub fn to_string(&self) -> Option<String> {
|
||||
let mut buf = String::from(" ");
|
||||
if let Some(class) = &*self.class.read().or_poisoned() {
|
||||
buf.push_str("class=\"");
|
||||
buf.push_str(&class.get());
|
||||
buf.push_str("\" ");
|
||||
};
|
||||
|
||||
let id = self.id.borrow().as_ref().map(|val| {
|
||||
format!(
|
||||
"id=\"{}\"",
|
||||
leptos::leptos_dom::ssr::escape_attr(&val.get())
|
||||
)
|
||||
});
|
||||
let attributes = self.attributes.borrow();
|
||||
let attributes = (!attributes.is_empty()).then(|| {
|
||||
attributes
|
||||
.iter()
|
||||
.filter_map(|(n, v)| {
|
||||
v.as_nameless_value_string().map(|v| {
|
||||
format!(
|
||||
"{}=\"{}\"",
|
||||
n,
|
||||
leptos::leptos_dom::ssr::escape_attr(&v)
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
});
|
||||
let attributes = mem::take(&mut *self.attributes.write().or_poisoned());
|
||||
|
||||
let mut val = [id, class, attributes]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
if val.is_empty() {
|
||||
for attr in attributes {
|
||||
attr.to_html(
|
||||
&mut buf,
|
||||
&mut String::new(),
|
||||
&mut String::new(),
|
||||
&mut String::new(),
|
||||
);
|
||||
buf.push(' ');
|
||||
}
|
||||
|
||||
if buf.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
val.insert(0, ' ');
|
||||
Some(val)
|
||||
Some(buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for BodyContext {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_tuple("TitleContext").finish()
|
||||
f.debug_struct("BodyContext").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,7 +100,7 @@ impl core::fmt::Debug for BodyContext {
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[component(transparent)]
|
||||
#[component]
|
||||
pub fn Body(
|
||||
/// The `class` attribute on the `<body>`.
|
||||
#[prop(optional, into)]
|
||||
|
@ -106,49 +110,137 @@ pub fn Body(
|
|||
id: Option<TextProp>,
|
||||
/// Arbitrary attributes to add to the `<body>`
|
||||
#[prop(attrs)]
|
||||
attributes: Vec<(&'static str, Attribute)>,
|
||||
mut attributes: Vec<AnyAttribute<Dom>>,
|
||||
) -> impl IntoView {
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", any(feature = "csr", feature = "hydrate")))] {
|
||||
use wasm_bindgen::JsCast;
|
||||
if let Some(meta) = use_context::<ServerMetaContext>() {
|
||||
*meta.body.class.write().or_poisoned() = class.clone();
|
||||
|
||||
let el = document().body().expect("there to be a <body> element");
|
||||
// these can safely be taken out if the server context is present
|
||||
// server rendering is handled separately, not via RenderHtml
|
||||
*meta.body.attributes.write().or_poisoned() = mem::take(&mut attributes)
|
||||
}
|
||||
|
||||
if let Some(class) = class {
|
||||
create_render_effect({
|
||||
let el = el.clone();
|
||||
move |_| {
|
||||
let value = class.get();
|
||||
_ = el.set_attribute("class", &value);
|
||||
BodyView { class, attributes }
|
||||
}
|
||||
|
||||
struct BodyView {
|
||||
class: Option<TextProp>,
|
||||
attributes: Vec<AnyAttribute<Dom>>,
|
||||
}
|
||||
|
||||
struct BodyViewState {
|
||||
el: HtmlElement,
|
||||
class: Option<RenderEffect<Oco<'static, str>>>,
|
||||
attributes: Vec<AnyAttributeState<Dom>>,
|
||||
}
|
||||
|
||||
impl Render<Dom> for BodyView {
|
||||
type State = BodyViewState;
|
||||
type FallibleState = BodyViewState;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
let el = document().body().expect("there to be a <body> element");
|
||||
let class = self.class.map(|class| {
|
||||
RenderEffect::new({
|
||||
let el = el.clone();
|
||||
move |prev| {
|
||||
let next = class.get();
|
||||
if prev.as_ref() != Some(&next) {
|
||||
if let Err(e) = el.set_attribute("class", &next) {
|
||||
web_sys::console::error_1(&e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
next
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
let attributes = self
|
||||
.attributes
|
||||
.into_iter()
|
||||
.map(|attr| attr.build(&el))
|
||||
.collect();
|
||||
|
||||
if let Some(id) = id {
|
||||
create_render_effect({
|
||||
let el = el.clone();
|
||||
move |_| {
|
||||
let value = id.get();
|
||||
_ = el.set_attribute("id", &value);
|
||||
BodyViewState {
|
||||
el,
|
||||
class,
|
||||
attributes,
|
||||
}
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
// TODO rebuilding dynamic things like this
|
||||
}
|
||||
|
||||
fn try_build(self) -> Result<Self::FallibleState> {
|
||||
Ok(self.build())
|
||||
}
|
||||
|
||||
fn try_rebuild(self, state: &mut Self::FallibleState) -> Result<()> {
|
||||
self.rebuild(state);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderHtml<Dom> for BodyView {
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position) {}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &Cursor<Dom>,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
let el = document().body().expect("there to be a <body> element");
|
||||
let class = self.class.map(|class| {
|
||||
RenderEffect::new({
|
||||
let el = el.clone();
|
||||
move |prev| {
|
||||
let next = class.get();
|
||||
if prev.is_none() {
|
||||
return next;
|
||||
}
|
||||
});
|
||||
}
|
||||
for (name, value) in attributes {
|
||||
leptos::leptos_dom::attribute_helper(el.unchecked_ref(), name.into(), value);
|
||||
}
|
||||
} else if #[cfg(feature = "ssr")] {
|
||||
let meta = crate::use_head();
|
||||
*meta.body.class.borrow_mut() = class;
|
||||
*meta.body.id.borrow_mut() = id;
|
||||
meta.body.attributes.borrow_mut().extend(attributes);
|
||||
} else {
|
||||
_ = class;
|
||||
_ = id;
|
||||
_ = attributes;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
crate::feature_warning();
|
||||
if prev.as_ref() != Some(&next) {
|
||||
if let Err(e) = el.set_attribute("class", &next) {
|
||||
web_sys::console::error_1(&e);
|
||||
}
|
||||
}
|
||||
next
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
let attributes = self
|
||||
.attributes
|
||||
.into_iter()
|
||||
.map(|attr| attr.hydrate::<FROM_SERVER>(&el))
|
||||
.collect();
|
||||
|
||||
BodyViewState {
|
||||
el,
|
||||
class,
|
||||
attributes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mountable<Dom> for BodyViewState {
|
||||
fn unmount(&mut self) {}
|
||||
|
||||
fn mount(
|
||||
&mut self,
|
||||
parent: &<Dom as Renderer>::Element,
|
||||
marker: Option<&<Dom as Renderer>::Node>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn insert_before_this(
|
||||
&self,
|
||||
parent: &<Dom as Renderer>::Element,
|
||||
child: &mut dyn Mountable<Dom>,
|
||||
) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
146
meta/src/lib.rs
146
meta/src/lib.rs
|
@ -47,11 +47,10 @@
|
|||
//! **Important Note:** You must enable one of `csr`, `hydrate`, or `ssr` to tell Leptos
|
||||
//! which mode your app is operating in.
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use indexmap::IndexMap;
|
||||
use leptos::{
|
||||
leptos_dom::{debug_warn, html::AnyElement},
|
||||
*,
|
||||
debug_warn,
|
||||
reactive_graph::owner::{provide_context, use_context},
|
||||
};
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
|
@ -62,21 +61,21 @@ use std::{
|
|||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||
|
||||
mod body;
|
||||
mod html;
|
||||
/*mod html;
|
||||
mod link;
|
||||
mod meta_tags;
|
||||
mod script;
|
||||
mod style;
|
||||
mod stylesheet;
|
||||
mod title;
|
||||
mod title;*/
|
||||
pub use body::*;
|
||||
pub use html::*;
|
||||
/*pub use html::*;
|
||||
pub use link::*;
|
||||
pub use meta_tags::*;
|
||||
pub use script::*;
|
||||
pub use style::*;
|
||||
pub use stylesheet::*;
|
||||
pub use title::*;
|
||||
pub use title::*;*/
|
||||
|
||||
/// Contains the current state of meta tags. To access it, you can use [`use_head`].
|
||||
///
|
||||
|
@ -84,108 +83,37 @@ pub use title::*;
|
|||
/// [`provide_meta_context`].
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct MetaContext {
|
||||
/// Metadata associated with the `<html>` element
|
||||
/*/// Metadata associated with the `<html>` element
|
||||
pub html: HtmlContext,
|
||||
/// Metadata associated with the `<title>` element.
|
||||
pub title: TitleContext,
|
||||
/// Metadata associated with the `<body>` element
|
||||
pub body: BodyContext,
|
||||
pub title: TitleContext,*/
|
||||
/*
|
||||
/// Other metadata tags.
|
||||
pub tags: MetaTagsContext,
|
||||
*/
|
||||
}
|
||||
|
||||
/// Manages all of the element created by components.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct MetaTagsContext {
|
||||
next_id: Rc<Cell<MetaTagId>>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
els: Rc<
|
||||
RefCell<
|
||||
IndexMap<
|
||||
Oco<'static, str>,
|
||||
(HtmlElement<AnyElement>, Option<web_sys::Element>),
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
/// Contains the state of meta tags for server rendering.
|
||||
///
|
||||
/// This should be provided as context during server rendering.
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct ServerMetaContext {
|
||||
/*/// Metadata associated with the `<html>` element
|
||||
pub html: HtmlContext,
|
||||
/// Metadata associated with the `<title>` element.
|
||||
pub title: TitleContext,*/
|
||||
/// Metadata associated with the `<body>` element
|
||||
pub(crate) body: BodyContext,
|
||||
/*
|
||||
/// Other metadata tags.
|
||||
pub tags: MetaTagsContext,
|
||||
*/
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for MetaTagsContext {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("MetaTagsContext").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl MetaTagsContext {
|
||||
/// Converts metadata tags into an HTML string.
|
||||
#[cfg(any(feature = "ssr", doc))]
|
||||
pub fn as_string(&self) -> String {
|
||||
self.els
|
||||
.borrow()
|
||||
.iter()
|
||||
.map(|(_, (builder_el, _))| {
|
||||
builder_el.clone().into_view().render_to_string()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn register(
|
||||
&self,
|
||||
|
||||
id: Oco<'static, str>,
|
||||
builder_el: HtmlElement<AnyElement>,
|
||||
) {
|
||||
cfg_if! {
|
||||
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
|
||||
use leptos::document;
|
||||
|
||||
let element_to_hydrate = document()
|
||||
.get_element_by_id(&id);
|
||||
|
||||
let el = element_to_hydrate.unwrap_or_else({
|
||||
let builder_el = builder_el.clone();
|
||||
move || {
|
||||
let head = document().head().unwrap_throw();
|
||||
head
|
||||
.append_child(&builder_el)
|
||||
.unwrap_throw();
|
||||
|
||||
(*builder_el).clone().unchecked_into()
|
||||
}
|
||||
});
|
||||
|
||||
on_cleanup({
|
||||
let el = el.clone();
|
||||
let els = self.els.clone();
|
||||
let id = id.clone();
|
||||
move || {
|
||||
let head = document().head().unwrap_throw();
|
||||
_ = head.remove_child(&el);
|
||||
els.borrow_mut().swap_remove(&id);
|
||||
}
|
||||
});
|
||||
|
||||
self
|
||||
.els
|
||||
.borrow_mut()
|
||||
.insert(id, (builder_el.into_any(), Some(el)));
|
||||
|
||||
} else {
|
||||
self.els.borrow_mut().insert(id, (builder_el, None));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
|
||||
struct MetaTagId(usize);
|
||||
|
||||
impl MetaTagsContext {
|
||||
fn get_next_id(&self) -> MetaTagId {
|
||||
let current_id = self.next_id.get();
|
||||
let next_id = MetaTagId(current_id.0 + 1);
|
||||
self.next_id.set(next_id);
|
||||
next_id
|
||||
impl ServerMetaContext {
|
||||
/// Creates an empty [`ServerMetaContext`].
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -207,9 +135,6 @@ pub fn provide_meta_context() {
|
|||
/// call `use_head()` but a single [`MetaContext`] has not been provided at the application root.
|
||||
/// The best practice is always to call [`provide_meta_context`] early in the application.
|
||||
pub fn use_head() -> MetaContext {
|
||||
#[cfg(debug_assertions)]
|
||||
feature_warning();
|
||||
|
||||
match use_context::<MetaContext>() {
|
||||
None => {
|
||||
debug_warn!(
|
||||
|
@ -265,9 +190,6 @@ impl MetaContext {
|
|||
/// # }
|
||||
/// ```
|
||||
pub fn dehydrate(&self) -> String {
|
||||
use leptos::leptos_dom::HydrationCtx;
|
||||
|
||||
let prev_key = HydrationCtx::peek();
|
||||
let mut tags = String::new();
|
||||
|
||||
// Title
|
||||
|
@ -278,9 +200,6 @@ impl MetaContext {
|
|||
}
|
||||
tags.push_str(&self.tags.as_string());
|
||||
|
||||
if let Some(prev_key) = prev_key {
|
||||
HydrationCtx::continue_from(prev_key);
|
||||
}
|
||||
tags
|
||||
}
|
||||
}
|
||||
|
@ -310,10 +229,3 @@ pub fn generate_head_metadata_separated() -> (String, String) {
|
|||
.unwrap_or_default();
|
||||
(head, format!("<body{body_meta}>"))
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) fn feature_warning() {
|
||||
if !cfg!(any(feature = "csr", feature = "hydrate", feature = "ssr")) {
|
||||
leptos::logging::debug_warn!("WARNING: `leptos_meta` does nothing unless you enable one of its features (`csr`, `hydrate`, or `ssr`). See the docs at https://docs.rs/leptos_meta/latest/leptos_meta/ for more information.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,37 +1,6 @@
|
|||
//! This module contains the `Oco` (Owned Clones Once) smart pointer,
|
||||
//! which is used to store immutable references to values.
|
||||
//! This is useful for storing, for example, strings.
|
||||
//!
|
||||
//! Imagine this as an alternative to [`Cow`] with an additional, reference-counted
|
||||
//! branch.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use oco_ref::Oco;
|
||||
//! use std::rc::Rc;
|
||||
//!
|
||||
//! let static_str = "foo";
|
||||
//! let rc_str: Rc<str> = "bar".into();
|
||||
//! let owned_str: String = "baz".into();
|
||||
//!
|
||||
//! fn uses_oco(value: impl Into<Oco<'static, str>>) {
|
||||
//! let mut value = value.into();
|
||||
//!
|
||||
//! // ensures that the value is either a reference, or reference-counted
|
||||
//! // O(n) at worst
|
||||
//! let clone1 = value.clone_inplace();
|
||||
//!
|
||||
//! // these subsequent clones are O(1)
|
||||
//! let clone2 = value.clone();
|
||||
//! let clone3 = value.clone();
|
||||
//! }
|
||||
//!
|
||||
//! uses_oco(static_str);
|
||||
//! uses_oco(rc_str);
|
||||
//! uses_oco(owned_str);
|
||||
//! ```
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use std::{
|
||||
|
@ -44,24 +13,16 @@ use std::{
|
|||
rc::Rc,
|
||||
};
|
||||
|
||||
/// "Owned Clones Once": a smart pointer that can be either a reference,
|
||||
/// an owned value, or a reference-counted pointer. This is useful for
|
||||
/// "Owned Clones Once" - a smart pointer that can be either a reference,
|
||||
/// an owned value, or a reference counted pointer. This is useful for
|
||||
/// storing immutable values, such as strings, in a way that is cheap to
|
||||
/// clone and pass around.
|
||||
///
|
||||
/// The cost of the `Clone` implementation depends on the branch. Cloning the [`Oco::Borrowed`]
|
||||
/// The `Clone` implementation is amortized `O(1)`. Cloning the [`Oco::Borrowed`]
|
||||
/// variant simply copies the references (`O(1)`). Cloning the [`Oco::Counted`]
|
||||
/// variant increments a reference count (`O(1)`). Cloning the [`Oco::Owned`]
|
||||
/// variant requires an `O(n)` clone of the data.
|
||||
///
|
||||
/// For an amortized `O(1)` clone, you can use [`Oco::clone_inplace()`]. Using this method,
|
||||
/// [`Oco::Borrowed`] and [`Oco::Counted`] are still `O(1)`. [`Oco::Owned`] does a single `O(n)`
|
||||
/// clone, but converts the object to the [`Oco::Counted`] branch, which means future clones will
|
||||
/// be `O(1)`.
|
||||
///
|
||||
/// In general, you'll either want to call `clone_inplace()` once, before sharing the `Oco` with
|
||||
/// other parts of your application (so that all future clones are `O(1)`), or simply use this as
|
||||
/// if it is a [`Cow`] with an additional branch for reference-counted values.
|
||||
/// variant upgrades it to [`Oco::Counted`], which requires an `O(n)` clone of the
|
||||
/// data, but all subsequent clones will be `O(1)`.
|
||||
pub enum Oco<'a, T: ?Sized + ToOwned + 'a> {
|
||||
/// A static reference to a value.
|
||||
Borrowed(&'a T),
|
||||
|
@ -85,7 +46,7 @@ impl<'a, T: ?Sized + ToOwned> Oco<'a, T> {
|
|||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::rc::Rc;
|
||||
/// # use oco_ref::Oco;
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// assert!(Oco::<str>::Borrowed("Hello").is_borrowed());
|
||||
/// assert!(!Oco::<str>::Counted(Rc::from("Hello")).is_borrowed());
|
||||
/// assert!(!Oco::<str>::Owned("Hello".to_string()).is_borrowed());
|
||||
|
@ -98,7 +59,7 @@ impl<'a, T: ?Sized + ToOwned> Oco<'a, T> {
|
|||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::rc::Rc;
|
||||
/// # use oco_ref::Oco;
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// assert!(Oco::<str>::Counted(Rc::from("Hello")).is_counted());
|
||||
/// assert!(!Oco::<str>::Borrowed("Hello").is_counted());
|
||||
/// assert!(!Oco::<str>::Owned("Hello".to_string()).is_counted());
|
||||
|
@ -111,7 +72,7 @@ impl<'a, T: ?Sized + ToOwned> Oco<'a, T> {
|
|||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::rc::Rc;
|
||||
/// # use oco_ref::Oco;
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// assert!(Oco::<str>::Owned("Hello".to_string()).is_owned());
|
||||
/// assert!(!Oco::<str>::Borrowed("Hello").is_owned());
|
||||
/// assert!(!Oco::<str>::Counted(Rc::from("Hello")).is_owned());
|
||||
|
@ -169,7 +130,7 @@ impl Oco<'_, str> {
|
|||
/// Returns a `&str` slice of this [`Oco`].
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use oco_ref::Oco;
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// let oco = Oco::<str>::Borrowed("Hello");
|
||||
/// let s: &str = oco.as_str();
|
||||
/// assert_eq!(s, "Hello");
|
||||
|
@ -184,7 +145,7 @@ impl Oco<'_, CStr> {
|
|||
/// Returns a `&CStr` slice of this [`Oco`].
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use oco_ref::Oco;
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// use std::ffi::CStr;
|
||||
///
|
||||
/// let oco =
|
||||
|
@ -202,7 +163,7 @@ impl Oco<'_, OsStr> {
|
|||
/// Returns a `&OsStr` slice of this [`Oco`].
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use oco_ref::Oco;
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// use std::ffi::OsStr;
|
||||
///
|
||||
/// let oco = Oco::<OsStr>::Borrowed(OsStr::new("Hello"));
|
||||
|
@ -219,7 +180,7 @@ impl Oco<'_, Path> {
|
|||
/// Returns a `&Path` slice of this [`Oco`].
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use oco_ref::Oco;
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// use std::path::Path;
|
||||
///
|
||||
/// let oco = Oco::<Path>::Borrowed(Path::new("Hello"));
|
||||
|
@ -239,7 +200,7 @@ where
|
|||
/// Returns a `&[T]` slice of this [`Oco`].
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use oco_ref::Oco;
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// let oco = Oco::<[u8]>::Borrowed(b"Hello");
|
||||
/// let s: &[u8] = oco.as_slice();
|
||||
/// assert_eq!(s, b"Hello");
|
||||
|
@ -261,7 +222,7 @@ where
|
|||
/// # Examples
|
||||
/// [`String`] :
|
||||
/// ```
|
||||
/// # use oco_ref::Oco;
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// let oco = Oco::<str>::Owned("Hello".to_string());
|
||||
/// let oco2 = oco.clone();
|
||||
/// assert_eq!(oco, oco2);
|
||||
|
@ -269,7 +230,7 @@ where
|
|||
/// ```
|
||||
/// [`Vec`] :
|
||||
/// ```
|
||||
/// # use oco_ref::Oco;
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// let oco = Oco::<[u8]>::Owned(b"Hello".to_vec());
|
||||
/// let oco2 = oco.clone();
|
||||
/// assert_eq!(oco, oco2);
|
||||
|
@ -293,7 +254,7 @@ where
|
|||
/// was previously [`Oco::Owned`].
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use oco_ref::Oco;
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// let mut oco1 = Oco::<str>::Owned("Hello".to_string());
|
||||
/// let oco2 = oco1.clone_inplace();
|
||||
/// assert_eq!(oco1, oco2);
|
||||
|
|
179
tachys/src/html/attribute/any_attribute.rs
Normal file
179
tachys/src/html/attribute/any_attribute.rs
Normal file
|
@ -0,0 +1,179 @@
|
|||
use super::{Attribute, NextAttribute};
|
||||
use crate::renderer::Renderer;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
pub struct AnyAttribute<R: Renderer> {
|
||||
type_id: TypeId,
|
||||
value: Box<dyn Any + Send + Sync>,
|
||||
to_html:
|
||||
fn(Box<dyn Any>, &mut String, &mut String, &mut String, &mut String),
|
||||
build: fn(Box<dyn Any>, el: &R::Element) -> AnyAttributeState<R>,
|
||||
rebuild: fn(TypeId, Box<dyn Any>, &mut AnyAttributeState<R>),
|
||||
hydrate_from_server: fn(Box<dyn Any>, &R::Element) -> AnyAttributeState<R>,
|
||||
hydrate_from_template:
|
||||
fn(Box<dyn Any>, &R::Element) -> AnyAttributeState<R>,
|
||||
}
|
||||
|
||||
pub struct AnyAttributeState<R>
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
type_id: TypeId,
|
||||
state: Box<dyn Any>,
|
||||
el: R::Element,
|
||||
rndr: PhantomData<R>,
|
||||
}
|
||||
|
||||
pub trait IntoAnyAttribute<R>
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
fn into_any_attr(self) -> AnyAttribute<R>;
|
||||
}
|
||||
|
||||
impl<T, R> IntoAnyAttribute<R> for T
|
||||
where
|
||||
Self: Send + Sync,
|
||||
T: Attribute<R> + 'static,
|
||||
T::State: 'static,
|
||||
R: Renderer + 'static,
|
||||
R::Element: Clone,
|
||||
{
|
||||
// inlining allows the compiler to remove the unused functions
|
||||
// i.e., doesn't ship HTML-generating code that isn't used
|
||||
#[inline(always)]
|
||||
fn into_any_attr(self) -> AnyAttribute<R> {
|
||||
let value = Box::new(self) as Box<dyn Any + Send + Sync>;
|
||||
|
||||
let to_html = |value: Box<dyn Any>,
|
||||
buf: &mut String,
|
||||
class: &mut String,
|
||||
style: &mut String,
|
||||
inner_html: &mut String| {
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.expect("AnyAttribute::to_html could not be downcast");
|
||||
value.to_html(buf, class, style, inner_html);
|
||||
};
|
||||
let build = |value: Box<dyn Any>, el: &R::Element| {
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.expect("AnyAttribute::build couldn't downcast");
|
||||
let state = Box::new(value.build(el));
|
||||
|
||||
AnyAttributeState {
|
||||
type_id: TypeId::of::<T>(),
|
||||
state,
|
||||
el: el.clone(),
|
||||
rndr: PhantomData,
|
||||
}
|
||||
};
|
||||
let hydrate_from_server = |value: Box<dyn Any>, el: &R::Element| {
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.expect("AnyAttribute::hydrate_from_server couldn't downcast");
|
||||
let state = Box::new(value.hydrate::<true>(el));
|
||||
|
||||
AnyAttributeState {
|
||||
type_id: TypeId::of::<T>(),
|
||||
state,
|
||||
el: el.clone(),
|
||||
rndr: PhantomData,
|
||||
}
|
||||
};
|
||||
let hydrate_from_template = |value: Box<dyn Any>, el: &R::Element| {
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.expect("AnyAttribute::hydrate_from_server couldn't downcast");
|
||||
let state = Box::new(value.hydrate::<true>(el));
|
||||
|
||||
AnyAttributeState {
|
||||
type_id: TypeId::of::<T>(),
|
||||
state,
|
||||
el: el.clone(),
|
||||
rndr: PhantomData,
|
||||
}
|
||||
};
|
||||
let rebuild = |new_type_id: TypeId,
|
||||
value: Box<dyn Any>,
|
||||
state: &mut AnyAttributeState<R>| {
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.expect("AnyAttribute::rebuild couldn't downcast value");
|
||||
if new_type_id == state.type_id {
|
||||
let state = state
|
||||
.state
|
||||
.downcast_mut()
|
||||
.expect("AnyAttribute::rebuild couldn't downcast state");
|
||||
value.rebuild(state);
|
||||
} else {
|
||||
let new = value.into_any_attr().build(&state.el);
|
||||
*state = new;
|
||||
}
|
||||
};
|
||||
AnyAttribute {
|
||||
type_id: TypeId::of::<T>(),
|
||||
value,
|
||||
to_html,
|
||||
build,
|
||||
rebuild,
|
||||
hydrate_from_server,
|
||||
hydrate_from_template,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> NextAttribute<R> for AnyAttribute<R>
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
type Output<NewAttr: Attribute<R>> = (Self, NewAttr);
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<R>>(
|
||||
self,
|
||||
new_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr> {
|
||||
(self, new_attr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Attribute<R> for AnyAttribute<R>
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
type State = AnyAttributeState<R>;
|
||||
|
||||
fn to_html(
|
||||
self,
|
||||
buf: &mut String,
|
||||
class: &mut String,
|
||||
style: &mut String,
|
||||
inner_html: &mut String,
|
||||
) {
|
||||
(self.to_html)(self.value, buf, class, style, inner_html);
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
el: &<R as Renderer>::Element,
|
||||
) -> Self::State {
|
||||
if FROM_SERVER {
|
||||
(self.hydrate_from_server)(self.value, el)
|
||||
} else {
|
||||
(self.hydrate_from_template)(self.value, el)
|
||||
}
|
||||
}
|
||||
|
||||
fn build(self, el: &<R as Renderer>::Element) -> Self::State {
|
||||
(self.build)(self.value, el)
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
(self.rebuild)(self.type_id, self.value, state)
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod any_attribute;
|
||||
pub mod aria;
|
||||
pub mod custom;
|
||||
pub mod global;
|
||||
|
|
|
@ -12,6 +12,7 @@ where
|
|||
{
|
||||
type_id: TypeId,
|
||||
value: Box<dyn Any>,
|
||||
// TODO add async HTML rendering for AnyView
|
||||
to_html: fn(Box<dyn Any>, &mut String, &mut Position),
|
||||
build: fn(Box<dyn Any>) -> AnyViewState<R>,
|
||||
rebuild: fn(TypeId, Box<dyn Any>, &mut AnyViewState<R>),
|
||||
|
|
Loading…
Reference in a new issue