Skip to content

Commit

Permalink
Merge pull request #3062 from leptos-rs/into-render
Browse files Browse the repository at this point in the history
feat: add `IntoRender` for rendering custom data
  • Loading branch information
gbj authored Oct 4, 2024
2 parents 36df36e + ab9de1b commit c1dc8c7
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 24 deletions.
50 changes: 35 additions & 15 deletions leptos_macro/src/view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,9 @@ fn node_to_tokens(
view_marker,
disable_inert_html,
),
Node::Block(block) => Some(quote! { #block }),
Node::Block(block) => {
Some(quote! { ::leptos::prelude::IntoRender::into_render(#block) })
}
Node::Text(text) => Some(text_to_tokens(&text.value)),
Node::RawText(raw) => {
let text = raw.to_string_best();
Expand Down Expand Up @@ -878,7 +880,7 @@ fn attribute_to_tokens(
NodeName::Path(path) => path.path.get_ident(),
_ => unreachable!(),
};
let value = attribute_value(node);
let value = attribute_value(node, false);
quote! {
.#node_ref(#value)
}
Expand Down Expand Up @@ -928,13 +930,13 @@ fn attribute_to_tokens(
// we don't provide statically-checked methods for SVG attributes
|| (tag_type == TagType::Svg && name != "inner_html")
{
let value = attribute_value(node);
let value = attribute_value(node, true);
quote! {
.attr(#name, #value)
}
} else {
let key = attribute_name(&node.key);
let value = attribute_value(node);
let value = attribute_value(node, true);

// special case of global_class and class attribute
if &node.key.to_string() == "class"
Expand Down Expand Up @@ -971,18 +973,19 @@ pub(crate) fn attribute_absolute(
let id = &parts[0];
match id {
NodeNameFragment::Ident(id) => {
let value = attribute_value(node);
// ignore `let:` and `clone:`
if id == "let" || id == "clone" {
None
} else if id == "attr" {
let value = attribute_value(node, true);
let key = &parts[1];
let key_name = key.to_string();
if key_name == "class" || key_name == "style" {
Some(
quote! { ::leptos::tachys::html::#key::#key(#value) },
)
} else if key_name == "aria" {
let value = attribute_value(node, true);
let mut parts_iter = parts.iter();
parts_iter.next();
let fn_name = parts_iter.map(|p| p.to_string()).collect::<Vec<String>>().join("_");
Expand Down Expand Up @@ -1011,6 +1014,7 @@ pub(crate) fn attribute_absolute(
},
)
} else if id == "style" || id == "class" {
let value = attribute_value(node, false);
let key = &node.key.to_string();
let key = key
.replacen("style:", "", 1)
Expand All @@ -1019,6 +1023,7 @@ pub(crate) fn attribute_absolute(
quote! { ::leptos::tachys::html::#id::#id((#key, #value)) },
)
} else if id == "prop" {
let value = attribute_value(node, false);
let key = &node.key.to_string();
let key = key.replacen("prop:", "", 1);
Some(
Expand Down Expand Up @@ -1075,7 +1080,7 @@ pub(crate) fn two_way_binding_to_tokens(
name: &str,
node: &KeyedAttribute,
) -> TokenStream {
let value = attribute_value(node);
let value = attribute_value(node, false);

let ident =
format_ident!("{}", name.to_case(UpperCamel), span = node.key.span());
Expand All @@ -1100,7 +1105,7 @@ pub(crate) fn event_type_and_handler(
name: &str,
node: &KeyedAttribute,
) -> (TokenStream, TokenStream, TokenStream) {
let handler = attribute_value(node);
let handler = attribute_value(node, false);

let (event_type, is_custom, is_force_undelegated, is_targeted) =
parse_event_name(name);
Expand Down Expand Up @@ -1157,7 +1162,7 @@ fn class_to_tokens(
class: TokenStream,
class_name: Option<&str>,
) -> TokenStream {
let value = attribute_value(node);
let value = attribute_value(node, false);
if let Some(class_name) = class_name {
quote! {
.#class((#class_name, #value))
Expand All @@ -1174,7 +1179,7 @@ fn style_to_tokens(
style: TokenStream,
style_name: Option<&str>,
) -> TokenStream {
let value = attribute_value(node);
let value = attribute_value(node, false);
if let Some(style_name) = style_name {
quote! {
.#style((#style_name, #value))
Expand All @@ -1191,7 +1196,7 @@ fn prop_to_tokens(
prop: TokenStream,
key: &str,
) -> TokenStream {
let value = attribute_value(node);
let value = attribute_value(node, false);
quote! {
.#prop(#key, #value)
}
Expand Down Expand Up @@ -1348,7 +1353,10 @@ fn attribute_name(name: &NodeName) -> TokenStream {
}
}

fn attribute_value(attr: &KeyedAttribute) -> TokenStream {
fn attribute_value(
attr: &KeyedAttribute,
is_attribute_proper: bool,
) -> TokenStream {
match attr.possible_value.to_value() {
None => quote! { true },
Some(value) => match &value.value {
Expand All @@ -1363,14 +1371,26 @@ fn attribute_value(attr: &KeyedAttribute) -> TokenStream {
}
}

quote! {
{#expr}
if matches!(expr, Expr::Lit(_)) || !is_attribute_proper {
quote! {
#expr
}
} else {
quote! {
::leptos::prelude::IntoAttributeValue::into_attribute_value(#expr)
}
}
}
// any value in braces: expand as-is to give proper r-a support
KVAttributeValue::InvalidBraced(block) => {
quote! {
#block
if is_attribute_proper {
quote! {
::leptos::prelude::IntoAttributeValue::into_attribute_value(#block)
}
} else {
quote! {
#block
}
}
}
},
Expand Down
20 changes: 20 additions & 0 deletions tachys/src/html/attribute/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,26 @@ use std::{
sync::Arc,
};

/// Declares that this type can be converted into some other type, which is a valid attribute value.
pub trait IntoAttributeValue {
/// The attribute value into which this type can be converted.
type Output;

/// Consumes this value, transforming it into an attribute value.
fn into_attribute_value(self) -> Self::Output;
}

impl<T> IntoAttributeValue for T
where
T: AttributeValue,
{
type Output = Self;

fn into_attribute_value(self) -> Self::Output {
self
}
}

/// A possible value for an HTML attribute.
pub trait AttributeValue: Send {
/// The state that should be retained between building and rebuilding.
Expand Down
16 changes: 9 additions & 7 deletions tachys/src/html/element/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use crate::{
renderer::{CastFrom, Rndr},
ssr::StreamBuilder,
view::{
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
RenderHtml, ToTemplate,
add_attr::AddAnyAttr, IntoRender, Mountable, Position, PositionState,
Render, RenderHtml, ToTemplate,
},
};
use const_str_slice_concat::{
Expand Down Expand Up @@ -65,11 +65,13 @@ impl<E, At, Ch, NewChild> ElementChild<NewChild> for HtmlElement<E, At, Ch>
where
E: ElementWithChildren,
Ch: Render + NextTuple,
<Ch as NextTuple>::Output<NewChild>: Render,
<Ch as NextTuple>::Output<NewChild::Output>: Render,

NewChild: Render,
NewChild: IntoRender,
NewChild::Output: Render,
{
type Output = HtmlElement<E, At, <Ch as NextTuple>::Output<NewChild>>;
type Output =
HtmlElement<E, At, <Ch as NextTuple>::Output<NewChild::Output>>;

fn child(self, child: NewChild) -> Self::Output {
let HtmlElement {
Expand All @@ -82,7 +84,7 @@ where
tag,

attributes,
children: children.next_tuple(child),
children: children.next_tuple(child.into_render()),
}
}
}
Expand Down Expand Up @@ -116,7 +118,7 @@ where
/// Adds a child to the element.
pub trait ElementChild<NewChild>
where
NewChild: Render,
NewChild: IntoRender,
{
/// The type of the element, with the child added.
type Output;
Expand Down
5 changes: 3 additions & 2 deletions tachys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@ pub mod prelude {
OnAttribute, OnTargetAttribute, PropAttribute,
StyleAttribute,
},
IntoAttributeValue,
},
directive::DirectiveAttribute,
element::{ElementChild, ElementExt, InnerHtmlAttribute},
node_ref::NodeRefAttribute,
},
renderer::{dom::Dom, Renderer},
view::{
add_attr::AddAnyAttr, any_view::IntoAny, Mountable, Render,
RenderHtml,
add_attr::AddAnyAttr, any_view::IntoAny, IntoRender, Mountable,
Render, RenderHtml,
},
};
}
Expand Down
20 changes: 20 additions & 0 deletions tachys/src/view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,3 +432,23 @@ pub enum Position {
/// This is the last child of its parent.
LastChild,
}

/// Declares that this type can be converted into some other type, which can be renderered.
pub trait IntoRender {
/// The renderable type into which this type can be converted.
type Output;

/// Consumes this value, transforming it into the renderable type.
fn into_render(self) -> Self::Output;
}

impl<T> IntoRender for T
where
T: Render,
{
type Output = Self;

fn into_render(self) -> Self::Output {
self
}
}

0 comments on commit c1dc8c7

Please sign in to comment.