mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +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.
|
||||
///
|
||||
|
|
|
@ -9,32 +9,8 @@ description = "DOM operations for the Leptos web framework."
|
|||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-recursion = "1"
|
||||
base64 = { version = "0.22", optional = 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"] }
|
||||
tachys = { workspace = true }
|
||||
reactive_graph = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
leptos = { path = "../leptos" }
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
#![deny(missing_docs)]
|
||||
#![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))]
|
||||
// to prevent warnings from popping up when a nightly feature is stabilized
|
||||
#![allow(stable_features)]
|
||||
|
@ -1311,4 +1323,4 @@ cfg_if! {
|
|||
std::backtrace::Backtrace
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
|
|
@ -6,6 +6,7 @@ use convert_case::{
|
|||
use itertools::Itertools;
|
||||
use leptos_hot_reload::parsing::value_to_string;
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use proc_macro_error::abort;
|
||||
use quote::{format_ident, quote, quote_spanned, ToTokens, TokenStreamExt};
|
||||
use syn::{
|
||||
parse::Parse, parse_quote, spanned::Spanned, token::Colon,
|
||||
|
@ -16,7 +17,6 @@ use syn::{
|
|||
};
|
||||
|
||||
pub struct Model {
|
||||
is_transparent: bool,
|
||||
is_island: bool,
|
||||
docs: Docs,
|
||||
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 {
|
||||
is_transparent: false,
|
||||
is_island: false,
|
||||
docs,
|
||||
vis: item.vis.clone(),
|
||||
|
@ -108,7 +98,6 @@ pub fn convert_from_snake_case(name: &Ident) -> Ident {
|
|||
impl ToTokens for Model {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let Self {
|
||||
is_transparent,
|
||||
is_island,
|
||||
docs,
|
||||
vis,
|
||||
|
@ -121,24 +110,23 @@ impl ToTokens for Model {
|
|||
let no_props = props.is_empty();
|
||||
|
||||
// check for components that end ;
|
||||
if !is_transparent {
|
||||
let ends_semi =
|
||||
body.block.stmts.iter().last().and_then(|stmt| match stmt {
|
||||
Stmt::Item(Item::Macro(mac)) => mac.semi_token.as_ref(),
|
||||
_ => None,
|
||||
});
|
||||
if let Some(semi) = ends_semi {
|
||||
proc_macro_error::emit_error!(
|
||||
semi.span(),
|
||||
"A component that ends with a `view!` macro followed by a \
|
||||
semicolon will return (), an empty view. This is usually \
|
||||
an accident, not intentional, so we prevent it. If you’d \
|
||||
like to return (), you can do it it explicitly by \
|
||||
returning () as the last item from the component."
|
||||
);
|
||||
}
|
||||
let ends_semi =
|
||||
body.block.stmts.iter().last().and_then(|stmt| match stmt {
|
||||
Stmt::Item(Item::Macro(mac)) => mac.semi_token.as_ref(),
|
||||
_ => None,
|
||||
});
|
||||
if let Some(semi) = ends_semi {
|
||||
proc_macro_error::emit_error!(
|
||||
semi.span(),
|
||||
"A component that ends with a `view!` macro followed by a \
|
||||
semicolon will return (), an empty view. This is usually an \
|
||||
accident, not intentional, so we prevent it. If you’d like \
|
||||
to return (), you can do it it explicitly by returning () as \
|
||||
the last item from the component."
|
||||
);
|
||||
}
|
||||
|
||||
//body.sig.ident = format_ident!("__{}", body.sig.ident);
|
||||
#[allow(clippy::redundant_clone)] // false positive
|
||||
let body_name = body.sig.ident.clone();
|
||||
|
||||
|
@ -204,21 +192,21 @@ impl ToTokens for Model {
|
|||
#[allow(clippy::let_with_type_underscore)]
|
||||
#[cfg_attr(
|
||||
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! {
|
||||
let span = ::leptos::leptos_dom::tracing::Span::current();
|
||||
let span = ::leptos::tracing::Span::current();
|
||||
},
|
||||
quote! {
|
||||
#[cfg(debug_assertions)]
|
||||
let _guard = span.entered();
|
||||
},
|
||||
if no_props || !cfg!(feature = "trace-component-props") {
|
||||
if no_props {
|
||||
quote! {}
|
||||
} else {
|
||||
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_expr = if *is_island {
|
||||
quote! {
|
||||
::leptos::SharedContext::with_hydration(move || {
|
||||
::leptos::reactive_graph::Owner::with_hydration(move || {
|
||||
#body_name(#prop_names)
|
||||
})
|
||||
}
|
||||
|
@ -258,32 +246,25 @@ impl ToTokens for Model {
|
|||
}
|
||||
};
|
||||
|
||||
let component = if *is_transparent {
|
||||
body_expr
|
||||
} else {
|
||||
quote! {
|
||||
::leptos::leptos_dom::Component::new(
|
||||
::std::stringify!(#name),
|
||||
move || {
|
||||
#tracing_guard_expr
|
||||
#tracing_props_expr
|
||||
#body_expr
|
||||
}
|
||||
)
|
||||
}
|
||||
let component = quote! {
|
||||
::leptos::reactive_graph::untrack(
|
||||
move || {
|
||||
#tracing_guard_expr
|
||||
#tracing_props_expr
|
||||
#body_expr
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
// add island wrapper if island
|
||||
let component = if *is_island {
|
||||
quote! {
|
||||
{
|
||||
::leptos::leptos_dom::html::custom(
|
||||
::leptos::leptos_dom::html::Custom::new("leptos-island"),
|
||||
::leptos::tachys::html::islands::Island::new(
|
||||
#component_id,
|
||||
#component
|
||||
)
|
||||
.attr("data-component", #component_id)
|
||||
.attr("data-hkc", ::leptos::leptos_dom::HydrationCtx::peek_always().to_string())
|
||||
#island_serialized_props
|
||||
.child(#component)
|
||||
// #island_serialized_props
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -301,20 +282,10 @@ impl ToTokens for Model {
|
|||
let destructure_props = if no_props {
|
||||
quote! {}
|
||||
} else {
|
||||
let wrapped_children = if is_island_with_children
|
||||
&& cfg!(feature = "ssr")
|
||||
{
|
||||
let wrapped_children = if is_island_with_children {
|
||||
quote! {
|
||||
let children = ::std::boxed::Box::new(|| ::leptos::Fragment::lazy(|| ::std::vec![
|
||||
::leptos::SharedContext::with_hydration(move || {
|
||||
::leptos::IntoView::into_view(
|
||||
::leptos::leptos_dom::html::custom(
|
||||
::leptos::leptos_dom::html::Custom::new("leptos-children"),
|
||||
)
|
||||
.child(::leptos::SharedContext::no_hydration(children))
|
||||
)
|
||||
})
|
||||
]));
|
||||
use leptos::tachys::view::any_view::IntoAny;
|
||||
let children = Box::new(|| ::leptos::tachys::html::islands::IslandChildren::new(children()).into_any());
|
||||
}
|
||||
} else {
|
||||
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
|
||||
.iter()
|
||||
.filter(
|
||||
|
@ -412,7 +365,7 @@ impl ToTokens for Model {
|
|||
#component
|
||||
};
|
||||
|
||||
let binding = if *is_island && cfg!(feature = "hydrate") {
|
||||
let binding = if *is_island {
|
||||
let island_props = if is_island_with_children
|
||||
|| is_island_with_other_props
|
||||
{
|
||||
|
@ -453,15 +406,20 @@ impl ToTokens for Model {
|
|||
};
|
||||
let children = if is_island_with_children {
|
||||
quote! {
|
||||
.children(::std::boxed::Box::new(move || ::leptos::Fragment::lazy(|| ::std::vec![
|
||||
::leptos::SharedContext::with_hydration(move || {
|
||||
::leptos::IntoView::into_view(
|
||||
::leptos::leptos_dom::html::custom(
|
||||
::leptos::leptos_dom::html::Custom::new("leptos-children"),
|
||||
)
|
||||
.prop("$$owner", ::leptos::Owner::current().map(|n| n.as_ffi()))
|
||||
)
|
||||
})])))
|
||||
.children({Box::new(|| {
|
||||
use leptos::tachys::view::any_view::IntoAny;
|
||||
::leptos::tachys::html::islands::IslandChildren::new(
|
||||
// TODO owner restoration for context
|
||||
()
|
||||
).into_any()})})
|
||||
//.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 {
|
||||
quote! {}
|
||||
|
@ -477,30 +435,26 @@ impl ToTokens for Model {
|
|||
} else {
|
||||
quote! {}
|
||||
};
|
||||
let deserialize_island_props = if is_island_with_other_props {
|
||||
quote! {
|
||||
let props = el.dataset().get(::leptos::wasm_bindgen::intern("props"))
|
||||
.and_then(|data| ::leptos::serde_json::from_str::<#props_serialized_name>(&data).ok())
|
||||
.expect("could not deserialize props");
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
let deserialize_island_props = quote! {}; /*if is_island_with_other_props {
|
||||
quote! {
|
||||
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())
|
||||
.expect("could not deserialize props");
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};*/
|
||||
// TODO
|
||||
|
||||
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)]
|
||||
pub fn #hydrate_fn_name(el: ::leptos::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);
|
||||
}
|
||||
pub fn #hydrate_fn_name(el: ::leptos::tachys::web_sys::HtmlElement) {
|
||||
#deserialize_island_props
|
||||
_ = ::leptos::run_as_child(move || {
|
||||
::leptos::SharedContext::register_island(&el);
|
||||
::leptos::leptos_dom::mount_to_with_stop_hydrating(el, false, move || {
|
||||
#name(#island_props)
|
||||
})
|
||||
});
|
||||
let island = #name(#island_props);
|
||||
let state = island.hydrate_from_position::<true>(&el, ::leptos::tachys::view::Position::Current);
|
||||
// TODO better cleanup
|
||||
std::mem::forget(state);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -520,6 +474,7 @@ impl ToTokens for Model {
|
|||
#[derive(::leptos::typed_builder_macro::TypedBuilder #props_derive_serialize)]
|
||||
//#[builder(doc)]
|
||||
#[builder(crate_module_path=::leptos::typed_builder)]
|
||||
#[allow(non_snake_case)]
|
||||
#vis struct #props_name #impl_generics #where_clause {
|
||||
#prop_builder_fields
|
||||
}
|
||||
|
@ -529,36 +484,21 @@ impl ToTokens for Model {
|
|||
#[allow(missing_docs)]
|
||||
#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;
|
||||
|
||||
fn builder() -> Self::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 {
|
||||
#dyn_attrs_props
|
||||
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
|
||||
#[allow(non_snake_case, clippy::too_many_arguments)]
|
||||
|
@ -579,15 +519,8 @@ impl ToTokens for Model {
|
|||
|
||||
impl Model {
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub fn is_transparent(mut self, is_transparent: bool) -> Self {
|
||||
self.is_transparent = is_transparent;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub fn is_island(mut self) -> Self {
|
||||
self.is_island = true;
|
||||
pub fn is_island(mut self, is_island: bool) -> Self {
|
||||
self.is_island = is_island;
|
||||
|
||||
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 {
|
||||
Ident::new(&format!("__{ident}"), ident.span())
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ use component::DummyModel;
|
|||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Span, TokenTree};
|
||||
use quote::{quote, ToTokens};
|
||||
use rstml::{node::KeyedAttribute, parse};
|
||||
use rstml::node::KeyedAttribute;
|
||||
use syn::{parse_macro_input, spanned::Spanned, token::Pub, Visibility};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
|
@ -35,7 +35,6 @@ impl Default for Mode {
|
|||
mod params;
|
||||
mod view;
|
||||
use crate::component::unmodified_fn_name_from_fn_name;
|
||||
use view::{client_template::render_template, render_view};
|
||||
mod component;
|
||||
mod slice;
|
||||
mod slot;
|
||||
|
@ -358,12 +357,7 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
|||
let parser = rstml::Parser::new(config);
|
||||
let (nodes, errors) = parser.parse_recoverable(tokens).split_vec();
|
||||
let errors = errors.into_iter().map(|e| e.emit_as_expr_tokens());
|
||||
let nodes_output = render_view(
|
||||
&nodes,
|
||||
Mode::default(),
|
||||
global_class.as_ref(),
|
||||
normalized_call_site(proc_macro::Span::call_site()),
|
||||
);
|
||||
let nodes_output = view::render_view(&nodes, global_class.as_ref(), None);
|
||||
quote! {
|
||||
{
|
||||
#(#errors;)*
|
||||
|
@ -373,38 +367,6 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
|||
.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/>`.
|
||||
///
|
||||
/// 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_attribute]
|
||||
pub fn component(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
let is_transparent = if !args.is_empty() {
|
||||
let transparent = parse_macro_input!(args as syn::Ident);
|
||||
|
||||
if transparent != "transparent" {
|
||||
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()
|
||||
pub fn component(
|
||||
_args: proc_macro::TokenStream,
|
||||
s: TokenStream,
|
||||
) -> TokenStream {
|
||||
component_macro(s, false)
|
||||
}
|
||||
|
||||
/// 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_attribute]
|
||||
pub fn island(_args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
let Ok(mut dummy) = syn::parse::<DummyModel>(s.clone()) else {
|
||||
return s;
|
||||
};
|
||||
component_macro(s, true)
|
||||
}
|
||||
|
||||
fn component_macro(s: TokenStream, island: bool) -> TokenStream {
|
||||
let mut dummy = syn::parse::<DummyModel>(s.clone());
|
||||
let parse_result = syn::parse::<component::Model>(s);
|
||||
|
||||
if let (ref mut unexpanded, Ok(model)) = (&mut dummy, parse_result) {
|
||||
let expanded = model.is_island().into_token_stream();
|
||||
if let (Ok(ref mut unexpanded), Ok(model)) = (&mut dummy, parse_result) {
|
||||
let expanded = model.is_island(island).into_token_stream();
|
||||
if !matches!(unexpanded.vis, Visibility::Public(_)) {
|
||||
unexpanded.vis = Visibility::Public(Pub {
|
||||
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);
|
||||
quote! {
|
||||
#expanded
|
||||
|
||||
#[doc(hidden)]
|
||||
#[allow(non_snake_case, dead_code, clippy::too_many_arguments)]
|
||||
#unexpanded
|
||||
}
|
||||
} else {
|
||||
} else if let Ok(mut dummy) = dummy {
|
||||
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
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
}
|
||||
.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.
|
||||
///
|
||||
/// 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::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 super::{fragment_to_tokens, TagType};
|
||||
use proc_macro2::{Ident, TokenStream, TokenTree};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use rstml::node::{NodeAttribute, NodeElement};
|
||||
use rstml::node::{NodeAttribute, NodeElement, NodeName};
|
||||
use std::collections::HashMap;
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
|
@ -97,7 +91,8 @@ pub(crate) fn component_to_tokens(
|
|||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let events = attrs
|
||||
// TODO events and directives
|
||||
/* let events = attrs
|
||||
.clone()
|
||||
.filter(|attr| attr.key.to_string().starts_with("on:"))
|
||||
.map(|attr| {
|
||||
|
@ -120,7 +115,7 @@ pub(crate) fn component_to_tokens(
|
|||
.collect::<Vec<_>>();
|
||||
|
||||
let events_and_directives =
|
||||
events.into_iter().chain(directives).collect::<Vec<_>>();
|
||||
events.into_iter().chain(directives).collect::<Vec<_>>(); */
|
||||
|
||||
let dyn_attrs = attrs
|
||||
.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| {
|
||||
quote! { #v }
|
||||
})?;
|
||||
Some(quote! { (#name, ::leptos::IntoAttribute::into_attribute(#value)) })
|
||||
Some(quote! { (#name, #value.into_attribute()) })
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
@ -146,7 +141,6 @@ pub(crate) fn component_to_tokens(
|
|||
} else {
|
||||
let children = fragment_to_tokens(
|
||||
&node.children,
|
||||
true,
|
||||
TagType::Unknown,
|
||||
Some(&mut slots),
|
||||
global_class,
|
||||
|
@ -178,7 +172,7 @@ pub(crate) fn component_to_tokens(
|
|||
.children({
|
||||
#(#clonables)*
|
||||
|
||||
move |#(#bindables)*| #children #view_marker
|
||||
move |#(#bindables)*| #children
|
||||
})
|
||||
}
|
||||
} else {
|
||||
|
@ -186,7 +180,7 @@ pub(crate) fn component_to_tokens(
|
|||
.children({
|
||||
#(#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()=>
|
||||
::leptos::component_props_builder(#name_ref #generics)
|
||||
::leptos::component::component_props_builder(#name_ref #generics)
|
||||
};
|
||||
|
||||
#[allow(unused_mut)] // used in debug
|
||||
|
@ -243,9 +237,15 @@ pub(crate) fn component_to_tokens(
|
|||
#(#slots)*
|
||||
#children
|
||||
#build
|
||||
#dyn_attrs
|
||||
#(#spread_bindings)*
|
||||
)
|
||||
#dyn_attrs;
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
::leptos::component::component_view(
|
||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
||||
#name_ref,
|
||||
props
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// (Temporarily?) removed
|
||||
|
@ -253,12 +253,39 @@ pub(crate) fn component_to_tokens(
|
|||
/* #[cfg(debug_assertions)]
|
||||
IdeTagHelper::add_component_completion(&mut component, node); */
|
||||
|
||||
if events_and_directives.is_empty() {
|
||||
// TODO events and directives
|
||||
/* if events_and_directives.is_empty() {
|
||||
component
|
||||
} else {
|
||||
quote_spanned! {node.span()=>
|
||||
::leptos::IntoView::into_view(#[allow(unused_braces)] {#component})
|
||||
#component.into_view()
|
||||
#(#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 traits;
|
||||
|
||||
pub use graph::untrack;
|
||||
|
||||
#[cfg(feature = "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>) {
|
||||
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},
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
FallibleRender, InfallibleRender, Mountable, Position, PositionState,
|
||||
Render, RenderHtml, ToTemplate,
|
||||
InfallibleRender, Mountable, Position, PositionState, Render,
|
||||
RenderHtml, ToTemplate,
|
||||
},
|
||||
};
|
||||
use reactive_graph::{computed::ScopedFuture, effect::RenderEffect};
|
||||
use std::mem;
|
||||
|
||||
mod class;
|
||||
mod guards;
|
||||
pub mod node_ref;
|
||||
mod style;
|
||||
|
||||
|
|
Loading…
Reference in a new issue