mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 14:54:16 +00:00
stash
This commit is contained in:
parent
17732a6e6a
commit
0fddfb4823
41 changed files with 1230 additions and 11699 deletions
|
@ -1,4 +1,4 @@
|
||||||
use leptos::*;
|
use leptos::{component, create_signal, prelude::*, view, IntoView};
|
||||||
|
|
||||||
/// A simple counter component.
|
/// A simple counter component.
|
||||||
///
|
///
|
||||||
|
|
|
@ -9,32 +9,8 @@ description = "DOM operations for the Leptos web framework."
|
||||||
rust-version.workspace = true
|
rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-recursion = "1"
|
tachys = { workspace = true }
|
||||||
base64 = { version = "0.22", optional = true }
|
reactive_graph = { workspace = true }
|
||||||
cfg-if = "1"
|
|
||||||
drain_filter_polyfill = "0.1"
|
|
||||||
futures = "0.3"
|
|
||||||
getrandom = { version = "0.2", optional = true }
|
|
||||||
html-escape = "0.2"
|
|
||||||
indexmap = "2"
|
|
||||||
itertools = "0.12"
|
|
||||||
js-sys = "0.3"
|
|
||||||
leptos_reactive = { workspace = true }
|
|
||||||
server_fn = { workspace = true }
|
|
||||||
once_cell = "1"
|
|
||||||
pad-adapter = "0.1"
|
|
||||||
paste = "1"
|
|
||||||
rand = { version = "0.8", optional = true }
|
|
||||||
rustc-hash = "1.1.0"
|
|
||||||
serde_json = "1"
|
|
||||||
smallvec = "1"
|
|
||||||
tracing = "0.1"
|
|
||||||
wasm-bindgen = { version = "0.2", features = ["enable-interning"] }
|
|
||||||
wasm-bindgen-futures = "0.4.31"
|
|
||||||
serde = "1"
|
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
|
||||||
getrandom = { version = "0.2", features = ["js"] }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
leptos = { path = "../leptos" }
|
leptos = { path = "../leptos" }
|
||||||
|
|
|
@ -1,6 +1,18 @@
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![cfg_attr(feature = "nightly", feature(fn_traits))]
|
|
||||||
|
pub use tachys::*;
|
||||||
|
use web_sys::HtmlElement;
|
||||||
|
|
||||||
|
pub fn mount_to<F, N>(parent: HtmlElement, f: F)
|
||||||
|
where
|
||||||
|
F: FnOnce() -> N + 'static,
|
||||||
|
N: IntoView,
|
||||||
|
{
|
||||||
|
mount_to_with_stop_hydrating(parent, true, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*#![cfg_attr(feature = "nightly", feature(fn_traits))]
|
||||||
#![cfg_attr(feature = "nightly", feature(unboxed_closures))]
|
#![cfg_attr(feature = "nightly", feature(unboxed_closures))]
|
||||||
// to prevent warnings from popping up when a nightly feature is stabilized
|
// to prevent warnings from popping up when a nightly feature is stabilized
|
||||||
#![allow(stable_features)]
|
#![allow(stable_features)]
|
||||||
|
@ -1311,4 +1323,4 @@ cfg_if! {
|
||||||
std::backtrace::Backtrace
|
std::backtrace::Backtrace
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
|
@ -6,6 +6,7 @@ use convert_case::{
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use leptos_hot_reload::parsing::value_to_string;
|
use leptos_hot_reload::parsing::value_to_string;
|
||||||
use proc_macro2::{Ident, Span, TokenStream};
|
use proc_macro2::{Ident, Span, TokenStream};
|
||||||
|
use proc_macro_error::abort;
|
||||||
use quote::{format_ident, quote, quote_spanned, ToTokens, TokenStreamExt};
|
use quote::{format_ident, quote, quote_spanned, ToTokens, TokenStreamExt};
|
||||||
use syn::{
|
use syn::{
|
||||||
parse::Parse, parse_quote, spanned::Spanned, token::Colon,
|
parse::Parse, parse_quote, spanned::Spanned, token::Colon,
|
||||||
|
@ -16,7 +17,6 @@ use syn::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
is_transparent: bool,
|
|
||||||
is_island: bool,
|
is_island: bool,
|
||||||
docs: Docs,
|
docs: Docs,
|
||||||
vis: Visibility,
|
vis: Visibility,
|
||||||
|
@ -58,17 +58,7 @@ impl Parse for Model {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Make sure return type is correct
|
|
||||||
if !is_valid_into_view_return_type(&item.sig.output) {
|
|
||||||
abort!(
|
|
||||||
item.sig,
|
|
||||||
"return type is incorrect";
|
|
||||||
help = "return signature must be `-> impl IntoView`"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
is_transparent: false,
|
|
||||||
is_island: false,
|
is_island: false,
|
||||||
docs,
|
docs,
|
||||||
vis: item.vis.clone(),
|
vis: item.vis.clone(),
|
||||||
|
@ -108,7 +98,6 @@ pub fn convert_from_snake_case(name: &Ident) -> Ident {
|
||||||
impl ToTokens for Model {
|
impl ToTokens for Model {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
let Self {
|
let Self {
|
||||||
is_transparent,
|
|
||||||
is_island,
|
is_island,
|
||||||
docs,
|
docs,
|
||||||
vis,
|
vis,
|
||||||
|
@ -121,7 +110,6 @@ impl ToTokens for Model {
|
||||||
let no_props = props.is_empty();
|
let no_props = props.is_empty();
|
||||||
|
|
||||||
// check for components that end ;
|
// check for components that end ;
|
||||||
if !is_transparent {
|
|
||||||
let ends_semi =
|
let ends_semi =
|
||||||
body.block.stmts.iter().last().and_then(|stmt| match stmt {
|
body.block.stmts.iter().last().and_then(|stmt| match stmt {
|
||||||
Stmt::Item(Item::Macro(mac)) => mac.semi_token.as_ref(),
|
Stmt::Item(Item::Macro(mac)) => mac.semi_token.as_ref(),
|
||||||
|
@ -131,14 +119,14 @@ impl ToTokens for Model {
|
||||||
proc_macro_error::emit_error!(
|
proc_macro_error::emit_error!(
|
||||||
semi.span(),
|
semi.span(),
|
||||||
"A component that ends with a `view!` macro followed by a \
|
"A component that ends with a `view!` macro followed by a \
|
||||||
semicolon will return (), an empty view. This is usually \
|
semicolon will return (), an empty view. This is usually an \
|
||||||
an accident, not intentional, so we prevent it. If you’d \
|
accident, not intentional, so we prevent it. If you’d like \
|
||||||
like to return (), you can do it it explicitly by \
|
to return (), you can do it it explicitly by returning () as \
|
||||||
returning () as the last item from the component."
|
the last item from the component."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
//body.sig.ident = format_ident!("__{}", body.sig.ident);
|
||||||
#[allow(clippy::redundant_clone)] // false positive
|
#[allow(clippy::redundant_clone)] // false positive
|
||||||
let body_name = body.sig.ident.clone();
|
let body_name = body.sig.ident.clone();
|
||||||
|
|
||||||
|
@ -204,21 +192,21 @@ impl ToTokens for Model {
|
||||||
#[allow(clippy::let_with_type_underscore)]
|
#[allow(clippy::let_with_type_underscore)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
any(debug_assertions, feature="ssr"),
|
any(debug_assertions, feature="ssr"),
|
||||||
::leptos::leptos_dom::tracing::instrument(level = "trace", name = #trace_name, skip_all)
|
::leptos::tracing::instrument(level = "info", name = #trace_name, skip_all)
|
||||||
)]
|
)]
|
||||||
},
|
},
|
||||||
quote! {
|
quote! {
|
||||||
let span = ::leptos::leptos_dom::tracing::Span::current();
|
let span = ::leptos::tracing::Span::current();
|
||||||
},
|
},
|
||||||
quote! {
|
quote! {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
let _guard = span.entered();
|
let _guard = span.entered();
|
||||||
},
|
},
|
||||||
if no_props || !cfg!(feature = "trace-component-props") {
|
if no_props {
|
||||||
quote! {}
|
quote! {}
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
::leptos::leptos_dom::tracing_props![#prop_names];
|
::leptos::tracing_props![#prop_names];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -248,7 +236,7 @@ impl ToTokens for Model {
|
||||||
let body_name = unmodified_fn_name_from_fn_name(&body_name);
|
let body_name = unmodified_fn_name_from_fn_name(&body_name);
|
||||||
let body_expr = if *is_island {
|
let body_expr = if *is_island {
|
||||||
quote! {
|
quote! {
|
||||||
::leptos::SharedContext::with_hydration(move || {
|
::leptos::reactive_graph::Owner::with_hydration(move || {
|
||||||
#body_name(#prop_names)
|
#body_name(#prop_names)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -258,32 +246,25 @@ impl ToTokens for Model {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let component = if *is_transparent {
|
let component = quote! {
|
||||||
body_expr
|
::leptos::reactive_graph::untrack(
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
::leptos::leptos_dom::Component::new(
|
|
||||||
::std::stringify!(#name),
|
|
||||||
move || {
|
move || {
|
||||||
#tracing_guard_expr
|
#tracing_guard_expr
|
||||||
#tracing_props_expr
|
#tracing_props_expr
|
||||||
#body_expr
|
#body_expr
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// add island wrapper if island
|
// add island wrapper if island
|
||||||
let component = if *is_island {
|
let component = if *is_island {
|
||||||
quote! {
|
quote! {
|
||||||
{
|
{
|
||||||
::leptos::leptos_dom::html::custom(
|
::leptos::tachys::html::islands::Island::new(
|
||||||
::leptos::leptos_dom::html::Custom::new("leptos-island"),
|
#component_id,
|
||||||
|
#component
|
||||||
)
|
)
|
||||||
.attr("data-component", #component_id)
|
// #island_serialized_props
|
||||||
.attr("data-hkc", ::leptos::leptos_dom::HydrationCtx::peek_always().to_string())
|
|
||||||
#island_serialized_props
|
|
||||||
.child(#component)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -301,20 +282,10 @@ impl ToTokens for Model {
|
||||||
let destructure_props = if no_props {
|
let destructure_props = if no_props {
|
||||||
quote! {}
|
quote! {}
|
||||||
} else {
|
} else {
|
||||||
let wrapped_children = if is_island_with_children
|
let wrapped_children = if is_island_with_children {
|
||||||
&& cfg!(feature = "ssr")
|
|
||||||
{
|
|
||||||
quote! {
|
quote! {
|
||||||
let children = ::std::boxed::Box::new(|| ::leptos::Fragment::lazy(|| ::std::vec![
|
use leptos::tachys::view::any_view::IntoAny;
|
||||||
::leptos::SharedContext::with_hydration(move || {
|
let children = Box::new(|| ::leptos::tachys::html::islands::IslandChildren::new(children()).into_any());
|
||||||
::leptos::IntoView::into_view(
|
|
||||||
::leptos::leptos_dom::html::custom(
|
|
||||||
::leptos::leptos_dom::html::Custom::new("leptos-children"),
|
|
||||||
)
|
|
||||||
.child(::leptos::SharedContext::no_hydration(children))
|
|
||||||
)
|
|
||||||
})
|
|
||||||
]));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote! {}
|
quote! {}
|
||||||
|
@ -328,24 +299,6 @@ impl ToTokens for Model {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let into_view = if no_props {
|
|
||||||
quote! {
|
|
||||||
impl #impl_generics ::leptos::IntoView for #props_name #generics #where_clause {
|
|
||||||
fn into_view(self) -> ::leptos::View {
|
|
||||||
#name().into_view()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
impl #impl_generics ::leptos::IntoView for #props_name #generics #where_clause {
|
|
||||||
fn into_view(self) -> ::leptos::View {
|
|
||||||
#name(self).into_view()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let count = props
|
let count = props
|
||||||
.iter()
|
.iter()
|
||||||
.filter(
|
.filter(
|
||||||
|
@ -412,7 +365,7 @@ impl ToTokens for Model {
|
||||||
#component
|
#component
|
||||||
};
|
};
|
||||||
|
|
||||||
let binding = if *is_island && cfg!(feature = "hydrate") {
|
let binding = if *is_island {
|
||||||
let island_props = if is_island_with_children
|
let island_props = if is_island_with_children
|
||||||
|| is_island_with_other_props
|
|| is_island_with_other_props
|
||||||
{
|
{
|
||||||
|
@ -453,15 +406,20 @@ impl ToTokens for Model {
|
||||||
};
|
};
|
||||||
let children = if is_island_with_children {
|
let children = if is_island_with_children {
|
||||||
quote! {
|
quote! {
|
||||||
.children(::std::boxed::Box::new(move || ::leptos::Fragment::lazy(|| ::std::vec![
|
.children({Box::new(|| {
|
||||||
::leptos::SharedContext::with_hydration(move || {
|
use leptos::tachys::view::any_view::IntoAny;
|
||||||
::leptos::IntoView::into_view(
|
::leptos::tachys::html::islands::IslandChildren::new(
|
||||||
::leptos::leptos_dom::html::custom(
|
// TODO owner restoration for context
|
||||||
::leptos::leptos_dom::html::Custom::new("leptos-children"),
|
()
|
||||||
)
|
).into_any()})})
|
||||||
.prop("$$owner", ::leptos::Owner::current().map(|n| n.as_ffi()))
|
//.children(children)
|
||||||
)
|
/*.children(Box::new(|| {
|
||||||
})])))
|
use leptos::tachys::view::any_view::IntoAny;
|
||||||
|
::leptos::tachys::html::islands::IslandChildren::new(
|
||||||
|
// TODO owner restoration for context
|
||||||
|
()
|
||||||
|
).into_any()
|
||||||
|
}))*/
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote! {}
|
quote! {}
|
||||||
|
@ -477,30 +435,26 @@ impl ToTokens for Model {
|
||||||
} else {
|
} else {
|
||||||
quote! {}
|
quote! {}
|
||||||
};
|
};
|
||||||
let deserialize_island_props = if is_island_with_other_props {
|
let deserialize_island_props = quote! {}; /*if is_island_with_other_props {
|
||||||
quote! {
|
quote! {
|
||||||
let props = el.dataset().get(::leptos::wasm_bindgen::intern("props"))
|
let props = el.dataset().get("props") // TODO ::leptos::wasm_bindgen::intern("props"))
|
||||||
.and_then(|data| ::leptos::serde_json::from_str::<#props_serialized_name>(&data).ok())
|
.and_then(|data| ::leptos::serde_json::from_str::<#props_serialized_name>(&data).ok())
|
||||||
.expect("could not deserialize props");
|
.expect("could not deserialize props");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote! {}
|
quote! {}
|
||||||
};
|
};*/
|
||||||
|
// TODO
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#[::leptos::wasm_bindgen::prelude::wasm_bindgen(wasm_bindgen = ::leptos::wasm_bindgen)]
|
#[::leptos::tachys::wasm_bindgen::prelude::wasm_bindgen(wasm_bindgen = ::leptos::wasm_bindgen)]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn #hydrate_fn_name(el: ::leptos::web_sys::HtmlElement) {
|
pub fn #hydrate_fn_name(el: ::leptos::tachys::web_sys::HtmlElement) {
|
||||||
if let Some(Ok(key)) = el.dataset().get(::leptos::wasm_bindgen::intern("hkc")).map(|key| std::str::FromStr::from_str(&key)) {
|
|
||||||
::leptos::leptos_dom::HydrationCtx::continue_from(key);
|
|
||||||
}
|
|
||||||
#deserialize_island_props
|
#deserialize_island_props
|
||||||
_ = ::leptos::run_as_child(move || {
|
let island = #name(#island_props);
|
||||||
::leptos::SharedContext::register_island(&el);
|
let state = island.hydrate_from_position::<true>(&el, ::leptos::tachys::view::Position::Current);
|
||||||
::leptos::leptos_dom::mount_to_with_stop_hydrating(el, false, move || {
|
// TODO better cleanup
|
||||||
#name(#island_props)
|
std::mem::forget(state);
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -520,6 +474,7 @@ impl ToTokens for Model {
|
||||||
#[derive(::leptos::typed_builder_macro::TypedBuilder #props_derive_serialize)]
|
#[derive(::leptos::typed_builder_macro::TypedBuilder #props_derive_serialize)]
|
||||||
//#[builder(doc)]
|
//#[builder(doc)]
|
||||||
#[builder(crate_module_path=::leptos::typed_builder)]
|
#[builder(crate_module_path=::leptos::typed_builder)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
#vis struct #props_name #impl_generics #where_clause {
|
#vis struct #props_name #impl_generics #where_clause {
|
||||||
#prop_builder_fields
|
#prop_builder_fields
|
||||||
}
|
}
|
||||||
|
@ -529,36 +484,21 @@ impl ToTokens for Model {
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#binding
|
#binding
|
||||||
|
|
||||||
impl #impl_generics ::leptos::Props for #props_name #generics #where_clause {
|
impl #impl_generics ::leptos::component::Props for #props_name #generics #where_clause {
|
||||||
type Builder = #props_builder_name #generics;
|
type Builder = #props_builder_name #generics;
|
||||||
|
|
||||||
fn builder() -> Self::Builder {
|
fn builder() -> Self::Builder {
|
||||||
#props_name::builder()
|
#props_name::builder()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl #impl_generics ::leptos::DynAttrs for #props_name #generics #where_clause {
|
// TODO restore dyn attrs
|
||||||
|
/*impl #impl_generics ::leptos::DynAttrs for #props_name #generics #where_clause {
|
||||||
fn dyn_attrs(mut self, v: Vec<(&'static str, ::leptos::Attribute)>) -> Self {
|
fn dyn_attrs(mut self, v: Vec<(&'static str, ::leptos::Attribute)>) -> Self {
|
||||||
#dyn_attrs_props
|
#dyn_attrs_props
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
|
|
||||||
impl #impl_generics ::leptos::DynBindings for #props_name #generics #where_clause {
|
|
||||||
fn dyn_bindings<B: Into<::leptos::leptos_dom::html::Binding>>(mut self, bindings: impl std::iter::IntoIterator<Item = B>) -> Self {
|
|
||||||
for binding in bindings.into_iter() {
|
|
||||||
let binding: ::leptos::leptos_dom::html::Binding = binding.into();
|
|
||||||
|
|
||||||
match binding {
|
|
||||||
#dyn_binding_props
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#into_view
|
|
||||||
|
|
||||||
#docs_and_prop_docs
|
#docs_and_prop_docs
|
||||||
#[allow(non_snake_case, clippy::too_many_arguments)]
|
#[allow(non_snake_case, clippy::too_many_arguments)]
|
||||||
|
@ -579,15 +519,8 @@ impl ToTokens for Model {
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
#[allow(clippy::wrong_self_convention)]
|
#[allow(clippy::wrong_self_convention)]
|
||||||
pub fn is_transparent(mut self, is_transparent: bool) -> Self {
|
pub fn is_island(mut self, is_island: bool) -> Self {
|
||||||
self.is_transparent = is_transparent;
|
self.is_island = is_island;
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::wrong_self_convention)]
|
|
||||||
pub fn is_island(mut self) -> Self {
|
|
||||||
self.is_island = true;
|
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -1218,16 +1151,6 @@ fn prop_to_doc(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_valid_into_view_return_type(ty: &ReturnType) -> bool {
|
|
||||||
[
|
|
||||||
parse_quote!(-> impl IntoView),
|
|
||||||
parse_quote!(-> impl leptos::IntoView),
|
|
||||||
parse_quote!(-> impl ::leptos::IntoView),
|
|
||||||
]
|
|
||||||
.iter()
|
|
||||||
.any(|test| ty == test)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unmodified_fn_name_from_fn_name(ident: &Ident) -> Ident {
|
pub fn unmodified_fn_name_from_fn_name(ident: &Ident) -> Ident {
|
||||||
Ident::new(&format!("__{ident}"), ident.span())
|
Ident::new(&format!("__{ident}"), ident.span())
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use component::DummyModel;
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::{Span, TokenTree};
|
use proc_macro2::{Span, TokenTree};
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
use rstml::{node::KeyedAttribute, parse};
|
use rstml::node::KeyedAttribute;
|
||||||
use syn::{parse_macro_input, spanned::Spanned, token::Pub, Visibility};
|
use syn::{parse_macro_input, spanned::Spanned, token::Pub, Visibility};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
@ -35,7 +35,6 @@ impl Default for Mode {
|
||||||
mod params;
|
mod params;
|
||||||
mod view;
|
mod view;
|
||||||
use crate::component::unmodified_fn_name_from_fn_name;
|
use crate::component::unmodified_fn_name_from_fn_name;
|
||||||
use view::{client_template::render_template, render_view};
|
|
||||||
mod component;
|
mod component;
|
||||||
mod slice;
|
mod slice;
|
||||||
mod slot;
|
mod slot;
|
||||||
|
@ -358,12 +357,7 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
||||||
let parser = rstml::Parser::new(config);
|
let parser = rstml::Parser::new(config);
|
||||||
let (nodes, errors) = parser.parse_recoverable(tokens).split_vec();
|
let (nodes, errors) = parser.parse_recoverable(tokens).split_vec();
|
||||||
let errors = errors.into_iter().map(|e| e.emit_as_expr_tokens());
|
let errors = errors.into_iter().map(|e| e.emit_as_expr_tokens());
|
||||||
let nodes_output = render_view(
|
let nodes_output = view::render_view(&nodes, global_class.as_ref(), None);
|
||||||
&nodes,
|
|
||||||
Mode::default(),
|
|
||||||
global_class.as_ref(),
|
|
||||||
normalized_call_site(proc_macro::Span::call_site()),
|
|
||||||
);
|
|
||||||
quote! {
|
quote! {
|
||||||
{
|
{
|
||||||
#(#errors;)*
|
#(#errors;)*
|
||||||
|
@ -373,38 +367,6 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normalized_call_site(site: proc_macro::Span) -> Option<String> {
|
|
||||||
cfg_if::cfg_if! {
|
|
||||||
if #[cfg(all(debug_assertions, feature = "nightly"))] {
|
|
||||||
Some(leptos_hot_reload::span_to_stable_id(
|
|
||||||
site.source_file().path(),
|
|
||||||
site.start().line()
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
_ = site;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An optimized, cached template for client-side rendering. Follows the same
|
|
||||||
/// syntax as the [view!] macro. In hydration or server-side rendering mode,
|
|
||||||
/// behaves exactly as the `view` macro. In client-side rendering mode, uses a `<template>`
|
|
||||||
/// node to efficiently render the element. Should only be used with a single root element.
|
|
||||||
#[proc_macro_error::proc_macro_error]
|
|
||||||
#[proc_macro]
|
|
||||||
pub fn template(tokens: TokenStream) -> TokenStream {
|
|
||||||
if cfg!(feature = "csr") {
|
|
||||||
match parse(tokens) {
|
|
||||||
Ok(nodes) => render_template(&nodes),
|
|
||||||
Err(error) => error.to_compile_error(),
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
} else {
|
|
||||||
view(tokens)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Annotates a function so that it can be used with your template as a Leptos `<Component/>`.
|
/// Annotates a function so that it can be used with your template as a Leptos `<Component/>`.
|
||||||
///
|
///
|
||||||
/// The `#[component]` macro allows you to annotate plain Rust functions as components
|
/// The `#[component]` macro allows you to annotate plain Rust functions as components
|
||||||
|
@ -585,47 +547,11 @@ pub fn template(tokens: TokenStream) -> TokenStream {
|
||||||
/// ```
|
/// ```
|
||||||
#[proc_macro_error::proc_macro_error]
|
#[proc_macro_error::proc_macro_error]
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn component(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
pub fn component(
|
||||||
let is_transparent = if !args.is_empty() {
|
_args: proc_macro::TokenStream,
|
||||||
let transparent = parse_macro_input!(args as syn::Ident);
|
s: TokenStream,
|
||||||
|
) -> TokenStream {
|
||||||
if transparent != "transparent" {
|
component_macro(s, false)
|
||||||
abort!(
|
|
||||||
transparent,
|
|
||||||
"only `transparent` is supported";
|
|
||||||
help = "try `#[component(transparent)]` or `#[component]`"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(mut dummy) = syn::parse::<DummyModel>(s.clone()) else {
|
|
||||||
return s;
|
|
||||||
};
|
|
||||||
let parse_result = syn::parse::<component::Model>(s);
|
|
||||||
|
|
||||||
if let (ref mut unexpanded, Ok(model)) = (&mut dummy, parse_result) {
|
|
||||||
let expanded = model.is_transparent(is_transparent).into_token_stream();
|
|
||||||
unexpanded.sig.ident =
|
|
||||||
unmodified_fn_name_from_fn_name(&unexpanded.sig.ident);
|
|
||||||
quote! {
|
|
||||||
#expanded
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[allow(non_snake_case, dead_code, clippy::too_many_arguments)]
|
|
||||||
#unexpanded
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dummy.sig.ident = unmodified_fn_name_from_fn_name(&dummy.sig.ident);
|
|
||||||
quote! {
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[allow(non_snake_case, dead_code, clippy::too_many_arguments)]
|
|
||||||
#dummy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines a component as an interactive island when you are using the
|
/// Defines a component as an interactive island when you are using the
|
||||||
|
@ -702,13 +628,15 @@ pub fn component(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||||
#[proc_macro_error::proc_macro_error]
|
#[proc_macro_error::proc_macro_error]
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn island(_args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
pub fn island(_args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||||
let Ok(mut dummy) = syn::parse::<DummyModel>(s.clone()) else {
|
component_macro(s, true)
|
||||||
return s;
|
}
|
||||||
};
|
|
||||||
|
fn component_macro(s: TokenStream, island: bool) -> TokenStream {
|
||||||
|
let mut dummy = syn::parse::<DummyModel>(s.clone());
|
||||||
let parse_result = syn::parse::<component::Model>(s);
|
let parse_result = syn::parse::<component::Model>(s);
|
||||||
|
|
||||||
if let (ref mut unexpanded, Ok(model)) = (&mut dummy, parse_result) {
|
if let (Ok(ref mut unexpanded), Ok(model)) = (&mut dummy, parse_result) {
|
||||||
let expanded = model.is_island().into_token_stream();
|
let expanded = model.is_island(island).into_token_stream();
|
||||||
if !matches!(unexpanded.vis, Visibility::Public(_)) {
|
if !matches!(unexpanded.vis, Visibility::Public(_)) {
|
||||||
unexpanded.vis = Visibility::Public(Pub {
|
unexpanded.vis = Visibility::Public(Pub {
|
||||||
span: unexpanded.vis.span(),
|
span: unexpanded.vis.span(),
|
||||||
|
@ -718,17 +646,20 @@ pub fn island(_args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||||
unmodified_fn_name_from_fn_name(&unexpanded.sig.ident);
|
unmodified_fn_name_from_fn_name(&unexpanded.sig.ident);
|
||||||
quote! {
|
quote! {
|
||||||
#expanded
|
#expanded
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[allow(non_snake_case, dead_code, clippy::too_many_arguments)]
|
#[allow(non_snake_case, dead_code, clippy::too_many_arguments)]
|
||||||
#unexpanded
|
#unexpanded
|
||||||
}
|
}
|
||||||
} else {
|
} else if let Ok(mut dummy) = dummy {
|
||||||
dummy.sig.ident = unmodified_fn_name_from_fn_name(&dummy.sig.ident);
|
dummy.sig.ident = unmodified_fn_name_from_fn_name(&dummy.sig.ident);
|
||||||
quote! {
|
quote! {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[allow(non_snake_case, dead_code, clippy::too_many_arguments)]
|
#[allow(non_snake_case, dead_code, clippy::too_many_arguments)]
|
||||||
#dummy
|
#dummy
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
quote! {}
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
@ -989,13 +920,6 @@ pub fn params_derive(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn attribute_value(attr: &KeyedAttribute) -> &syn::Expr {
|
|
||||||
match attr.value() {
|
|
||||||
Some(value) => value,
|
|
||||||
None => abort!(attr.key, "attribute should have value"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generates a `slice` into a struct with a default getter and setter.
|
/// Generates a `slice` into a struct with a default getter and setter.
|
||||||
///
|
///
|
||||||
/// Can be used to access deeply nested fields within a global state object.
|
/// Can be used to access deeply nested fields within a global state object.
|
||||||
|
|
|
@ -1,512 +0,0 @@
|
||||||
use super::{
|
|
||||||
component_builder::component_to_tokens,
|
|
||||||
expr_to_ident, fancy_class_name, fancy_style_name,
|
|
||||||
ide_helper::IdeTagHelper,
|
|
||||||
is_ambiguous_element, is_custom_element, is_math_ml_element,
|
|
||||||
is_self_closing, is_svg_element, parse_event_name,
|
|
||||||
slot_helper::{get_slot, slot_to_tokens},
|
|
||||||
};
|
|
||||||
use crate::{attribute_value, view::directive_call_from_attribute_node};
|
|
||||||
use leptos_hot_reload::parsing::{is_component_node, value_to_string};
|
|
||||||
use proc_macro2::{Ident, Span, TokenStream, TokenTree};
|
|
||||||
use quote::{quote, quote_spanned};
|
|
||||||
use rstml::node::{KeyedAttribute, Node, NodeAttribute, NodeElement, NodeName};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use syn::spanned::Spanned;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub(crate) enum TagType {
|
|
||||||
Unknown,
|
|
||||||
Html,
|
|
||||||
Svg,
|
|
||||||
Math,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub(crate) fn fragment_to_tokens(
|
|
||||||
nodes: &[Node],
|
|
||||||
lazy: bool,
|
|
||||||
parent_type: TagType,
|
|
||||||
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
|
||||||
global_class: Option<&TokenTree>,
|
|
||||||
view_marker: Option<String>,
|
|
||||||
) -> Option<TokenStream> {
|
|
||||||
let mut slots = HashMap::new();
|
|
||||||
let has_slots = parent_slots.is_some();
|
|
||||||
|
|
||||||
let original_span = nodes
|
|
||||||
.first()
|
|
||||||
.zip(nodes.last())
|
|
||||||
.and_then(|(first, last)| first.span().join(last.span()))
|
|
||||||
.unwrap_or_else(Span::call_site);
|
|
||||||
|
|
||||||
let mut nodes = nodes
|
|
||||||
.iter()
|
|
||||||
.filter_map(|node| {
|
|
||||||
let span = node.span();
|
|
||||||
let node = node_to_tokens(
|
|
||||||
node,
|
|
||||||
parent_type,
|
|
||||||
has_slots.then_some(&mut slots),
|
|
||||||
global_class,
|
|
||||||
None,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let node = quote_spanned!(span => { #node });
|
|
||||||
|
|
||||||
Some(quote! {
|
|
||||||
::leptos::IntoView::into_view(#[allow(unused_braces)] #node)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.peekable();
|
|
||||||
|
|
||||||
if nodes.peek().is_none() {
|
|
||||||
_ = nodes.collect::<Vec<_>>();
|
|
||||||
if let Some(parent_slots) = parent_slots {
|
|
||||||
for (slot, mut values) in slots.drain() {
|
|
||||||
parent_slots
|
|
||||||
.entry(slot)
|
|
||||||
.and_modify(|entry| entry.append(&mut values))
|
|
||||||
.or_insert(values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let view_marker = if let Some(marker) = view_marker {
|
|
||||||
quote! { .with_view_marker(#marker) }
|
|
||||||
} else {
|
|
||||||
quote! {}
|
|
||||||
};
|
|
||||||
|
|
||||||
let tokens = if lazy {
|
|
||||||
quote_spanned! {original_span=>
|
|
||||||
{
|
|
||||||
::leptos::Fragment::lazy(|| ::std::vec![
|
|
||||||
#(#nodes),*
|
|
||||||
])
|
|
||||||
#view_marker
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote_spanned! {original_span=>
|
|
||||||
{
|
|
||||||
::leptos::Fragment::new(::std::vec![
|
|
||||||
#(#nodes),*
|
|
||||||
])
|
|
||||||
#view_marker
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(parent_slots) = parent_slots {
|
|
||||||
for (slot, mut values) in slots.drain() {
|
|
||||||
parent_slots
|
|
||||||
.entry(slot)
|
|
||||||
.and_modify(|entry| entry.append(&mut values))
|
|
||||||
.or_insert(values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(tokens)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn node_to_tokens(
|
|
||||||
node: &Node,
|
|
||||||
parent_type: TagType,
|
|
||||||
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
|
||||||
global_class: Option<&TokenTree>,
|
|
||||||
view_marker: Option<String>,
|
|
||||||
) -> Option<TokenStream> {
|
|
||||||
match node {
|
|
||||||
Node::Fragment(fragment) => fragment_to_tokens(
|
|
||||||
&fragment.children,
|
|
||||||
true,
|
|
||||||
parent_type,
|
|
||||||
None,
|
|
||||||
global_class,
|
|
||||||
view_marker,
|
|
||||||
),
|
|
||||||
Node::Comment(_) | Node::Doctype(_) => Some(quote! {}),
|
|
||||||
Node::Text(node) => Some(quote! {
|
|
||||||
::leptos::leptos_dom::html::text(#node)
|
|
||||||
}),
|
|
||||||
Node::Block(node) => Some(quote! { #node }),
|
|
||||||
Node::RawText(r) => {
|
|
||||||
let text = r.to_string_best();
|
|
||||||
if text == "cx," {
|
|
||||||
proc_macro_error::abort!(
|
|
||||||
r.span(),
|
|
||||||
"`cx,` is not used with the `view!` macro in 0.5."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
let text = syn::LitStr::new(&text, r.span());
|
|
||||||
Some(quote! { #text })
|
|
||||||
}
|
|
||||||
Node::Element(node) => element_to_tokens(
|
|
||||||
node,
|
|
||||||
parent_type,
|
|
||||||
parent_slots,
|
|
||||||
global_class,
|
|
||||||
view_marker,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn element_to_tokens(
|
|
||||||
node: &NodeElement,
|
|
||||||
mut parent_type: TagType,
|
|
||||||
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
|
||||||
global_class: Option<&TokenTree>,
|
|
||||||
view_marker: Option<String>,
|
|
||||||
) -> Option<TokenStream> {
|
|
||||||
let name = node.name();
|
|
||||||
if is_component_node(node) {
|
|
||||||
if let Some(slot) = get_slot(node) {
|
|
||||||
slot_to_tokens(node, slot, parent_slots, global_class);
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(component_to_tokens(node, global_class))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let tag = name.to_string();
|
|
||||||
// collect close_tag name to emit semantic information for IDE.
|
|
||||||
let mut ide_helper_close_tag = IdeTagHelper::new();
|
|
||||||
let close_tag = node.close_tag.as_ref().map(|c| &c.name);
|
|
||||||
let name = if is_custom_element(&tag) {
|
|
||||||
let name = node.name().to_string();
|
|
||||||
// link custom ident to name span for IDE docs
|
|
||||||
let custom = Ident::new("custom", node.name().span());
|
|
||||||
quote! { ::leptos::leptos_dom::html::#custom(::leptos::leptos_dom::html::Custom::new(#name)) }
|
|
||||||
} else if is_svg_element(&tag) {
|
|
||||||
parent_type = TagType::Svg;
|
|
||||||
quote! { ::leptos::leptos_dom::svg::#name() }
|
|
||||||
} else if is_math_ml_element(&tag) {
|
|
||||||
parent_type = TagType::Math;
|
|
||||||
quote! { ::leptos::leptos_dom::math::#name() }
|
|
||||||
} else if is_ambiguous_element(&tag) {
|
|
||||||
match parent_type {
|
|
||||||
TagType::Unknown => {
|
|
||||||
// We decided this warning was too aggressive, but I'll leave it here in case we want it later
|
|
||||||
/* proc_macro_error::emit_warning!(name.span(), "The view macro is assuming this is an HTML element, \
|
|
||||||
but it is ambiguous; if it is an SVG or MathML element, prefix with svg:: or math::"); */
|
|
||||||
quote! {
|
|
||||||
::leptos::leptos_dom::html::#name()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TagType::Html => {
|
|
||||||
quote! { ::leptos::leptos_dom::html::#name() }
|
|
||||||
}
|
|
||||||
TagType::Svg => {
|
|
||||||
quote! { ::leptos::leptos_dom::svg::#name() }
|
|
||||||
}
|
|
||||||
TagType::Math => {
|
|
||||||
quote! { ::leptos::leptos_dom::math::#name() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
parent_type = TagType::Html;
|
|
||||||
quote! { ::leptos::leptos_dom::html::#name() }
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(close_tag) = close_tag {
|
|
||||||
ide_helper_close_tag.save_tag_completion(close_tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
let attrs = node.attributes().iter().filter_map(|node| {
|
|
||||||
if let NodeAttribute::Attribute(node) = node {
|
|
||||||
let name = node.key.to_string();
|
|
||||||
let name = name.trim();
|
|
||||||
if name.starts_with("class:")
|
|
||||||
|| fancy_class_name(name, node).is_some()
|
|
||||||
|| name.starts_with("style:")
|
|
||||||
|| fancy_style_name(name, node).is_some()
|
|
||||||
{
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(attribute_to_tokens(node, global_class))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let bindings = node.attributes().iter().filter_map(|node| {
|
|
||||||
use rstml::node::NodeBlock;
|
|
||||||
use syn::{Expr, ExprRange, RangeLimits, Stmt};
|
|
||||||
|
|
||||||
if let NodeAttribute::Block(NodeBlock::ValidBlock(block)) = node {
|
|
||||||
match block.stmts.first()? {
|
|
||||||
Stmt::Expr(
|
|
||||||
Expr::Range(ExprRange {
|
|
||||||
start: None,
|
|
||||||
limits: RangeLimits::HalfOpen(_),
|
|
||||||
end: Some(end),
|
|
||||||
..
|
|
||||||
}),
|
|
||||||
_,
|
|
||||||
) => Some(quote! { .bindings(#end) }),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let class_attrs = node.attributes().iter().filter_map(|node| {
|
|
||||||
if let NodeAttribute::Attribute(node) = node {
|
|
||||||
let name = node.key.to_string();
|
|
||||||
if let Some((fancy, _, _)) = fancy_class_name(&name, node) {
|
|
||||||
Some(fancy)
|
|
||||||
} else if name.trim().starts_with("class:") {
|
|
||||||
Some(attribute_to_tokens(node, global_class))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let style_attrs = node.attributes().iter().filter_map(|node| {
|
|
||||||
if let NodeAttribute::Attribute(node) = node {
|
|
||||||
let name = node.key.to_string();
|
|
||||||
if let Some((fancy, _, _)) = fancy_style_name(&name, node) {
|
|
||||||
Some(fancy)
|
|
||||||
} else if name.trim().starts_with("style:") {
|
|
||||||
Some(attribute_to_tokens(node, global_class))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let global_class_expr = match global_class {
|
|
||||||
None => quote! {},
|
|
||||||
Some(class) => quote! { .classes(#class) },
|
|
||||||
};
|
|
||||||
|
|
||||||
if is_self_closing(node) && !node.children.is_empty() {
|
|
||||||
proc_macro_error::abort!(
|
|
||||||
node.name().span(),
|
|
||||||
format!(
|
|
||||||
"<{tag}> is a self-closing tag and cannot have children."
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let children = node
|
|
||||||
.children
|
|
||||||
.iter()
|
|
||||||
.filter_map(|node| match node {
|
|
||||||
Node::Fragment(fragment) => Some(
|
|
||||||
fragment_to_tokens(
|
|
||||||
&fragment.children,
|
|
||||||
true,
|
|
||||||
parent_type,
|
|
||||||
None,
|
|
||||||
global_class,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.unwrap_or(quote_spanned! {
|
|
||||||
Span::call_site()=> ::leptos::leptos_dom::Unit
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
Node::Text(node) => Some(quote! { #node }),
|
|
||||||
Node::RawText(node) => {
|
|
||||||
let text = node.to_string_best();
|
|
||||||
let text = syn::LitStr::new(&text, node.span());
|
|
||||||
Some(quote! { #text })
|
|
||||||
}
|
|
||||||
Node::Block(node) => Some(quote! { #node }),
|
|
||||||
Node::Element(node) => Some(
|
|
||||||
element_to_tokens(
|
|
||||||
node,
|
|
||||||
parent_type,
|
|
||||||
None,
|
|
||||||
global_class,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.unwrap_or_default(),
|
|
||||||
),
|
|
||||||
Node::Comment(_) | Node::Doctype(_) => None,
|
|
||||||
})
|
|
||||||
.map(|node| quote!(.child(#node)));
|
|
||||||
|
|
||||||
let view_marker = if let Some(marker) = view_marker {
|
|
||||||
quote! { .with_view_marker(#marker) }
|
|
||||||
} else {
|
|
||||||
quote! {}
|
|
||||||
};
|
|
||||||
let ide_helper_close_tag = ide_helper_close_tag.into_iter();
|
|
||||||
let result = quote_spanned! {node.span()=> {
|
|
||||||
#(#ide_helper_close_tag)*
|
|
||||||
#name
|
|
||||||
#(#attrs)*
|
|
||||||
#(#bindings)*
|
|
||||||
#(#class_attrs)*
|
|
||||||
#(#style_attrs)*
|
|
||||||
#global_class_expr
|
|
||||||
#(#children)*
|
|
||||||
#view_marker
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// We need to move "allow" out of "quote_spanned" because it breaks hovering in rust-analyzer
|
|
||||||
Some(quote!(#[allow(unused_braces)] #result))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn attribute_to_tokens(
|
|
||||||
node: &KeyedAttribute,
|
|
||||||
global_class: Option<&TokenTree>,
|
|
||||||
) -> TokenStream {
|
|
||||||
let span = node.key.span();
|
|
||||||
let name = node.key.to_string();
|
|
||||||
if name == "ref" || name == "_ref" || name == "ref_" || name == "node_ref" {
|
|
||||||
let value = expr_to_ident(attribute_value(node));
|
|
||||||
let node_ref = quote_spanned! { span=> node_ref };
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
.#node_ref(#value)
|
|
||||||
}
|
|
||||||
} else if let Some(name) = name.strip_prefix("use:") {
|
|
||||||
directive_call_from_attribute_node(node, name)
|
|
||||||
} else if let Some(name) = name.strip_prefix("on:") {
|
|
||||||
let handler = attribute_value(node);
|
|
||||||
|
|
||||||
let (event_type, is_custom, is_force_undelegated) =
|
|
||||||
parse_event_name(name);
|
|
||||||
|
|
||||||
let event_name_ident = match &node.key {
|
|
||||||
NodeName::Punctuated(parts) => {
|
|
||||||
if parts.len() >= 2 {
|
|
||||||
Some(&parts[1])
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
let undelegated_ident = match &node.key {
|
|
||||||
NodeName::Punctuated(parts) => parts.last().and_then(|last| {
|
|
||||||
if last.to_string() == "undelegated" {
|
|
||||||
Some(last)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
let on = match &node.key {
|
|
||||||
NodeName::Punctuated(parts) => &parts[0],
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
let on = quote_spanned! {
|
|
||||||
on.span()=> .on
|
|
||||||
};
|
|
||||||
let event_type = if is_custom {
|
|
||||||
event_type
|
|
||||||
} else if let Some(ev_name) = event_name_ident {
|
|
||||||
quote_spanned! {
|
|
||||||
ev_name.span()=> #ev_name
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
event_type
|
|
||||||
};
|
|
||||||
|
|
||||||
let event_type = if is_force_undelegated {
|
|
||||||
let undelegated = if let Some(undelegated) = undelegated_ident {
|
|
||||||
quote_spanned! {
|
|
||||||
undelegated.span()=> #undelegated
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! { undelegated }
|
|
||||||
};
|
|
||||||
quote! { ::leptos::ev::#undelegated(::leptos::ev::#event_type) }
|
|
||||||
} else {
|
|
||||||
quote! { ::leptos::ev::#event_type }
|
|
||||||
};
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
#on(#event_type, #handler)
|
|
||||||
}
|
|
||||||
} else if let Some(name) = name.strip_prefix("prop:") {
|
|
||||||
let value = attribute_value(node);
|
|
||||||
let prop = match &node.key {
|
|
||||||
NodeName::Punctuated(parts) => &parts[0],
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
let prop = quote_spanned! {
|
|
||||||
prop.span()=> .prop
|
|
||||||
};
|
|
||||||
quote! {
|
|
||||||
#prop(#name, #value)
|
|
||||||
}
|
|
||||||
} else if let Some(name) = name.strip_prefix("class:") {
|
|
||||||
let value = attribute_value(node);
|
|
||||||
let class = match &node.key {
|
|
||||||
NodeName::Punctuated(parts) => &parts[0],
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
let class = quote_spanned! {
|
|
||||||
class.span()=> .class
|
|
||||||
};
|
|
||||||
quote! {
|
|
||||||
#class(#name, #value)
|
|
||||||
}
|
|
||||||
} else if let Some(name) = name.strip_prefix("style:") {
|
|
||||||
let value = attribute_value(node);
|
|
||||||
let style = match &node.key {
|
|
||||||
NodeName::Punctuated(parts) => &parts[0],
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
let style = quote_spanned! {
|
|
||||||
style.span()=> .style
|
|
||||||
};
|
|
||||||
quote! {
|
|
||||||
#style(#name, #value)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let name = name.replacen("attr:", "", 1);
|
|
||||||
|
|
||||||
if let Some((fancy, _, _)) = fancy_class_name(&name, node) {
|
|
||||||
return fancy;
|
|
||||||
}
|
|
||||||
|
|
||||||
// special case of global_class and class attribute
|
|
||||||
if name == "class"
|
|
||||||
&& global_class.is_some()
|
|
||||||
&& node.value().and_then(value_to_string).is_none()
|
|
||||||
{
|
|
||||||
let span = node.key.span();
|
|
||||||
proc_macro_error::emit_error!(span, "Combining a global class (view! { class = ... }) \
|
|
||||||
and a dynamic `class=` attribute on an element causes runtime inconsistencies. You can \
|
|
||||||
toggle individual classes dynamically with the `class:name=value` syntax. \n\nSee this issue \
|
|
||||||
for more information and an example: https://github.com/leptos-rs/leptos/issues/773")
|
|
||||||
};
|
|
||||||
|
|
||||||
// all other attributes
|
|
||||||
let value = match node.value() {
|
|
||||||
Some(value) => {
|
|
||||||
quote! { #value }
|
|
||||||
}
|
|
||||||
None => quote_spanned! { span=> "" },
|
|
||||||
};
|
|
||||||
|
|
||||||
let attr = match &node.key {
|
|
||||||
NodeName::Punctuated(parts) => Some(&parts[0]),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
let attr = if let Some(attr) = attr {
|
|
||||||
quote_spanned! {
|
|
||||||
attr.span()=> .attr
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
.attr
|
|
||||||
}
|
|
||||||
};
|
|
||||||
quote! {
|
|
||||||
#attr(#name, #value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,540 +0,0 @@
|
||||||
use super::{component_builder::component_to_tokens, IdeTagHelper};
|
|
||||||
use crate::attribute_value;
|
|
||||||
use itertools::Either;
|
|
||||||
use leptos_hot_reload::parsing::{
|
|
||||||
block_to_primitive_expression, is_component_node, value_to_string,
|
|
||||||
};
|
|
||||||
use proc_macro2::{Ident, Span, TokenStream};
|
|
||||||
use quote::{quote, quote_spanned, ToTokens};
|
|
||||||
use rstml::node::{
|
|
||||||
KeyedAttribute, Node, NodeAttribute, NodeBlock, NodeElement,
|
|
||||||
};
|
|
||||||
use syn::spanned::Spanned;
|
|
||||||
|
|
||||||
pub(crate) fn render_template(nodes: &[Node]) -> TokenStream {
|
|
||||||
// No reason to make template unique, because its "static" is in inner scope.
|
|
||||||
let template_uid = Ident::new("__TEMPLATE", Span::call_site());
|
|
||||||
|
|
||||||
match nodes.first() {
|
|
||||||
Some(Node::Element(node)) => {
|
|
||||||
root_element_to_tokens(&template_uid, node)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
abort!(Span::call_site(), "template! takes a single root element.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn root_element_to_tokens(
|
|
||||||
template_uid: &Ident,
|
|
||||||
node: &NodeElement,
|
|
||||||
) -> TokenStream {
|
|
||||||
let mut template = String::new();
|
|
||||||
let mut navigations = Vec::new();
|
|
||||||
let mut stmts_for_ide = IdeTagHelper::new();
|
|
||||||
let mut expressions = Vec::new();
|
|
||||||
|
|
||||||
if is_component_node(node) {
|
|
||||||
component_to_tokens(node, None)
|
|
||||||
} else {
|
|
||||||
element_to_tokens(
|
|
||||||
node,
|
|
||||||
&Ident::new("root", Span::call_site()),
|
|
||||||
None,
|
|
||||||
&mut 0,
|
|
||||||
&mut 0,
|
|
||||||
&mut template,
|
|
||||||
&mut navigations,
|
|
||||||
&mut stmts_for_ide,
|
|
||||||
&mut expressions,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
// create the root element from which navigations and expressions will begin
|
|
||||||
let generate_root = quote! {
|
|
||||||
let root = #template_uid.with(|tpl| tpl.content().clone_node_with_deep(true))
|
|
||||||
.unwrap()
|
|
||||||
.first_child()
|
|
||||||
.unwrap();
|
|
||||||
};
|
|
||||||
|
|
||||||
let tag_name = node.name().to_string();
|
|
||||||
let stmts_for_ide = stmts_for_ide.into_iter();
|
|
||||||
quote! {
|
|
||||||
{
|
|
||||||
thread_local! {
|
|
||||||
static #template_uid: ::leptos::web_sys::HtmlTemplateElement = {
|
|
||||||
let document = ::leptos::document();
|
|
||||||
let el = document.create_element("template").unwrap();
|
|
||||||
el.set_inner_html(#template);
|
|
||||||
::leptos::wasm_bindgen::JsCast::unchecked_into(el)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#(#stmts_for_ide)*
|
|
||||||
#generate_root
|
|
||||||
|
|
||||||
#(#navigations)*
|
|
||||||
#(#expressions;)*
|
|
||||||
|
|
||||||
::leptos::leptos_dom::View::Element(leptos::leptos_dom::Element {
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
name: #tag_name.into(),
|
|
||||||
element: ::leptos::wasm_bindgen::JsCast::unchecked_into(root),
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
view_marker: None
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
enum PrevSibChange {
|
|
||||||
Sib(Ident),
|
|
||||||
Parent,
|
|
||||||
Skip,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn attributes(node: &NodeElement) -> impl Iterator<Item = &KeyedAttribute> {
|
|
||||||
node.attributes().iter().filter_map(|node| {
|
|
||||||
if let NodeAttribute::Attribute(attribute) = node {
|
|
||||||
Some(attribute)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn element_to_tokens(
|
|
||||||
node: &NodeElement,
|
|
||||||
parent: &Ident,
|
|
||||||
prev_sib: Option<Ident>,
|
|
||||||
next_el_id: &mut usize,
|
|
||||||
next_co_id: &mut usize,
|
|
||||||
template: &mut String,
|
|
||||||
navigations: &mut Vec<TokenStream>,
|
|
||||||
stmts_for_ide: &mut IdeTagHelper,
|
|
||||||
expressions: &mut Vec<TokenStream>,
|
|
||||||
is_root_el: bool,
|
|
||||||
) -> Ident {
|
|
||||||
// create this element
|
|
||||||
*next_el_id += 1;
|
|
||||||
|
|
||||||
// Use any other span instead of node.name.span(), to avoid missundestanding in IDE helpers.
|
|
||||||
// same as view::root_element_to_tokens_ssr::typed_element_name
|
|
||||||
let this_el_ident = child_ident(*next_el_id, Span::call_site());
|
|
||||||
|
|
||||||
// Open tag
|
|
||||||
let name_str = node.name().to_string();
|
|
||||||
// Span for diagnostic message in case of error in quote_spanned! macro
|
|
||||||
let span = node.open_tag.span();
|
|
||||||
|
|
||||||
// CSR/hydrate, push to template
|
|
||||||
template.push('<');
|
|
||||||
template.push_str(&name_str);
|
|
||||||
|
|
||||||
// attributes
|
|
||||||
for attr in attributes(node) {
|
|
||||||
attr_to_tokens(attr, &this_el_ident, template, expressions);
|
|
||||||
}
|
|
||||||
|
|
||||||
// navigation for this el
|
|
||||||
let debug_name = node.name().to_string();
|
|
||||||
let this_nav = if is_root_el {
|
|
||||||
quote_spanned! {
|
|
||||||
span=> let #this_el_ident = #debug_name;
|
|
||||||
let #this_el_ident =
|
|
||||||
::leptos::wasm_bindgen::JsCast::unchecked_into::<::leptos::web_sys::Node>(
|
|
||||||
#parent.clone()
|
|
||||||
);
|
|
||||||
//debug!("=> got {}", #this_el_ident.node_name());
|
|
||||||
}
|
|
||||||
} else if let Some(prev_sib) = &prev_sib {
|
|
||||||
quote_spanned! {
|
|
||||||
span=> let #this_el_ident = #debug_name;
|
|
||||||
//log::debug!("next_sibling ({})", #debug_name);
|
|
||||||
let #this_el_ident = #prev_sib.next_sibling().unwrap_or_else(|| ::std::panic!("error : {} => {} ", #debug_name, "nextSibling"));
|
|
||||||
//log::debug!("=> got {}", #this_el_ident.node_name());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote_spanned! {
|
|
||||||
span=> let #this_el_ident = #debug_name;
|
|
||||||
//log::debug!("first_child ({})", #debug_name);
|
|
||||||
let #this_el_ident = #parent.first_child().unwrap_or_else(|| ::std::panic!("error: {} => {}", #debug_name, "firstChild"));
|
|
||||||
//log::debug!("=> got {}", #this_el_ident.node_name());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
navigations.push(this_nav);
|
|
||||||
// emit ide helper info
|
|
||||||
stmts_for_ide.save_element_completion(node);
|
|
||||||
// self-closing tags
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Glossary/Empty_element
|
|
||||||
if matches!(
|
|
||||||
name_str.as_str(),
|
|
||||||
"area"
|
|
||||||
| "base"
|
|
||||||
| "br"
|
|
||||||
| "col"
|
|
||||||
| "embed"
|
|
||||||
| "hr"
|
|
||||||
| "img"
|
|
||||||
| "input"
|
|
||||||
| "link"
|
|
||||||
| "meta"
|
|
||||||
| "param"
|
|
||||||
| "source"
|
|
||||||
| "track"
|
|
||||||
| "wbr"
|
|
||||||
) {
|
|
||||||
template.push_str("/>");
|
|
||||||
return this_el_ident;
|
|
||||||
} else {
|
|
||||||
template.push('>');
|
|
||||||
}
|
|
||||||
|
|
||||||
// iterate over children
|
|
||||||
let mut prev_sib = prev_sib;
|
|
||||||
for (idx, child) in node.children.iter().enumerate() {
|
|
||||||
// set next sib (for any insertions)
|
|
||||||
let next_sib =
|
|
||||||
match next_sibling_node(&node.children, idx + 1, next_el_id) {
|
|
||||||
Ok(next_sib) => next_sib,
|
|
||||||
Err(err) => abort!(span, "{}", err),
|
|
||||||
};
|
|
||||||
|
|
||||||
let curr_id = child_to_tokens(
|
|
||||||
child,
|
|
||||||
&this_el_ident,
|
|
||||||
if idx == 0 { None } else { prev_sib.clone() },
|
|
||||||
next_sib,
|
|
||||||
next_el_id,
|
|
||||||
next_co_id,
|
|
||||||
template,
|
|
||||||
navigations,
|
|
||||||
stmts_for_ide,
|
|
||||||
expressions,
|
|
||||||
);
|
|
||||||
|
|
||||||
prev_sib = match curr_id {
|
|
||||||
PrevSibChange::Sib(id) => Some(id),
|
|
||||||
PrevSibChange::Parent => None,
|
|
||||||
PrevSibChange::Skip => prev_sib,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// close tag
|
|
||||||
template.push_str("</");
|
|
||||||
template.push_str(&name_str);
|
|
||||||
template.push('>');
|
|
||||||
|
|
||||||
this_el_ident
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_sibling_node(
|
|
||||||
children: &[Node],
|
|
||||||
idx: usize,
|
|
||||||
next_el_id: &mut usize,
|
|
||||||
) -> Result<Option<Ident>, String> {
|
|
||||||
if children.len() <= idx {
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
let sibling = &children[idx];
|
|
||||||
|
|
||||||
match sibling {
|
|
||||||
Node::Element(sibling) => {
|
|
||||||
if is_component_node(sibling) {
|
|
||||||
next_sibling_node(children, idx + 1, next_el_id)
|
|
||||||
} else {
|
|
||||||
Ok(Some(child_ident(
|
|
||||||
*next_el_id + 1,
|
|
||||||
sibling.name().span(),
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Node::Block(sibling) => {
|
|
||||||
Ok(Some(child_ident(*next_el_id + 1, sibling.span())))
|
|
||||||
}
|
|
||||||
Node::Text(sibling) => {
|
|
||||||
Ok(Some(child_ident(*next_el_id + 1, sibling.span())))
|
|
||||||
}
|
|
||||||
_ => Err("expected either an element or a block".to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn attr_to_tokens(
|
|
||||||
node: &KeyedAttribute,
|
|
||||||
el_id: &Ident,
|
|
||||||
template: &mut String,
|
|
||||||
expressions: &mut Vec<TokenStream>,
|
|
||||||
) {
|
|
||||||
let name = node.key.to_string();
|
|
||||||
let name = name.strip_prefix('_').unwrap_or(&name);
|
|
||||||
let name = name.strip_prefix("attr:").unwrap_or(name);
|
|
||||||
|
|
||||||
let value = match &node.value() {
|
|
||||||
Some(expr) => match expr {
|
|
||||||
syn::Expr::Lit(expr_lit) => {
|
|
||||||
if let syn::Lit::Str(s) = &expr_lit.lit {
|
|
||||||
AttributeValue::Static(s.value())
|
|
||||||
} else {
|
|
||||||
AttributeValue::Dynamic(expr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => AttributeValue::Dynamic(expr),
|
|
||||||
},
|
|
||||||
None => AttributeValue::Empty,
|
|
||||||
};
|
|
||||||
|
|
||||||
let span = node.key.span();
|
|
||||||
|
|
||||||
// refs
|
|
||||||
if name == "ref" {
|
|
||||||
abort!(span, "node_ref not yet supported in template! macro")
|
|
||||||
}
|
|
||||||
// Event Handlers
|
|
||||||
else if name.starts_with("on:") {
|
|
||||||
let (event_type, handler) =
|
|
||||||
crate::view::event_from_attribute_node(node, false);
|
|
||||||
expressions.push(quote! {
|
|
||||||
::leptos::leptos_dom::add_event_helper(
|
|
||||||
::leptos::wasm_bindgen::JsCast::unchecked_ref(&#el_id),
|
|
||||||
#event_type,
|
|
||||||
#handler,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// Properties
|
|
||||||
else if let Some(name) = name.strip_prefix("prop:") {
|
|
||||||
let value = attribute_value(node);
|
|
||||||
|
|
||||||
expressions.push(quote_spanned! {
|
|
||||||
span=> ::leptos::leptos_dom::property(
|
|
||||||
::leptos::wasm_bindgen::JsCast::unchecked_ref(&#el_id),
|
|
||||||
#name,
|
|
||||||
::leptos::IntoProperty::into_property(#value),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Classes
|
|
||||||
else if let Some(name) = name.strip_prefix("class:") {
|
|
||||||
let value = attribute_value(node);
|
|
||||||
|
|
||||||
expressions.push(quote_spanned! {
|
|
||||||
span=> ::leptos::leptos_dom::class_helper(
|
|
||||||
::leptos::wasm_bindgen::JsCast::unchecked_ref(&#el_id),
|
|
||||||
#name.into(),
|
|
||||||
::leptos::IntoClass::into_class(#value),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Attributes
|
|
||||||
else {
|
|
||||||
match value {
|
|
||||||
AttributeValue::Empty => {
|
|
||||||
template.push(' ');
|
|
||||||
template.push_str(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Static attributes (i.e., just a literal given as value, not an expression)
|
|
||||||
// are just set in the template — again, nothing programmatic
|
|
||||||
AttributeValue::Static(value) => {
|
|
||||||
template.push(' ');
|
|
||||||
template.push_str(name);
|
|
||||||
template.push_str("=\"");
|
|
||||||
template.push_str(&value);
|
|
||||||
template.push('"');
|
|
||||||
}
|
|
||||||
AttributeValue::Dynamic(value) => {
|
|
||||||
// For client-side rendering, dynamic attributes don't need to be rendered in the template
|
|
||||||
// They'll immediately be set synchronously before the cloned template is mounted
|
|
||||||
expressions.push(quote_spanned! {
|
|
||||||
span=> ::leptos::leptos_dom::attribute_helper(
|
|
||||||
::leptos::wasm_bindgen::JsCast::unchecked_ref(&#el_id),
|
|
||||||
#name.into(),
|
|
||||||
::leptos::IntoAttribute::into_attribute(#[allow(unused_braces)] {#value}),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum AttributeValue<'a> {
|
|
||||||
Static(String),
|
|
||||||
Dynamic(&'a syn::Expr),
|
|
||||||
Empty,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn child_to_tokens(
|
|
||||||
node: &Node,
|
|
||||||
parent: &Ident,
|
|
||||||
prev_sib: Option<Ident>,
|
|
||||||
next_sib: Option<Ident>,
|
|
||||||
next_el_id: &mut usize,
|
|
||||||
next_co_id: &mut usize,
|
|
||||||
template: &mut String,
|
|
||||||
navigations: &mut Vec<TokenStream>,
|
|
||||||
stmts_for_ide: &mut IdeTagHelper,
|
|
||||||
expressions: &mut Vec<TokenStream>,
|
|
||||||
) -> PrevSibChange {
|
|
||||||
match node {
|
|
||||||
Node::Element(node) => {
|
|
||||||
if is_component_node(node) {
|
|
||||||
proc_macro_error::emit_error!(
|
|
||||||
node.name().span(),
|
|
||||||
"component children not allowed in template!, use view! \
|
|
||||||
instead"
|
|
||||||
);
|
|
||||||
PrevSibChange::Skip
|
|
||||||
} else {
|
|
||||||
PrevSibChange::Sib(element_to_tokens(
|
|
||||||
node,
|
|
||||||
parent,
|
|
||||||
prev_sib,
|
|
||||||
next_el_id,
|
|
||||||
next_co_id,
|
|
||||||
template,
|
|
||||||
navigations,
|
|
||||||
stmts_for_ide,
|
|
||||||
expressions,
|
|
||||||
false,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Node::Text(node) => block_to_tokens(
|
|
||||||
Either::Left(node.value_string()),
|
|
||||||
node.value.span(),
|
|
||||||
parent,
|
|
||||||
prev_sib,
|
|
||||||
next_sib,
|
|
||||||
next_el_id,
|
|
||||||
template,
|
|
||||||
expressions,
|
|
||||||
navigations,
|
|
||||||
),
|
|
||||||
Node::RawText(node) => block_to_tokens(
|
|
||||||
Either::Left(node.to_string_best()),
|
|
||||||
node.span(),
|
|
||||||
parent,
|
|
||||||
prev_sib,
|
|
||||||
next_sib,
|
|
||||||
next_el_id,
|
|
||||||
template,
|
|
||||||
expressions,
|
|
||||||
navigations,
|
|
||||||
),
|
|
||||||
Node::Block(NodeBlock::ValidBlock(b)) => {
|
|
||||||
let value = match block_to_primitive_expression(b)
|
|
||||||
.and_then(value_to_string)
|
|
||||||
{
|
|
||||||
Some(v) => Either::Left(v),
|
|
||||||
None => Either::Right(b.into_token_stream()),
|
|
||||||
};
|
|
||||||
block_to_tokens(
|
|
||||||
value,
|
|
||||||
b.span(),
|
|
||||||
parent,
|
|
||||||
prev_sib,
|
|
||||||
next_sib,
|
|
||||||
next_el_id,
|
|
||||||
template,
|
|
||||||
expressions,
|
|
||||||
navigations,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Node::Block(b @ NodeBlock::Invalid { .. }) => block_to_tokens(
|
|
||||||
Either::Right(b.into_token_stream()),
|
|
||||||
b.span(),
|
|
||||||
parent,
|
|
||||||
prev_sib,
|
|
||||||
next_sib,
|
|
||||||
next_el_id,
|
|
||||||
template,
|
|
||||||
expressions,
|
|
||||||
navigations,
|
|
||||||
),
|
|
||||||
_ => abort!(Span::call_site(), "unexpected child node type"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn block_to_tokens(
|
|
||||||
value: Either<String, TokenStream>,
|
|
||||||
span: Span,
|
|
||||||
parent: &Ident,
|
|
||||||
prev_sib: Option<Ident>,
|
|
||||||
next_sib: Option<Ident>,
|
|
||||||
next_el_id: &mut usize,
|
|
||||||
template: &mut String,
|
|
||||||
expressions: &mut Vec<TokenStream>,
|
|
||||||
navigations: &mut Vec<TokenStream>,
|
|
||||||
) -> PrevSibChange {
|
|
||||||
// code to navigate to this text node
|
|
||||||
|
|
||||||
let (name, location) = /* if is_first_child && mode == Mode::Client {
|
|
||||||
(None, quote! { })
|
|
||||||
}
|
|
||||||
else */ {
|
|
||||||
*next_el_id += 1;
|
|
||||||
let name = child_ident(*next_el_id, span);
|
|
||||||
let location = if let Some(sibling) = &prev_sib {
|
|
||||||
quote_spanned! {
|
|
||||||
span=> //log::debug!("-> next sibling");
|
|
||||||
let #name = #sibling.next_sibling().unwrap_or_else(|| ::std::panic!("error : {} => {} ", "{block}", "nextSibling"));
|
|
||||||
//log::debug!("\tnext sibling = {}", #name.node_name());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote_spanned! {
|
|
||||||
span=> //log::debug!("\\|/ first child on {}", #parent.node_name());
|
|
||||||
let #name = #parent.first_child().unwrap_or_else(|| ::std::panic!("error : {} => {} ", "{block}", "firstChild"));
|
|
||||||
//log::debug!("\tfirst child = {}", #name.node_name());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
(Some(name), location)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mount_kind = match &next_sib {
|
|
||||||
Some(child) => {
|
|
||||||
quote! { ::leptos::leptos_dom::MountKind::Before(&#child.clone()) }
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
quote! { ::leptos::leptos_dom::MountKind::Append(&#parent) }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match value {
|
|
||||||
Either::Left(v) => {
|
|
||||||
navigations.push(location);
|
|
||||||
template.push_str(&v);
|
|
||||||
|
|
||||||
if let Some(name) = name {
|
|
||||||
PrevSibChange::Sib(name)
|
|
||||||
} else {
|
|
||||||
PrevSibChange::Parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Either::Right(value) => {
|
|
||||||
template.push_str("<!>");
|
|
||||||
navigations.push(location);
|
|
||||||
|
|
||||||
expressions.push(quote! {
|
|
||||||
::leptos::leptos_dom::mount_child(#mount_kind, &::leptos::IntoView::into_view(#[allow(unused_braces)] {#value}));
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(name) = name {
|
|
||||||
PrevSibChange::Sib(name)
|
|
||||||
} else {
|
|
||||||
PrevSibChange::Parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn child_ident(el_id: usize, span: Span) -> Ident {
|
|
||||||
let id = format!("_el{el_id}");
|
|
||||||
Ident::new(&id, span)
|
|
||||||
}
|
|
|
@ -1,13 +1,7 @@
|
||||||
#[cfg(debug_assertions)]
|
use super::{fragment_to_tokens, TagType};
|
||||||
use super::ident_from_tag_name;
|
|
||||||
use super::{
|
|
||||||
client_builder::{fragment_to_tokens, TagType},
|
|
||||||
event_from_attribute_node,
|
|
||||||
};
|
|
||||||
use crate::view::directive_call_from_attribute_node;
|
|
||||||
use proc_macro2::{Ident, TokenStream, TokenTree};
|
use proc_macro2::{Ident, TokenStream, TokenTree};
|
||||||
use quote::{format_ident, quote, quote_spanned};
|
use quote::{format_ident, quote, quote_spanned};
|
||||||
use rstml::node::{NodeAttribute, NodeElement};
|
use rstml::node::{NodeAttribute, NodeElement, NodeName};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
|
|
||||||
|
@ -97,7 +91,8 @@ pub(crate) fn component_to_tokens(
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let events = attrs
|
// TODO events and directives
|
||||||
|
/* let events = attrs
|
||||||
.clone()
|
.clone()
|
||||||
.filter(|attr| attr.key.to_string().starts_with("on:"))
|
.filter(|attr| attr.key.to_string().starts_with("on:"))
|
||||||
.map(|attr| {
|
.map(|attr| {
|
||||||
|
@ -120,7 +115,7 @@ pub(crate) fn component_to_tokens(
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let events_and_directives =
|
let events_and_directives =
|
||||||
events.into_iter().chain(directives).collect::<Vec<_>>();
|
events.into_iter().chain(directives).collect::<Vec<_>>(); */
|
||||||
|
|
||||||
let dyn_attrs = attrs
|
let dyn_attrs = attrs
|
||||||
.filter(|attr| attr.key.to_string().starts_with("attr:"))
|
.filter(|attr| attr.key.to_string().starts_with("attr:"))
|
||||||
|
@ -130,7 +125,7 @@ pub(crate) fn component_to_tokens(
|
||||||
let value = attr.value().map(|v| {
|
let value = attr.value().map(|v| {
|
||||||
quote! { #v }
|
quote! { #v }
|
||||||
})?;
|
})?;
|
||||||
Some(quote! { (#name, ::leptos::IntoAttribute::into_attribute(#value)) })
|
Some(quote! { (#name, #value.into_attribute()) })
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
@ -146,7 +141,6 @@ pub(crate) fn component_to_tokens(
|
||||||
} else {
|
} else {
|
||||||
let children = fragment_to_tokens(
|
let children = fragment_to_tokens(
|
||||||
&node.children,
|
&node.children,
|
||||||
true,
|
|
||||||
TagType::Unknown,
|
TagType::Unknown,
|
||||||
Some(&mut slots),
|
Some(&mut slots),
|
||||||
global_class,
|
global_class,
|
||||||
|
@ -178,7 +172,7 @@ pub(crate) fn component_to_tokens(
|
||||||
.children({
|
.children({
|
||||||
#(#clonables)*
|
#(#clonables)*
|
||||||
|
|
||||||
move |#(#bindables)*| #children #view_marker
|
move |#(#bindables)*| #children
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -186,7 +180,7 @@ pub(crate) fn component_to_tokens(
|
||||||
.children({
|
.children({
|
||||||
#(#clonables)*
|
#(#clonables)*
|
||||||
|
|
||||||
::leptos::ToChildren::to_children(move || #children #view_marker)
|
::leptos::children::ToChildren::to_children(move || #children)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,7 +224,7 @@ pub(crate) fn component_to_tokens(
|
||||||
};
|
};
|
||||||
|
|
||||||
let component_props_builder = quote_spanned! {name.span()=>
|
let component_props_builder = quote_spanned! {name.span()=>
|
||||||
::leptos::component_props_builder(#name_ref #generics)
|
::leptos::component::component_props_builder(#name_ref #generics)
|
||||||
};
|
};
|
||||||
|
|
||||||
#[allow(unused_mut)] // used in debug
|
#[allow(unused_mut)] // used in debug
|
||||||
|
@ -243,9 +237,15 @@ pub(crate) fn component_to_tokens(
|
||||||
#(#slots)*
|
#(#slots)*
|
||||||
#children
|
#children
|
||||||
#build
|
#build
|
||||||
#dyn_attrs
|
#dyn_attrs;
|
||||||
#(#spread_bindings)*
|
|
||||||
|
#[allow(unreachable_code)]
|
||||||
|
::leptos::component::component_view(
|
||||||
|
#[allow(clippy::needless_borrows_for_generic_args)]
|
||||||
|
#name_ref,
|
||||||
|
props
|
||||||
)
|
)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// (Temporarily?) removed
|
// (Temporarily?) removed
|
||||||
|
@ -253,12 +253,39 @@ pub(crate) fn component_to_tokens(
|
||||||
/* #[cfg(debug_assertions)]
|
/* #[cfg(debug_assertions)]
|
||||||
IdeTagHelper::add_component_completion(&mut component, node); */
|
IdeTagHelper::add_component_completion(&mut component, node); */
|
||||||
|
|
||||||
if events_and_directives.is_empty() {
|
// TODO events and directives
|
||||||
|
/* if events_and_directives.is_empty() {
|
||||||
component
|
component
|
||||||
} else {
|
} else {
|
||||||
quote_spanned! {node.span()=>
|
quote_spanned! {node.span()=>
|
||||||
::leptos::IntoView::into_view(#[allow(unused_braces)] {#component})
|
#component.into_view()
|
||||||
#(#events_and_directives)*
|
#(#events_and_directives)*
|
||||||
}
|
}
|
||||||
|
} */
|
||||||
|
component
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
fn ident_from_tag_name(tag_name: &NodeName) -> Ident {
|
||||||
|
match tag_name {
|
||||||
|
NodeName::Path(path) => path
|
||||||
|
.path
|
||||||
|
.segments
|
||||||
|
.iter()
|
||||||
|
.last()
|
||||||
|
.map(|segment| segment.ident.clone())
|
||||||
|
.expect("element needs to have a name"),
|
||||||
|
NodeName::Block(_) => {
|
||||||
|
let span = tag_name.span();
|
||||||
|
proc_macro_error::emit_error!(
|
||||||
|
span,
|
||||||
|
"blocks not allowed in tag-name position"
|
||||||
|
);
|
||||||
|
Ident::new("", span)
|
||||||
|
}
|
||||||
|
_ => Ident::new(
|
||||||
|
&tag_name.to_string().replace(['-', ':'], "_"),
|
||||||
|
tag_name.span(),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,151 +0,0 @@
|
||||||
use leptos_hot_reload::parsing::is_component_tag_name;
|
|
||||||
use proc_macro2::{Ident, Span, TokenStream};
|
|
||||||
use quote::quote;
|
|
||||||
use rstml::node::{NodeElement, NodeName};
|
|
||||||
use syn::spanned::Spanned;
|
|
||||||
|
|
||||||
/// Helper type to emit semantic info about tags, for IDE.
|
|
||||||
/// Implement `IntoIterator` with `Item="let _ = foo::docs;"`.
|
|
||||||
///
|
|
||||||
/// `IdeTagHelper` uses warning instead of errors everywhere,
|
|
||||||
/// it's aim is to add usability, not introduce additional typecheck in `view`/`template` code.
|
|
||||||
/// On stable `emit_warning` don't produce anything.
|
|
||||||
pub(crate) struct IdeTagHelper(Vec<TokenStream>);
|
|
||||||
|
|
||||||
// TODO: Unhandled cases:
|
|
||||||
// - svg::div, my_elements::foo - tags with custom paths, that doesnt look like component
|
|
||||||
// - my_component::Foo - components with custom paths
|
|
||||||
// - html:div - tags punctuated by `:`
|
|
||||||
// - {div}, {"div"} - any rust expression
|
|
||||||
impl IdeTagHelper {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self(Vec::new())
|
|
||||||
}
|
|
||||||
/// Save stmts for tag name.
|
|
||||||
/// Emit warning if tag is component.
|
|
||||||
pub fn save_tag_completion(&mut self, name: &NodeName) {
|
|
||||||
if is_component_tag_name(name) {
|
|
||||||
proc_macro_error::emit_warning!(
|
|
||||||
name.span(),
|
|
||||||
"BUG: Component tag is used in regular tag completion."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for path in Self::completion_stmts(name) {
|
|
||||||
self.0.push(quote! {
|
|
||||||
let _ = #path;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Save stmts for open and close tags.
|
|
||||||
/// Emit warning if tag is component.
|
|
||||||
pub fn save_element_completion(&mut self, node: &NodeElement) {
|
|
||||||
self.save_tag_completion(node.name());
|
|
||||||
if let Some(close_tag) = node.close_tag.as_ref().map(|c| &c.name) {
|
|
||||||
self.save_tag_completion(close_tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This has been (temporarily?) removed.
|
|
||||||
* Its purpose was simply to add syntax highlighting and IDE hints for
|
|
||||||
* component closing tags in debug mode by associating the closing tag
|
|
||||||
* ident with the component function.
|
|
||||||
*
|
|
||||||
* Doing this in a way that correctly inferred types, however, required
|
|
||||||
* duplicating the entire component constructor.
|
|
||||||
*
|
|
||||||
* In view trees with many nested components, this led to a massive explosion
|
|
||||||
* in compile times.
|
|
||||||
*
|
|
||||||
* See https://github.com/leptos-rs/leptos/issues/1283
|
|
||||||
*
|
|
||||||
/// Add completion to the closing tag of the component.
|
|
||||||
///
|
|
||||||
/// In order to ensure that generics are passed through correctly in the
|
|
||||||
/// current builder pattern, this clones the whole component constructor,
|
|
||||||
/// but it will never be used.
|
|
||||||
///
|
|
||||||
/// ```no_build
|
|
||||||
/// if false {
|
|
||||||
/// close_tag(unreachable!())
|
|
||||||
/// }
|
|
||||||
/// else {
|
|
||||||
/// open_tag(open_tag.props().slots().children().build())
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
pub fn add_component_completion(
|
|
||||||
|
|
||||||
component: &mut TokenStream,
|
|
||||||
node: &NodeElement,
|
|
||||||
) {
|
|
||||||
// emit ide helper info
|
|
||||||
if let Some(close_tag) = node.close_tag.as_ref().map(|c| &c.name) {
|
|
||||||
*component = quote! {
|
|
||||||
{
|
|
||||||
let #close_tag = || #component;
|
|
||||||
#close_tag()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// Returns `syn::Path`-like `TokenStream` to the fn in docs.
|
|
||||||
/// If tag name is `Component` returns `None`.
|
|
||||||
fn create_regular_tag_fn_path(name: &Ident) -> TokenStream {
|
|
||||||
let tag_name = name.to_string();
|
|
||||||
let namespace = if crate::view::is_svg_element(&tag_name) {
|
|
||||||
quote! { ::leptos::leptos_dom::svg }
|
|
||||||
} else if crate::view::is_math_ml_element(&tag_name) {
|
|
||||||
quote! { ::leptos::leptos_dom::math }
|
|
||||||
} else {
|
|
||||||
// todo: check is html, and emit_warning in case of custom tag
|
|
||||||
quote! { ::leptos::leptos_dom::html }
|
|
||||||
};
|
|
||||||
quote! { #namespace::#name }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `syn::Path`-like `TokenStream` to the `custom` section in docs.
|
|
||||||
fn create_custom_tag_fn_path(span: Span) -> TokenStream {
|
|
||||||
let custom_ident = Ident::new("custom", span);
|
|
||||||
quote! { ::leptos::leptos_dom::html::#custom_ident::<::leptos::leptos_dom::html::Custom> }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract from NodeName completion idents.
|
|
||||||
// Custom tags (like foo-bar-baz) is mapped
|
|
||||||
// to vec!["custom", "custom",.. ] for each token in tag, even for "-".
|
|
||||||
// Only last ident from `Path` is used.
|
|
||||||
fn completion_stmts(name: &NodeName) -> Vec<TokenStream> {
|
|
||||||
match name {
|
|
||||||
NodeName::Block(_) => vec![],
|
|
||||||
NodeName::Punctuated(c) => c
|
|
||||||
.pairs()
|
|
||||||
.flat_map(|c| {
|
|
||||||
let mut idents =
|
|
||||||
vec![Self::create_custom_tag_fn_path(c.value().span())];
|
|
||||||
if let Some(p) = c.punct() {
|
|
||||||
idents.push(Self::create_custom_tag_fn_path(p.span()))
|
|
||||||
}
|
|
||||||
idents
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
NodeName::Path(e) => e
|
|
||||||
.path
|
|
||||||
.segments
|
|
||||||
.last()
|
|
||||||
.map(|p| &p.ident)
|
|
||||||
.map(Self::create_regular_tag_fn_path)
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoIterator for IdeTagHelper {
|
|
||||||
type Item = TokenStream;
|
|
||||||
type IntoIter = <Vec<TokenStream> as IntoIterator>::IntoIter;
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
self.0.into_iter()
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,745 +0,0 @@
|
||||||
use super::{
|
|
||||||
camel_case_tag_name,
|
|
||||||
component_builder::component_to_tokens,
|
|
||||||
fancy_class_name, fancy_style_name,
|
|
||||||
ide_helper::IdeTagHelper,
|
|
||||||
is_custom_element, is_math_ml_element, is_self_closing, is_svg_element,
|
|
||||||
parse_event_name,
|
|
||||||
slot_helper::{get_slot, slot_to_tokens},
|
|
||||||
};
|
|
||||||
use crate::attribute_value;
|
|
||||||
use leptos_hot_reload::parsing::{
|
|
||||||
block_to_primitive_expression, is_component_node, value_to_string,
|
|
||||||
};
|
|
||||||
use proc_macro2::{Ident, Span, TokenStream, TokenTree};
|
|
||||||
use quote::{quote, quote_spanned};
|
|
||||||
use rstml::node::{
|
|
||||||
KeyedAttribute, Node, NodeAttribute, NodeBlock, NodeElement,
|
|
||||||
};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use syn::spanned::Spanned;
|
|
||||||
|
|
||||||
pub(crate) enum SsrElementChunks {
|
|
||||||
String {
|
|
||||||
template: String,
|
|
||||||
holes: Vec<TokenStream>,
|
|
||||||
},
|
|
||||||
View(TokenStream),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn root_node_to_tokens_ssr(
|
|
||||||
node: &Node,
|
|
||||||
global_class: Option<&TokenTree>,
|
|
||||||
view_marker: Option<String>,
|
|
||||||
) -> TokenStream {
|
|
||||||
match node {
|
|
||||||
Node::Fragment(fragment) => fragment_to_tokens_ssr(
|
|
||||||
&fragment.children,
|
|
||||||
global_class,
|
|
||||||
view_marker,
|
|
||||||
),
|
|
||||||
Node::Comment(_) | Node::Doctype(_) => quote! {},
|
|
||||||
Node::Text(node) => {
|
|
||||||
quote! {
|
|
||||||
::leptos::leptos_dom::html::text(#node)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Node::RawText(r) => {
|
|
||||||
let text = r.to_string_best();
|
|
||||||
let text = syn::LitStr::new(&text, r.span());
|
|
||||||
quote! {
|
|
||||||
::leptos::leptos_dom::html::text(#text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Node::Block(node) => {
|
|
||||||
quote! {
|
|
||||||
#node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Node::Element(node) => {
|
|
||||||
root_element_to_tokens_ssr(node, global_class, view_marker)
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn fragment_to_tokens_ssr(
|
|
||||||
nodes: &[Node],
|
|
||||||
global_class: Option<&TokenTree>,
|
|
||||||
view_marker: Option<String>,
|
|
||||||
) -> TokenStream {
|
|
||||||
let original_span = nodes
|
|
||||||
.first()
|
|
||||||
.zip(nodes.last())
|
|
||||||
.and_then(|(first, last)| first.span().join(last.span()))
|
|
||||||
.unwrap_or_else(Span::call_site);
|
|
||||||
|
|
||||||
let view_marker = if let Some(marker) = view_marker {
|
|
||||||
quote! { .with_view_marker(#marker) }
|
|
||||||
} else {
|
|
||||||
quote! {}
|
|
||||||
};
|
|
||||||
|
|
||||||
let nodes = nodes.iter().map(|node| {
|
|
||||||
let span = node.span();
|
|
||||||
let node = root_node_to_tokens_ssr(node, global_class, None);
|
|
||||||
let node = quote_spanned!(span=> { #node });
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
::leptos::IntoView::into_view(#[allow(unused_braces)] #node)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
quote_spanned! {original_span=>
|
|
||||||
{
|
|
||||||
::leptos::Fragment::lazy(|| ::std::vec![
|
|
||||||
#(#nodes),*
|
|
||||||
])
|
|
||||||
#view_marker
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn root_element_to_tokens_ssr(
|
|
||||||
node: &NodeElement,
|
|
||||||
global_class: Option<&TokenTree>,
|
|
||||||
view_marker: Option<String>,
|
|
||||||
) -> Option<TokenStream> {
|
|
||||||
// TODO: simplify, this is checked twice, second time in `element_to_tokens_ssr` body
|
|
||||||
if is_component_node(node) {
|
|
||||||
if let Some(slot) = get_slot(node) {
|
|
||||||
slot_to_tokens(node, slot, None, global_class);
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(component_to_tokens(node, global_class))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut stmts_for_ide = IdeTagHelper::new();
|
|
||||||
let mut exprs_for_compiler = Vec::<TokenStream>::new();
|
|
||||||
|
|
||||||
let mut template = String::new();
|
|
||||||
let mut holes = Vec::new();
|
|
||||||
let mut chunks = Vec::new();
|
|
||||||
element_to_tokens_ssr(
|
|
||||||
node,
|
|
||||||
None,
|
|
||||||
&mut template,
|
|
||||||
&mut holes,
|
|
||||||
&mut chunks,
|
|
||||||
&mut stmts_for_ide,
|
|
||||||
&mut exprs_for_compiler,
|
|
||||||
true,
|
|
||||||
global_class,
|
|
||||||
);
|
|
||||||
|
|
||||||
// push final chunk
|
|
||||||
if !template.is_empty() {
|
|
||||||
chunks.push(SsrElementChunks::String { template, holes })
|
|
||||||
}
|
|
||||||
|
|
||||||
let chunks = chunks.into_iter().map(|chunk| match chunk {
|
|
||||||
SsrElementChunks::String { template, holes } => {
|
|
||||||
if holes.is_empty() {
|
|
||||||
let template = template.replace("\\{", "{").replace("\\}", "}");
|
|
||||||
quote! {
|
|
||||||
::leptos::leptos_dom::html::StringOrView::String(#template.into())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let template = template.replace("\\{", "{{").replace("\\}", "}}");
|
|
||||||
quote! {
|
|
||||||
::leptos::leptos_dom::html::StringOrView::String(
|
|
||||||
::std::format!(
|
|
||||||
#template,
|
|
||||||
#(#holes),*
|
|
||||||
).into()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SsrElementChunks::View(view) => {
|
|
||||||
quote! {
|
|
||||||
#[allow(unused_braces)]
|
|
||||||
{
|
|
||||||
let view = #view;
|
|
||||||
::leptos::leptos_dom::html::StringOrView::View(::std::rc::Rc::new(move || view.clone()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let tag_name = node.name().to_string();
|
|
||||||
let is_custom_element = is_custom_element(&tag_name);
|
|
||||||
|
|
||||||
// Use any other span instead of node.name.span(), to avoid misunderstanding in IDE.
|
|
||||||
// We can use open_tag.span(), to provide similar (to name span) diagnostic
|
|
||||||
// in case of expansion error, but it will also highlight "<" token.
|
|
||||||
let typed_element_name = if is_custom_element {
|
|
||||||
Ident::new("Custom", Span::call_site())
|
|
||||||
} else {
|
|
||||||
let camel_cased = camel_case_tag_name(
|
|
||||||
tag_name
|
|
||||||
.trim_start_matches("svg::")
|
|
||||||
.trim_start_matches("math::")
|
|
||||||
.trim_end_matches('_'),
|
|
||||||
);
|
|
||||||
Ident::new(&camel_cased, Span::call_site())
|
|
||||||
};
|
|
||||||
let typed_element_name = if is_svg_element(&tag_name) {
|
|
||||||
quote! { svg::#typed_element_name }
|
|
||||||
} else if is_math_ml_element(&tag_name) {
|
|
||||||
quote! { math::#typed_element_name }
|
|
||||||
} else {
|
|
||||||
quote! { html::#typed_element_name }
|
|
||||||
};
|
|
||||||
let full_name = if is_custom_element {
|
|
||||||
quote! {
|
|
||||||
::leptos::leptos_dom::html::Custom::new(#tag_name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
<::leptos::leptos_dom::#typed_element_name as ::std::default::Default>::default()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let view_marker = if let Some(marker) = view_marker {
|
|
||||||
quote! { .with_view_marker(#marker) }
|
|
||||||
} else {
|
|
||||||
quote! {}
|
|
||||||
};
|
|
||||||
let stmts_for_ide = stmts_for_ide.into_iter();
|
|
||||||
Some(quote! {
|
|
||||||
#[allow(unused_braces)]
|
|
||||||
{
|
|
||||||
#(#stmts_for_ide)*
|
|
||||||
#(#exprs_for_compiler)*
|
|
||||||
::leptos::HtmlElement::from_chunks(#full_name, [#(#chunks),*])#view_marker
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn element_to_tokens_ssr(
|
|
||||||
node: &NodeElement,
|
|
||||||
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
|
||||||
template: &mut String,
|
|
||||||
holes: &mut Vec<TokenStream>,
|
|
||||||
chunks: &mut Vec<SsrElementChunks>,
|
|
||||||
stmts_for_ide: &mut IdeTagHelper,
|
|
||||||
exprs_for_compiler: &mut Vec<TokenStream>,
|
|
||||||
is_root: bool,
|
|
||||||
global_class: Option<&TokenTree>,
|
|
||||||
) {
|
|
||||||
if is_component_node(node) {
|
|
||||||
if let Some(slot) = get_slot(node) {
|
|
||||||
slot_to_tokens(node, slot, parent_slots, global_class);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let component = component_to_tokens(node, global_class);
|
|
||||||
|
|
||||||
if !template.is_empty() {
|
|
||||||
chunks.push(SsrElementChunks::String {
|
|
||||||
template: std::mem::take(template),
|
|
||||||
holes: std::mem::take(holes),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
chunks.push(SsrElementChunks::View(quote! {
|
|
||||||
::leptos::IntoView::into_view(#[allow(unused_braces)] {#component})
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
let tag_name = node.name().to_string();
|
|
||||||
let tag_name = tag_name
|
|
||||||
.trim_start_matches("svg::")
|
|
||||||
.trim_start_matches("math::")
|
|
||||||
.trim_end_matches('_');
|
|
||||||
let is_script_or_style = tag_name == "script" || tag_name == "style";
|
|
||||||
template.push('<');
|
|
||||||
template.push_str(tag_name);
|
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
stmts_for_ide.save_element_completion(node);
|
|
||||||
|
|
||||||
let mut inner_html = None;
|
|
||||||
|
|
||||||
for attr in node.attributes() {
|
|
||||||
if let NodeAttribute::Attribute(attr) = attr {
|
|
||||||
inner_html = attribute_to_tokens_ssr(
|
|
||||||
attr,
|
|
||||||
template,
|
|
||||||
holes,
|
|
||||||
exprs_for_compiler,
|
|
||||||
global_class,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for attr in node.attributes() {
|
|
||||||
use syn::{Expr, ExprRange, RangeLimits, Stmt};
|
|
||||||
|
|
||||||
if let NodeAttribute::Block(NodeBlock::ValidBlock(block)) = attr {
|
|
||||||
if let Some(Stmt::Expr(
|
|
||||||
Expr::Range(ExprRange {
|
|
||||||
start: None,
|
|
||||||
limits: RangeLimits::HalfOpen(_),
|
|
||||||
end: Some(end),
|
|
||||||
..
|
|
||||||
}),
|
|
||||||
_,
|
|
||||||
)) = block.stmts.first()
|
|
||||||
{
|
|
||||||
// should basically be the resolved attributes, joined on spaces, placed into
|
|
||||||
// the template
|
|
||||||
template.push_str(" {}");
|
|
||||||
let end_into_iter =
|
|
||||||
quote_spanned!(end.span()=> {#end}.into_iter());
|
|
||||||
holes.push(quote_spanned! {block.span()=>
|
|
||||||
#end_into_iter.filter_map(|(name, attr)| {
|
|
||||||
Some(::std::format!(
|
|
||||||
"{}=\"{}\"",
|
|
||||||
name,
|
|
||||||
::leptos::leptos_dom::ssr::escape_attr(&attr.as_nameless_value_string()?)
|
|
||||||
))
|
|
||||||
}).collect::<::std::vec::Vec<_>>().join(" ")
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert hydration ID
|
|
||||||
let hydration_id = if is_root {
|
|
||||||
quote! { ::leptos::leptos_dom::HydrationCtx::peek() }
|
|
||||||
} else {
|
|
||||||
quote! { ::leptos::leptos_dom::HydrationCtx::id() }
|
|
||||||
};
|
|
||||||
template.push_str("{}");
|
|
||||||
holes.push(quote! {
|
|
||||||
#hydration_id.map(|id| ::std::format!(" data-hk=\"{id}\"")).unwrap_or_default()
|
|
||||||
});
|
|
||||||
|
|
||||||
set_class_attribute_ssr(node, template, holes, global_class);
|
|
||||||
set_style_attribute_ssr(node, template, holes);
|
|
||||||
|
|
||||||
if is_self_closing(node) {
|
|
||||||
template.push_str("/>");
|
|
||||||
} else {
|
|
||||||
template.push('>');
|
|
||||||
|
|
||||||
if let Some(inner_html) = inner_html {
|
|
||||||
template.push_str("{}");
|
|
||||||
let value = inner_html;
|
|
||||||
|
|
||||||
holes.push(quote! {
|
|
||||||
::leptos::IntoAttribute::into_attribute(#value).as_nameless_value_string().unwrap_or_default()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
for child in &node.children {
|
|
||||||
match child {
|
|
||||||
Node::Element(child) => {
|
|
||||||
element_to_tokens_ssr(
|
|
||||||
child,
|
|
||||||
None,
|
|
||||||
template,
|
|
||||||
holes,
|
|
||||||
chunks,
|
|
||||||
stmts_for_ide,
|
|
||||||
exprs_for_compiler,
|
|
||||||
false,
|
|
||||||
global_class,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Node::Text(text) => {
|
|
||||||
let value = text.value_string();
|
|
||||||
let value = if is_script_or_style {
|
|
||||||
value.into()
|
|
||||||
} else {
|
|
||||||
html_escape::encode_safe(&value)
|
|
||||||
};
|
|
||||||
template.push_str(
|
|
||||||
&value.replace('{', "\\{").replace('}', "\\}"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Node::RawText(r) => {
|
|
||||||
let value = r.to_string_best();
|
|
||||||
let value = if is_script_or_style {
|
|
||||||
value.into()
|
|
||||||
} else {
|
|
||||||
html_escape::encode_safe(&value)
|
|
||||||
};
|
|
||||||
template.push_str(
|
|
||||||
&value.replace('{', "\\{").replace('}', "\\}"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Node::Block(NodeBlock::ValidBlock(block)) => {
|
|
||||||
if let Some(value) =
|
|
||||||
block_to_primitive_expression(block)
|
|
||||||
.and_then(value_to_string)
|
|
||||||
{
|
|
||||||
template.push_str(&value);
|
|
||||||
} else {
|
|
||||||
if !template.is_empty() {
|
|
||||||
chunks.push(SsrElementChunks::String {
|
|
||||||
template: std::mem::take(template),
|
|
||||||
holes: std::mem::take(holes),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
chunks.push(SsrElementChunks::View(quote! {
|
|
||||||
::leptos::IntoView::into_view(#block)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Keep invalid blocks for faster IDE diff (on user type)
|
|
||||||
Node::Block(block @ NodeBlock::Invalid { .. }) => {
|
|
||||||
chunks.push(SsrElementChunks::View(quote! {
|
|
||||||
::leptos::IntoView::into_view(#block)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
Node::Fragment(_) => abort!(
|
|
||||||
Span::call_site(),
|
|
||||||
"You can't nest a fragment inside an element."
|
|
||||||
),
|
|
||||||
Node::Comment(_) | Node::Doctype(_) => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template.push_str("</");
|
|
||||||
template.push_str(tag_name);
|
|
||||||
template.push('>');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns `inner_html`
|
|
||||||
fn attribute_to_tokens_ssr<'a>(
|
|
||||||
attr: &'a KeyedAttribute,
|
|
||||||
template: &mut String,
|
|
||||||
holes: &mut Vec<TokenStream>,
|
|
||||||
exprs_for_compiler: &mut Vec<TokenStream>,
|
|
||||||
global_class: Option<&TokenTree>,
|
|
||||||
) -> Option<&'a syn::Expr> {
|
|
||||||
let name = attr.key.to_string();
|
|
||||||
if name == "ref" || name == "_ref" || name == "ref_" || name == "node_ref" {
|
|
||||||
// ignore refs on SSR
|
|
||||||
} else if let Some(name) = name.strip_prefix("on:") {
|
|
||||||
let handler = attribute_value(attr);
|
|
||||||
let (event_type, _, _) = parse_event_name(name);
|
|
||||||
|
|
||||||
exprs_for_compiler.push(quote! {
|
|
||||||
::leptos::leptos_dom::helpers::ssr_event_listener(::leptos::ev::#event_type, #handler);
|
|
||||||
})
|
|
||||||
} else if name.strip_prefix("prop:").is_some()
|
|
||||||
|| name.strip_prefix("class:").is_some()
|
|
||||||
|| name.strip_prefix("style:").is_some()
|
|
||||||
{
|
|
||||||
// ignore props for SSR
|
|
||||||
// ignore classes and sdtyles: we'll handle these separately
|
|
||||||
if name.starts_with("prop:") {
|
|
||||||
let value = attr.value();
|
|
||||||
exprs_for_compiler.push(quote! {
|
|
||||||
#[allow(unused_braces)]
|
|
||||||
{ _ = #value; }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if let Some(directive_name) = name.strip_prefix("use:") {
|
|
||||||
let handler = syn::Ident::new(directive_name, attr.key.span());
|
|
||||||
let value = attr.value();
|
|
||||||
let value = value.map(|value| {
|
|
||||||
quote! {
|
|
||||||
_ = #value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
exprs_for_compiler.push(quote! {
|
|
||||||
#[allow(unused_braces)]
|
|
||||||
{
|
|
||||||
_ = #handler;
|
|
||||||
#value
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if name == "inner_html" {
|
|
||||||
return attr.value();
|
|
||||||
} else {
|
|
||||||
let name = name.replacen("attr:", "", 1);
|
|
||||||
|
|
||||||
// special case of global_class and class attribute
|
|
||||||
if name == "class"
|
|
||||||
&& global_class.is_some()
|
|
||||||
&& attr.value().and_then(value_to_string).is_none()
|
|
||||||
{
|
|
||||||
let span = attr.key.span();
|
|
||||||
proc_macro_error::emit_error!(span, "Combining a global class (view! { class = ... }) \
|
|
||||||
and a dynamic `class=` attribute on an element causes runtime inconsistencies. You can \
|
|
||||||
toggle individual classes dynamically with the `class:name=value` syntax. \n\nSee this issue \
|
|
||||||
for more information and an example: https://github.com/leptos-rs/leptos/issues/773")
|
|
||||||
};
|
|
||||||
|
|
||||||
if name != "class" && name != "style" {
|
|
||||||
template.push(' ');
|
|
||||||
|
|
||||||
if let Some(value) = attr.value() {
|
|
||||||
if let Some(value) = value_to_string(value) {
|
|
||||||
template.push_str(&name);
|
|
||||||
template.push_str("=\"");
|
|
||||||
template.push_str(&html_escape::encode_quoted_attribute(
|
|
||||||
&value,
|
|
||||||
));
|
|
||||||
template.push('"');
|
|
||||||
} else {
|
|
||||||
template.push_str("{}");
|
|
||||||
holes.push(quote! {
|
|
||||||
&::leptos::IntoAttribute::into_attribute(#value)
|
|
||||||
.as_nameless_value_string()
|
|
||||||
.map(|a| ::std::format!(
|
|
||||||
"{}=\"{}\"",
|
|
||||||
#name,
|
|
||||||
::leptos::leptos_dom::ssr::escape_attr(&a)
|
|
||||||
))
|
|
||||||
.unwrap_or_default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
template.push_str(&name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_class_attribute_ssr(
|
|
||||||
node: &NodeElement,
|
|
||||||
template: &mut String,
|
|
||||||
holes: &mut Vec<TokenStream>,
|
|
||||||
global_class: Option<&TokenTree>,
|
|
||||||
) {
|
|
||||||
let (static_global_class, dyn_global_class) = match global_class {
|
|
||||||
Some(TokenTree::Literal(lit)) => {
|
|
||||||
let str = lit.to_string();
|
|
||||||
// A lit here can be a string, byte_string, char, byte_char, int or float.
|
|
||||||
// If it's a string we remove the quotes so folks can use them directly
|
|
||||||
// without needing braces. E.g. view!{class="my-class", ... }
|
|
||||||
let str = if str.starts_with('"') && str.ends_with('"') {
|
|
||||||
str[1..str.len() - 1].to_string()
|
|
||||||
} else {
|
|
||||||
str
|
|
||||||
};
|
|
||||||
(str, None)
|
|
||||||
}
|
|
||||||
None => (String::new(), None),
|
|
||||||
Some(val) => (String::new(), Some(val)),
|
|
||||||
};
|
|
||||||
let static_class_attr = node
|
|
||||||
.attributes()
|
|
||||||
.iter()
|
|
||||||
.filter_map(|a| match a {
|
|
||||||
NodeAttribute::Attribute(attr)
|
|
||||||
if attr.key.to_string() == "class" =>
|
|
||||||
{
|
|
||||||
attr.value().and_then(value_to_string)
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.chain(Some(static_global_class))
|
|
||||||
.filter(|s| !s.is_empty())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(" ");
|
|
||||||
|
|
||||||
let dyn_class_attr = node
|
|
||||||
.attributes()
|
|
||||||
.iter()
|
|
||||||
.filter_map(|a| {
|
|
||||||
if let NodeAttribute::Attribute(a) = a {
|
|
||||||
if a.key.to_string() == "class" {
|
|
||||||
if a.value().and_then(value_to_string).is_some()
|
|
||||||
|| fancy_class_name(&a.key.to_string(), a).is_some()
|
|
||||||
{
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some((a.key.span(), a.value()))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let class_attrs = node
|
|
||||||
.attributes()
|
|
||||||
.iter()
|
|
||||||
.filter_map(|node| {
|
|
||||||
if let NodeAttribute::Attribute(node) = node {
|
|
||||||
let name = node.key.to_string();
|
|
||||||
if name == "class" {
|
|
||||||
return if let Some((_, name, value)) =
|
|
||||||
fancy_class_name(&name, node)
|
|
||||||
{
|
|
||||||
let span = node.key.span();
|
|
||||||
Some((span, name, value))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if name.starts_with("class:") || name.starts_with("class-") {
|
|
||||||
let name = if name.starts_with("class:") {
|
|
||||||
name.replacen("class:", "", 1)
|
|
||||||
} else if name.starts_with("class-") {
|
|
||||||
name.replacen("class-", "", 1)
|
|
||||||
} else {
|
|
||||||
name
|
|
||||||
};
|
|
||||||
let value = attribute_value(node);
|
|
||||||
let span = node.key.span();
|
|
||||||
Some((span, name, value))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
if !static_class_attr.is_empty()
|
|
||||||
|| !dyn_class_attr.is_empty()
|
|
||||||
|| !class_attrs.is_empty()
|
|
||||||
|| dyn_global_class.is_some()
|
|
||||||
{
|
|
||||||
template.push_str(" class=\"");
|
|
||||||
|
|
||||||
template.push_str(&html_escape::encode_quoted_attribute(
|
|
||||||
&static_class_attr,
|
|
||||||
));
|
|
||||||
|
|
||||||
for (_span, value) in dyn_class_attr {
|
|
||||||
if let Some(value) = value {
|
|
||||||
template.push_str(" {}");
|
|
||||||
holes.push(quote! {
|
|
||||||
&::leptos::IntoAttribute::into_attribute(#value).as_nameless_value_string()
|
|
||||||
.map(|a| ::leptos::leptos_dom::ssr::escape_attr(&a).to_string())
|
|
||||||
.unwrap_or_default()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (_span, name, value) in &class_attrs {
|
|
||||||
template.push_str(" {}");
|
|
||||||
holes.push(quote! {
|
|
||||||
::leptos::IntoClass::into_class(#value).as_value_string(#name)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(dyn_global_class) = dyn_global_class {
|
|
||||||
template.push_str(" {}");
|
|
||||||
holes.push(quote! { #dyn_global_class });
|
|
||||||
}
|
|
||||||
|
|
||||||
template.push('"');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_style_attribute_ssr(
|
|
||||||
node: &NodeElement,
|
|
||||||
template: &mut String,
|
|
||||||
holes: &mut Vec<TokenStream>,
|
|
||||||
) {
|
|
||||||
let static_style_attr = node
|
|
||||||
.attributes()
|
|
||||||
.iter()
|
|
||||||
.find_map(|a| match a {
|
|
||||||
NodeAttribute::Attribute(attr)
|
|
||||||
if attr.key.to_string() == "style" =>
|
|
||||||
{
|
|
||||||
attr.value().and_then(value_to_string)
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.map(|style| format!("{style};"));
|
|
||||||
|
|
||||||
let dyn_style_attr = node
|
|
||||||
.attributes()
|
|
||||||
.iter()
|
|
||||||
.filter_map(|a| {
|
|
||||||
if let NodeAttribute::Attribute(a) = a {
|
|
||||||
if a.key.to_string() == "style" {
|
|
||||||
if a.value().and_then(value_to_string).is_some()
|
|
||||||
|| fancy_style_name(&a.key.to_string(), a).is_some()
|
|
||||||
{
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some((a.key.span(), a.value()))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let style_attrs = node
|
|
||||||
.attributes()
|
|
||||||
.iter()
|
|
||||||
.filter_map(|node| {
|
|
||||||
if let NodeAttribute::Attribute(node) = node {
|
|
||||||
let name = node.key.to_string();
|
|
||||||
if name == "style" {
|
|
||||||
return if let Some((_, name, value)) =
|
|
||||||
fancy_style_name(&name, node)
|
|
||||||
{
|
|
||||||
let span = node.key.span();
|
|
||||||
Some((span, name, value))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if name.starts_with("style:") || name.starts_with("style-") {
|
|
||||||
let name = if name.starts_with("style:") {
|
|
||||||
name.replacen("style:", "", 1)
|
|
||||||
} else if name.starts_with("style-") {
|
|
||||||
name.replacen("style-", "", 1)
|
|
||||||
} else {
|
|
||||||
name
|
|
||||||
};
|
|
||||||
let value = attribute_value(node);
|
|
||||||
let span = node.key.span();
|
|
||||||
Some((span, name, value))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
if static_style_attr.is_some()
|
|
||||||
|| !dyn_style_attr.is_empty()
|
|
||||||
|| !style_attrs.is_empty()
|
|
||||||
{
|
|
||||||
template.push_str(" style=\"");
|
|
||||||
|
|
||||||
template.push_str(&static_style_attr.unwrap_or_default());
|
|
||||||
|
|
||||||
for (_span, value) in dyn_style_attr {
|
|
||||||
if let Some(value) = value {
|
|
||||||
template.push_str(" {};");
|
|
||||||
holes.push(quote! {
|
|
||||||
&::leptos::IntoAttribute::into_attribute(#value).as_nameless_value_string()
|
|
||||||
.map(|a| ::leptos::leptos_dom::ssr::escape_attr(&a).to_string())
|
|
||||||
.unwrap_or_default()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (_span, name, value) in &style_attrs {
|
|
||||||
template.push_str(" {}");
|
|
||||||
holes.push(quote! {
|
|
||||||
::leptos::IntoStyle::into_style(#value).as_value_string(#name).unwrap_or_default()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
template.push('"');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
---
|
|
||||||
source: leptos_macro/src/view/tests.rs
|
|
||||||
expression: pretty(result)
|
|
||||||
---
|
|
||||||
fn view() {
|
|
||||||
::leptos::component_view(
|
|
||||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
|
||||||
&SimpleCounter,
|
|
||||||
::leptos::component_props_builder(&SimpleCounter)
|
|
||||||
.initial_value(#[allow(unused_braces)] { 0 })
|
|
||||||
.step(#[allow(unused_braces)] { 1 })
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
---
|
|
||||||
source: leptos_macro/src/view/tests.rs
|
|
||||||
expression: pretty(result)
|
|
||||||
---
|
|
||||||
fn view() {
|
|
||||||
::leptos::IntoView::into_view(
|
|
||||||
#[allow(unused_braces)]
|
|
||||||
{
|
|
||||||
::leptos::component_view(
|
|
||||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
|
||||||
&ExternalComponent,
|
|
||||||
::leptos::component_props_builder(&ExternalComponent).build(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.on(
|
|
||||||
::leptos::leptos_dom::ev::undelegated(
|
|
||||||
::leptos::leptos_dom::ev::Custom::new("custom.event.clear"),
|
|
||||||
),
|
|
||||||
move |_: Event| set_value(0),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,399 +0,0 @@
|
||||||
---
|
|
||||||
source: leptos_macro/src/view/tests.rs
|
|
||||||
expression: result
|
|
||||||
---
|
|
||||||
TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: IntoView,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: into_view,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: '#',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Bracket,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: allow,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: unused_braces,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Brace,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: component_view,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: '#',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Bracket,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: allow,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: clippy,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: needless_borrows_for_generic_args,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '&',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: ExternalComponent,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ',',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: component_props_builder,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: '&',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: ExternalComponent,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '.',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: build,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [],
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '.',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: on,
|
|
||||||
span: bytes(29..50),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos_dom,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: ev,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: undelegated,
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos_dom,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: ev,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: Custom,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: new,
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Literal {
|
|
||||||
lit: "custom.event.clear",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ',',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: move,
|
|
||||||
span: bytes(51..55),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '|',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(56..57),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: _,
|
|
||||||
span: bytes(57..58),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(58..59),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: Event,
|
|
||||||
span: bytes(60..65),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '|',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(65..66),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: set_value,
|
|
||||||
span: bytes(67..76),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Literal {
|
|
||||||
lit: 0,
|
|
||||||
span: bytes(77..78),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(76..79),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,113 +0,0 @@
|
||||||
---
|
|
||||||
source: leptos_macro/src/view/tests.rs
|
|
||||||
expression: pretty(result)
|
|
||||||
---
|
|
||||||
fn view() {
|
|
||||||
{
|
|
||||||
thread_local! {
|
|
||||||
static __TEMPLATE : ::leptos::web_sys::HtmlTemplateElement = { let document =
|
|
||||||
::leptos::document(); let el = document.create_element("template").unwrap();
|
|
||||||
el
|
|
||||||
.set_inner_html("<div><button>Clear</button><button>-1</button><span>Value: <!>!</span><button>+1</button></div>");
|
|
||||||
::leptos::wasm_bindgen::JsCast::unchecked_into(el) }
|
|
||||||
}
|
|
||||||
let _ = ::leptos::leptos_dom::html::div;
|
|
||||||
let _ = ::leptos::leptos_dom::html::div;
|
|
||||||
let _ = ::leptos::leptos_dom::html::button;
|
|
||||||
let _ = ::leptos::leptos_dom::html::button;
|
|
||||||
let _ = ::leptos::leptos_dom::html::button;
|
|
||||||
let _ = ::leptos::leptos_dom::html::button;
|
|
||||||
let _ = ::leptos::leptos_dom::html::span;
|
|
||||||
let _ = ::leptos::leptos_dom::html::span;
|
|
||||||
let _ = ::leptos::leptos_dom::html::button;
|
|
||||||
let _ = ::leptos::leptos_dom::html::button;
|
|
||||||
let root = __TEMPLATE
|
|
||||||
.with(|tpl| tpl.content().clone_node_with_deep(true))
|
|
||||||
.unwrap()
|
|
||||||
.first_child()
|
|
||||||
.unwrap();
|
|
||||||
let _el1 = "div";
|
|
||||||
let _el1 = ::leptos::wasm_bindgen::JsCast::unchecked_into::<
|
|
||||||
::leptos::web_sys::Node,
|
|
||||||
>(root.clone());
|
|
||||||
let _el2 = "button";
|
|
||||||
let _el2 = _el1
|
|
||||||
.first_child()
|
|
||||||
.unwrap_or_else(|| ::std::panic!("error: {} => {}", "button", "firstChild"));
|
|
||||||
let _el3 = _el2
|
|
||||||
.first_child()
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
::std::panic!("error : {} => {} ", "{block}", "firstChild")
|
|
||||||
});
|
|
||||||
let _el4 = "button";
|
|
||||||
let _el4 = _el2
|
|
||||||
.next_sibling()
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
::std::panic!("error : {} => {} ", "button", "nextSibling")
|
|
||||||
});
|
|
||||||
let _el5 = _el4
|
|
||||||
.first_child()
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
::std::panic!("error : {} => {} ", "{block}", "firstChild")
|
|
||||||
});
|
|
||||||
let _el6 = "span";
|
|
||||||
let _el6 = _el4
|
|
||||||
.next_sibling()
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
::std::panic!("error : {} => {} ", "span", "nextSibling")
|
|
||||||
});
|
|
||||||
let _el7 = _el6
|
|
||||||
.first_child()
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
::std::panic!("error : {} => {} ", "{block}", "firstChild")
|
|
||||||
});
|
|
||||||
let _el8 = _el7
|
|
||||||
.next_sibling()
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
::std::panic!("error : {} => {} ", "{block}", "nextSibling")
|
|
||||||
});
|
|
||||||
let _el9 = _el8
|
|
||||||
.next_sibling()
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
::std::panic!("error : {} => {} ", "{block}", "nextSibling")
|
|
||||||
});
|
|
||||||
let _el10 = "button";
|
|
||||||
let _el10 = _el6
|
|
||||||
.next_sibling()
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
::std::panic!("error : {} => {} ", "button", "nextSibling")
|
|
||||||
});
|
|
||||||
let _el11 = _el10
|
|
||||||
.first_child()
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
::std::panic!("error : {} => {} ", "{block}", "firstChild")
|
|
||||||
});
|
|
||||||
::leptos::leptos_dom::add_event_helper(
|
|
||||||
::leptos::wasm_bindgen::JsCast::unchecked_ref(&_el2),
|
|
||||||
::leptos::leptos_dom::ev::click,
|
|
||||||
move |_| set_value(0),
|
|
||||||
);
|
|
||||||
::leptos::leptos_dom::add_event_helper(
|
|
||||||
::leptos::wasm_bindgen::JsCast::unchecked_ref(&_el4),
|
|
||||||
::leptos::leptos_dom::ev::click,
|
|
||||||
move |_| set_value.update(|value| *value -= step),
|
|
||||||
);
|
|
||||||
::leptos::leptos_dom::mount_child(
|
|
||||||
::leptos::leptos_dom::MountKind::Before(&_el8.clone()),
|
|
||||||
&::leptos::IntoView::into_view(#[allow(unused_braces)] { { value } }),
|
|
||||||
);
|
|
||||||
::leptos::leptos_dom::add_event_helper(
|
|
||||||
::leptos::wasm_bindgen::JsCast::unchecked_ref(&_el10),
|
|
||||||
::leptos::leptos_dom::ev::click,
|
|
||||||
move |_| set_value.update(|value| *value += step),
|
|
||||||
);
|
|
||||||
::leptos::leptos_dom::View::Element(leptos::leptos_dom::Element {
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
name: "div".into(),
|
|
||||||
element: ::leptos::wasm_bindgen::JsCast::unchecked_into(root),
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
view_marker: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
---
|
|
||||||
source: leptos_macro/src/view/tests.rs
|
|
||||||
expression: pretty(result)
|
|
||||||
---
|
|
||||||
fn view() {
|
|
||||||
::leptos::component_view(
|
|
||||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
|
||||||
&SimpleCounter,
|
|
||||||
::leptos::component_props_builder(&SimpleCounter)
|
|
||||||
.initial_value(#[allow(unused_braces)] { 0 })
|
|
||||||
.step(#[allow(unused_braces)] { 1 })
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
---
|
|
||||||
source: leptos_macro/src/view/tests.rs
|
|
||||||
expression: pretty(result)
|
|
||||||
---
|
|
||||||
fn view() {
|
|
||||||
::leptos::IntoView::into_view(
|
|
||||||
#[allow(unused_braces)]
|
|
||||||
{
|
|
||||||
::leptos::component_view(
|
|
||||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
|
||||||
&ExternalComponent,
|
|
||||||
::leptos::component_props_builder(&ExternalComponent).build(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.on(
|
|
||||||
::leptos::leptos_dom::ev::undelegated(
|
|
||||||
::leptos::leptos_dom::ev::Custom::new("custom.event.clear"),
|
|
||||||
),
|
|
||||||
move |_: Event| set_value(0),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,250 +0,0 @@
|
||||||
---
|
|
||||||
source: leptos_macro/src/view/tests.rs
|
|
||||||
expression: result
|
|
||||||
---
|
|
||||||
TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: component_view,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: '#',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Bracket,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: allow,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: clippy,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: needless_borrows_for_generic_args,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '&',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: SimpleCounter,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ',',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: component_props_builder,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: '&',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: SimpleCounter,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '.',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(37..52),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: initial_value,
|
|
||||||
span: bytes(37..50),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: '#',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(37..52),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Bracket,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: allow,
|
|
||||||
span: bytes(37..52),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: unused_braces,
|
|
||||||
span: bytes(37..52),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(37..52),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(37..52),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Brace,
|
|
||||||
stream: TokenStream [
|
|
||||||
Literal {
|
|
||||||
lit: 0,
|
|
||||||
span: bytes(51..52),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(51..52),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(37..52),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '.',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(65..71),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: step,
|
|
||||||
span: bytes(65..69),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: '#',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(65..71),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Bracket,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: allow,
|
|
||||||
span: bytes(65..71),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: unused_braces,
|
|
||||||
span: bytes(65..71),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(65..71),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(65..71),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Brace,
|
|
||||||
stream: TokenStream [
|
|
||||||
Literal {
|
|
||||||
lit: 1,
|
|
||||||
span: bytes(70..71),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(70..71),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(65..71),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '.',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: build,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [],
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
]
|
|
|
@ -1,399 +0,0 @@
|
||||||
---
|
|
||||||
source: leptos_macro/src/view/tests.rs
|
|
||||||
expression: result
|
|
||||||
---
|
|
||||||
TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: IntoView,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: into_view,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: '#',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Bracket,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: allow,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: unused_braces,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Brace,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: component_view,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: '#',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Bracket,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: allow,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: clippy,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: needless_borrows_for_generic_args,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '&',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: ExternalComponent,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ',',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: component_props_builder,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: '&',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: ExternalComponent,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '.',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: build,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [],
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '.',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: on,
|
|
||||||
span: bytes(29..50),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos_dom,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: ev,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: undelegated,
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos_dom,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: ev,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: Custom,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: new,
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Literal {
|
|
||||||
lit: "custom.event.clear",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ',',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: move,
|
|
||||||
span: bytes(51..55),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '|',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(56..57),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: _,
|
|
||||||
span: bytes(57..58),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(58..59),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: Event,
|
|
||||||
span: bytes(60..65),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '|',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(65..66),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: set_value,
|
|
||||||
span: bytes(67..76),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Literal {
|
|
||||||
lit: 0,
|
|
||||||
span: bytes(77..78),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(76..79),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,56 +0,0 @@
|
||||||
---
|
|
||||||
source: leptos_macro/src/view/tests.rs
|
|
||||||
assertion_line: 101
|
|
||||||
expression: pretty(result)
|
|
||||||
---
|
|
||||||
fn view() {
|
|
||||||
#[allow(unused_braces)]
|
|
||||||
{
|
|
||||||
let _ = ::leptos::leptos_dom::html::div;
|
|
||||||
::leptos::leptos_dom::html::div()
|
|
||||||
.child(
|
|
||||||
#[allow(unused_braces)]
|
|
||||||
{
|
|
||||||
let _ = ::leptos::leptos_dom::html::button;
|
|
||||||
::leptos::leptos_dom::html::button()
|
|
||||||
.on(::leptos::ev::click, move |_| set_value(0))
|
|
||||||
.child("Clear")
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
#[allow(unused_braces)]
|
|
||||||
{
|
|
||||||
let _ = ::leptos::leptos_dom::html::button;
|
|
||||||
::leptos::leptos_dom::html::button()
|
|
||||||
.on(
|
|
||||||
::leptos::ev::click,
|
|
||||||
move |_| set_value.update(|value| *value -= step),
|
|
||||||
)
|
|
||||||
.child("-1")
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
#[allow(unused_braces)]
|
|
||||||
{
|
|
||||||
let _ = ::leptos::leptos_dom::html::span;
|
|
||||||
::leptos::leptos_dom::html::span()
|
|
||||||
.child("Value: ")
|
|
||||||
.child({ value })
|
|
||||||
.child("!")
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
#[allow(unused_braces)]
|
|
||||||
{
|
|
||||||
let _ = ::leptos::leptos_dom::html::button;
|
|
||||||
::leptos::leptos_dom::html::button()
|
|
||||||
.on(
|
|
||||||
::leptos::ev::click,
|
|
||||||
move |_| set_value.update(|value| *value += step),
|
|
||||||
)
|
|
||||||
.child("+1")
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
---
|
|
||||||
source: leptos_macro/src/view/tests.rs
|
|
||||||
expression: pretty(result)
|
|
||||||
---
|
|
||||||
fn view() {
|
|
||||||
::leptos::component_view(
|
|
||||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
|
||||||
&SimpleCounter,
|
|
||||||
::leptos::component_props_builder(&SimpleCounter)
|
|
||||||
.initial_value(#[allow(unused_braces)] { 0 })
|
|
||||||
.step(#[allow(unused_braces)] { 1 })
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
---
|
|
||||||
source: leptos_macro/src/view/tests.rs
|
|
||||||
expression: pretty(result)
|
|
||||||
---
|
|
||||||
fn view() {
|
|
||||||
::leptos::IntoView::into_view(
|
|
||||||
#[allow(unused_braces)]
|
|
||||||
{
|
|
||||||
::leptos::component_view(
|
|
||||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
|
||||||
&ExternalComponent,
|
|
||||||
::leptos::component_props_builder(&ExternalComponent).build(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.on(
|
|
||||||
::leptos::leptos_dom::ev::undelegated(
|
|
||||||
::leptos::leptos_dom::ev::Custom::new("custom.event.clear"),
|
|
||||||
),
|
|
||||||
move |_: Event| set_value(0),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,250 +0,0 @@
|
||||||
---
|
|
||||||
source: leptos_macro/src/view/tests.rs
|
|
||||||
expression: result
|
|
||||||
---
|
|
||||||
TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: component_view,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: '#',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Bracket,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: allow,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: clippy,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: needless_borrows_for_generic_args,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '&',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: SimpleCounter,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ',',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: component_props_builder,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: '&',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: SimpleCounter,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '.',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(37..52),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: initial_value,
|
|
||||||
span: bytes(37..50),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: '#',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(37..52),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Bracket,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: allow,
|
|
||||||
span: bytes(37..52),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: unused_braces,
|
|
||||||
span: bytes(37..52),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(37..52),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(37..52),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Brace,
|
|
||||||
stream: TokenStream [
|
|
||||||
Literal {
|
|
||||||
lit: 0,
|
|
||||||
span: bytes(51..52),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(51..52),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(37..52),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '.',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(65..71),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: step,
|
|
||||||
span: bytes(65..69),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: '#',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(65..71),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Bracket,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: allow,
|
|
||||||
span: bytes(65..71),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: unused_braces,
|
|
||||||
span: bytes(65..71),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(65..71),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(65..71),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Brace,
|
|
||||||
stream: TokenStream [
|
|
||||||
Literal {
|
|
||||||
lit: 1,
|
|
||||||
span: bytes(70..71),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(70..71),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(65..71),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '.',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: build,
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [],
|
|
||||||
span: bytes(11..24),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
]
|
|
|
@ -1,399 +0,0 @@
|
||||||
---
|
|
||||||
source: leptos_macro/src/view/tests.rs
|
|
||||||
expression: result
|
|
||||||
---
|
|
||||||
TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: IntoView,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: into_view,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: '#',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Bracket,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: allow,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: unused_braces,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Brace,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: component_view,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: '#',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Bracket,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: allow,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Ident {
|
|
||||||
sym: clippy,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: needless_borrows_for_generic_args,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '&',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: ExternalComponent,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ',',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: component_props_builder,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: '&',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: ExternalComponent,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '.',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: build,
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [],
|
|
||||||
span: bytes(11..28),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(10..82),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '.',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: on,
|
|
||||||
span: bytes(29..50),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos_dom,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: ev,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: undelegated,
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: leptos_dom,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: ev,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: Custom,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Joint,
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: new,
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Literal {
|
|
||||||
lit: "custom.event.clear",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ',',
|
|
||||||
spacing: Alone,
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: move,
|
|
||||||
span: bytes(51..55),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '|',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(56..57),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: _,
|
|
||||||
span: bytes(57..58),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: ':',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(58..59),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: Event,
|
|
||||||
span: bytes(60..65),
|
|
||||||
},
|
|
||||||
Punct {
|
|
||||||
char: '|',
|
|
||||||
spacing: Alone,
|
|
||||||
span: bytes(65..66),
|
|
||||||
},
|
|
||||||
Ident {
|
|
||||||
sym: set_value,
|
|
||||||
span: bytes(67..76),
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
delimiter: Parenthesis,
|
|
||||||
stream: TokenStream [
|
|
||||||
Literal {
|
|
||||||
lit: 0,
|
|
||||||
span: bytes(77..78),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: bytes(76..79),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,67 +0,0 @@
|
||||||
---
|
|
||||||
source: leptos_macro/src/view/tests.rs
|
|
||||||
assertion_line: 101
|
|
||||||
expression: pretty(result)
|
|
||||||
---
|
|
||||||
fn view() {
|
|
||||||
#[allow(unused_braces)]
|
|
||||||
{
|
|
||||||
let _ = ::leptos::leptos_dom::html::div;
|
|
||||||
let _ = ::leptos::leptos_dom::html::div;
|
|
||||||
let _ = ::leptos::leptos_dom::html::button;
|
|
||||||
let _ = ::leptos::leptos_dom::html::button;
|
|
||||||
let _ = ::leptos::leptos_dom::html::button;
|
|
||||||
let _ = ::leptos::leptos_dom::html::button;
|
|
||||||
let _ = ::leptos::leptos_dom::html::span;
|
|
||||||
let _ = ::leptos::leptos_dom::html::span;
|
|
||||||
let _ = ::leptos::leptos_dom::html::button;
|
|
||||||
let _ = ::leptos::leptos_dom::html::button;
|
|
||||||
::leptos::leptos_dom::helpers::ssr_event_listener(
|
|
||||||
::leptos::ev::click,
|
|
||||||
move |_| set_value(0),
|
|
||||||
);
|
|
||||||
::leptos::leptos_dom::helpers::ssr_event_listener(
|
|
||||||
::leptos::ev::click,
|
|
||||||
move |_| set_value.update(|value| *value -= step),
|
|
||||||
);
|
|
||||||
::leptos::leptos_dom::helpers::ssr_event_listener(
|
|
||||||
::leptos::ev::click,
|
|
||||||
move |_| set_value.update(|value| *value += step),
|
|
||||||
);
|
|
||||||
::leptos::HtmlElement::from_chunks(
|
|
||||||
<::leptos::leptos_dom::html::Div as ::std::default::Default>::default(),
|
|
||||||
[
|
|
||||||
::leptos::leptos_dom::html::StringOrView::String(
|
|
||||||
::std::format!(
|
|
||||||
"<div{}><button{}>Clear</button><button{}>-1</button><span{}>Value: ",
|
|
||||||
::leptos::leptos_dom::HydrationCtx::peek().map(| id |
|
|
||||||
::std::format!(" data-hk=\"{id}\"")).unwrap_or_default(),
|
|
||||||
::leptos::leptos_dom::HydrationCtx::id().map(| id |
|
|
||||||
::std::format!(" data-hk=\"{id}\"")).unwrap_or_default(),
|
|
||||||
::leptos::leptos_dom::HydrationCtx::id().map(| id |
|
|
||||||
::std::format!(" data-hk=\"{id}\"")).unwrap_or_default(),
|
|
||||||
::leptos::leptos_dom::HydrationCtx::id().map(| id |
|
|
||||||
::std::format!(" data-hk=\"{id}\"")).unwrap_or_default()
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
),
|
|
||||||
#[allow(unused_braces)]
|
|
||||||
{
|
|
||||||
let view = ::leptos::IntoView::into_view({ value });
|
|
||||||
::leptos::leptos_dom::html::StringOrView::View(
|
|
||||||
::std::rc::Rc::new(move || view.clone()),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
::leptos::leptos_dom::html::StringOrView::String(
|
|
||||||
::std::format!(
|
|
||||||
"!</span><button{}>+1</button></div>",
|
|
||||||
::leptos::leptos_dom::HydrationCtx::id().map(| id |
|
|
||||||
::std::format!(" data-hk=\"{id}\"")).unwrap_or_default()
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,119 +0,0 @@
|
||||||
use proc_macro2::TokenStream;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use syn::parse_quote;
|
|
||||||
|
|
||||||
fn pretty(input: TokenStream) -> String {
|
|
||||||
let type_item: syn::Item = parse_quote! {
|
|
||||||
fn view(){
|
|
||||||
#input
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let file = syn::File {
|
|
||||||
shebang: None,
|
|
||||||
attrs: vec![],
|
|
||||||
items: vec![type_item],
|
|
||||||
};
|
|
||||||
|
|
||||||
prettyplease::unparse(&file)
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! assert_snapshot
|
|
||||||
{
|
|
||||||
(@assert text $result:ident) => {
|
|
||||||
insta::assert_snapshot!(pretty($result))
|
|
||||||
};
|
|
||||||
(@assert full $result:ident) => {
|
|
||||||
insta::assert_debug_snapshot!($result)
|
|
||||||
};
|
|
||||||
(client_template($assert:ident) => $input: expr) => {
|
|
||||||
{
|
|
||||||
let tokens = TokenStream::from_str($input).unwrap();
|
|
||||||
let nodes = rstml::parse2(tokens).unwrap();
|
|
||||||
let result = crate::view::client_template::render_template(&&nodes);
|
|
||||||
|
|
||||||
assert_snapshot!(@assert $assert result)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
(client_builder($assert:ident) => $input: expr) => {
|
|
||||||
{
|
|
||||||
let tokens = TokenStream::from_str($input).unwrap();
|
|
||||||
let nodes = rstml::parse2(tokens).unwrap();
|
|
||||||
let mode = crate::view::Mode::Client;
|
|
||||||
let global_class = None;
|
|
||||||
let call_site = None;
|
|
||||||
let result = crate::view::render_view(&&nodes, mode, global_class, call_site);
|
|
||||||
|
|
||||||
assert_snapshot!(@assert $assert result)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
(server_template($assert:ident) => $input: expr) => {
|
|
||||||
{
|
|
||||||
let tokens = TokenStream::from_str($input).unwrap();
|
|
||||||
let nodes = rstml::parse2(tokens).unwrap();
|
|
||||||
let mode = crate::view::Mode::Ssr;
|
|
||||||
let global_class = None;
|
|
||||||
let call_site = None;
|
|
||||||
let result = crate::view::render_view(&&nodes, mode, global_class, call_site);
|
|
||||||
|
|
||||||
assert_snapshot!(@assert $assert result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! for_all_modes {
|
|
||||||
(@ $module: ident, $type: ident => $(
|
|
||||||
$test_name:ident => $raw_str:expr
|
|
||||||
),*
|
|
||||||
) => {
|
|
||||||
mod $module {
|
|
||||||
use super::*;
|
|
||||||
$(
|
|
||||||
#[test]
|
|
||||||
fn $test_name() {
|
|
||||||
assert_snapshot!($type(text) => $raw_str)
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
mod full_span {
|
|
||||||
use super::*;
|
|
||||||
$(
|
|
||||||
#[test]
|
|
||||||
fn $test_name() {
|
|
||||||
assert_snapshot!($type(full) => $raw_str)
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
( $(
|
|
||||||
$tts:tt
|
|
||||||
)*
|
|
||||||
) => {
|
|
||||||
for_all_modes!{@ csr, client_builder => $($tts)*}
|
|
||||||
for_all_modes!{@ client_template, client_template => $($tts)*}
|
|
||||||
for_all_modes!{@ ssr, server_template => $($tts)*}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
for_all_modes! {
|
|
||||||
test_simple_counter => r#"
|
|
||||||
<div>
|
|
||||||
<button on:click=move |_| set_value(0)>"Clear"</button>
|
|
||||||
<button on:click=move |_| set_value.update(|value| *value -= step)>"-1"</button>
|
|
||||||
<span>"Value: " {value} "!"</span>
|
|
||||||
<button on:click=move |_| set_value.update(|value| *value += step)>"+1"</button>
|
|
||||||
</div>
|
|
||||||
"#,
|
|
||||||
test_counter_component => r#"
|
|
||||||
<SimpleCounter
|
|
||||||
initial_value=0
|
|
||||||
step=1
|
|
||||||
/>
|
|
||||||
"#,
|
|
||||||
test_custom_event => r#"
|
|
||||||
<ExternalComponent on:custom.event.clear=move |_: Event| set_value(0) />
|
|
||||||
"#
|
|
||||||
}
|
|
100
leptos_reactive/src/children.rs
Normal file
100
leptos_reactive/src/children.rs
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tachydom::{
|
||||||
|
renderer::dom::Dom,
|
||||||
|
view::{
|
||||||
|
any_view::{AnyView, IntoAny},
|
||||||
|
RenderHtml,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The most common type for the `children` property on components,
|
||||||
|
/// which can only be called once.
|
||||||
|
pub type Children = Box<dyn FnOnce() -> AnyView<Dom>>;
|
||||||
|
|
||||||
|
/// A type for the `children` property on components that can be called
|
||||||
|
/// more than once.
|
||||||
|
pub type ChildrenFn = Arc<dyn Fn() -> AnyView<Dom>>;
|
||||||
|
|
||||||
|
/// A type for the `children` property on components that can be called
|
||||||
|
/// more than once, but may mutate the children.
|
||||||
|
pub type ChildrenFnMut = Box<dyn FnMut() -> AnyView<Dom>>;
|
||||||
|
|
||||||
|
// This is to still support components that accept `Box<dyn Fn() -> AnyView>` as a children.
|
||||||
|
type BoxedChildrenFn = Box<dyn Fn() -> AnyView<Dom>>;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub trait ToChildren<F> {
|
||||||
|
fn to_children(f: F) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, C> ToChildren<F> for Children
|
||||||
|
where
|
||||||
|
F: FnOnce() -> C + 'static,
|
||||||
|
C: RenderHtml<Dom> + 'static,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn to_children(f: F) -> Self {
|
||||||
|
Box::new(move || f().into_any())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, C> ToChildren<F> for ChildrenFn
|
||||||
|
where
|
||||||
|
F: Fn() -> C + 'static,
|
||||||
|
C: RenderHtml<Dom> + 'static,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn to_children(f: F) -> Self {
|
||||||
|
Arc::new(move || f().into_any())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, C> ToChildren<F> for ChildrenFnMut
|
||||||
|
where
|
||||||
|
F: Fn() -> C + 'static,
|
||||||
|
C: RenderHtml<Dom> + 'static,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn to_children(f: F) -> Self {
|
||||||
|
Box::new(move || f().into_any())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, C> ToChildren<F> for BoxedChildrenFn
|
||||||
|
where
|
||||||
|
F: Fn() -> C + 'static,
|
||||||
|
C: RenderHtml<Dom> + 'static,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn to_children(f: F) -> Self {
|
||||||
|
Box::new(move || f().into_any())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// New-type wrapper for the a function that returns a view with `From` and `Default` traits implemented
|
||||||
|
/// to enable optional props in for example `<Show>` and `<Suspense>`.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ViewFn(Arc<dyn Fn() -> AnyView<Dom>>);
|
||||||
|
|
||||||
|
impl Default for ViewFn {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(Arc::new(|| ().into_any()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, C> From<F> for ViewFn
|
||||||
|
where
|
||||||
|
F: Fn() -> C + 'static,
|
||||||
|
C: RenderHtml<Dom> + 'static,
|
||||||
|
{
|
||||||
|
fn from(value: F) -> Self {
|
||||||
|
Self(Arc::new(move || value().into_any()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewFn {
|
||||||
|
/// Execute the wrapped function
|
||||||
|
pub fn run(&self) -> AnyView<Dom> {
|
||||||
|
(self.0)()
|
||||||
|
}
|
||||||
|
}
|
83
leptos_reactive/src/component.rs
Normal file
83
leptos_reactive/src/component.rs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
//! Utility traits and functions that allow building components,
|
||||||
|
//! as either functions of their props or functions with no arguments,
|
||||||
|
//! without knowing the name of the props struct.
|
||||||
|
|
||||||
|
pub trait Component<P> {}
|
||||||
|
|
||||||
|
pub trait Props {
|
||||||
|
type Builder;
|
||||||
|
|
||||||
|
fn builder() -> Self::Builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub trait PropsOrNoPropsBuilder {
|
||||||
|
type Builder;
|
||||||
|
|
||||||
|
fn builder_or_not() -> Self::Builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
pub struct EmptyPropsBuilder {}
|
||||||
|
|
||||||
|
impl EmptyPropsBuilder {
|
||||||
|
pub fn build(self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Props> PropsOrNoPropsBuilder for P {
|
||||||
|
type Builder = <P as Props>::Builder;
|
||||||
|
|
||||||
|
fn builder_or_not() -> Self::Builder {
|
||||||
|
Self::builder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PropsOrNoPropsBuilder for EmptyPropsBuilder {
|
||||||
|
type Builder = EmptyPropsBuilder;
|
||||||
|
|
||||||
|
fn builder_or_not() -> Self::Builder {
|
||||||
|
EmptyPropsBuilder {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, R> Component<EmptyPropsBuilder> for F where F: FnOnce() -> R {}
|
||||||
|
|
||||||
|
impl<P, F, R> Component<P> for F
|
||||||
|
where
|
||||||
|
F: FnOnce(P) -> R,
|
||||||
|
P: Props,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn component_props_builder<P: PropsOrNoPropsBuilder>(
|
||||||
|
_f: &impl Component<P>,
|
||||||
|
) -> <P as PropsOrNoPropsBuilder>::Builder {
|
||||||
|
<P as PropsOrNoPropsBuilder>::builder_or_not()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn component_view<P, T>(f: impl ComponentConstructor<P, T>, props: P) -> T {
|
||||||
|
f.construct(props)
|
||||||
|
}
|
||||||
|
pub trait ComponentConstructor<P, T> {
|
||||||
|
fn construct(self, props: P) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Func, T> ComponentConstructor<(), T> for Func
|
||||||
|
where
|
||||||
|
Func: FnOnce() -> T,
|
||||||
|
{
|
||||||
|
fn construct(self, (): ()) -> T {
|
||||||
|
(self)()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Func, T, P> ComponentConstructor<P, T> for Func
|
||||||
|
where
|
||||||
|
Func: FnOnce(P) -> T,
|
||||||
|
P: PropsOrNoPropsBuilder,
|
||||||
|
{
|
||||||
|
fn construct(self, props: P) -> T {
|
||||||
|
(self)(props)
|
||||||
|
}
|
||||||
|
}
|
63
leptos_reactive/src/for_loop.rs
Normal file
63
leptos_reactive/src/for_loop.rs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
use std::{hash::Hash, marker::PhantomData};
|
||||||
|
use tachy_maccy::component;
|
||||||
|
use tachydom::{
|
||||||
|
renderer::Renderer,
|
||||||
|
view::{keyed::keyed, RenderHtml},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn For<Rndr, IF, I, T, EF, N, KF, K>(
|
||||||
|
/// Items over which the component should iterate.
|
||||||
|
each: IF,
|
||||||
|
/// A key function that will be applied to each item.
|
||||||
|
key: KF,
|
||||||
|
/// A function that takes the item, and returns the view that will be displayed for each item.
|
||||||
|
children: EF,
|
||||||
|
#[prop(optional)] _rndr: PhantomData<Rndr>,
|
||||||
|
) -> impl RenderHtml<Rndr>
|
||||||
|
where
|
||||||
|
IF: Fn() -> I + 'static,
|
||||||
|
I: IntoIterator<Item = T>,
|
||||||
|
EF: Fn(T) -> N + Clone + 'static,
|
||||||
|
N: RenderHtml<Rndr> + 'static,
|
||||||
|
KF: Fn(&T) -> K + Clone + 'static,
|
||||||
|
K: Eq + Hash + 'static,
|
||||||
|
T: 'static,
|
||||||
|
Rndr: Renderer + 'static,
|
||||||
|
Rndr::Node: Clone,
|
||||||
|
Rndr::Element: Clone,
|
||||||
|
{
|
||||||
|
move || keyed(each(), key.clone(), children.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::For;
|
||||||
|
use tachy_maccy::view;
|
||||||
|
use tachy_reaccy::{signal::RwSignal, signal_traits::SignalGet};
|
||||||
|
use tachydom::{
|
||||||
|
html::element::HtmlElement, prelude::ElementChild,
|
||||||
|
renderer::mock_dom::MockDom, view::Render,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn creates_list() {
|
||||||
|
let values = RwSignal::new(vec![1, 2, 3, 4, 5]);
|
||||||
|
let list: HtmlElement<_, _, _, MockDom> = view! {
|
||||||
|
<ol>
|
||||||
|
<For
|
||||||
|
each=move || values.get()
|
||||||
|
key=|i| *i
|
||||||
|
let:i
|
||||||
|
>
|
||||||
|
<li>{i}</li>
|
||||||
|
</For>
|
||||||
|
</ol>
|
||||||
|
};
|
||||||
|
let list = list.build();
|
||||||
|
assert_eq!(
|
||||||
|
list.el.to_debug_html(),
|
||||||
|
"<ol><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ol>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
(function (pkg_path, output_name, wasm_output_name) {
|
||||||
|
import(`/${pkg_path}/${output_name}.js`)
|
||||||
|
.then(mod => {
|
||||||
|
mod.default(`/${pkg_path}/${wasm_output_name}.wasm`).then(() => {
|
||||||
|
mod.hydrate();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
26
leptos_reactive/src/hydration_scripts/island_script.js
Normal file
26
leptos_reactive/src/hydration_scripts/island_script.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
(function (pkg_path, output_name, wasm_output_name) {
|
||||||
|
function idle(c) {
|
||||||
|
if ("requestIdleCallback" in window) {
|
||||||
|
window.requestIdleCallback(c);
|
||||||
|
} else {
|
||||||
|
c();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
idle(() => {
|
||||||
|
import(`/${pkg_path}/${output_name}.js`)
|
||||||
|
.then(mod => {
|
||||||
|
mod.default(`/${pkg_path}/${wasm_output_name}.wasm`).then(() => {
|
||||||
|
mod.hydrate();
|
||||||
|
for (let e of document.querySelectorAll("leptos-island")) {
|
||||||
|
const l = e.dataset.component;
|
||||||
|
const islandFn = mod["_island_" + l];
|
||||||
|
if (islandFn) {
|
||||||
|
islandFn(e);
|
||||||
|
} else {
|
||||||
|
console.warn(`Could not find WASM function for the island ${l}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
})
|
57
leptos_reactive/src/hydration_scripts/mod.rs
Normal file
57
leptos_reactive/src/hydration_scripts/mod.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
#![allow(clippy::needless_lifetimes)]
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use leptos_config::LeptosOptions;
|
||||||
|
use tachydom::view::RenderHtml;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn AutoReload<'a>(
|
||||||
|
#[prop(optional)] disable_watch: bool,
|
||||||
|
#[prop(optional)] nonce: Option<&'a str>,
|
||||||
|
options: LeptosOptions,
|
||||||
|
) -> impl RenderHtml<Dom> + 'a {
|
||||||
|
(!disable_watch && std::env::var("LEPTOS_WATCH").is_ok()).then(|| {
|
||||||
|
let reload_port = match options.reload_external_port {
|
||||||
|
Some(val) => val,
|
||||||
|
None => options.reload_port,
|
||||||
|
};
|
||||||
|
let protocol = match options.reload_ws_protocol {
|
||||||
|
leptos_config::ReloadWSProtocol::WS => "'ws://'",
|
||||||
|
leptos_config::ReloadWSProtocol::WSS => "'wss://'",
|
||||||
|
};
|
||||||
|
|
||||||
|
let script = include_str!("reload_script.js");
|
||||||
|
view! {
|
||||||
|
<script crossorigin=nonce>
|
||||||
|
{format!("{script}({reload_port:?}, {protocol})")}
|
||||||
|
</script>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn HydrationScripts(
|
||||||
|
options: LeptosOptions,
|
||||||
|
#[prop(optional)] islands: bool,
|
||||||
|
) -> impl RenderHtml<Dom> {
|
||||||
|
let pkg_path = &options.site_pkg_dir;
|
||||||
|
let output_name = &options.output_name;
|
||||||
|
let mut wasm_output_name = output_name.clone();
|
||||||
|
if std::option_env!("LEPTOS_OUTPUT_NAME").is_none() {
|
||||||
|
wasm_output_name.push_str("_bg");
|
||||||
|
}
|
||||||
|
let nonce = None::<String>; // use_nonce(); // TODO
|
||||||
|
let script = if islands {
|
||||||
|
include_str!("./island_script.js")
|
||||||
|
} else {
|
||||||
|
include_str!("./hydration_script.js")
|
||||||
|
};
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<link rel="modulepreload" href=format!("/{pkg_path}/{output_name}.js") nonce=nonce.clone()/>
|
||||||
|
<link rel="preload" href=format!("/{pkg_path}/{wasm_output_name}.wasm") r#as="fetch" r#type="application/wasm" crossorigin=nonce.clone().unwrap_or_default()/>
|
||||||
|
<script type="module" nonce=nonce>
|
||||||
|
{format!("{script}({pkg_path:?}, {output_name:?}, {wasm_output_name:?})")}
|
||||||
|
</script>
|
||||||
|
}
|
||||||
|
}
|
23
leptos_reactive/src/hydration_scripts/reload_script.js
Normal file
23
leptos_reactive/src/hydration_scripts/reload_script.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
(function (reload_port, protocol) {
|
||||||
|
let host = window.location.hostname;
|
||||||
|
let ws = new WebSocket(`${protocol}${host}:${reload_port}/live_reload`);
|
||||||
|
ws.onmessage = (ev) => {
|
||||||
|
let msg = JSON.parse(ev.data);
|
||||||
|
if (msg.all) window.location.reload();
|
||||||
|
if (msg.css) {
|
||||||
|
let found = false;
|
||||||
|
document.querySelectorAll("link").forEach((link) => {
|
||||||
|
if (link.getAttribute('href').includes(msg.css)) {
|
||||||
|
let newHref = '/' + msg.css + '?version=' + new Date().getMilliseconds();
|
||||||
|
link.setAttribute('href', newHref);
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!found) console.warn(`CSS hot-reload: Could not find a <link href=/\"${msg.css}\"> element`);
|
||||||
|
};
|
||||||
|
if(msg.view) {
|
||||||
|
patch(msg.view);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ws.onclose = () => console.warn('Live-reload stopped. Manual reload necessary.');
|
||||||
|
})
|
28
leptos_reactive/src/show.rs
Normal file
28
leptos_reactive/src/show.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
use crate::children::{ChildrenFn, ViewFn};
|
||||||
|
use tachy_maccy::component;
|
||||||
|
use tachy_reaccy::{memo::ArcMemo, signal_traits::SignalGet};
|
||||||
|
use tachydom::{
|
||||||
|
renderer::dom::Dom,
|
||||||
|
view::{either::Either, RenderHtml},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn Show<W>(
|
||||||
|
/// The children will be shown whenever the condition in the `when` closure returns `true`.
|
||||||
|
children: ChildrenFn,
|
||||||
|
/// A closure that returns a bool that determines whether this thing runs
|
||||||
|
when: W,
|
||||||
|
/// A closure that returns what gets rendered if the when statement is false. By default this is the empty view.
|
||||||
|
#[prop(optional, into)]
|
||||||
|
fallback: ViewFn,
|
||||||
|
) -> impl RenderHtml<Dom>
|
||||||
|
where
|
||||||
|
W: Fn() -> bool + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
let memoized_when = ArcMemo::new(move |_| when());
|
||||||
|
|
||||||
|
move || match memoized_when.get() {
|
||||||
|
true => Either::Left(children()),
|
||||||
|
false => Either::Right(fallback.run()),
|
||||||
|
}
|
||||||
|
}
|
|
@ -81,6 +81,8 @@ pub mod owner;
|
||||||
pub mod signal;
|
pub mod signal;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
|
|
||||||
|
pub use graph::untrack;
|
||||||
|
|
||||||
#[cfg(feature = "nightly")]
|
#[cfg(feature = "nightly")]
|
||||||
mod nightly;
|
mod nightly;
|
||||||
|
|
||||||
|
|
|
@ -26,3 +26,13 @@ pub fn arc_signal<T>(value: T) -> (ArcReadSignal<T>, ArcWriteSignal<T>) {
|
||||||
pub fn signal<T: Send + Sync>(value: T) -> (ReadSignal<T>, WriteSignal<T>) {
|
pub fn signal<T: Send + Sync>(value: T) -> (ReadSignal<T>, WriteSignal<T>) {
|
||||||
RwSignal::new(value).split()
|
RwSignal::new(value).split()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
#[track_caller]
|
||||||
|
#[deprecated = "This function is being renamed to `signal()` to conform to \
|
||||||
|
Rust idioms."]
|
||||||
|
pub fn create_signal<T: Send + Sync>(
|
||||||
|
value: T,
|
||||||
|
) -> (ReadSignal<T>, WriteSignal<T>) {
|
||||||
|
signal(value)
|
||||||
|
}
|
||||||
|
|
46
tachys/src/reactive_graph/guards.rs
Normal file
46
tachys/src/reactive_graph/guards.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
//! Implements the [`Render`] and [`RenderHtml`] traits for signal guard types.
|
||||||
|
|
||||||
|
use crate::{prelude::RenderHtml, renderer::Renderer, view::Render};
|
||||||
|
use reactive_graph::signal::SignalReadGuard;
|
||||||
|
|
||||||
|
impl<T, Rndr> Render<Rndr> for SignalReadGuard<T>
|
||||||
|
where
|
||||||
|
T: PartialEq + Clone + Render<Rndr>,
|
||||||
|
Rndr: Renderer,
|
||||||
|
{
|
||||||
|
type State = T::State;
|
||||||
|
|
||||||
|
fn build(self) -> Self::State {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rebuild(self, state: &mut Self::State) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, Rndr> RenderHtml<Rndr> for SignalReadGuard<T>
|
||||||
|
where
|
||||||
|
T: PartialEq + Clone + RenderHtml<Rndr>,
|
||||||
|
Rndr: Renderer,
|
||||||
|
Rndr::Element: Clone,
|
||||||
|
Rndr::Node: Clone,
|
||||||
|
{
|
||||||
|
const MIN_LENGTH: usize = T::MIN_LENGTH;
|
||||||
|
|
||||||
|
fn to_html_with_buf(
|
||||||
|
self,
|
||||||
|
buf: &mut String,
|
||||||
|
position: &mut crate::view::Position,
|
||||||
|
) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hydrate<const FROM_SERVER: bool>(
|
||||||
|
self,
|
||||||
|
cursor: &crate::hydration::Cursor<Rndr>,
|
||||||
|
position: &crate::view::PositionState,
|
||||||
|
) -> Self::State {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,14 +5,14 @@ use crate::{
|
||||||
renderer::{DomRenderer, Renderer},
|
renderer::{DomRenderer, Renderer},
|
||||||
ssr::StreamBuilder,
|
ssr::StreamBuilder,
|
||||||
view::{
|
view::{
|
||||||
FallibleRender, InfallibleRender, Mountable, Position, PositionState,
|
InfallibleRender, Mountable, Position, PositionState, Render,
|
||||||
Render, RenderHtml, ToTemplate,
|
RenderHtml, ToTemplate,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use reactive_graph::{computed::ScopedFuture, effect::RenderEffect};
|
use reactive_graph::{computed::ScopedFuture, effect::RenderEffect};
|
||||||
use std::mem;
|
|
||||||
|
|
||||||
mod class;
|
mod class;
|
||||||
|
mod guards;
|
||||||
pub mod node_ref;
|
pub mod node_ref;
|
||||||
mod style;
|
mod style;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue