diff --git a/meta/src/additional_attributes.rs b/leptos/src/additional_attributes.rs similarity index 95% rename from meta/src/additional_attributes.rs rename to leptos/src/additional_attributes.rs index 58ca5c09f..4daf27bb0 100644 --- a/meta/src/additional_attributes.rs +++ b/leptos/src/additional_attributes.rs @@ -42,6 +42,6 @@ impl<'a> IntoIterator for &'a AdditionalAttributes { type IntoIter = AdditionalAttributesIter<'a>; fn into_iter(self) -> Self::IntoIter { - todo!() + AdditionalAttributesIter(self.0.iter()) } } diff --git a/leptos/src/lib.rs b/leptos/src/lib.rs index 4f7b6cd9c..6e6bd0178 100644 --- a/leptos/src/lib.rs +++ b/leptos/src/lib.rs @@ -141,6 +141,8 @@ //! # } //! ``` +mod additional_attributes; +pub use additional_attributes::*; pub use leptos_config::{self, get_configuration, LeptosOptions}; #[cfg(not(all( target_arch = "wasm32", @@ -180,7 +182,9 @@ pub use for_loop::*; pub use show::*; mod suspense; pub use suspense::*; +mod text_prop; mod transition; +pub use text_prop::TextProp; #[cfg(debug_assertions)] #[doc(hidden)] pub use tracing; diff --git a/leptos/src/text_prop.rs b/leptos/src/text_prop.rs new file mode 100644 index 000000000..a56a3eba6 --- /dev/null +++ b/leptos/src/text_prop.rs @@ -0,0 +1,43 @@ +use std::{fmt::Debug, rc::Rc}; + +/// Describes a value that is either a static or a reactive string, i.e., +/// a [String], a [&str], or a reactive `Fn() -> String`. +#[derive(Clone)] +pub struct TextProp(Rc String>); + +impl TextProp { + /// Accesses the current value of the property. + #[inline(always)] + pub fn get(&self) -> String { + (self.0)() + } +} + +impl Debug for TextProp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("TextProp").finish() + } +} + +impl From for TextProp { + fn from(s: String) -> Self { + TextProp(Rc::new(move || s.clone())) + } +} + +impl From<&str> for TextProp { + fn from(s: &str) -> Self { + let s = s.to_string(); + TextProp(Rc::new(move || s.clone())) + } +} + +impl From for TextProp +where + F: Fn() -> String + 'static, +{ + #[inline(always)] + fn from(s: F) -> Self { + TextProp(Rc::new(s)) + } +} diff --git a/meta/src/body.rs b/meta/src/body.rs index 8a04a8483..beeb7f6a2 100644 --- a/meta/src/body.rs +++ b/meta/src/body.rs @@ -1,4 +1,3 @@ -use crate::{additional_attributes::AdditionalAttributes, TextProp}; use cfg_if::cfg_if; use leptos::*; use std::{cell::RefCell, rc::Rc}; @@ -20,8 +19,7 @@ impl BodyContext { .map(|val| format!("class=\"{}\"", val.get())); let attributes = self.attributes.borrow().as_ref().map(|val| { val.with(|val| { - val.0 - .iter() + val.into_iter() .map(|(n, v)| format!("{}=\"{}\"", n, v.get())) .collect::>() .join(" ") @@ -102,8 +100,10 @@ pub fn Body( if let Some(attributes) = attributes { let attributes = attributes.get(); - for (attr_name, attr_value) in attributes.0.into_iter() { + for (attr_name, attr_value) in attributes.into_iter() { let el = el.clone(); + let attr_name = attr_name.to_owned(); + let attr_value = attr_value.to_owned(); create_render_effect(cx, move |_|{ let value = attr_value.get(); _ = el.set_attribute(&attr_name, &value); diff --git a/meta/src/html.rs b/meta/src/html.rs index c4888dc46..ee8889773 100644 --- a/meta/src/html.rs +++ b/meta/src/html.rs @@ -1,4 +1,3 @@ -use crate::{additional_attributes::AdditionalAttributes, TextProp}; use cfg_if::cfg_if; use leptos::*; use std::{cell::RefCell, rc::Rc}; @@ -32,8 +31,7 @@ impl HtmlContext { .map(|val| format!("class=\"{}\"", val.get())); let attributes = self.attributes.borrow().as_ref().map(|val| { val.with(|val| { - val.0 - .iter() + val.into_iter() .map(|(n, v)| format!("{}=\"{}\"", n, v.get())) .collect::>() .join(" ") @@ -131,8 +129,10 @@ pub fn Html( if let Some(attributes) = attributes { let attributes = attributes.get(); - for (attr_name, attr_value) in attributes.0.into_iter() { + for (attr_name, attr_value) in attributes.into_iter() { let el = el.clone(); + let attr_name = attr_name.to_owned(); + let attr_value = attr_value.to_owned(); create_render_effect(cx, move |_|{ let value = attr_value.get(); _ = el.set_attribute(&attr_name, &value); diff --git a/meta/src/lib.rs b/meta/src/lib.rs index be2fbba2e..212ca3410 100644 --- a/meta/src/lib.rs +++ b/meta/src/lib.rs @@ -59,7 +59,6 @@ use std::{ #[cfg(any(feature = "csr", feature = "hydrate"))] use wasm_bindgen::{JsCast, UnwrapThrowExt}; -mod additional_attributes; mod body; mod html; mod link; @@ -68,7 +67,6 @@ mod script; mod style; mod stylesheet; mod title; -pub use additional_attributes::*; pub use body::*; pub use html::*; pub use link::*; @@ -309,47 +307,6 @@ pub fn generate_head_metadata_separated(cx: Scope) -> (String, String) { (head, format!("")) } -/// Describes a value that is either a static or a reactive string, i.e., -/// a [String], a [&str], or a reactive `Fn() -> String`. -#[derive(Clone)] -pub struct TextProp(Rc String>); - -impl TextProp { - #[inline(always)] - fn get(&self) -> String { - (self.0)() - } -} - -impl Debug for TextProp { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("TextProp").finish() - } -} - -impl From for TextProp { - fn from(s: String) -> Self { - TextProp(Rc::new(move || s.clone())) - } -} - -impl From<&str> for TextProp { - fn from(s: &str) -> Self { - let s = s.to_string(); - TextProp(Rc::new(move || s.clone())) - } -} - -impl From for TextProp -where - F: Fn() -> String + 'static, -{ - #[inline(always)] - fn from(s: F) -> Self { - TextProp(Rc::new(s)) - } -} - #[cfg(debug_assertions)] pub(crate) fn feature_warning() { if !cfg!(any(feature = "csr", feature = "hydrate", feature = "ssr")) { diff --git a/meta/src/title.rs b/meta/src/title.rs index 9d6426f3d..9109c036d 100644 --- a/meta/src/title.rs +++ b/meta/src/title.rs @@ -17,7 +17,7 @@ pub struct TitleContext { impl TitleContext { /// Converts the title into a string that can be used as the text content of a `` tag. pub fn as_string(&self) -> Option<String> { - let title = self.text.borrow().as_ref().map(|f| (f.0)()); + let title = self.text.borrow().as_ref().map(|f| f.get()); title.map(|title| { if let Some(formatter) = &*self.formatter.borrow() { (formatter.0)(title) diff --git a/router/src/components/form.rs b/router/src/components/form.rs index c20155e10..423f2cb67 100644 --- a/router/src/components/form.rs +++ b/router/src/components/form.rs @@ -1,5 +1,5 @@ use crate::{use_navigate, use_resolved_path, ToHref, Url}; -use leptos::*; +use leptos::{html::form, *}; use std::{error::Error, rc::Rc}; use wasm_bindgen::{JsCast, UnwrapThrowExt}; use wasm_bindgen_futures::JsFuture; @@ -42,6 +42,12 @@ pub fn Form<A>( /// to a form submission. #[prop(optional)] on_response: Option<OnResponse>, + /// A [`NodeRef`] in which the `<form>` element should be stored. + #[prop(optional)] + node_ref: Option<NodeRef<html::Form>>, + /// Arbitrary attributes to add to the `<form>` + #[prop(optional, into)] + attributes: Option<MaybeSignal<AdditionalAttributes>>, /// Component children; should include the HTML of the form elements. children: Children, ) -> impl IntoView @@ -59,6 +65,8 @@ where on_response: Option<OnResponse>, class: Option<Attribute>, children: Children, + node_ref: Option<NodeRef<html::Form>>, + attributes: Option<MaybeSignal<AdditionalAttributes>>, ) -> HtmlElement<html::Form> { let action_version = version; let on_submit = move |ev: web_sys::SubmitEvent| { @@ -152,17 +160,25 @@ where let method = method.unwrap_or("get"); - view! { cx, - <form - method=method - action=move || action.get() - enctype=enctype - on:submit=on_submit - class=class - > - {children(cx)} - </form> + let mut form = form(cx) + .attr("method", method) + .attr("action", move || action.get()) + .attr("enctype", enctype) + .on(ev::submit, on_submit) + .attr("class", class) + .child(children(cx)); + if let Some(node_ref) = node_ref { + form = form.node_ref(node_ref) + }; + if let Some(attributes) = attributes { + let attributes = attributes.get(); + for (attr_name, attr_value) in attributes.into_iter() { + let attr_name = attr_name.to_owned(); + let attr_value = attr_value.to_owned(); + form = form.attr(attr_name, move || attr_value.get()); + } } + form } let action = use_resolved_path(cx, move || action.to_href()()); @@ -178,6 +194,8 @@ where on_response, class, children, + node_ref, + attributes, ) } @@ -197,6 +215,12 @@ pub fn ActionForm<I, O>( /// A signal that will be set if the form submission ends in an error. #[prop(optional)] error: Option<RwSignal<Option<Box<dyn Error>>>>, + /// A [`NodeRef`] in which the `<form>` element should be stored. + #[prop(optional)] + node_ref: Option<NodeRef<html::Form>>, + /// Arbitrary attributes to add to the `<form>` + #[prop(optional, into)] + attributes: Option<MaybeSignal<AdditionalAttributes>>, /// Component children; should include the HTML of the form elements. children: Children, ) -> impl IntoView @@ -286,19 +310,18 @@ where }); }); let class = class.map(|bx| bx.into_attribute_boxed(cx)); - let props = FormProps::builder() + let mut props = FormProps::builder() .action(action_url) .version(version) .on_form_data(on_form_data) .on_response(on_response) .method("post") .class(class) - .children(children); - let props = if let Some(error) = error { - props.error(error).build() - } else { - props.build() - }; + .children(children) + .build(); + props.error = error; + props.node_ref = node_ref; + props.attributes = attributes; Form(cx, props) } @@ -318,6 +341,12 @@ pub fn MultiActionForm<I, O>( /// A signal that will be set if the form submission ends in an error. #[prop(optional)] error: Option<RwSignal<Option<Box<dyn Error>>>>, + /// A [`NodeRef`] in which the `<form>` element should be stored. + #[prop(optional)] + node_ref: Option<NodeRef<html::Form>>, + /// Arbitrary attributes to add to the `<form>` + #[prop(optional, into)] + attributes: Option<MaybeSignal<AdditionalAttributes>>, /// Component children; should include the HTML of the form elements. children: Children, ) -> impl IntoView @@ -360,16 +389,24 @@ where }; let class = class.map(|bx| bx.into_attribute_boxed(cx)); - view! { cx, - <form - method="POST" - action=action - class=class - on:submit=on_submit - > - {children(cx)} - </form> + let mut form = form(cx) + .attr("method", "POST") + .attr("action", action) + .on(ev::submit, on_submit) + .attr("class", class) + .child(children(cx)); + if let Some(node_ref) = node_ref { + form = form.node_ref(node_ref) + }; + if let Some(attributes) = attributes { + let attributes = attributes.get(); + for (attr_name, attr_value) in attributes.into_iter() { + let attr_name = attr_name.to_owned(); + let attr_value = attr_value.to_owned(); + form = form.attr(attr_name, move || attr_value.get()); + } } + form } fn extract_form_attributes(