Skip to content

Commit

Permalink
Implicit optional attributes (yewstack#1637)
Browse files Browse the repository at this point in the history
  • Loading branch information
siku2 authored and ranile committed Jun 5, 2021
1 parent 5fec49c commit 4eecb75
Show file tree
Hide file tree
Showing 51 changed files with 890 additions and 1,023 deletions.
6 changes: 3 additions & 3 deletions examples/boids/src/slider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,13 @@ impl Component for Slider {

html! {
<div class="slider">
<label for=id class="slider__label">{ label }</label>
<label for=id.clone() class="slider__label">{ label }</label>
<input type="range"
id=id
class="slider__input"
min=min max=max step=step
min=min.to_string() max=max.to_string() step=step.to_string()
oninput=onchange.reform(|data: InputData| data.value.parse().unwrap())
value=value
value=value.to_string()
/>
<span class="slider__value">{ display_value }</span>
</div>
Expand Down
6 changes: 3 additions & 3 deletions examples/crm/src/add_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,19 +78,19 @@ impl Component for AddClientForm {
<input
class=classes!("new-client", "firstname")
placeholder="First name"
value=&client.first_name
value=client.first_name.clone()
oninput=link.callback(|e: InputData| Msg::UpdateFirstName(e.value))
/>
<input
class=classes!("new-client", "lastname")
placeholder="Last name"
value=&client.last_name
value=client.last_name.clone()
oninput=link.callback(|e: InputData| Msg::UpdateLastName(e.value))
/>
<textarea
class=classes!("new-client", "description")
placeholder="Description"
value=&client.description
value=client.description.clone()
oninput=link.callback(|e: InputData| Msg::UpdateDescription(e.value))
/>
</div>
Expand Down
2 changes: 1 addition & 1 deletion examples/js_callback/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ impl Component for Model {
<textarea
class="code-block"
oninput=self.link.callback(|input: InputData| Msg::Payload(input.value))
value=&self.payload
value=self.payload.clone()
/>
<button onclick=self.link.callback(|_| Msg::Payload(bindings::get_payload()))>
{ "Get the payload!" }
Expand Down
2 changes: 1 addition & 1 deletion examples/keyed_list/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ impl Model {
{ self.build_component_ratio }
</p>
<input name="ratio" type="range" class="form-control-range" min="0.0" max="1.0" step="any"
value=self.build_component_ratio
value=self.build_component_ratio.to_string()
oninput=self.link.callback(|e: InputData| Msg::ChangeRatio(e.value))
/>
</div>
Expand Down
4 changes: 2 additions & 2 deletions examples/keyed_list/src/person.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ impl PersonType {
}
Self::Component(info) => {
if keyed {
html! { <PersonComponent key=info.id.to_string() info=info /> }
html! { <PersonComponent key=info.id.to_string() info=info.clone() /> }
} else {
html! { <PersonComponent info=info /> }
html! { <PersonComponent info=info.clone() /> }
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion examples/mount_point/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ impl Component for Model {
html! {
<div>
<input
value=&self.name
value=self.name.clone()
oninput=self.link.callback(|e: InputData| Msg::UpdateName(e.value))
/>
<p>{ self.name.chars().rev().collect::<String>() }</p>
Expand Down
3 changes: 2 additions & 1 deletion examples/nested_list/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::cell::RefCell;
use std::fmt;
use std::ops::Deref;
use std::rc::Rc;
use yew::html::{Component, ComponentLink};
use yew::html::{Component, ComponentLink, ImplicitClone};

pub struct WeakComponentLink<COMP: Component>(Rc<RefCell<Option<ComponentLink<COMP>>>>);

Expand All @@ -16,6 +16,7 @@ impl<COMP: Component> Clone for WeakComponentLink<COMP> {
Self(Rc::clone(&self.0))
}
}
impl<COMP: Component> ImplicitClone for WeakComponentLink<COMP> {}

impl<COMP: Component> Default for WeakComponentLink<COMP> {
fn default() -> Self {
Expand Down
2 changes: 1 addition & 1 deletion examples/router/src/components/author_card.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl Component for AuthorCard {
<div class="media">
<div class="media-left">
<figure class="image is-128x128">
<img src=author.image_url />
<img src=author.image_url.clone() />
</figure>
</div>
<div class="media-content">
Expand Down
2 changes: 1 addition & 1 deletion examples/router/src/components/post_card.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ impl Component for PostCard {
<div class="card">
<div class="card-image">
<figure class="image is-2by1">
<img src={ &post.image_url } loading="lazy" />
<img src=post.image_url.clone() loading="lazy" />
</figure>
</div>
<div class="card-content">
Expand Down
2 changes: 1 addition & 1 deletion examples/router/src/components/progress_delay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ impl Component for ProgressDelay {
fn view(&self) -> Html {
let value = self.value;
html! {
<progress class="progress is-primary" value=value max=1.0>
<progress class="progress is-primary" value=value.to_string() max=1.0>
{ format!("{:.0}%", 100.0 * value) }
</progress>
}
Expand Down
2 changes: 1 addition & 1 deletion examples/router/src/pages/author.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl Component for Author {
</div>
<div class="tile is-parent">
<figure class="tile is-child image is-square">
<img src=author.image_url />
<img src=author.image_url.clone() />
</figure>
</div>
<div class="tile is-parent">
Expand Down
2 changes: 1 addition & 1 deletion examples/router/src/pages/author_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ impl Component for AuthorList {
}

fn view(&self) -> Html {
let authors = self.seeds.iter().map(|seed| {
let authors = self.seeds.iter().map(|&seed| {
html! {
<div class="tile is-parent">
<div class="tile is-child">
Expand Down
2 changes: 1 addition & 1 deletion examples/store/src/post.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl Component for Post {
<h2>{ format!("Post #{}", self.id) }</h2>
<p>{text}</p>

<TextInput value=text onsubmit=self.link.callback(Msg::UpdateText) />
<TextInput value=text.to_owned() onsubmit=self.link.callback(Msg::UpdateText) />
<button onclick=self.link.callback(|_| Msg::Delete)>
{ "Delete" }
</button>
Expand Down
2 changes: 1 addition & 1 deletion examples/store/src/text_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ impl Component for TextInput {
html! {
<input
type="text"
value=&self.text
value=self.text.clone()
oninput=self.link.callback(|e: InputData| Msg::SetText(e.value))
onkeydown=self.link.batch_callback(move |e: KeyboardEvent| {
e.stop_propagation();
Expand Down
4 changes: 2 additions & 2 deletions examples/todomvc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ impl Model {
<input
class="new-todo"
placeholder="What needs to be done?"
value=&self.state.value
value=self.state.value.clone()
oninput=self.link.callback(|e: InputData| Msg::Update(e.value))
onkeypress=self.link.batch_callback(|e: KeyboardEvent| {
if e.key() == "Enter" { Some(Msg::Add) } else { None }
Expand Down Expand Up @@ -242,7 +242,7 @@ impl Model {
class="edit"
type="text"
ref=self.focus_ref.clone()
value=&self.state.edit_value
value=self.state.edit_value.clone()
onmouseover=self.link.callback(|_| Msg::Focus)
oninput=self.link.callback(|e: InputData| Msg::UpdateEdit(e.value))
onblur=self.link.callback(move |_| Msg::Edit(idx))
Expand Down
2 changes: 1 addition & 1 deletion packages/yew-components/src/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ where
html! {
<select
ref=self.select_ref.clone()
id=self.props.id
id=self.props.id.clone()
class=self.props.class.clone()
disabled=self.props.disabled
onchange=self.on_change()
Expand Down
90 changes: 53 additions & 37 deletions packages/yew-macro/src/derive_props/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
use std::convert::TryFrom;
use syn::parse::Result;
use syn::spanned::Spanned;
use syn::{Error, Expr, Field, Type, Visibility};
use syn::{Error, Expr, Field, Type, TypePath, Visibility};

#[allow(clippy::large_enum_variant)]
#[derive(PartialEq, Eq)]
enum PropAttr {
Required { wrapped_name: Ident },
Option,
PropOr(Expr),
PropOrElse(Expr),
PropOrDefault,
Expand All @@ -37,69 +38,69 @@ impl PropField {
)
}

/// Ident of the wrapped field name
fn wrapped_name(&self) -> &Ident {
match &self.attr {
PropAttr::Required { wrapped_name } => wrapped_name,
_ => &self.name,
}
}

/// Used to transform the `PropWrapper` struct into `Properties`
pub fn to_field_setter(&self) -> proc_macro2::TokenStream {
let name = &self.name;
match &self.attr {
PropAttr::Required { wrapped_name } => {
quote! {
#name: self.wrapped.#wrapped_name.unwrap(),
#name: ::std::option::Option::unwrap(self.wrapped.#wrapped_name),
}
}
_ => {
PropAttr::Option => {
quote! {
#name: self.wrapped.#name,
}
}
PropAttr::PropOr(value) => {
quote_spanned! {value.span()=>
#name: ::std::option::Option::unwrap_or(self.wrapped.#name, #value),
}
}
PropAttr::PropOrElse(func) => {
quote_spanned! {func.span()=>
#name: ::std::option::Option::unwrap_or_else(self.wrapped.#name, #func),
}
}
PropAttr::PropOrDefault => {
quote! {
#name: ::std::option::Option::unwrap_or_default(self.wrapped.#name),
}
}
}
}

/// Wrap all required props in `Option`
pub fn to_field_def(&self) -> proc_macro2::TokenStream {
let ty = &self.ty;
let wrapped_name = self.wrapped_name();
match &self.attr {
PropAttr::Required { wrapped_name } => {
PropAttr::Option => {
quote! {
#wrapped_name: ::std::option::Option<#ty>,
#wrapped_name: #ty,
}
}
_ => {
let name = &self.name;
quote! {
#name: #ty,
#wrapped_name: ::std::option::Option<#ty>,
}
}
}
}

/// All optional props must implement the `Default` trait
pub fn to_default_setter(&self) -> proc_macro2::TokenStream {
match &self.attr {
PropAttr::Required { wrapped_name } => {
quote! {
#wrapped_name: ::std::option::Option::None,
}
}
PropAttr::PropOr(value) => {
let name = &self.name;
let span = value.span();
quote_spanned! {span=>
#name: #value,
}
}
PropAttr::PropOrElse(func) => {
let name = &self.name;
let span = func.span();
quote_spanned! {span=>
#name: (#func)(),
}
}
PropAttr::PropOrDefault => {
let name = &self.name;
quote! {
#name: ::std::default::Default::default(),
}
}
let wrapped_name = self.wrapped_name();
quote! {
#wrapped_name: ::std::option::Option::None,
}
}

Expand All @@ -115,20 +116,29 @@ impl PropField {
PropAttr::Required { wrapped_name } => {
quote! {
#[doc(hidden)]
#vis fn #name(mut self, #name: #ty) -> #builder_name<#generic_arguments> {
self.wrapped.#wrapped_name = ::std::option::Option::Some(#name);
#vis fn #name(mut self, #name: impl ::yew::html::IntoPropValue<#ty>) -> #builder_name<#generic_arguments> {
self.wrapped.#wrapped_name = ::std::option::Option::Some(#name.into_prop_value());
#builder_name {
wrapped: self.wrapped,
_marker: ::std::marker::PhantomData,
}
}
}
}
PropAttr::Option => {
quote! {
#[doc(hidden)]
#vis fn #name(mut self, #name: impl ::yew::html::IntoPropValue<#ty>) -> #builder_name<#generic_arguments> {
self.wrapped.#name = #name.into_prop_value();
self
}
}
}
_ => {
quote! {
#[doc(hidden)]
#vis fn #name(mut self, #name: #ty) -> #builder_name<#generic_arguments> {
self.wrapped.#name = #name;
#vis fn #name(mut self, #name: impl ::yew::html::IntoPropValue<#ty>) -> #builder_name<#generic_arguments> {
self.wrapped.#name = ::std::option::Option::Some(#name.into_prop_value());
self
}
}
Expand All @@ -154,6 +164,12 @@ impl PropField {
} else {
unreachable!()
}
} else if matches!(
&named_field.ty,
Type::Path(TypePath { path, .. })
if path.segments.len() == 1 && path.segments[0].ident == "Option"
) {
Ok(PropAttr::Option)
} else {
let ident = named_field.ident.as_ref().unwrap();
let wrapped_name = Ident::new(&format!("{}_wrapper", ident), Span::call_site());
Expand Down
Loading

0 comments on commit 4eecb75

Please sign in to comment.