mirror of
https://github.com/leptos-rs/leptos
synced 2024-09-20 14:32:00 +00:00
Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2
This commit is contained in:
commit
d2b4ae30d1
10 changed files with 225 additions and 41 deletions
|
@ -20,6 +20,7 @@ default = ["csr", "serde"]
|
|||
csr = [
|
||||
"leptos_core/csr",
|
||||
"leptos_dom/csr",
|
||||
"leptos_dom/web",
|
||||
"leptos_macro/csr",
|
||||
"leptos_reactive/csr",
|
||||
"leptos_server/csr",
|
||||
|
@ -27,6 +28,7 @@ csr = [
|
|||
hydrate = [
|
||||
"leptos_core/hydrate",
|
||||
"leptos_dom/hydrate",
|
||||
"leptos_dom/web",
|
||||
"leptos_macro/hydrate",
|
||||
"leptos_reactive/hydrate",
|
||||
"leptos_server/hydrate",
|
||||
|
|
|
@ -12,6 +12,7 @@ leptos_dom = { path = "../leptos_dom", default-features = false, version = "0.0.
|
|||
leptos_macro = { path = "../leptos_macro", default-features = false, version = "0.0.18" }
|
||||
leptos_reactive = { path = "../leptos_reactive", default-features = false, version = "0.0.18" }
|
||||
log = "0.4"
|
||||
typed-builder = "0.11"
|
||||
|
||||
[dev-dependencies]
|
||||
leptos = { path = "../leptos", default-features = false, version = "0.0" }
|
||||
|
|
|
@ -3,20 +3,12 @@
|
|||
//! This crate contains several utility pieces that depend on multiple crates.
|
||||
//! They are all re-exported in the main `leptos` crate.
|
||||
|
||||
mod for_component;
|
||||
mod map;
|
||||
mod suspense;
|
||||
//mod for_component;
|
||||
//mod map;
|
||||
//mod suspense;
|
||||
|
||||
pub use for_component::*;
|
||||
pub use map::*;
|
||||
pub use suspense::*;
|
||||
//pub use for_component::*;
|
||||
//pub use map::*;
|
||||
//pub use suspense::*;
|
||||
|
||||
/// Describes the properties of a component. This is typically generated by the `Prop` derive macro
|
||||
/// as part of the `#[component]` macro.
|
||||
pub trait Prop {
|
||||
/// Builder type, automatically generated.
|
||||
type Builder;
|
||||
|
||||
/// The builder should be automatically generated using the `Prop` derive macro.
|
||||
fn builder() -> Self::Builder;
|
||||
}
|
||||
pub use typed_builder::TypedBuilder;
|
||||
|
|
|
@ -44,8 +44,11 @@ pub struct ComponentRepr {
|
|||
|
||||
impl Drop for ComponentRepr {
|
||||
fn drop(&mut self) {
|
||||
// TODO: all ComponentReprs are immediately dropped,
|
||||
// which means their scopes are immediately disposed,
|
||||
// which means components have no reactivity
|
||||
if let Some(disposer) = self.disposer.take() {
|
||||
disposer.dispose();
|
||||
//disposer.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -130,7 +133,7 @@ impl ComponentRepr {
|
|||
/// A user-defined `leptos` component.
|
||||
pub struct Component<F>
|
||||
where
|
||||
F: FnOnce(Scope) -> Vec<Node>,
|
||||
F: FnOnce(Scope) -> Node,
|
||||
{
|
||||
name: Cow<'static, str>,
|
||||
children_fn: F,
|
||||
|
@ -138,7 +141,7 @@ where
|
|||
|
||||
impl<F> Component<F>
|
||||
where
|
||||
F: FnOnce(Scope) -> Vec<Node>,
|
||||
F: FnOnce(Scope) -> Node,
|
||||
{
|
||||
/// Creates a new component.
|
||||
pub fn new(name: impl Into<Cow<'static, str>>, f: F) -> Self {
|
||||
|
@ -151,21 +154,21 @@ where
|
|||
|
||||
impl<F> IntoNode for Component<F>
|
||||
where
|
||||
F: FnOnce(Scope) -> Vec<Node>,
|
||||
F: FnOnce(Scope) -> Node,
|
||||
{
|
||||
fn into_node(self, cx: Scope) -> Node {
|
||||
let Self { name, children_fn } = self;
|
||||
|
||||
let mut children = None;
|
||||
|
||||
let disposer = cx.child_scope(|cx| children = Some(children_fn(cx)));
|
||||
let disposer = cx.child_scope(|cx| children = Some(cx.untrack(move || children_fn(cx))));
|
||||
|
||||
let children = children.unwrap();
|
||||
|
||||
let mut repr = ComponentRepr::new(name);
|
||||
|
||||
repr.disposer = Some(disposer);
|
||||
repr.children = children;
|
||||
repr.children = vec![children];
|
||||
|
||||
repr.into_node(cx)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ use itertools::{EitherOrBoth, Itertools};
|
|||
use leptos_reactive::{create_effect, Scope};
|
||||
use rustc_hash::FxHasher;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use typed_builder::TypedBuilder;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cell::RefCell,
|
||||
|
@ -388,7 +389,7 @@ fn diff<K: Eq + Hash>(from: &FxIndexSet<K>, to: &FxIndexSet<K>) -> Diff {
|
|||
}
|
||||
}
|
||||
|
||||
normalized_idx += 1;
|
||||
normalized_idx = normalized_idx.wrapping_add(1);
|
||||
}
|
||||
|
||||
let mut diffs = Diff {
|
||||
|
@ -598,3 +599,78 @@ fn apply_cmds<T, EF, N>(
|
|||
// items
|
||||
children.drain_filter(|c| c.is_none());
|
||||
}
|
||||
|
||||
|
||||
/// Properties for the [For](crate::For) component, a keyed list.
|
||||
#[derive(TypedBuilder)]
|
||||
pub struct ForProps<IF, I, T, EF, N, KF, K>
|
||||
where
|
||||
IF: Fn() -> I + 'static,
|
||||
I: IntoIterator<Item = T>,
|
||||
EF: Fn(T) -> N + 'static,
|
||||
N: IntoNode,
|
||||
KF: Fn(&T) -> K + 'static,
|
||||
K: Eq + Hash + 'static,
|
||||
T: 'static,
|
||||
{
|
||||
/// Items over which the component should iterate.
|
||||
pub each: IF,
|
||||
/// A key function that will be applied to each item
|
||||
pub key: KF,
|
||||
/// Should provide a single child function, which takes
|
||||
pub children: Box<dyn Fn() -> Vec<EF>>,
|
||||
}
|
||||
|
||||
/// Iterates over children and displays them, keyed by the `key` function given.
|
||||
///
|
||||
/// This is much more efficient than naively iterating over nodes with `.iter().map(|n| view! { cx, ... })...`,
|
||||
/// as it avoids re-creating DOM nodes that are not being changed.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::*;
|
||||
///
|
||||
/// #[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
/// struct Counter {
|
||||
/// id: usize,
|
||||
/// count: RwSignal<i32>
|
||||
/// }
|
||||
///
|
||||
/// fn Counters(cx: Scope) -> Element {
|
||||
/// let (counters, set_counters) = create_signal::<Vec<Counter>>(cx, vec![]);
|
||||
///
|
||||
/// view! {
|
||||
/// cx,
|
||||
/// <div>
|
||||
/// <For
|
||||
/// // a function that returns the items we're iterating over; a signal is fine
|
||||
/// each=counters
|
||||
/// // a unique key for each item
|
||||
/// key=|counter| counter.id
|
||||
/// >
|
||||
/// {|cx: Scope, counter: &Counter| {
|
||||
/// let count = counter.count;
|
||||
/// view! {
|
||||
/// cx,
|
||||
/// <button>"Value: " {move || count.get()}</button>
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// </For>
|
||||
/// </div>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[allow(non_snake_case)]
|
||||
pub fn For<IF, I, T, EF, N, KF, K>(cx: Scope, props: ForProps<IF, I, T, EF, N, KF, K>) -> Node
|
||||
where
|
||||
IF: Fn() -> I + 'static,
|
||||
I: IntoIterator<Item = T>,
|
||||
EF: Fn(T) -> N + 'static,
|
||||
N: IntoNode,
|
||||
KF: Fn(&T) -> K + 'static,
|
||||
K: Eq + Hash + 'static,
|
||||
T: 'static,
|
||||
{
|
||||
let each_fn = (props.children)().swap_remove(0);
|
||||
EachKey::new(props.each, props.key, each_fn).into_node(cx)
|
||||
}
|
|
@ -76,7 +76,7 @@ macro_rules! generate_event_types {
|
|||
pub struct $event;
|
||||
|
||||
impl EventDescriptor for $event {
|
||||
type EventType = web_sys::MouseEvent;
|
||||
type EventType = web_sys::$web_sys_event;
|
||||
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
concat!("on", stringify!([<$event:lower>])).into()
|
||||
|
|
|
@ -409,7 +409,7 @@ impl<El: IntoElement> HtmlElement<El> {
|
|||
}
|
||||
|
||||
} else {
|
||||
_ = event_name;
|
||||
_ = event;
|
||||
_ = event_handler;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::{cell::{OnceCell, RefCell}, hash::Hash, rc::Rc};
|
||||
use cfg_if::cfg_if;
|
||||
use leptos_reactive::{Scope, create_effect};
|
||||
use crate::{IntoNode, ComponentRepr, EachKey, Node, HtmlElement, Text, Element, Fragment, Unit, text, DynChild, IntoElement};
|
||||
use crate::{IntoNode, ComponentRepr, EachKey, Node, HtmlElement, Text, Element, Fragment, Unit, text, DynChild, IntoElement, Component};
|
||||
|
||||
pub enum Child {
|
||||
/// A (presumably reactive) function, which will be run inside an effect to do targeted updates to the node.
|
||||
|
@ -43,7 +43,7 @@ impl IntoChild for Node {
|
|||
|
||||
impl IntoChild for String {
|
||||
fn into_child(self, _cx: Scope) -> Child {
|
||||
Child::Text(self.into())
|
||||
Child::Text(self)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,12 +102,22 @@ node_type!(Vec<Node>);
|
|||
node_type!(Fragment);
|
||||
node_type!(ComponentRepr);
|
||||
|
||||
|
||||
impl<El: IntoElement> IntoChild for HtmlElement<El> {
|
||||
fn into_child(self, cx: Scope) -> Child {
|
||||
Child::Node(self.into_node(cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> IntoChild for Component<F>
|
||||
where
|
||||
F: FnOnce(Scope) -> Node,
|
||||
{
|
||||
fn into_child(self, cx: Scope) -> Child {
|
||||
Child::Node(self.into_node(cx))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! text_type {
|
||||
($child_type:ty) => {
|
||||
impl IntoChild for $child_type {
|
||||
|
|
|
@ -121,11 +121,7 @@ impl ToTokens for InlinePropsBody {
|
|||
None
|
||||
};
|
||||
|
||||
//let modifiers = if first_lifetime.is_some() {
|
||||
let modifiers = quote! { #[derive(Props)] };
|
||||
/* } else {
|
||||
quote! { #[derive(Props, PartialEq, Eq)] }
|
||||
}; */
|
||||
let modifiers = quote! { #[derive(leptos::TypedBuilder)] };
|
||||
|
||||
let (_scope_lifetime, fn_generics, struct_generics) = if let Some(lt) = first_lifetime {
|
||||
let struct_generics: Punctuated<_, token::Comma> = generics
|
||||
|
|
|
@ -136,7 +136,7 @@ fn node_to_tokens(cx: &Ident, node: &Node, mode: Mode) -> TokenStream {
|
|||
let span = node.value.span();
|
||||
let value = node.value.as_ref();
|
||||
quote_spanned! {
|
||||
span => leptos::text(#value)
|
||||
span => text(#value)
|
||||
}
|
||||
},
|
||||
Node::Block(node) => {
|
||||
|
@ -154,7 +154,7 @@ fn node_to_tokens(cx: &Ident, node: &Node, mode: Mode) -> TokenStream {
|
|||
fn element_to_tokens(cx: &Ident, node: &NodeElement, mode: Mode) -> TokenStream {
|
||||
let span = node.name.span();
|
||||
if is_component_node(node) {
|
||||
todo!("component node")
|
||||
component_to_tokens(cx, node, mode)
|
||||
} else {
|
||||
let name = if is_custom_element(&node.name) {
|
||||
let name = node.name.to_string();
|
||||
|
@ -171,7 +171,27 @@ fn element_to_tokens(cx: &Ident, node: &NodeElement, mode: Mode) -> TokenStream
|
|||
}
|
||||
});
|
||||
let children = node.children.iter().map(|node| {
|
||||
let child = node_to_tokens(cx, node, mode);
|
||||
let child = match node {
|
||||
Node::Fragment(fragment) => {
|
||||
fragment_to_tokens(cx, Span::call_site(), &fragment.children, mode)
|
||||
},
|
||||
Node::Text(node) => {
|
||||
let span = node.value.span();
|
||||
let value = node.value.as_ref();
|
||||
quote_spanned! {
|
||||
span => #[allow(unused_braces)] #value
|
||||
}
|
||||
},
|
||||
Node::Block(node) => {
|
||||
let span = node.value.span();
|
||||
let value = node.value.as_ref();
|
||||
quote_spanned! {
|
||||
span => #[allow(unused_braces)] #value
|
||||
}
|
||||
},
|
||||
Node::Element(node) => element_to_tokens(cx, node, mode),
|
||||
Node::Comment(_) | Node::Doctype(_) | Node::Attribute(_) => quote! { },
|
||||
};
|
||||
quote! {
|
||||
._child(cx, #child)
|
||||
}
|
||||
|
@ -192,7 +212,7 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute, mode: Mode) -> TokenStr
|
|||
if mode != Mode::Ssr {
|
||||
let value = node.value.as_ref().and_then(|expr| expr_to_ident(expr)).expect("'_ref' needs to be passed a variable name");
|
||||
quote_spanned! {
|
||||
span => ._ref(#value)
|
||||
span => ._ref(#[allow(unused_braces)] #value)
|
||||
}
|
||||
} else {
|
||||
todo!()
|
||||
|
@ -209,11 +229,11 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute, mode: Mode) -> TokenStr
|
|||
|
||||
if NON_BUBBLING_EVENTS.contains(&name) {
|
||||
quote_spanned! {
|
||||
span => .on::<leptos::web_sys::#event_type>(#name, #handler)
|
||||
span => .on::<leptos::web_sys::#event_type>(#name, #[allow(unused_braces)] #handler)
|
||||
}
|
||||
} else {
|
||||
quote_spanned! {
|
||||
span => .on_delegated::<leptos::web_sys::#event_type>(#name, #handler)
|
||||
span => .on_delegated::<leptos::web_sys::#event_type>(#name, #[allow(unused_braces)] #handler)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -223,7 +243,7 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute, mode: Mode) -> TokenStr
|
|||
let value = node.value.as_ref().expect("prop: attributes need a value").as_ref();
|
||||
if mode != Mode::Ssr {
|
||||
quote_spanned! {
|
||||
span => ._prop(#cx, #name, #value)
|
||||
span => ._prop(#cx, #name, #[allow(unused_braces)] #value)
|
||||
}
|
||||
} else {
|
||||
todo!()
|
||||
|
@ -232,14 +252,13 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute, mode: Mode) -> TokenStr
|
|||
let value = node.value.as_ref().expect("class: attributes need a value").as_ref();
|
||||
if mode != Mode::Ssr {
|
||||
quote_spanned! {
|
||||
span => ._class(#cx, #name, #value)
|
||||
span => ._class(#cx, #name, #[allow(unused_braces)] #value)
|
||||
}
|
||||
} else {
|
||||
todo!()
|
||||
}
|
||||
} else {
|
||||
let name = name.replacen("attr:", "", 1);
|
||||
let node_name = node.key;
|
||||
let value = match node.value.as_ref() {
|
||||
Some(value) => {
|
||||
let value = value.as_ref();
|
||||
|
@ -250,7 +269,7 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute, mode: Mode) -> TokenStr
|
|||
};
|
||||
if mode != Mode::Ssr {
|
||||
quote_spanned! {
|
||||
span => ._attr(#cx, #name, #value)
|
||||
span => ._attr(#cx, #name, #[allow(unused_braces)] #value)
|
||||
}
|
||||
} else {
|
||||
quote! { }
|
||||
|
@ -258,6 +277,91 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute, mode: Mode) -> TokenStr
|
|||
}
|
||||
}
|
||||
|
||||
fn component_to_tokens(cx: &Ident, node: &NodeElement, mode: Mode) -> TokenStream {
|
||||
let name = &node.name;
|
||||
let component_name = ident_from_tag_name(&node.name);
|
||||
let component_name_str = name.to_string();
|
||||
let span = node.name.span();
|
||||
let component_props_name = Ident::new(&format!("{component_name}Props"), span);
|
||||
|
||||
let children = if node.children.is_empty() {
|
||||
quote! {}
|
||||
} else if node.children.len() == 1 {
|
||||
let child = component_child(cx, &node.children[0], mode);
|
||||
quote_spanned! { span => .children(Box::new(move || vec![#child])) }
|
||||
} else {
|
||||
let children = node.children.iter()
|
||||
.map(|node| component_child(cx, node, mode));
|
||||
quote_spanned! { span => .children(Box::new(move || vec![#(#children),*])) }
|
||||
};
|
||||
|
||||
let props = node.attributes.iter()
|
||||
.filter_map(|node| if let Node::Attribute(node) = node { Some(node) } else { None })
|
||||
.map(|attr| {
|
||||
let name = &attr.key;
|
||||
let span = attr.key.span();
|
||||
let value = attr
|
||||
.value
|
||||
.as_ref()
|
||||
.map(|v| {
|
||||
let v = v.as_ref();
|
||||
quote_spanned! { span => #v }
|
||||
})
|
||||
.unwrap_or_else(|| quote_spanned! { span => #name });
|
||||
|
||||
quote_spanned! {
|
||||
span => .#name(#[allow(unused_braces)] #value)
|
||||
}
|
||||
});
|
||||
|
||||
let component_itself = quote_spanned! {
|
||||
span => #name(
|
||||
cx,
|
||||
#component_props_name::builder()
|
||||
#(#props)*
|
||||
#children
|
||||
.build(),
|
||||
)
|
||||
};
|
||||
|
||||
quote_spanned! {
|
||||
span => leptos::Component::new(
|
||||
#component_name_str,
|
||||
move |cx| #component_itself
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn component_child(cx: &Ident, node: &Node, mode: Mode) -> TokenStream {
|
||||
match node {
|
||||
Node::Block(node) => {
|
||||
let span = node.value.span();
|
||||
let value = node.value.as_ref();
|
||||
quote_spanned! {
|
||||
span => #value
|
||||
}
|
||||
},
|
||||
_ => node_to_tokens(cx, node, mode)
|
||||
}
|
||||
}
|
||||
|
||||
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(_) => panic!("blocks not allowed in tag-name position"),
|
||||
_ => Ident::new(
|
||||
&tag_name.to_string().replace(['-', ':'], "_"),
|
||||
tag_name.span(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn expr_to_ident(expr: &syn::Expr) -> Option<&ExprPath> {
|
||||
match expr {
|
||||
syn::Expr::Block(block) => block.block.stmts.last().and_then(|stmt| {
|
||||
|
|
Loading…
Reference in a new issue