Skip to content

Commit 73a85b4

Browse files
authored
feat: use attr: syntax rather than AdditionalAttributes (#1728)
1 parent 2c12256 commit 73a85b4

File tree

5 files changed

+65
-74
lines changed

5 files changed

+65
-74
lines changed

docs/book/src/metadata.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ There’s a very simple way to determine whether you should use a capital-S `<Sc
3030

3131
There are even a couple elements designed to make semantic HTML and styling easier. [`<Html/>`](https://docs.rs/leptos_meta/latest/leptos_meta/fn.Html.html) lets you set the `lang` and `dir` on your `<html>` tag from your application code. `<Html/>` and [`<Body/>`](https://docs.rs/leptos_meta/latest/leptos_meta/fn.Html.html) both have `class` props that let you set their respective `class` attributes, which is sometimes needed by CSS frameworks for styling.
3232

33-
`<Body/>` and `<Html/>` both also have `attributes` props which can be used to set any number of additional attributes on them via the [`AdditionalAttributes`](https://docs.rs/leptos/latest/leptos/struct.AdditionalAttributes.html) type:
33+
`<Body/>` and `<Html/>` both also have `attributes` props which can be used to set any number of additional attributes on them via the `attr:` syntax:
3434

3535
```rust
3636
<Html
3737
lang="he"
3838
dir="rtl"
39-
attributes=AdditionalAttributes::from(vec![("data-theme", "dark")])
39+
attr:data-theme="dark"
4040
/>
4141
```
4242

leptos/src/additional_attributes.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1+
#![allow(deprecated)]
2+
13
use crate::TextProp;
24
use std::rc::Rc;
35

46
/// A collection of additional HTML attributes to be applied to an element,
57
/// each of which may or may not be reactive.
68
#[derive(Clone)]
79
#[repr(transparent)]
10+
#[deprecated = "Most uses of `AdditionalAttributes` can be replaced with `#[prop(attrs)]` \
11+
and the `attr:` syntax. If you have a use case that still requires `AdditionalAttributes`, please \
12+
open a GitHub issue here and share it: https://github.com/leptos-rs/leptos"]
813
pub struct AdditionalAttributes(pub(crate) Rc<[(String, TextProp)]>);
914

1015
impl<I, T, U> From<I> for AdditionalAttributes

meta/src/body.rs

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use cfg_if::cfg_if;
22
use leptos::*;
33
#[cfg(feature = "ssr")]
4+
use std::collections::HashMap;
5+
#[cfg(feature = "ssr")]
46
use std::{cell::RefCell, rc::Rc};
57

68
/// Contains the current metadata for the document's `<body>`.
@@ -9,7 +11,7 @@ pub struct BodyContext {
911
#[cfg(feature = "ssr")]
1012
class: Rc<RefCell<Option<TextProp>>>,
1113
#[cfg(feature = "ssr")]
12-
attributes: Rc<RefCell<Option<MaybeSignal<AdditionalAttributes>>>>,
14+
attributes: Rc<RefCell<HashMap<&'static str, Attribute>>>,
1315
}
1416

1517
impl BodyContext {
@@ -22,20 +24,23 @@ impl BodyContext {
2224
leptos::leptos_dom::ssr::escape_attr(&val.get())
2325
)
2426
});
25-
let attributes = self.attributes.borrow().as_ref().map(|val| {
26-
val.with(|val| {
27-
val.into_iter()
28-
.map(|(n, v)| {
27+
let attributes = self.attributes.borrow();
28+
let attributes = (!attributes.is_empty()).then(|| {
29+
attributes
30+
.iter()
31+
.filter_map(|(n, v)| {
32+
v.as_nameless_value_string().map(|v| {
2933
format!(
3034
"{}=\"{}\"",
3135
n,
32-
leptos::leptos_dom::ssr::escape_attr(&v.get())
36+
leptos::leptos_dom::ssr::escape_attr(&v)
3337
)
3438
})
35-
.collect::<Vec<_>>()
36-
.join(" ")
37-
})
39+
})
40+
.collect::<Vec<_>>()
41+
.join(" ")
3842
});
43+
3944
let mut val = [class, attributes]
4045
.into_iter()
4146
.flatten()
@@ -77,7 +82,7 @@ impl std::fmt::Debug for BodyContext {
7782
///
7883
/// view! {
7984
/// <main>
80-
/// <Body class=body_class/>
85+
/// <Body class=body_class attr:class="foo"/>
8186
/// </main>
8287
/// }
8388
/// }
@@ -88,11 +93,13 @@ pub fn Body(
8893
#[prop(optional, into)]
8994
class: Option<TextProp>,
9095
/// Arbitrary attributes to add to the `<html>`
91-
#[prop(optional, into)]
92-
attributes: Option<MaybeSignal<AdditionalAttributes>>,
96+
#[prop(attrs)]
97+
attributes: Vec<(&'static str, Attribute)>,
9398
) -> impl IntoView {
9499
cfg_if! {
95-
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
100+
if #[cfg(all(target_arch = "wasm32", any(feature = "csr", feature = "hydrate")))] {
101+
use wasm_bindgen::JsCast;
102+
96103
let el = document().body().expect("there to be a <body> element");
97104

98105
if let Some(class) = class {
@@ -105,24 +112,15 @@ pub fn Body(
105112
});
106113
}
107114

108-
if let Some(attributes) = attributes {
109-
let attributes = attributes.get();
110-
for (attr_name, attr_value) in attributes.into_iter() {
111-
let el = el.clone();
112-
let attr_name = attr_name.to_owned();
113-
let attr_value = attr_value.to_owned();
114-
create_render_effect(move |_|{
115-
let value = attr_value.get();
116-
_ = el.set_attribute(&attr_name, &value);
117-
});
118-
}
115+
for (name, value) in attributes {
116+
leptos::leptos_dom::attribute_helper(el.unchecked_ref(), name.into(), value);
119117
}
120118
} else if #[cfg(feature = "ssr")] {
121119
let meta = crate::use_head();
122120
*meta.body.class.borrow_mut() = class;
123-
*meta.body.attributes.borrow_mut() = attributes;
121+
meta.body.attributes.borrow_mut().extend(attributes);
124122
} else {
125-
_ = class;
123+
_ = class;
126124
_ = attributes;
127125

128126
#[cfg(debug_assertions)]

meta/src/html.rs

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use cfg_if::cfg_if;
22
use leptos::*;
33
#[cfg(feature = "ssr")]
4+
use std::collections::HashMap;
5+
#[cfg(feature = "ssr")]
46
use std::{cell::RefCell, rc::Rc};
57

68
/// Contains the current metadata for the document's `<html>`.
@@ -13,7 +15,7 @@ pub struct HtmlContext {
1315
#[cfg(feature = "ssr")]
1416
class: Rc<RefCell<Option<TextProp>>>,
1517
#[cfg(feature = "ssr")]
16-
attributes: Rc<RefCell<Option<MaybeSignal<AdditionalAttributes>>>>,
18+
attributes: Rc<RefCell<HashMap<&'static str, Attribute>>>,
1719
}
1820

1921
impl HtmlContext {
@@ -38,19 +40,21 @@ impl HtmlContext {
3840
leptos::leptos_dom::ssr::escape_attr(&val.get())
3941
)
4042
});
41-
let attributes = self.attributes.borrow().as_ref().map(|val| {
42-
val.with(|val| {
43-
val.into_iter()
44-
.map(|(n, v)| {
43+
let attributes = self.attributes.borrow();
44+
let attributes = (!attributes.is_empty()).then(|| {
45+
attributes
46+
.iter()
47+
.filter_map(|(n, v)| {
48+
v.as_nameless_value_string().map(|v| {
4549
format!(
4650
"{}=\"{}\"",
4751
n,
48-
leptos::leptos_dom::ssr::escape_attr(&v.get())
52+
leptos::leptos_dom::ssr::escape_attr(&v)
4953
)
5054
})
51-
.collect::<Vec<_>>()
52-
.join(" ")
53-
})
55+
})
56+
.collect::<Vec<_>>()
57+
.join(" ")
5458
});
5559
let mut val = [lang, dir, class, attributes]
5660
.into_iter()
@@ -88,8 +92,8 @@ impl std::fmt::Debug for HtmlContext {
8892
/// <Html
8993
/// lang="he"
9094
/// dir="rtl"
91-
/// // arbitrary additional attributes can be passed via `attributes`
92-
/// attributes=AdditionalAttributes::from(vec![("data-theme", "dark")])
95+
/// // arbitrary additional attributes can be passed via `attr:`
96+
/// attr:data-theme="dark"
9397
/// />
9498
/// </main>
9599
/// }
@@ -107,11 +111,13 @@ pub fn Html(
107111
#[prop(optional, into)]
108112
class: Option<TextProp>,
109113
/// Arbitrary attributes to add to the `<html>`
110-
#[prop(optional, into)]
111-
attributes: Option<MaybeSignal<AdditionalAttributes>>,
114+
#[prop(attrs)]
115+
attributes: Vec<(&'static str, Attribute)>,
112116
) -> impl IntoView {
113117
cfg_if! {
114-
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
118+
if #[cfg(all(target_arch = "wasm32", any(feature = "csr", feature = "hydrate")))] {
119+
use wasm_bindgen::JsCast;
120+
115121
let el = document().document_element().expect("there to be a <html> element");
116122

117123
if let Some(lang) = lang {
@@ -138,24 +144,15 @@ pub fn Html(
138144
});
139145
}
140146

141-
if let Some(attributes) = attributes {
142-
let attributes = attributes.get();
143-
for (attr_name, attr_value) in attributes.into_iter() {
144-
let el = el.clone();
145-
let attr_name = attr_name.to_owned();
146-
let attr_value = attr_value.to_owned();
147-
create_render_effect(move |_|{
148-
let value = attr_value.get();
149-
_ = el.set_attribute(&attr_name, &value);
150-
});
151-
}
147+
for (name, value) in attributes {
148+
leptos::leptos_dom::attribute_helper(el.unchecked_ref(), name.into(), value);
152149
}
153150
} else if #[cfg(feature = "ssr")] {
154151
let meta = crate::use_head();
155152
*meta.html.lang.borrow_mut() = lang;
156153
*meta.html.dir.borrow_mut() = dir;
157154
*meta.html.class.borrow_mut() = class;
158-
*meta.html.attributes.borrow_mut() = attributes;
155+
meta.html.attributes.borrow_mut().extend(attributes);
159156
} else {
160157
_ = lang;
161158
_ = dir;

router/src/components/form.rs

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,10 @@ pub fn Form<A>(
5656
/// Sets whether the page should be scrolled to the top when the form is submitted.
5757
#[prop(optional)]
5858
noscroll: bool,
59-
/// Arbitrary attributes to add to the `<form>`
60-
#[prop(optional, into)]
61-
attributes: Option<MaybeSignal<AdditionalAttributes>>,
59+
/// Arbitrary attributes to add to the `<form>`. Attributes can be added with the
60+
/// `attr:` syntax in the `view` macro.
61+
#[prop(attrs)]
62+
attributes: Vec<(&'static str, Attribute)>,
6263
/// Component children; should include the HTML of the form elements.
6364
children: Children,
6465
) -> impl IntoView
@@ -78,7 +79,7 @@ where
7879
children: Children,
7980
node_ref: Option<NodeRef<html::Form>>,
8081
noscroll: bool,
81-
attributes: Option<MaybeSignal<AdditionalAttributes>>,
82+
attributes: Vec<(&'static str, Attribute)>,
8283
) -> HtmlElement<html::Form> {
8384
let action_version = version;
8485
let on_submit = {
@@ -276,13 +277,8 @@ where
276277
if let Some(node_ref) = node_ref {
277278
form = form.node_ref(node_ref)
278279
};
279-
if let Some(attributes) = attributes {
280-
let attributes = attributes.get();
281-
for (attr_name, attr_value) in attributes.into_iter() {
282-
let attr_name = attr_name.to_owned();
283-
let attr_value = attr_value.to_owned();
284-
form = form.attr(attr_name, move || attr_value.get());
285-
}
280+
for (attr_name, attr_value) in attributes {
281+
form = form.attr(attr_name, attr_value);
286282
}
287283
form
288284
}
@@ -352,7 +348,7 @@ pub fn ActionForm<I, O>(
352348
noscroll: bool,
353349
/// Arbitrary attributes to add to the `<form>`
354350
#[prop(optional, into)]
355-
attributes: Option<MaybeSignal<AdditionalAttributes>>,
351+
attributes: Vec<(&'static str, Attribute)>,
356352
/// Component children; should include the HTML of the form elements.
357353
children: Children,
358354
) -> impl IntoView
@@ -539,7 +535,7 @@ pub fn MultiActionForm<I, O>(
539535
node_ref: Option<NodeRef<html::Form>>,
540536
/// Arbitrary attributes to add to the `<form>`
541537
#[prop(optional, into)]
542-
attributes: Option<MaybeSignal<AdditionalAttributes>>,
538+
attributes: Vec<(&'static str, Attribute)>,
543539
/// Component children; should include the HTML of the form elements.
544540
children: Children,
545541
) -> impl IntoView
@@ -591,13 +587,8 @@ where
591587
if let Some(node_ref) = node_ref {
592588
form = form.node_ref(node_ref)
593589
};
594-
if let Some(attributes) = attributes {
595-
let attributes = attributes.get();
596-
for (attr_name, attr_value) in attributes.into_iter() {
597-
let attr_name = attr_name.to_owned();
598-
let attr_value = attr_value.to_owned();
599-
form = form.attr(attr_name, move || attr_value.get());
600-
}
590+
for (attr_name, attr_value) in attributes {
591+
form = form.attr(attr_name, attr_value);
601592
}
602593
form
603594
}

0 commit comments

Comments
 (0)