feature: reintroduce limited template-node cloning w/ `template` macro (#526)
This commit is contained in:
parent
8d42e91eb8
commit
0071a48b8a
|
@ -11,9 +11,28 @@ thread_local! {
|
|||
pub(crate) static GLOBAL_EVENTS: RefCell<HashSet<Cow<'static, str>>> = RefCell::new(HashSet::new());
|
||||
}
|
||||
|
||||
/// Adds an event listener to the target DOM element using implicit event delegation.
|
||||
// Used in template macro
|
||||
#[doc(hidden)]
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
pub(crate) fn add_event_listener<E>(
|
||||
pub fn add_event_helper<E: crate::ev::EventDescriptor + 'static>(
|
||||
target: &web_sys::Element,
|
||||
event: E,
|
||||
#[allow(unused_mut)] // used for tracing in debug
|
||||
mut event_handler: impl FnMut(E::EventType) + 'static,
|
||||
) {
|
||||
let event_name = event.name();
|
||||
|
||||
if event.bubbles() {
|
||||
add_event_listener(target, event_name, event_handler);
|
||||
} else {
|
||||
add_event_listener_undelegated(target, &event_name, event_handler);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds an event listener to the target DOM element using implicit event delegation.
|
||||
#[doc(hidden)]
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
pub fn add_event_listener<E>(
|
||||
target: &web_sys::Element,
|
||||
event_name: Cow<'static, str>,
|
||||
#[cfg(debug_assertions)] mut cb: impl FnMut(E) + 'static,
|
||||
|
|
|
@ -23,6 +23,8 @@ pub mod svg;
|
|||
mod transparent;
|
||||
use cfg_if::cfg_if;
|
||||
pub use components::*;
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
pub use events::add_event_helper;
|
||||
pub use events::typed as ev;
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use events::{add_event_listener, add_event_listener_undelegated};
|
||||
|
@ -60,7 +62,8 @@ pub trait IntoView {
|
|||
}
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
trait Mountable {
|
||||
#[doc(hidden)]
|
||||
pub trait Mountable {
|
||||
/// Gets the [`web_sys::Node`] that can be directly inserted as
|
||||
/// a child of another node. Typically, this is a [`web_sys::DocumentFragment`]
|
||||
/// for components, and [`web_sys::HtmlElement`] for elements.
|
||||
|
@ -138,9 +141,11 @@ cfg_if! {
|
|||
/// HTML element.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct Element {
|
||||
#[doc(hidden)]
|
||||
#[cfg(debug_assertions)]
|
||||
name: Cow<'static, str>,
|
||||
element: web_sys::HtmlElement,
|
||||
pub name: Cow<'static, str>,
|
||||
#[doc(hidden)]
|
||||
pub element: web_sys::HtmlElement,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Element {
|
||||
|
@ -615,7 +620,11 @@ impl View {
|
|||
#[cfg_attr(debug_assertions, instrument)]
|
||||
#[track_caller]
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
fn mount_child<GWSN: Mountable + fmt::Debug>(kind: MountKind, child: &GWSN) {
|
||||
#[doc(hidden)]
|
||||
pub fn mount_child<GWSN: Mountable + fmt::Debug>(
|
||||
kind: MountKind,
|
||||
child: &GWSN,
|
||||
) {
|
||||
let child = child.get_mountable_node();
|
||||
|
||||
match kind {
|
||||
|
@ -678,7 +687,8 @@ fn prepare_to_move(
|
|||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
#[derive(Debug)]
|
||||
enum MountKind<'a> {
|
||||
#[doc(hidden)]
|
||||
pub enum MountKind<'a> {
|
||||
Before(
|
||||
// The closing node
|
||||
&'a web_sys::Node,
|
||||
|
|
|
@ -254,7 +254,8 @@ attr_type!(char);
|
|||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use std::borrow::Cow;
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
pub(crate) fn attribute_helper(
|
||||
#[doc(hidden)]
|
||||
pub fn attribute_helper(
|
||||
el: &web_sys::Element,
|
||||
name: Cow<'static, str>,
|
||||
value: Attribute,
|
||||
|
|
|
@ -71,7 +71,8 @@ impl<T: IntoClass> IntoClass for (Scope, T) {
|
|||
use std::borrow::Cow;
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
pub(crate) fn class_helper(
|
||||
#[doc(hidden)]
|
||||
pub fn class_helper(
|
||||
el: &web_sys::Element,
|
||||
name: Cow<'static, str>,
|
||||
value: Class,
|
||||
|
|
|
@ -25,6 +25,7 @@ leptos_dom = { workspace = true }
|
|||
leptos_reactive = { workspace = true }
|
||||
leptos_server = { workspace = true }
|
||||
convert_case = "0.6.0"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
|
||||
[dev-dependencies]
|
||||
log = "0.4"
|
||||
|
|
|
@ -32,9 +32,11 @@ impl Default for Mode {
|
|||
|
||||
mod params;
|
||||
mod view;
|
||||
use template::render_template;
|
||||
use view::render_view;
|
||||
mod component;
|
||||
mod server;
|
||||
mod template;
|
||||
|
||||
/// The `view` macro uses RSX (like JSX, but Rust!) It follows most of the
|
||||
/// same rules as HTML, with the following differences:
|
||||
|
@ -344,6 +346,43 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
|||
}
|
||||
}
|
||||
|
||||
/// An optimized, cached template for client-side rendering. Follows the same
|
||||
/// syntax as the [view](crate::macro) 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") {
|
||||
let tokens: proc_macro2::TokenStream = tokens.into();
|
||||
let mut tokens = tokens.into_iter();
|
||||
let (cx, comma) = (tokens.next(), tokens.next());
|
||||
match (cx, comma) {
|
||||
(Some(TokenTree::Ident(cx)), Some(TokenTree::Punct(punct)))
|
||||
if punct.as_char() == ',' =>
|
||||
{
|
||||
match parse(tokens.collect::<proc_macro2::TokenStream>().into())
|
||||
{
|
||||
Ok(nodes) => render_template(
|
||||
&proc_macro2::Ident::new(&cx.to_string(), cx.span()),
|
||||
&nodes,
|
||||
),
|
||||
Err(error) => error.to_compile_error(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
_ => {
|
||||
panic!(
|
||||
"view! macro needs a context and RSX: e.g., view! {{ cx, \
|
||||
<div>...</div> }}"
|
||||
)
|
||||
}
|
||||
}
|
||||
} 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
|
||||
|
|
|
@ -0,0 +1,522 @@
|
|||
use crate::is_component_node;
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::spanned::Spanned;
|
||||
use syn_rsx::{Node, NodeAttribute, NodeElement, NodeValueExpr};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub(crate) fn render_template(cx: &Ident, nodes: &[Node]) -> TokenStream {
|
||||
let template_uid = Ident::new(
|
||||
&format!("TEMPLATE_{}", Uuid::new_v4().simple()),
|
||||
Span::call_site(),
|
||||
);
|
||||
|
||||
if nodes.len() == 1 {
|
||||
first_node_to_tokens(cx, &template_uid, &nodes[0])
|
||||
} else {
|
||||
panic!("template! takes a single root element.")
|
||||
}
|
||||
}
|
||||
|
||||
fn first_node_to_tokens(
|
||||
cx: &Ident,
|
||||
template_uid: &Ident,
|
||||
node: &Node,
|
||||
) -> TokenStream {
|
||||
match node {
|
||||
Node::Element(node) => root_element_to_tokens(cx, template_uid, node),
|
||||
_ => panic!("template! takes a single root element."),
|
||||
}
|
||||
}
|
||||
|
||||
fn root_element_to_tokens(
|
||||
cx: &Ident,
|
||||
template_uid: &Ident,
|
||||
node: &NodeElement,
|
||||
) -> TokenStream {
|
||||
let mut template = String::new();
|
||||
let mut navigations = Vec::new();
|
||||
let mut expressions = Vec::new();
|
||||
|
||||
if is_component_node(node) {
|
||||
crate::view::component_to_tokens(cx, node, None)
|
||||
} else {
|
||||
element_to_tokens(
|
||||
cx,
|
||||
node,
|
||||
&Ident::new("root", Span::call_site()),
|
||||
None,
|
||||
&mut 0,
|
||||
&mut 0,
|
||||
&mut template,
|
||||
&mut navigations,
|
||||
&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 span = node.name.span();
|
||||
|
||||
let navigations = if navigations.is_empty() {
|
||||
quote! {}
|
||||
} else {
|
||||
quote! { #(#navigations);* }
|
||||
};
|
||||
|
||||
let expressions = if expressions.is_empty() {
|
||||
quote! {}
|
||||
} else {
|
||||
quote! { #(#expressions;);* }
|
||||
};
|
||||
|
||||
let tag_name = node.name.to_string();
|
||||
|
||||
quote_spanned! {
|
||||
span => {
|
||||
thread_local! {
|
||||
static #template_uid: web_sys::HtmlTemplateElement = {
|
||||
let document = leptos::document();
|
||||
let el = document.create_element("template").unwrap();
|
||||
el.set_inner_html(#template);
|
||||
el.unchecked_into()
|
||||
};
|
||||
}
|
||||
|
||||
#generate_root
|
||||
|
||||
#navigations
|
||||
#expressions
|
||||
|
||||
leptos::leptos_dom::View::Element(leptos::leptos_dom::Element {
|
||||
#[cfg(debug_assertions)]
|
||||
name: #tag_name.into(),
|
||||
element: root.unchecked_into()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum PrevSibChange {
|
||||
Sib(Ident),
|
||||
Parent,
|
||||
Skip,
|
||||
}
|
||||
|
||||
fn attributes(node: &NodeElement) -> impl Iterator<Item = &NodeAttribute> {
|
||||
node.attributes.iter().filter_map(|node| {
|
||||
if let Node::Attribute(attribute) = node {
|
||||
Some(attribute)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn element_to_tokens(
|
||||
cx: &Ident,
|
||||
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>,
|
||||
expressions: &mut Vec<TokenStream>,
|
||||
is_root_el: bool,
|
||||
) -> Ident {
|
||||
// create this element
|
||||
*next_el_id += 1;
|
||||
let this_el_ident = child_ident(*next_el_id, node.name.span());
|
||||
|
||||
// Open tag
|
||||
let name_str = node.name.to_string();
|
||||
let span = node.name.span();
|
||||
|
||||
// CSR/hydrate, push to template
|
||||
template.push('<');
|
||||
template.push_str(&name_str);
|
||||
|
||||
// attributes
|
||||
for attr in attributes(node) {
|
||||
attr_to_tokens(cx, 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 = #parent.clone().unchecked_into::<web_sys::Node>();
|
||||
//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(|| 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(|| panic!("error: {} => {}", #debug_name, "firstChild"));
|
||||
//log::debug!("=> got {}", #this_el_ident.node_name());
|
||||
}
|
||||
};
|
||||
navigations.push(this_nav);
|
||||
|
||||
// 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 = next_sibling_node(&node.children, idx + 1, next_el_id);
|
||||
|
||||
let curr_id = child_to_tokens(
|
||||
cx,
|
||||
child,
|
||||
&this_el_ident,
|
||||
if idx == 0 { None } else { prev_sib.clone() },
|
||||
next_sib,
|
||||
next_el_id,
|
||||
next_co_id,
|
||||
template,
|
||||
navigations,
|
||||
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,
|
||||
) -> Option<Ident> {
|
||||
if children.len() <= idx {
|
||||
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 {
|
||||
Some(child_ident(*next_el_id + 1, sibling.name.span()))
|
||||
}
|
||||
}
|
||||
Node::Block(sibling) => {
|
||||
Some(child_ident(*next_el_id + 1, sibling.value.span()))
|
||||
}
|
||||
Node::Text(sibling) => {
|
||||
Some(child_ident(*next_el_id + 1, sibling.value.span()))
|
||||
}
|
||||
_ => panic!("expected either an element or a block"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn attr_to_tokens(
|
||||
cx: &Ident,
|
||||
node: &NodeAttribute,
|
||||
el_id: &Ident,
|
||||
template: &mut String,
|
||||
expressions: &mut Vec<TokenStream>,
|
||||
) {
|
||||
let name = node.key.to_string();
|
||||
let name = if name.starts_with('_') {
|
||||
name.replacen('_', "", 1)
|
||||
} else {
|
||||
name
|
||||
};
|
||||
let name = if name.starts_with("attr:") {
|
||||
name.replacen("attr:", "", 1)
|
||||
} else {
|
||||
name
|
||||
};
|
||||
let value = match &node.value {
|
||||
Some(expr) => match expr.as_ref() {
|
||||
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" {
|
||||
panic!("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(#el_id.unchecked_ref(), #event_type, #handler);
|
||||
})
|
||||
}
|
||||
// Properties
|
||||
else if name.starts_with("prop:") {
|
||||
let name = name.replacen("prop:", "", 1);
|
||||
let value = node
|
||||
.value
|
||||
.as_ref()
|
||||
.expect("prop: blocks need values")
|
||||
.as_ref();
|
||||
expressions.push(quote_spanned! {
|
||||
span => leptos_dom::property(#cx, #el_id.unchecked_ref(), #name, #value.into_property(#cx))
|
||||
});
|
||||
}
|
||||
// Classes
|
||||
else if name.starts_with("class:") {
|
||||
let name = name.replacen("class:", "", 1);
|
||||
let value = node
|
||||
.value
|
||||
.as_ref()
|
||||
.expect("class: attributes need values")
|
||||
.as_ref();
|
||||
expressions.push(quote_spanned! {
|
||||
span => leptos::leptos_dom::class_helper(#el_id.unchecked_ref(), #name.into(), #value.into_class(#cx))
|
||||
});
|
||||
}
|
||||
// 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(#el_id.unchecked_ref(), #name.into(), {#value}.into_attribute(#cx))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum AttributeValue<'a> {
|
||||
Static(String),
|
||||
Dynamic(&'a syn::Expr),
|
||||
Empty,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn child_to_tokens(
|
||||
cx: &Ident,
|
||||
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>,
|
||||
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(
|
||||
cx,
|
||||
node,
|
||||
parent,
|
||||
prev_sib,
|
||||
next_el_id,
|
||||
next_co_id,
|
||||
template,
|
||||
navigations,
|
||||
expressions,
|
||||
false,
|
||||
))
|
||||
}
|
||||
}
|
||||
Node::Text(node) => block_to_tokens(
|
||||
cx,
|
||||
&node.value,
|
||||
node.value.span(),
|
||||
parent,
|
||||
prev_sib,
|
||||
next_sib,
|
||||
next_el_id,
|
||||
template,
|
||||
expressions,
|
||||
navigations,
|
||||
),
|
||||
Node::Block(node) => block_to_tokens(
|
||||
cx,
|
||||
&node.value,
|
||||
node.value.span(),
|
||||
parent,
|
||||
prev_sib,
|
||||
next_sib,
|
||||
next_el_id,
|
||||
template,
|
||||
expressions,
|
||||
navigations,
|
||||
),
|
||||
_ => panic!("unexpected child node type"),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn block_to_tokens(
|
||||
_cx: &Ident,
|
||||
value: &NodeValueExpr,
|
||||
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 {
|
||||
let value = value.as_ref();
|
||||
let str_value = match value {
|
||||
syn::Expr::Lit(lit) => match &lit.lit {
|
||||
syn::Lit::Str(s) => Some(s.value()),
|
||||
syn::Lit::Char(c) => Some(c.value().to_string()),
|
||||
syn::Lit::Int(i) => Some(i.base10_digits().to_string()),
|
||||
syn::Lit::Float(f) => Some(f.base10_digits().to_string()),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// 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(|| 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(|| 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) }
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(v) = str_value {
|
||||
navigations.push(location);
|
||||
template.push_str(&v);
|
||||
|
||||
if let Some(name) = name {
|
||||
PrevSibChange::Sib(name)
|
||||
} else {
|
||||
PrevSibChange::Parent
|
||||
}
|
||||
} else {
|
||||
template.push_str("<!>");
|
||||
navigations.push(location);
|
||||
|
||||
expressions.push(quote! {
|
||||
leptos::leptos_dom::mount_child(#mount_kind, &{#value}.into_view(cx));
|
||||
});
|
||||
|
||||
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)
|
||||
}
|
|
@ -832,7 +832,7 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute) -> TokenStream {
|
|||
.expect("couldn't parse event name");
|
||||
|
||||
let event_type = if is_custom {
|
||||
quote! { leptos::leptos_dom::leptos_dom::events::Custom::new(#name) }
|
||||
quote! { leptos::ev::Custom::new(#name) }
|
||||
} else {
|
||||
event_type
|
||||
};
|
||||
|
@ -887,9 +887,9 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute) -> TokenStream {
|
|||
} else {
|
||||
quote! { undelegated }
|
||||
};
|
||||
quote! { ::leptos::leptos_dom::ev::#undelegated(::leptos::leptos_dom::ev::#event_type) }
|
||||
quote! { ::leptos::ev::#undelegated(::leptos::ev::#event_type) }
|
||||
} else {
|
||||
quote! { ::leptos::leptos_dom::ev::#event_type }
|
||||
quote! { ::leptos::ev::#event_type }
|
||||
};
|
||||
|
||||
quote! {
|
||||
|
@ -969,7 +969,7 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute) -> TokenStream {
|
|||
}
|
||||
}
|
||||
|
||||
fn component_to_tokens(
|
||||
pub(crate) fn component_to_tokens(
|
||||
cx: &Ident,
|
||||
node: &NodeElement,
|
||||
global_class: Option<&TokenTree>,
|
||||
|
@ -1081,7 +1081,7 @@ fn component_to_tokens(
|
|||
}
|
||||
}
|
||||
|
||||
fn event_from_attribute_node(
|
||||
pub(crate) fn event_from_attribute_node(
|
||||
attr: &NodeAttribute,
|
||||
force_undelegated: bool,
|
||||
) -> (TokenStream, &Expr) {
|
||||
|
|
Loading…
Reference in New Issue