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
77 changes: 77 additions & 0 deletions packages/yew-macro/tests/html_macro/children-api-pass.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::yew::Properties)]
pub struct ContainerProps {
#[prop_or_default]
pub children: ::yew::Html,
}

#[::yew::component]
fn Container(_props: &ContainerProps) -> ::yew::Html {
::yew::html! {}
}

#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::yew::Properties)]
pub struct AccordionProps {
pub children: ::yew::html::ChildrenRenderer<::yew::virtual_dom::VNode>,
}

#[::yew::component]
fn Accordion(props: &AccordionProps) -> ::yew::Html {
::yew::html! {
<div>
{ for props.children.iter().map(|item| ::yew::html!(
<div class="item">
{ item }
</div>
)) }
</div>
}
}

#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::yew::Properties)]
pub struct PanelProps {
#[prop_or_default]
pub children: ::yew::html::ChildrenRenderer<::yew::virtual_dom::VNode>,
}

#[::yew::component]
fn Panel(props: &PanelProps) -> ::yew::Html {
if props.children.is_empty() {
::yew::html! { <div>{"no children"}</div> }
} else {
::yew::html! { <div>{ for props.children.iter() }</div> }
}
}

#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::yew::Properties)]
pub struct LabelProps {
pub content: ::yew::Html,
}

#[::yew::component]
fn Label(_props: &LabelProps) -> ::yew::Html {
::yew::html! {}
}

fn main() {
let _ = ::yew::html! {
<Container>
<div>{"hello"}</div>
<span>{"world"}</span>
</Container>
};

let _ = ::yew::html! {
<Container>{"just text"}</Container>
};

let _ = ::yew::html! { <Container /> };

let s = ::std::string::String::from("hello");
let _ = ::yew::html! { <Label content={s} /> };

let _ = ::yew::html! { <Label content={"world"} /> };

let label = ::std::string::String::from("hello");
let _ = ::yew::html! { <div>{&label}</div> };
let _ = ::yew::html! { <div>{label}</div> };
}
54 changes: 54 additions & 0 deletions packages/yew-macro/tests/html_macro/custom-type-in-block-fail.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Custom types with From<T> for VNode or Display do not automatically work
// in html! blocks, because blocks require IntoPropValue<VNode>, not Into<VNode>.
//
// A blanket `impl<T: Into<VNode>> IntoPropValue<VNode> for T` would fix this
// but conflicts with the identity impl `impl<T> IntoPropValue<T> for T` when
// T = VNode. Resolving this overlap requires the `specialization` feature
// (rust-lang/rust#31844), which is still unstable.
//
// Workaround: call `.into()` explicitly or implement IntoPropValue<VNode>.

struct Print {
text: ::std::string::String,
}

impl ::std::convert::From<Print> for ::yew::virtual_dom::VNode {
fn from(val: Print) -> Self {
::yew::html! {<>{"Hello "}{val.text}</>}
}
}

struct Ratio(::std::primitive::f64);

impl ::std::fmt::Display for Ratio {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
::std::write!(f, "{:.2}", self.0)
}
}

enum Label {
Text(::std::string::String),
Number(::std::primitive::i32),
}

impl ::std::convert::From<Label> for ::yew::virtual_dom::VNode {
fn from(val: Label) -> Self {
match val {
Label::Text(t) => ::yew::html! { <span>{t}</span> },
Label::Number(n) => ::yew::html! { <span>{n}</span> },
}
}
}

fn main() {
let text = Print {
text: "World".into(),
};
let _ = ::yew::html! { <div>{text}</div> };

let setback = Ratio(3.14);
let _ = ::yew::html! { <div>{setback}</div> };

let label = Label::Text("hello".into());
let _ = ::yew::html! { <div>{label}</div> };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
error[E0277]: the trait bound `Print: IntoPropValue<VNode>` is not satisfied
--> tests/html_macro/custom-type-in-block-fail.rs:47:34
|
47 | let _ = ::yew::html! { <div>{text}</div> };
| ---------------------^^^^---------
| | |
| | the trait `IntoPropValue<VNode>` is not implemented for `Print`
| required by a bound introduced by this call
|
= help: the following other types implement trait `IntoPropValue<T>`:
`&&String` implements `IntoPropValue<Option<VNode>>`
`&&String` implements `IntoPropValue<VNode>`
`&&str` implements `IntoPropValue<Option<VNode>>`
`&&str` implements `IntoPropValue<VNode>`
`&'static [(K, V)]` implements `IntoPropValue<implicit_clone::unsync::map::IMap<K, V>>`
`&'static [T]` implements `IntoPropValue<implicit_clone::unsync::array::IArray<T>>`
`&'static str` implements `IntoPropValue<Classes>`
`&'static str` implements `IntoPropValue<Cow<'static, str>>`
and $N others

error[E0277]: the trait bound `Ratio: IntoPropValue<VNode>` is not satisfied
--> tests/html_macro/custom-type-in-block-fail.rs:50:34
|
50 | let _ = ::yew::html! { <div>{setback}</div> };
| ---------------------^^^^^^^---------
| | |
| | the trait `IntoPropValue<VNode>` is not implemented for `Ratio`
| required by a bound introduced by this call
|
= help: the following other types implement trait `IntoPropValue<T>`:
`&&String` implements `IntoPropValue<Option<VNode>>`
`&&String` implements `IntoPropValue<VNode>`
`&&str` implements `IntoPropValue<Option<VNode>>`
`&&str` implements `IntoPropValue<VNode>`
`&'static [(K, V)]` implements `IntoPropValue<implicit_clone::unsync::map::IMap<K, V>>`
`&'static [T]` implements `IntoPropValue<implicit_clone::unsync::array::IArray<T>>`
`&'static str` implements `IntoPropValue<Classes>`
`&'static str` implements `IntoPropValue<Cow<'static, str>>`
and $N others

error[E0277]: the trait bound `Label: IntoPropValue<VNode>` is not satisfied
--> tests/html_macro/custom-type-in-block-fail.rs:53:34
|
53 | let _ = ::yew::html! { <div>{label}</div> };
| ---------------------^^^^^---------
| | |
| | the trait `IntoPropValue<VNode>` is not implemented for `Label`
| required by a bound introduced by this call
|
= help: the following other types implement trait `IntoPropValue<T>`:
`&&String` implements `IntoPropValue<Option<VNode>>`
`&&String` implements `IntoPropValue<VNode>`
`&&str` implements `IntoPropValue<Option<VNode>>`
`&&str` implements `IntoPropValue<VNode>`
`&'static [(K, V)]` implements `IntoPropValue<implicit_clone::unsync::map::IMap<K, V>>`
`&'static [T]` implements `IntoPropValue<implicit_clone::unsync::array::IArray<T>>`
`&'static str` implements `IntoPropValue<Classes>`
`&'static str` implements `IntoPropValue<Cow<'static, str>>`
and $N others
43 changes: 43 additions & 0 deletions packages/yew-macro/tests/html_macro/string-children-pass.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::yew::Properties)]
pub struct TextProps {
pub children: ::yew::html::ChildrenRenderer<::yew::virtual_dom::VNode>,
}

