Skip to content

Commit 0091679

Browse files
Make <textarea> a void element (#3465)
* made <textarea> a void element * added defaultvalue special attr to <textarea> * updated error message when trying to pass children to textarea * updated docs, fixed formatting * fixed hydration test * fixed suspense test * fixed heading in docs * fixed clippy warnings * fixed SSR, added SSR test for precedence of value over defaultvalue * fixing wasm-bindgen-test screwups & replacing deprecated function use
1 parent 35f8dde commit 0091679

File tree

29 files changed

+973
-544
lines changed

29 files changed

+973
-544
lines changed

Cargo.lock

Lines changed: 696 additions & 425 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,11 @@ lto = true
2020
codegen-units = 1
2121
opt-level = 3
2222

23+
[workspace.lints.rust]
24+
unexpected_cfgs = { level = "warn", check-cfg = [
25+
"cfg(documenting)",
26+
"cfg(verbose_tests)",
27+
"cfg(yew_lints)",
28+
"cfg(nightly_yew)",
29+
"cfg(wasm_bindgen_unstable_test_coverage)"
30+
]}

examples/mount_point/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ fn create_canvas(document: &Document) -> HtmlCanvasElement {
5353
canvas.set_height(100);
5454
let ctx =
5555
CanvasRenderingContext2d::from(JsValue::from(canvas.get_context("2d").unwrap().unwrap()));
56-
ctx.set_fill_style(&JsValue::from_str("green"));
56+
ctx.set_fill_style_str("green");
5757
ctx.fill_rect(10., 10., 50., 50.);
5858

5959
canvas

examples/suspense/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ fn app_content() -> HtmlResult {
3636

3737
Ok(html! {
3838
<div class="content-area">
39-
<textarea value={value.to_string()} oninput={on_text_input}></textarea>
39+
<textarea value={value.to_string()} oninput={on_text_input} />
4040
<div class="action-area">
4141
<button onclick={on_take_a_break}>{"Take a break!"}</button>
4242
<div class="hint">{"You can take a break at anytime"}<br />{"and your work will be preserved."}</div>

examples/suspense/src/struct_consumer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ impl Component for BaseAppContent {
5959
let on_take_a_break = ctx.link().callback(|_| Msg::TakeABreak);
6060
html! {
6161
<div class="content-area">
62-
<textarea value={self.value.clone()} {oninput}></textarea>
62+
<textarea value={self.value.clone()} {oninput} />
6363
<div class="action-area">
6464
<button onclick={on_take_a_break}>{"Take a break!"}</button>
6565
<div class="hint">{"You can take a break at anytime"}<br />{"and your work will be preserved."}</div>

packages/yew-macro/Cargo.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,5 @@ rustversion = "1"
2929
trybuild = "1"
3030
yew = { path = "../yew" }
3131

32-
[lints.rust]
33-
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(nightly_yew)'] }
34-
32+
[lints]
33+
workspace = true

packages/yew-macro/src/html_tree/html_element.rs

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,16 @@ impl Parse for HtmlElement {
9292
//
9393
// For dynamic tags this is done at runtime!
9494
match name.to_ascii_lowercase_string().as_str() {
95+
"textarea" => {
96+
return Err(syn::Error::new_spanned(
97+
open.to_spanned(),
98+
"the tag `<textarea>` is a void element and cannot have children (hint: \
99+
to provide value to it, rewrite it as `<textarea value={x} />`. If you \
100+
wish to set the default value, rewrite it as `<textarea defaultvalue={x} \
101+
/>`)",
102+
))
103+
}
104+
95105
"area" | "base" | "br" | "col" | "embed" | "hr" | "img" | "input" | "link"
96106
| "meta" | "param" | "source" | "track" | "wbr" => {
97107
return Err(syn::Error::new_spanned(
@@ -100,8 +110,9 @@ impl Parse for HtmlElement {
100110
"the tag `<{name}>` is a void element and cannot have children (hint: \
101111
rewrite this as `<{name} />`)",
102112
),
103-
));
113+
))
104114
}
115+
105116
_ => {}
106117
}
107118
}
@@ -156,23 +167,34 @@ impl ToTokens for HtmlElement {
156167
checked,
157168
listeners,
158169
special,
170+
defaultvalue,
159171
} = &props;
160172

161173
// attributes with special treatment
162174

163175
let node_ref = special.wrap_node_ref_attr();
164176
let key = special.wrap_key_attr();
165-
let value = value
166-
.as_ref()
167-
.map(|prop| wrap_attr_value(prop.value.optimize_literals()))
168-
.unwrap_or(quote! { ::std::option::Option::None });
169-
let checked = checked
170-
.as_ref()
171-
.map(|attr| {
172-
let value = &attr.value;
173-
quote! { ::std::option::Option::Some( #value ) }
174-
})
175-
.unwrap_or(quote! { ::std::option::Option::None });
177+
let value = || {
178+
value
179+
.as_ref()
180+
.map(|prop| wrap_attr_value(prop.value.optimize_literals()))
181+
.unwrap_or(quote! { ::std::option::Option::None })
182+
};
183+
let checked = || {
184+
checked
185+
.as_ref()
186+
.map(|attr| {
187+
let value = &attr.value;
188+
quote! { ::std::option::Option::Some( #value ) }
189+
})
190+
.unwrap_or(quote! { ::std::option::Option::None })
191+
};
192+
let defaultvalue = || {
193+
defaultvalue
194+
.as_ref()
195+
.map(|prop| wrap_attr_value(prop.value.optimize_literals()))
196+
.unwrap_or(quote! { ::std::option::Option::None })
197+
};
176198

177199
// other attributes
178200

@@ -360,6 +382,8 @@ impl ToTokens for HtmlElement {
360382
}
361383
let node = match &*name {
362384
"input" => {
385+
let value = value();
386+
let checked = checked();
363387
quote! {
364388
::std::convert::Into::<::yew::virtual_dom::VNode>::into(
365389
::yew::virtual_dom::VTag::__new_input(
@@ -374,10 +398,13 @@ impl ToTokens for HtmlElement {
374398
}
375399
}
376400
"textarea" => {
401+
let value = value();
402+
let defaultvalue = defaultvalue();
377403
quote! {
378404
::std::convert::Into::<::yew::virtual_dom::VNode>::into(
379405
::yew::virtual_dom::VTag::__new_textarea(
380406
#value,
407+
#defaultvalue,
381408
#node_ref,
382409
#key,
383410
#attributes,
@@ -439,6 +466,9 @@ impl ToTokens for HtmlElement {
439466
#[cfg(not(nightly_yew))]
440467
let invalid_void_tag_msg_start = "";
441468

469+
let value = value();
470+
let checked = checked();
471+
let defaultvalue = defaultvalue();
442472
// this way we get a nice error message (with the correct span) when the expression
443473
// doesn't return a valid value
444474
quote_spanned! {expr.span()=> {
@@ -466,6 +496,7 @@ impl ToTokens for HtmlElement {
466496
_ if "textarea".eq_ignore_ascii_case(::std::convert::AsRef::<::std::primitive::str>::as_ref(&#vtag_name)) => {
467497
::yew::virtual_dom::VTag::__new_textarea(
468498
#value,
499+
#defaultvalue,
469500
#node_ref,
470501
#key,
471502
#attributes,
@@ -500,7 +531,7 @@ impl ToTokens for HtmlElement {
500531
::std::debug_assert!(
501532
!::std::matches!(#vtag.tag().to_ascii_lowercase().as_str(),
502533
"area" | "base" | "br" | "col" | "embed" | "hr" | "img" | "input"
503-
| "link" | "meta" | "param" | "source" | "track" | "wbr"
534+
| "link" | "meta" | "param" | "source" | "track" | "wbr" | "textarea"
504535
),
505536
concat!(#invalid_void_tag_msg_start, "a dynamic tag tried to create a `<{0}>` tag with children. `<{0}>` is a void element which can't have any children."),
506537
#vtag.tag(),

packages/yew-macro/src/props/element.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub struct ElementProps {
1111
pub classes: Option<Prop>,
1212
pub booleans: Vec<Prop>,
1313
pub value: Option<Prop>,
14+
pub defaultvalue: Option<Prop>,
1415
pub checked: Option<Prop>,
1516
pub special: SpecialProps,
1617
}
@@ -31,6 +32,7 @@ impl Parse for ElementProps {
3132
let classes = props.pop("class");
3233
let value = props.pop("value");
3334
let checked = props.pop("checked");
35+
let defaultvalue = props.pop("defaultvalue");
3436
let special = props.special;
3537

3638
Ok(Self {
@@ -41,6 +43,7 @@ impl Parse for ElementProps {
4143
booleans: booleans.into_vec(),
4244
value,
4345
special,
46+
defaultvalue,
4447
})
4548
}
4649
}

packages/yew-macro/tests/html_macro/element-fail.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ fn compile_fail() {
6464

6565
// void element with children
6666
html! { <input type="text"></input> };
67+
// <textarea> should have a custom error message explaining how to set its default value
68+
html! { <textarea>{"default value"}</textarea> }
6769
// make sure that capitalization doesn't matter for the void children check
6870
html! { <iNpUt type="text"></iNpUt> };
6971

0 commit comments

Comments
 (0)