Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,121 changes: 696 additions & 425 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,11 @@ lto = true
codegen-units = 1
opt-level = 3

[workspace.lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = [
"cfg(documenting)",
"cfg(verbose_tests)",
"cfg(yew_lints)",
"cfg(nightly_yew)",
"cfg(wasm_bindgen_unstable_test_coverage)"
]}
2 changes: 1 addition & 1 deletion examples/mount_point/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ fn create_canvas(document: &Document) -> HtmlCanvasElement {
canvas.set_height(100);
let ctx =
CanvasRenderingContext2d::from(JsValue::from(canvas.get_context("2d").unwrap().unwrap()));
ctx.set_fill_style(&JsValue::from_str("green"));
ctx.set_fill_style_str("green");
ctx.fill_rect(10., 10., 50., 50.);

canvas
Expand Down
2 changes: 1 addition & 1 deletion examples/suspense/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ fn app_content() -> HtmlResult {

Ok(html! {
<div class="content-area">
<textarea value={value.to_string()} oninput={on_text_input}></textarea>
<textarea value={value.to_string()} oninput={on_text_input} />
<div class="action-area">
<button onclick={on_take_a_break}>{"Take a break!"}</button>
<div class="hint">{"You can take a break at anytime"}<br />{"and your work will be preserved."}</div>
Expand Down
2 changes: 1 addition & 1 deletion examples/suspense/src/struct_consumer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl Component for BaseAppContent {
let on_take_a_break = ctx.link().callback(|_| Msg::TakeABreak);
html! {
<div class="content-area">
<textarea value={self.value.clone()} {oninput}></textarea>
<textarea value={self.value.clone()} {oninput} />
<div class="action-area">
<button onclick={on_take_a_break}>{"Take a break!"}</button>
<div class="hint">{"You can take a break at anytime"}<br />{"and your work will be preserved."}</div>
Expand Down
5 changes: 2 additions & 3 deletions packages/yew-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,5 @@ rustversion = "1"
trybuild = "1"
yew = { path = "../yew" }

[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(nightly_yew)'] }

[lints]
workspace = true
57 changes: 44 additions & 13 deletions packages/yew-macro/src/html_tree/html_element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ impl Parse for HtmlElement {
//
// For dynamic tags this is done at runtime!
match name.to_ascii_lowercase_string().as_str() {
"textarea" => {
return Err(syn::Error::new_spanned(
open.to_spanned(),
"the tag `<textarea>` is a void element and cannot have children (hint: \
to provide value to it, rewrite it as `<textarea value={x} />`. If you \
wish to set the default value, rewrite it as `<textarea defaultvalue={x} \
/>`)",
))
}

"area" | "base" | "br" | "col" | "embed" | "hr" | "img" | "input" | "link"
| "meta" | "param" | "source" | "track" | "wbr" => {
return Err(syn::Error::new_spanned(
Expand All @@ -100,8 +110,9 @@ impl Parse for HtmlElement {
"the tag `<{name}>` is a void element and cannot have children (hint: \
rewrite this as `<{name} />`)",
),
));
))
}

_ => {}
}
}
Expand Down Expand Up @@ -156,23 +167,34 @@ impl ToTokens for HtmlElement {
checked,
listeners,
special,
defaultvalue,
} = &props;

// attributes with special treatment

let node_ref = special.wrap_node_ref_attr();
let key = special.wrap_key_attr();
let value = value
.as_ref()
.map(|prop| wrap_attr_value(prop.value.optimize_literals()))
.unwrap_or(quote! { ::std::option::Option::None });
let checked = checked
.as_ref()
.map(|attr| {
let value = &attr.value;
quote! { ::std::option::Option::Some( #value ) }
})
.unwrap_or(quote! { ::std::option::Option::None });
let value = || {
value
.as_ref()
.map(|prop| wrap_attr_value(prop.value.optimize_literals()))
.unwrap_or(quote! { ::std::option::Option::None })
};
let checked = || {
checked
.as_ref()
.map(|attr| {
let value = &attr.value;
quote! { ::std::option::Option::Some( #value ) }
})
.unwrap_or(quote! { ::std::option::Option::None })
};
let defaultvalue = || {
defaultvalue
.as_ref()
.map(|prop| wrap_attr_value(prop.value.optimize_literals()))
.unwrap_or(quote! { ::std::option::Option::None })
};

// other attributes

Expand Down Expand Up @@ -360,6 +382,8 @@ impl ToTokens for HtmlElement {
}
let node = match &*name {
"input" => {
let value = value();
let checked = checked();
quote! {
::std::convert::Into::<::yew::virtual_dom::VNode>::into(
::yew::virtual_dom::VTag::__new_input(
Expand All @@ -374,10 +398,13 @@ impl ToTokens for HtmlElement {
}
}
"textarea" => {
let value = value();
let defaultvalue = defaultvalue();
quote! {
::std::convert::Into::<::yew::virtual_dom::VNode>::into(
::yew::virtual_dom::VTag::__new_textarea(
#value,
#defaultvalue,
#node_ref,
#key,
#attributes,
Expand Down Expand Up @@ -439,6 +466,9 @@ impl ToTokens for HtmlElement {
#[cfg(not(nightly_yew))]
let invalid_void_tag_msg_start = "";

let value = value();
let checked = checked();
let defaultvalue = defaultvalue();
// this way we get a nice error message (with the correct span) when the expression
// doesn't return a valid value
quote_spanned! {expr.span()=> {
Expand Down Expand Up @@ -466,6 +496,7 @@ impl ToTokens for HtmlElement {
_ if "textarea".eq_ignore_ascii_case(::std::convert::AsRef::<::std::primitive::str>::as_ref(&#vtag_name)) => {
::yew::virtual_dom::VTag::__new_textarea(
#value,
#defaultvalue,
#node_ref,
#key,
#attributes,
Expand Down Expand Up @@ -500,7 +531,7 @@ impl ToTokens for HtmlElement {
::std::debug_assert!(
!::std::matches!(#vtag.tag().to_ascii_lowercase().as_str(),
"area" | "base" | "br" | "col" | "embed" | "hr" | "img" | "input"
| "link" | "meta" | "param" | "source" | "track" | "wbr"
| "link" | "meta" | "param" | "source" | "track" | "wbr" | "textarea"
),
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."),
#vtag.tag(),
Expand Down
3 changes: 3 additions & 0 deletions packages/yew-macro/src/props/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub struct ElementProps {
pub classes: Option<Prop>,
pub booleans: Vec<Prop>,
pub value: Option<Prop>,
pub defaultvalue: Option<Prop>,
pub checked: Option<Prop>,
pub special: SpecialProps,
}
Expand All @@ -31,6 +32,7 @@ impl Parse for ElementProps {
let classes = props.pop("class");
let value = props.pop("value");
let checked = props.pop("checked");
let defaultvalue = props.pop("defaultvalue");
let special = props.special;

Ok(Self {
Expand All @@ -41,6 +43,7 @@ impl Parse for ElementProps {
booleans: booleans.into_vec(),
value,
special,
defaultvalue,
})
}
}
Expand Down
2 changes: 2 additions & 0 deletions packages/yew-macro/tests/html_macro/element-fail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ fn compile_fail() {

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

Expand Down
Loading
Loading