#[::yew::component]
fn Text(_props: &TextProps) -> ::yew::Html {
::yew::html! {}
}

#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::yew::Properties)]
pub struct ExampleProps {
pub text: ::yew::AttrValue,
}

#[::yew::component]
fn Example(props: &ExampleProps) -> ::yew::Html {
::yew::html! {
<Text>
{&props.text}
</Text>
}
}

fn main() {
let _ = ::yew::html! { <Text>{"hello"}</Text> };

let s = ::std::string::String::from("world");
let _ = ::yew::html! { <Text>{s}</Text> };

let _ = ::yew::html! { <Text>{ ::std::format!("year {}", 2024) }</Text> };

let status: ::std::option::Option<::std::string::String> =
::std::option::Option::Some("Active".into());
let _ = ::yew::html! {
<Text>
{ status.as_ref().map_or_else(
::std::string::String::new,
|s| ::std::format!("Status: {}", s)
) }
</Text>
};
}
12 changes: 6 additions & 6 deletions packages/yew/src/html/conversion/into_prop_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,6 @@ impl<C: BaseComponent> IntoPropValue<VList> for VChild<C> {
}
}

impl IntoPropValue<ChildrenRenderer<VNode>> for AttrValue {
fn into_prop_value(self) -> ChildrenRenderer<VNode> {
ChildrenRenderer::new(vec![VNode::VText(VText::new(self))])
}
}

impl IntoPropValue<VNode> for Vec<VNode> {
#[inline]
fn into_prop_value(self) -> VNode {
Expand Down Expand Up @@ -343,6 +337,12 @@ macro_rules! impl_into_prop_value_via_attr_value {
self.map(|v| VText::new(v).into())
}
}
impl IntoPropValue<ChildrenRenderer<VNode>> for $from_ty {
#[inline(always)]
fn into_prop_value(self) -> ChildrenRenderer<VNode> {
ChildrenRenderer::new(vec![VText::new(self).into()])
}
}
};
}

Expand Down
34 changes: 34 additions & 0 deletions website/docs/advanced-topics/children.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,40 @@ impl Component for List {
}
```

### String children

When using `Children` (which is `ChildrenRenderer<VNode>`) as the prop type, string-like types
such as `String`, `AttrValue`, `Rc<str>`, and `Cow<'static, str>` can be passed directly as
children. They are automatically converted to text nodes.

```rust
use yew::prelude::*;

#[derive(Properties, PartialEq)]
pub struct TextProps {
pub children: Children,
}

#[component]
fn Text(props: &TextProps) -> Html {
html! {
<span>{ for props.children.iter() }</span>
}
}

#[component]
fn App() -> Html {
let s = String::from("world");
html! {
<>
<Text>{"hello"}</Text>
<Text>{s}</Text>
<Text>{ format!("year {}", 2024) }</Text>
</>
}
}
```

## Advanced usage

### Typed children
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Yew は、子コンポーネントのプロパティの型として `Html` を

_ほとんどの場合、_ コンポーネントに子コンポーネントを持たせる場合、子コンポーネントの型を気にする必要はありません。この場合、以下の例で十分です。

````rust
```rust
use yew::{html, Component, Context, Html, Properties};

#[derive(Properties, PartialEq)]
Expand All @@ -41,6 +41,39 @@ impl Component for List {
}
}
}
```

### 文字列の子要素

`Children`(`ChildrenRenderer<VNode>`)をプロパティの型として使用する場合、`String`、`AttrValue`、`Rc<str>`、`Cow<'static, str>` などの文字列型を直接子要素として渡すことができます。それらは自動的にテキストノードに変換されます。

```rust
use yew::prelude::*;

#[derive(Properties, PartialEq)]
pub struct TextProps {
pub children: Children,
}

#[component]
fn Text(props: &TextProps) -> Html {
html! {
<span>{ for props.children.iter() }</span>
}
}

#[component]
fn App() -> Html {
let s = String::from("world");
html! {
<>
<Text>{"hello"}</Text>
<Text>{s}</Text>
<Text>{ format!("year {}", 2024) }</Text>
</>
}
}
```

## 高度な使用法

Expand Down Expand Up @@ -92,7 +125,7 @@ impl Component for List {
}
}
}
````
```

## プロパティを持つネストされた子コンポーネント

Expand Down
Loading
Loading